From b735900f2e6bde2abb94f62a1aace0d4ae99a600 Mon Sep 17 00:00:00 2001 From: divo <418@cryspace.space> Date: Mon, 19 Oct 2026 00:12:36 -0400 Subject: [PATCH] 0.0.002 ddat interop with dat js cable interop with cabal js ddat's frontend = dat-thang cable fe = cable-thang dillo support based on cel's dillo-dat bootstrap node xplatform support Signed-off-by: divo <418@cryspace.space> --- .dat/metadata/bitfield | Bin 0 -> 32 bytes .dat/metadata/key | 1 + .dat/metadata/signatures | Bin 0 -> 32 bytes .dat/metadata/tree | Bin 0 -> 32 bytes .gitignore | 8 + INSTALL | 77 +- LICENSE | 664 + Makefile | 365 + Makefile.windows | 150 + NETWORK_PROTOCOL | 1 + README.txt | 21 +- VERSION | 1 + assets/dat-shell.cmd | 3 + assets/dat-thang.ico | Bin 0 -> 1113 bytes assets/dat-thang.rc | 1 + assets/datthang-speaker.bmp | Bin 0 -> 21222 bytes assets/fonts/MorePerfectDOSVGA.ttf | Bin 0 -> 78252 bytes assets/fonts/README.md | 7 + assets/fonts/Unifont-18.0.01.otf | Bin 0 -> 5320148 bytes assets/fonts/Unifont-OFL-1.1.txt | 90 + build-windows-installer.ps1 | 37 + build-windows.ps1 | 51 + configure | 97 + dat-thang.iss | 149 + docs/beaker-networking.md | 109 + docs/cable-interop.md | 154 + docs/cable-live-streaming.md | 60 + docs/cable-thang.1 | 124 + docs/cable.1 | 114 + docs/cesspool-bootstrap.1 | 82 + docs/dat-paper.pdf | Bin 248958 -> 0 bytes docs/dat-thang.1 | 188 + docs/dat.1 | 72 +- docs/ddat-gui.md | 126 + docs/dillo.md | 201 + docs/key-management.md | 85 + docs/libcess.md | 62 + docs/network-protocol.md | 41 + docs/primitives.md | 107 + docs/scheme-interop.md | 80 + docs/sleep.pdf | Bin 207531 -> 0 bytes docs/source-distribution.md | 38 + docs/windows-build.md | 96 + extra/cabal-core-5.0.1.tgz | Bin 0 -> 18630 bytes extra/cable.png | Bin 0 -> 8958 bytes extra/dat-dpi/.gitignore | 1 + extra/dat-dpi/LICENSE | 661 + extra/dat-dpi/Makefile | 72 + extra/dat-dpi/NETWORK_PROTOCOL | 1 + extra/dat-dpi/README.md | 92 + extra/dat-dpi/inc/base32768.h | 17 + extra/dat-dpi/inc/cable.h | 81 + extra/dat-dpi/inc/cable_media.h | 56 + extra/dat-dpi/inc/cable_room.h | 30 + extra/dat-dpi/inc/cable_video.h | 22 + extra/dat-dpi/inc/cesspool.h | 172 + extra/dat-dpi/inc/dat.h | 114 + extra/dat-dpi/inc/dat_dns.h | 27 + extra/dat-dpi/inc/dat_fetch.h | 12 + extra/dat-dpi/inc/dat_net.h | 36 + extra/dat-dpi/inc/dat_paths.h | 48 + extra/dat-dpi/inc/dat_s7.h | 16 + extra/dat-dpi/inc/dat_session.h | 151 + extra/dat-dpi/inc/dat_socket.h | 18 + extra/dat-dpi/inc/dat_wire.h | 110 + extra/dat-dpi/inc/errno_compat.h | 12 + extra/dat-dpi/inc/keys.h | 17 + extra/dat-dpi/inc/s7.h | 1240 + extra/dat-dpi/inc/sleep.h | 127 + extra/dat-dpi/inc/sleep_bitfield.h | 27 + extra/dat-dpi/inc/sleep_crypto.h | 29 + extra/dat-dpi/inc/sleep_feed.h | 43 + extra/dat-dpi/inc/sleep_proof.h | 43 + extra/dat-dpi/inc/sleep_proto.h | 26 + extra/dat-dpi/inc/sleep_replication.h | 44 + extra/dat-dpi/inc/sleep_utp.h | 39 + extra/dat-dpi/inc/sleep_verify.h | 6 + extra/dat-dpi/inc/sleep_writer.h | 18 + extra/dat-dpi/inc/win_compat.h | 80 + extra/dat-dpi/inc/win_dirent.h | 53 + extra/dat-dpi/inc/win_fnmatch.h | 24 + extra/dat-dpi/src/base32768.c | 231 + extra/dat-dpi/src/cable.c | 200 + extra/dat-dpi/src/cable_feed.c | 205 + extra/dat-dpi/src/cesspool.c | 748 + extra/dat-dpi/src/dat.c | 235 + extra/dat-dpi/src/dat_browse.c | 314 + extra/dat-dpi/src/dat_dns.c | 421 + extra/dat-dpi/src/dat_dpi.c | 758 + extra/dat-dpi/src/dat_dpi_icons.h | 33 + extra/dat-dpi/src/dat_fetch.c | 617 + extra/dat-dpi/src/dat_net.c | 166 + extra/dat-dpi/src/dat_session.c | 884 + extra/dat-dpi/src/dat_walk.c | 207 + extra/dat-dpi/src/dat_wire.c | 421 + extra/dat-dpi/src/s7.c | 96503 ++++++++++++++++ extra/dat-dpi/src/sleep.c | 24 + extra/dat-dpi/src/sleep_bitfield.c | 34 + extra/dat-dpi/src/sleep_crypto.c | 116 + extra/dat-dpi/src/sleep_feed.c | 131 + extra/dat-dpi/src/sleep_proof.c | 302 + extra/dat-dpi/src/sleep_proto.c | 58 + extra/dat-dpi/src/sleep_replication.c | 160 + extra/dat-dpi/src/sleep_tree.c | 287 + extra/dat-dpi/src/sleep_utp.c | 114 + extra/dat-dpi/src/sleep_verify.c | 589 + extra/dat-dpi/src/sleep_writer.c | 530 + extra/dat-thang-current.png | Bin 0 -> 20271 bytes extra/datthang.png | Bin 16798 -> 19123 bytes extra/online-status.png | Bin 0 -> 29397 bytes extra/sleep_verify.c | 576 + extra/sleep_verify.h | 6 + extra/sleep_writer_pass/README.txt | 16 + extra/sleep_writer_pass/inc/sleep_writer.h | 18 + extra/sleep_writer_pass/src/sleep_tree.c | 272 + extra/sleep_writer_pass/src/sleep_writer.c | 530 + hypercore | 1 + inc/base32768.h | 17 + inc/cable.h | 81 + inc/cable_media.h | 56 + inc/cable_room.h | 30 + inc/cable_video.h | 22 + inc/cesspool.h | 19 +- inc/dat.h | 110 + inc/dat_dns.h | 27 + inc/dat_fetch.h | 12 + inc/dat_net.h | 47 +- inc/dat_paths.h | 48 + inc/dat_s7.h | 16 + inc/dat_session.h | 151 + inc/dat_socket.h | 18 + inc/dat_wire.h | 110 + inc/errno_compat.h | 12 + inc/sleep.h | 31 +- inc/sleep_bitfield.h | 27 + inc/sleep_crypto.h | 19 +- inc/sleep_feed.h | 43 + inc/sleep_proof.h | 43 + inc/sleep_proto.h | 26 + inc/sleep_replication.h | 44 + inc/sleep_utp.h | 39 + inc/sleep_verify.h | 6 + inc/sleep_writer.h | 18 + inc/win_compat.h | 80 + inc/win_dirent.h | 53 + inc/win_fnmatch.h | 24 + libcess/Makefile | 49 + libcess/NETWORK_PROTOCOL | 1 + libcess/README.md | 32 + libcess/cess_net.h | 1 + libcess/cesspool.h | 1 + libcess/docs/cesspool-bootstrap.1 | 28 + libcess/include/cess_compat.h | 19 + libcess/include/cess_net.h | 50 + libcess/include/cesspool.h | 129 + libcess/src/cess_identity.c | 75 + libcess/src/cess_net.c | 167 + libcess/src/cesspool.c | 793 + libcess/tools/cesspool-bootstrap.c | 247 + list.txt | 2 + package-source.ps1 | 67 + scheme/cable-room.scm | 14 + scheme/dat-init.scm | 42 + scheme/dat-json.scm | 244 + scheme/dat-record.scm | 42 + scheme/dat-wire.scm | 31 + site/.dat/content.bitfield | Bin 0 -> 3616 bytes site/.dat/content.data | Bin 0 -> 26226 bytes site/.dat/content.key | 1 + site/.dat/content.signatures | Bin 0 -> 288 bytes site/.dat/content.tree | Bin 0 -> 312 bytes site/.dat/metadata.bitfield | Bin 0 -> 3616 bytes site/.dat/metadata.data | 7 + site/.dat/metadata.key | 1 + site/.dat/metadata.signatures | Bin 0 -> 288 bytes site/.dat/metadata.tree | Bin 0 -> 312 bytes site/dat-logo.png | Bin 0 -> 24921 bytes site/dat.scm | 1 + site/index.html | 25 + src/base32768.c | 231 + src/cable.c | 200 + src/cable_cli.c | 220 + src/cable_feed.c | 205 + src/cable_media.c | 303 + src/cable_video.c | 270 + src/cesspool.c | 5 +- src/cesspool_bootstrap.c | 2 + src/dat.c | 235 + src/dat_browse.c | 314 + src/dat_cli.c | 248 + src/dat_dns.c | 421 + src/dat_dpi.c | 758 + src/dat_dpi_icons.h | 33 + src/dat_fetch.c | 617 + src/dat_net.c | 6 +- src/dat_s7.c | 195 + src/dat_scheme.c | 181 + src/dat_scheme_cli.c | 58 + src/dat_session.c | 884 + src/dat_thang_icons.h | 114 + src/dat_walk.c | 207 + src/dat_wire.c | 421 + src/ddat_gui.c | 5455 + src/sleep.c | 52 +- src/sleep_bitfield.c | 34 + src/sleep_crypto.c | 105 +- src/sleep_feed.c | 131 + src/sleep_proof.c | 302 + src/sleep_proto.c | 93 +- src/sleep_replication.c | 160 + src/sleep_tree.c | 417 +- src/sleep_utp.c | 114 + src/sleep_verify.c | 589 + src/sleep_writer.c | 530 + tests/fixtures/README.md | 13 + .../site-previous/.dat/content.bitfield | Bin 0 -> 3616 bytes .../site-previous/.dat/content.data | Bin 0 -> 28374 bytes .../site-previous/.dat/content.key | 1 + .../site-previous/.dat/content.signatures | Bin 0 -> 416 bytes .../site-previous/.dat/content.tree | Bin 0 -> 472 bytes .../site-previous/.dat/metadata.bitfield | Bin 0 -> 3616 bytes .../site-previous/.dat/metadata.data | 11 + .../site-previous/.dat/metadata.key | 1 + .../site-previous/.dat/metadata.signatures | Bin 0 -> 416 bytes .../site-previous/.dat/metadata.tree | Bin 0 -> 472 bytes .../first/.dat/content.bitfield | Bin 0 -> 3616 bytes .../first/.dat/content.data | 0 .../first/.dat/content.key | 1 + .../first/.dat/content.signatures | Bin 0 -> 96 bytes .../first/.dat/content.tree | Bin 0 -> 72 bytes .../first/.dat/metadata.bitfield | Bin 0 -> 3616 bytes .../first/.dat/metadata.data | 3 + .../first/.dat/metadata.key | 1 + .../first/.dat/metadata.signatures | Bin 0 -> 96 bytes .../first/.dat/metadata.tree | Bin 0 -> 72 bytes .../second/.dat/content.bitfield | Bin 0 -> 3616 bytes .../second/.dat/content.data | 0 .../second/.dat/content.key | Bin 0 -> 32 bytes .../second/.dat/content.signatures | Bin 0 -> 96 bytes .../second/.dat/content.tree | Bin 0 -> 72 bytes .../second/.dat/metadata.bitfield | Bin 0 -> 3616 bytes .../second/.dat/metadata.data | Bin 0 -> 46 bytes .../second/.dat/metadata.key | 1 + .../second/.dat/metadata.signatures | Bin 0 -> 96 bytes .../second/.dat/metadata.tree | Bin 0 -> 72 bytes .../test-version-bar-282-0/.dat/dat-dns | 0 .../first/.dat/content.bitfield | Bin 0 -> 3616 bytes .../first/.dat/content.data | 0 .../first/.dat/content.key | 2 + .../first/.dat/content.signatures | Bin 0 -> 96 bytes .../first/.dat/content.tree | Bin 0 -> 72 bytes .../first/.dat/metadata.bitfield | Bin 0 -> 3616 bytes .../first/.dat/metadata.data | 4 + .../first/.dat/metadata.key | 1 + .../first/.dat/metadata.signatures | Bin 0 -> 96 bytes .../first/.dat/metadata.tree | Bin 0 -> 72 bytes .../second/.dat/content.bitfield | Bin 0 -> 3616 bytes .../second/.dat/content.data | 0 .../second/.dat/content.key | 1 + .../second/.dat/content.signatures | Bin 0 -> 96 bytes .../second/.dat/content.tree | Bin 0 -> 72 bytes .../second/.dat/metadata.bitfield | Bin 0 -> 3616 bytes .../second/.dat/metadata.data | 3 + .../second/.dat/metadata.key | 1 + .../second/.dat/metadata.signatures | Bin 0 -> 96 bytes .../second/.dat/metadata.tree | Bin 0 -> 72 bytes .../.dat/content.bitfield | Bin 0 -> 3616 bytes .../.dat/content.data | Bin 0 -> 28374 bytes .../.dat/content.key | 1 + .../.dat/content.signatures | Bin 0 -> 416 bytes .../.dat/content.tree | Bin 0 -> 472 bytes .../.dat/metadata.bitfield | Bin 0 -> 3616 bytes .../.dat/metadata.data | 11 + .../.dat/metadata.key | 1 + .../.dat/metadata.signatures | Bin 0 -> 416 bytes .../.dat/metadata.tree | Bin 0 -> 472 bytes .../dat-logo.png | Bin 0 -> 24921 bytes .../dat.json | 4 + .../index.html | 25 + tests/fixtures/dillo-dpip/cold-input | 3 + tests/fixtures/windows/hello.c | 1 + tests/helpers/dillo-title-probe.c | 24 + tests/integration/dillo/README.md | 16 + tests/integration/dillo/cold-site-dpip.sh | 14 + tests/integration/dillo/real-site-dpip.sh | 15 + .../interop/cabal/cabal_cable_side_by_side.js | 150 + tests/interop/cabal/cabal_cli_features.js | 162 + .../cabal/cabal_core_hyperswarm_live.js | 118 + tests/interop/cabal/cable_bootstrap.js | 33 + .../cabal/cable_cabal_hyperswarm_live.js | 78 + tests/interop/cabal/cable_hyperswarm.c | 226 + tests/interop/cabal/cable_hyperswarm.h | 42 + tests/interop/cabal/cable_hyperswarm.js | 362 + tests/interop/cabal/cable_hyperswarm_win.inc | 226 + tests/interop/cabal/cable_js_c_live.js | 184 + tests/interop/cabal/cable_sexpr_boundary.js | 15 + tests/interop/cabal/hyperswarm_loopback.js | 99 + tests/interop/dat/cable_hypercore_compat.js | 87 + tests/interop/dat/cable_hypercore_compat.ps1 | 44 + tests/interop/dat/legacy_js_p2p.js | 69 + tests/test-json.scm | 34 + tests/test_archive.c | 225 + tests/test_base32768.c | 46 + tests/test_bitfield.c | 4 + tests/test_cable.c | 100 + tests/test_cable_thang.c | 104 + tests/test_cli_parity.sh | 44 + tests/test_discovery.c | 234 + tests/test_dns.c | 113 + tests/test_dpi.sh | 71 + tests/test_dpi_socket.c | 118 + tests/test_gui_p2p.c | 391 + tests/test_paths.c | 42 + tests/test_proto.c | 4 + tests/test_replication.c | 29 + tests/test_session.c | 476 + tests/test_tree.c | 4 + tests/test_utp.c | 154 + tests/test_version_bar.c | 160 + tests/test_wire.c | 11 + tests/test_writer.c | 62 + third_party/base32768.LICENSE | 21 + tools/make_windows_icon.ps1 | 47 + 323 files changed, 134347 insertions(+), 343 deletions(-) create mode 100644 .dat/metadata/bitfield create mode 100644 .dat/metadata/key create mode 100644 .dat/metadata/signatures create mode 100644 .dat/metadata/tree create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 Makefile.windows create mode 100644 NETWORK_PROTOCOL create mode 100644 VERSION create mode 100644 assets/dat-shell.cmd create mode 100644 assets/dat-thang.ico create mode 100644 assets/dat-thang.rc create mode 100644 assets/datthang-speaker.bmp create mode 100644 assets/fonts/MorePerfectDOSVGA.ttf create mode 100644 assets/fonts/README.md create mode 100644 assets/fonts/Unifont-18.0.01.otf create mode 100644 assets/fonts/Unifont-OFL-1.1.txt create mode 100644 build-windows-installer.ps1 create mode 100644 build-windows.ps1 create mode 100644 configure create mode 100644 dat-thang.iss create mode 100644 docs/beaker-networking.md create mode 100644 docs/cable-interop.md create mode 100644 docs/cable-live-streaming.md create mode 100644 docs/cable-thang.1 create mode 100644 docs/cable.1 create mode 100644 docs/cesspool-bootstrap.1 delete mode 100644 docs/dat-paper.pdf create mode 100644 docs/dat-thang.1 create mode 100644 docs/ddat-gui.md create mode 100644 docs/dillo.md create mode 100644 docs/key-management.md create mode 100644 docs/libcess.md create mode 100644 docs/network-protocol.md create mode 100644 docs/primitives.md create mode 100644 docs/scheme-interop.md delete mode 100644 docs/sleep.pdf create mode 100644 docs/source-distribution.md create mode 100644 docs/windows-build.md create mode 100644 extra/cabal-core-5.0.1.tgz create mode 100644 extra/cable.png create mode 100644 extra/dat-dpi/.gitignore create mode 100644 extra/dat-dpi/LICENSE create mode 100644 extra/dat-dpi/Makefile create mode 100644 extra/dat-dpi/NETWORK_PROTOCOL create mode 100644 extra/dat-dpi/README.md create mode 100644 extra/dat-dpi/inc/base32768.h create mode 100644 extra/dat-dpi/inc/cable.h create mode 100644 extra/dat-dpi/inc/cable_media.h create mode 100644 extra/dat-dpi/inc/cable_room.h create mode 100644 extra/dat-dpi/inc/cable_video.h create mode 100644 extra/dat-dpi/inc/cesspool.h create mode 100644 extra/dat-dpi/inc/dat.h create mode 100644 extra/dat-dpi/inc/dat_dns.h create mode 100644 extra/dat-dpi/inc/dat_fetch.h create mode 100644 extra/dat-dpi/inc/dat_net.h create mode 100644 extra/dat-dpi/inc/dat_paths.h create mode 100644 extra/dat-dpi/inc/dat_s7.h create mode 100644 extra/dat-dpi/inc/dat_session.h create mode 100644 extra/dat-dpi/inc/dat_socket.h create mode 100644 extra/dat-dpi/inc/dat_wire.h create mode 100644 extra/dat-dpi/inc/errno_compat.h create mode 100644 extra/dat-dpi/inc/keys.h create mode 100644 extra/dat-dpi/inc/s7.h create mode 100644 extra/dat-dpi/inc/sleep.h create mode 100644 extra/dat-dpi/inc/sleep_bitfield.h create mode 100644 extra/dat-dpi/inc/sleep_crypto.h create mode 100644 extra/dat-dpi/inc/sleep_feed.h create mode 100644 extra/dat-dpi/inc/sleep_proof.h create mode 100644 extra/dat-dpi/inc/sleep_proto.h create mode 100644 extra/dat-dpi/inc/sleep_replication.h create mode 100644 extra/dat-dpi/inc/sleep_utp.h create mode 100644 extra/dat-dpi/inc/sleep_verify.h create mode 100644 extra/dat-dpi/inc/sleep_writer.h create mode 100644 extra/dat-dpi/inc/win_compat.h create mode 100644 extra/dat-dpi/inc/win_dirent.h create mode 100644 extra/dat-dpi/inc/win_fnmatch.h create mode 100644 extra/dat-dpi/src/base32768.c create mode 100644 extra/dat-dpi/src/cable.c create mode 100644 extra/dat-dpi/src/cable_feed.c create mode 100644 extra/dat-dpi/src/cesspool.c create mode 100644 extra/dat-dpi/src/dat.c create mode 100644 extra/dat-dpi/src/dat_browse.c create mode 100644 extra/dat-dpi/src/dat_dns.c create mode 100644 extra/dat-dpi/src/dat_dpi.c create mode 100644 extra/dat-dpi/src/dat_dpi_icons.h create mode 100644 extra/dat-dpi/src/dat_fetch.c create mode 100644 extra/dat-dpi/src/dat_net.c create mode 100644 extra/dat-dpi/src/dat_session.c create mode 100644 extra/dat-dpi/src/dat_walk.c create mode 100644 extra/dat-dpi/src/dat_wire.c create mode 100644 extra/dat-dpi/src/s7.c create mode 100644 extra/dat-dpi/src/sleep.c create mode 100644 extra/dat-dpi/src/sleep_bitfield.c create mode 100644 extra/dat-dpi/src/sleep_crypto.c create mode 100644 extra/dat-dpi/src/sleep_feed.c create mode 100644 extra/dat-dpi/src/sleep_proof.c create mode 100644 extra/dat-dpi/src/sleep_proto.c create mode 100644 extra/dat-dpi/src/sleep_replication.c create mode 100644 extra/dat-dpi/src/sleep_tree.c create mode 100644 extra/dat-dpi/src/sleep_utp.c create mode 100644 extra/dat-dpi/src/sleep_verify.c create mode 100644 extra/dat-dpi/src/sleep_writer.c create mode 100644 extra/dat-thang-current.png create mode 100644 extra/online-status.png create mode 100644 extra/sleep_verify.c create mode 100644 extra/sleep_verify.h create mode 100644 extra/sleep_writer_pass/README.txt create mode 100644 extra/sleep_writer_pass/inc/sleep_writer.h create mode 100644 extra/sleep_writer_pass/src/sleep_tree.c create mode 100644 extra/sleep_writer_pass/src/sleep_writer.c create mode 100644 hypercore create mode 100644 inc/base32768.h create mode 100644 inc/cable.h create mode 100644 inc/cable_media.h create mode 100644 inc/cable_room.h create mode 100644 inc/cable_video.h create mode 100644 inc/dat_dns.h create mode 100644 inc/dat_fetch.h create mode 100644 inc/dat_paths.h create mode 100644 inc/dat_s7.h create mode 100644 inc/dat_session.h create mode 100644 inc/dat_socket.h create mode 100644 inc/dat_wire.h create mode 100644 inc/errno_compat.h create mode 100644 inc/sleep_bitfield.h create mode 100644 inc/sleep_feed.h create mode 100644 inc/sleep_proof.h create mode 100644 inc/sleep_proto.h create mode 100644 inc/sleep_replication.h create mode 100644 inc/sleep_utp.h create mode 100644 inc/sleep_verify.h create mode 100644 inc/sleep_writer.h create mode 100644 inc/win_compat.h create mode 100644 inc/win_dirent.h create mode 100644 inc/win_fnmatch.h create mode 100644 libcess/Makefile create mode 100644 libcess/NETWORK_PROTOCOL create mode 100644 libcess/README.md create mode 100644 libcess/cess_net.h create mode 100644 libcess/cesspool.h create mode 100644 libcess/docs/cesspool-bootstrap.1 create mode 100644 libcess/include/cess_compat.h create mode 100644 libcess/include/cess_net.h create mode 100644 libcess/include/cesspool.h create mode 100644 libcess/src/cess_identity.c create mode 100644 libcess/src/cess_net.c create mode 100644 libcess/src/cesspool.c create mode 100644 libcess/tools/cesspool-bootstrap.c create mode 100644 list.txt create mode 100644 package-source.ps1 create mode 100644 scheme/cable-room.scm create mode 100644 scheme/dat-init.scm create mode 100644 scheme/dat-json.scm create mode 100644 scheme/dat-record.scm create mode 100644 scheme/dat-wire.scm create mode 100644 site/.dat/content.bitfield create mode 100644 site/.dat/content.data create mode 100644 site/.dat/content.key create mode 100644 site/.dat/content.signatures create mode 100644 site/.dat/content.tree create mode 100644 site/.dat/metadata.bitfield create mode 100644 site/.dat/metadata.data create mode 100644 site/.dat/metadata.key create mode 100644 site/.dat/metadata.signatures create mode 100644 site/.dat/metadata.tree create mode 100644 site/dat-logo.png create mode 100644 site/dat.scm create mode 100644 site/index.html create mode 100644 src/base32768.c create mode 100644 src/cable.c create mode 100644 src/cable_cli.c create mode 100644 src/cable_feed.c create mode 100644 src/cable_media.c create mode 100644 src/cable_video.c create mode 100644 src/cesspool_bootstrap.c create mode 100644 src/dat.c create mode 100644 src/dat_browse.c create mode 100644 src/dat_cli.c create mode 100644 src/dat_dns.c create mode 100644 src/dat_dpi.c create mode 100644 src/dat_dpi_icons.h create mode 100644 src/dat_fetch.c create mode 100644 src/dat_s7.c create mode 100644 src/dat_scheme.c create mode 100644 src/dat_scheme_cli.c create mode 100644 src/dat_session.c create mode 100644 src/dat_thang_icons.h create mode 100644 src/dat_walk.c create mode 100644 src/dat_wire.c create mode 100644 src/ddat_gui.c create mode 100644 src/sleep_bitfield.c create mode 100644 src/sleep_feed.c create mode 100644 src/sleep_proof.c create mode 100644 src/sleep_replication.c create mode 100644 src/sleep_verify.c create mode 100644 src/sleep_writer.c create mode 100644 tests/fixtures/README.md create mode 100644 tests/fixtures/dat-history/site-previous/.dat/content.bitfield create mode 100644 tests/fixtures/dat-history/site-previous/.dat/content.data create mode 100644 tests/fixtures/dat-history/site-previous/.dat/content.key create mode 100644 tests/fixtures/dat-history/site-previous/.dat/content.signatures create mode 100644 tests/fixtures/dat-history/site-previous/.dat/content.tree create mode 100644 tests/fixtures/dat-history/site-previous/.dat/metadata.bitfield create mode 100644 tests/fixtures/dat-history/site-previous/.dat/metadata.data create mode 100644 tests/fixtures/dat-history/site-previous/.dat/metadata.key create mode 100644 tests/fixtures/dat-history/site-previous/.dat/metadata.signatures create mode 100644 tests/fixtures/dat-history/site-previous/.dat/metadata.tree create mode 100644 tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/content.bitfield create mode 100644 tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/content.data create mode 100644 tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/content.key create mode 100644 tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/content.signatures create mode 100644 tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/content.tree create mode 100644 tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/metadata.bitfield create mode 100644 tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/metadata.data create mode 100644 tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/metadata.key create mode 100644 tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/metadata.signatures create mode 100644 tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/metadata.tree create mode 100644 tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/content.bitfield create mode 100644 tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/content.data create mode 100644 tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/content.key create mode 100644 tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/content.signatures create mode 100644 tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/content.tree create mode 100644 tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/metadata.bitfield create mode 100644 tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/metadata.data create mode 100644 tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/metadata.key create mode 100644 tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/metadata.signatures create mode 100644 tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/metadata.tree create mode 100644 tests/fixtures/dat-history/test-version-bar-282-0/.dat/dat-dns create mode 100644 tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/content.bitfield create mode 100644 tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/content.data create mode 100644 tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/content.key create mode 100644 tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/content.signatures create mode 100644 tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/content.tree create mode 100644 tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/metadata.bitfield create mode 100644 tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/metadata.data create mode 100644 tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/metadata.key create mode 100644 tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/metadata.signatures create mode 100644 tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/metadata.tree create mode 100644 tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/content.bitfield create mode 100644 tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/content.data create mode 100644 tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/content.key create mode 100644 tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/content.signatures create mode 100644 tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/content.tree create mode 100644 tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/metadata.bitfield create mode 100644 tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/metadata.data create mode 100644 tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/metadata.key create mode 100644 tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/metadata.signatures create mode 100644 tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/metadata.tree create mode 100644 tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/.dat/content.bitfield create mode 100644 tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/.dat/content.data create mode 100644 tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/.dat/content.key create mode 100644 tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/.dat/content.signatures create mode 100644 tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/.dat/content.tree create mode 100644 tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/.dat/metadata.bitfield create mode 100644 tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/.dat/metadata.data create mode 100644 tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/.dat/metadata.key create mode 100644 tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/.dat/metadata.signatures create mode 100644 tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/.dat/metadata.tree create mode 100644 tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/dat-logo.png create mode 100644 tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/dat.json create mode 100644 tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/index.html create mode 100644 tests/fixtures/dillo-dpip/cold-input create mode 100644 tests/fixtures/windows/hello.c create mode 100644 tests/helpers/dillo-title-probe.c create mode 100644 tests/integration/dillo/README.md create mode 100644 tests/integration/dillo/cold-site-dpip.sh create mode 100644 tests/integration/dillo/real-site-dpip.sh create mode 100644 tests/interop/cabal/cabal_cable_side_by_side.js create mode 100644 tests/interop/cabal/cabal_cli_features.js create mode 100644 tests/interop/cabal/cabal_core_hyperswarm_live.js create mode 100644 tests/interop/cabal/cable_bootstrap.js create mode 100644 tests/interop/cabal/cable_cabal_hyperswarm_live.js create mode 100644 tests/interop/cabal/cable_hyperswarm.c create mode 100644 tests/interop/cabal/cable_hyperswarm.h create mode 100644 tests/interop/cabal/cable_hyperswarm.js create mode 100644 tests/interop/cabal/cable_hyperswarm_win.inc create mode 100644 tests/interop/cabal/cable_js_c_live.js create mode 100644 tests/interop/cabal/cable_sexpr_boundary.js create mode 100644 tests/interop/cabal/hyperswarm_loopback.js create mode 100644 tests/interop/dat/cable_hypercore_compat.js create mode 100644 tests/interop/dat/cable_hypercore_compat.ps1 create mode 100644 tests/interop/dat/legacy_js_p2p.js create mode 100644 tests/test-json.scm create mode 100644 tests/test_archive.c create mode 100644 tests/test_base32768.c create mode 100644 tests/test_bitfield.c create mode 100644 tests/test_cable.c create mode 100644 tests/test_cable_thang.c create mode 100644 tests/test_cli_parity.sh create mode 100644 tests/test_discovery.c create mode 100644 tests/test_dns.c create mode 100644 tests/test_dpi.sh create mode 100644 tests/test_dpi_socket.c create mode 100644 tests/test_gui_p2p.c create mode 100644 tests/test_paths.c create mode 100644 tests/test_proto.c create mode 100644 tests/test_replication.c create mode 100644 tests/test_session.c create mode 100644 tests/test_tree.c create mode 100644 tests/test_utp.c create mode 100644 tests/test_version_bar.c create mode 100644 tests/test_wire.c create mode 100644 tests/test_writer.c create mode 100644 third_party/base32768.LICENSE create mode 100644 tools/make_windows_icon.ps1 diff --git a/.dat/metadata/bitfield b/.dat/metadata/bitfield new file mode 100644 index 0000000000000000000000000000000000000000..69359e22d42490f6a9689729cb4a590e1230b421 GIT binary patch literal 32 PcmZQ&3TI&8W55Xj45|Qa literal 0 HcmV?d00001 diff --git a/.dat/metadata/key b/.dat/metadata/key new file mode 100644 index 0000000..2aea5d8 --- /dev/null +++ b/.dat/metadata/key @@ -0,0 +1 @@ +YØ “™Q#£Æ“ûX‡·»¡XoA÷ïXrb*Ú � \ No newline at end of file diff --git a/.dat/metadata/signatures b/.dat/metadata/signatures new file mode 100644 index 0000000000000000000000000000000000000000..8131d61e224bbbf89f85b604f9229b5d9c86eaae GIT binary patch literal 32 YcmZQ&3TI?saA0>$F)}qZv}8aB05)C%RsaA1 literal 0 HcmV?d00001 diff --git a/.dat/metadata/tree b/.dat/metadata/tree new file mode 100644 index 0000000000000000000000000000000000000000..4ff6ec0c936a76278d9b12c9e72834c33f704b1d GIT binary patch literal 32 YcmZQ&3TI+q&|r7+arAaIN@73<066FZg8%>k literal 0 HcmV?d00001 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1e389f7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/build/ +/dist/ +/local-data/ +/test-deps/ +/config.h +/config.mk +/config.log +/conftest-* diff --git a/INSTALL b/INSTALL index c122cc6..8a2b70d 100644 --- a/INSTALL +++ b/INSTALL @@ -1,22 +1,65 @@ -## x86_64 ** GCC ** -deps -NaCl/libsodium -libutp -//webrtc -//sdl2 +dat-thang requires a C99 compiler, make, libsodium development headers, and the +system DNS resolver development library (`libresolv` on glibc systems). -./configure -make -open a new tty console and log in as root and then cd to the dir then make install +Build and test: -CONFIG FLAGS y'all can pass --- -./configure CPPFLAGS="-DSLEEP_SALT" -make SLEEP_SALT=1 --- + ./configure + make + make test -** x86_64 MUSL ** +Install under /usr/local by default: -## aarch64 + make install -## rv64 +Choose another prefix during configuration: + + ./configure --prefix="$HOME/.local" + make + make test + make install + +Packagers can stage an install without writing to the live prefix: + + make DESTDIR=/tmp/dat-thang-package install + +The installed C command is named `ddat`, so the historical JavaScript `dat` +command can remain installed beside it. To build the old Hypercore V6 session +mode for side-by-side compatibility work, use: + + make NETWORK_PROTOCOL=hypercore build/linux/ddat + +The SDL2 mockup is optional. Install SDL2 development headers and pkg-config, +then build and run it with: + + make ENABLE_SDL2=1 + build/linux/dat-thang + +The GNU/Linux default output path is `build/linux`; the native Windows +development build is `build/windows`. Release files belong under `dist/`, so +the source tree and platform build outputs stay separate. Create a staged +GNU/Linux install-tree archive with: + + make package + +This creates `dist/linux-/dat-thang--linux-.tar.gz`. +The tarball contains the installed programs, headers, library, Dillo DPI, and +manuals; the target system supplies the linked system libraries. On Windows, +`build-windows.ps1` creates a portable ZIP and `build-windows-installer.ps1` +creates the install wizard package. + +To make a source-only ZIP from PowerShell without including ignored build or +release outputs, run `./package-source.ps1`. See +`docs/source-distribution.md` for its archive contents. + +To include the SDL2 clients in the Linux install and release archive, install +SDL2 and SDL2_ttf development files, then run `make ENABLE_SDL2=1 package`. + +`NETWORK_PROTOCOL` remains the repository-root setting; the make override above +is temporary and does not change that file. + +S7 is bundled and required. The default build includes the embedded Scheme +runtime, `ddat-scheme`, and the shared JSON reader used for JavaScript Dat +metadata. `--disable-s7` is rejected because Dat metadata parsing depends on it. + +Use `make uninstall` to remove installed dat-thang files and `make distclean` to +remove all generated build and configuration files. diff --git a/LICENSE b/LICENSE index 2e40e13..f7209a2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,667 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. + +~~~~ ADDENDUM + Creative Communism ☭ This is My Software Freedom is a spook diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b4a4e61 --- /dev/null +++ b/Makefile @@ -0,0 +1,365 @@ +-include config.mk + +BUILD ?= build/linux +VERSION ?= $(shell tr -d '\r\n' < VERSION) +PACKAGE_ARCH ?= $(shell uname -m) +PACKAGE_STAGE ?= $(CURDIR)/$(BUILD)/package-root +PACKAGE_DIR ?= $(CURDIR)/dist/linux-$(PACKAGE_ARCH) + +PREFIX ?= /usr/local +DPIDIR ?= $(PREFIX)/lib/dillo/dpi/dat +DAT_SCHEME_DIR ?= $(PREFIX)/share/datgpt/scheme +DESTDIR ?= +CC ?= cc +AR ?= ar +RANLIB ?= ranlib +OBJCOPY ?= objcopy +INSTALL ?= install +CFLAGS ?= -O2 -std=c99 -Wall -Wextra -Wpedantic +HOST_OS := $(shell uname -s 2>/dev/null) +ifneq (,$(findstring CYGWIN,$(HOST_OS))) +TEST_LDFLAGS += -Wl,--stack,8388608 +endif +CPPFLAGS ?= +LDFLAGS ?= +SODIUM_LIBS ?= -lsodium +CURL_CFLAGS ?= $(shell pkg-config --cflags libcurl 2>/dev/null) +CURL_LIBS ?= $(shell pkg-config --libs libcurl 2>/dev/null || echo -lcurl) +ENABLE_SDL2 ?= 0 +FFMPEG_MEDIA_AVAILABLE := $(shell pkg-config --exists 'libavcodec >= 59' 'libavformat >= 59' 'libavutil >= 57' 'libswresample >= 4' 'libswscale >= 6' 2>/dev/null && echo 1 || echo 0) +ENABLE_CABLE_MEDIA ?= 0 +ifeq ($(ENABLE_CABLE_MEDIA),1) +ifneq ($(FFMPEG_MEDIA_AVAILABLE),1) +$(error Cable media needs FFmpeg 5.0+ development files: libavcodec>=59, libavformat>=59, libavutil>=57, libswresample>=4, libswscale>=6) +endif +CABLE_MEDIA_CFLAGS = -DCABLE_HAVE_FFMPEG $(shell pkg-config --cflags libavcodec libavformat libavutil libswresample libswscale) +CABLE_MEDIA_LIBS = $(shell pkg-config --libs libavformat libavcodec libavutil libswresample libswscale) +endif +# Node and Cabal modules are used only by optional interoperability tests. +NODE ?= node +CABAL_JS_MODULE ?= cabal-core +CABAL_CLIENT_MODULE ?= cabal-client + +CPPFLAGS += -I. -Iinc -Ilibcess/include -DSLEEP_SALT -DCESSPOOL_HAVE_SODIUM $(CURL_CFLAGS) +CPPFLAGS += -DDAT_BUILD_DIR=\"$(BUILD)\" +# Root-level NETWORK_PROTOCOL is the Cesspool peer identity and discovery +# namespace. Hypercore-compatible protocol identifiers belong in test builds. +NETWORK_PROTOCOL := $(shell tr -d '\r\n' < NETWORK_PROTOCOL) +CPPFLAGS += -DDAT_NETWORK_PROTOCOL=\"$(NETWORK_PROTOCOL)\" +CPPFLAGS += -DCESSPOOL_NETWORK_PROTOCOL=\"$(NETWORK_PROTOCOL)\" +CPPFLAGS += -DCESSPOOL_CONFIG_DIRECTORY=\"dat-gpt\" +ifeq ($(NETWORK_PROTOCOL),hypercore) +CPPFLAGS += -DDAT_LEGACY_HYPERCORE +endif +LIBS = $(SODIUM_LIBS) $(CURL_LIBS) -lm -ldl +LIB_SOURCES = src/base32768.c src/cable.c src/cable_feed.c src/dat.c src/dat_browse.c src/dat_dns.c src/dat_fetch.c src/dat_walk.c src/dat_wire.c src/dat_session.c \ + src/sleep.c src/sleep_tree.c src/sleep_bitfield.c \ + src/sleep_proto.c src/sleep_crypto.c src/sleep_writer.c \ + src/sleep_feed.c src/sleep_proof.c src/sleep_replication.c src/sleep_utp.c +LIB_SOURCES += src/dat_s7.c src/dat_scheme.c src/s7.c +S7_INSTALL = $(INSTALL) -m 755 $(BUILD)/ddat-scheme "$(DESTDIR)$(PREFIX)/bin/ddat-scheme" +S7_SCHEME_INSTALL = $(INSTALL) -d "$(DESTDIR)$(DAT_SCHEME_DIR)" && \ + $(INSTALL) -m 644 scheme/dat-init.scm scheme/dat-record.scm scheme/dat-wire.scm scheme/dat-json.scm scheme/cable-room.scm "$(DESTDIR)$(DAT_SCHEME_DIR)/" +S7_UNINSTALL = rm -f "$(DESTDIR)$(PREFIX)/bin/ddat-scheme" "$(DESTDIR)$(DAT_SCHEME_DIR)/dat-init.scm" "$(DESTDIR)$(DAT_SCHEME_DIR)/dat-record.scm" "$(DESTDIR)$(DAT_SCHEME_DIR)/dat-wire.scm" "$(DESTDIR)$(DAT_SCHEME_DIR)/dat-json.scm" "$(DESTDIR)$(DAT_SCHEME_DIR)/cable-room.scm" +LIB_OBJECTS = $(LIB_SOURCES:src/%.c=$(BUILD)/%.o) $(BUILD)/sleep_verify_lib.o +LIBCESS_OBJECTS = $(BUILD)/libcess/cess_net.o $(BUILD)/libcess/cesspool.o $(BUILD)/libcess/cess_identity.o +PUBLIC_HEADERS = $(wildcard inc/*.h) +TESTS = $(BUILD)/test_tree $(BUILD)/test_bitfield $(BUILD)/test_base32768 $(BUILD)/test_cable $(BUILD)/test_proto $(BUILD)/test_wire \ + $(BUILD)/test_archive $(BUILD)/test_session $(BUILD)/test_dns $(BUILD)/test_utp \ + $(BUILD)/test_discovery $(BUILD)/test_replication $(BUILD)/test_writer $(BUILD)/test_paths +ifeq ($(ENABLE_SDL2),1) +GUI_TESTS = $(BUILD)/test_version_bar $(BUILD)/test_gui_p2p +CABLE_LIVE_TEST = $(BUILD)/test_cable_thang +endif + +.PHONY: all test install uninstall clean distclean package package-linux +.PHONY: test-cabal-interop +.PHONY: test-cable-live +.PHONY: FORCE +all: $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(BUILD)/sleep-verify $(BUILD)/dat $(BUILD)/ddat $(BUILD)/cable $(BUILD)/cesspool-bootstrap $(BUILD)/dat.dpi +all: $(BUILD)/ddat-scheme +ifeq ($(ENABLE_SDL2),1) +all: $(BUILD)/dat-thang $(BUILD)/cable-thang +endif + +$(BUILD): + mkdir -p $(BUILD) + +# A protocol identity changes wire bytes and must never reuse objects built for +# another value (including make command-line overrides used for compatibility). +$(BUILD)/.network-protocol: FORCE NETWORK_PROTOCOL | $(BUILD) + @protocol="$(NETWORK_PROTOCOL)"; \ + if test ! -f $@ || test "$$(cat $@)" != "$$protocol"; then \ + printf '%s\n' "$$protocol" > $@.tmp; mv $@.tmp $@; \ + fi + +$(BUILD)/%.o: src/%.c NETWORK_PROTOCOL $(BUILD)/.network-protocol | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) -MMD -MP -c $< -o $@ + +$(BUILD)/libcess: + mkdir -p $@ + +$(BUILD)/libcess/%.o: libcess/src/%.c libcess/include/cesspool.h libcess/include/cess_net.h libcess/include/cess_compat.h NETWORK_PROTOCOL $(BUILD)/.network-protocol | $(BUILD)/libcess + $(CC) $(CPPFLAGS) $(CFLAGS) -MMD -MP -c $< -o $@ + +$(BUILD)/libcess.a: $(LIBCESS_OBJECTS) + $(AR) rcs $@ $(LIBCESS_OBJECTS) + $(RANLIB) $@ + +# S7 is an upstream single-file interpreter with noisy legacy diagnostics. +# Suppression applies only to that vendored translation unit; our host stays +# under the normal project warning flags. +$(BUILD)/s7.o: src/s7.c NETWORK_PROTOCOL $(BUILD)/.network-protocol | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) -w -MMD -MP -c $< -o $@ + +$(BUILD)/sleep_verify_lib.o: src/sleep_verify.c $(BUILD)/.network-protocol | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) -MMD -MP -DSLEEP_VERIFY_NO_MAIN -c $< -o $@ + +-include $(LIB_OBJECTS:$(BUILD)/%.o=$(BUILD)/%.d) + +$(BUILD)/libdatgpt.a: $(LIB_OBJECTS) $(BUILD)/.network-protocol + $(AR) rcs $@ $(LIB_OBJECTS) + $(RANLIB) $@ + +# The DPI uses S7's reader for dat.scm, treating archive metadata as data and +# never evaluating archive-supplied Scheme forms. +DPI_LIB_OBJECTS = $(patsubst $(BUILD)/%.o,$(BUILD)/dpi/%.o,$(filter-out $(BUILD)/dat_s7.o $(BUILD)/dat_scheme.o $(BUILD)/s7.o,$(LIB_OBJECTS))) +$(BUILD)/dpi: + mkdir -p $@ + +$(BUILD)/dpi/%.o: src/%.c NETWORK_PROTOCOL $(BUILD)/.network-protocol | $(BUILD)/dpi + $(CC) $(CPPFLAGS) $(CFLAGS) -MMD -MP -c $< -o $@ + +$(BUILD)/dpi/sleep_verify_lib.o: src/sleep_verify.c NETWORK_PROTOCOL $(BUILD)/.network-protocol | $(BUILD)/dpi + $(CC) $(CPPFLAGS) $(CFLAGS) -MMD -MP -DSLEEP_VERIFY_NO_MAIN -c $< -o $@ + +$(BUILD)/dpi_s7.o: $(BUILD)/s7.o | $(BUILD) + $(OBJCOPY) --redefine-sym main=dat_dpi_s7_standalone_main $< $@ + +-include $(DPI_LIB_OBJECTS:$(BUILD)/dpi/%.o=$(BUILD)/dpi/%.d) $(LIBCESS_OBJECTS:$(BUILD)/libcess/%.o=$(BUILD)/libcess/%.d) + +$(BUILD)/libdatgpt-dpi.a: $(DPI_LIB_OBJECTS) $(BUILD)/.network-protocol + $(AR) rcs $@ $(DPI_LIB_OBJECTS) + $(RANLIB) $@ + +$(BUILD)/sleep-verify: src/sleep_verify.c $(BUILD)/libdatgpt.a $(BUILD)/.network-protocol + $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) src/sleep_verify.c $(BUILD)/libdatgpt.a $(LIBS) -o $@ + +$(BUILD)/dat: src/dat_cli.c NETWORK_PROTOCOL $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(BUILD)/.network-protocol + $(CC) $(CPPFLAGS) -DDAT_CLI_NAME=\"dat\" $(CFLAGS) $(LDFLAGS) src/dat_cli.c $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) -o $@ + +$(BUILD)/ddat: src/dat_cli.c NETWORK_PROTOCOL $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(BUILD)/.network-protocol + $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) src/dat_cli.c $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) -o $@ + +$(BUILD)/cable: src/cable_cli.c $(BUILD)/libdatgpt.a $(BUILD)/libcess.a + $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $< $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) -o $@ + +$(BUILD)/cesspool-bootstrap: libcess/tools/cesspool-bootstrap.c $(BUILD)/libcess.a + $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $< $(BUILD)/libcess.a $(SODIUM_LIBS) -o $@ + +$(BUILD)/ddat-scheme: src/dat_scheme_cli.c $(BUILD)/libdatgpt.a $(BUILD)/libcess.a + $(CC) $(CPPFLAGS) -DDAT_SCHEME_DIR=\"$(DAT_SCHEME_DIR)\" $(CFLAGS) $(LDFLAGS) $< $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) -o $@ + +SDL2_CFLAGS ?= $(shell pkg-config --cflags sdl2) +SDL2_LIBS ?= $(shell pkg-config --libs sdl2) +SDL2_TTF_CFLAGS ?= $(shell pkg-config --cflags SDL2_ttf) +SDL2_TTF_LIBS ?= $(shell pkg-config --libs SDL2_ttf) +DAT_FONT_PATH ?= $(PREFIX)/share/datgpt/fonts/MorePerfectDOSVGA.ttf +DAT_UNIFONT_PATH ?= $(PREFIX)/share/datgpt/fonts/Unifont-18.0.01.otf +DAT_SPEAKER_PATH ?= $(PREFIX)/share/datgpt/icons/datthang-speaker.bmp +ifeq ($(ENABLE_SDL2),1) +GUI_CPPFLAGS = -D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE=700 -DDAT_FONT_PATH=\"$(DAT_FONT_PATH)\" -DDAT_UNIFONT_PATH=\"$(DAT_UNIFONT_PATH)\" -DDAT_SPEAKER_PATH=\"$(DAT_SPEAKER_PATH)\" +GUI_INSTALL = rm -f "$(DESTDIR)$(PREFIX)/bin/ddat-gui" && \ + $(INSTALL) -d "$(DESTDIR)$(PREFIX)/share/datgpt/fonts" "$(DESTDIR)$(PREFIX)/share/datgpt/icons" && \ + $(INSTALL) -m 644 assets/fonts/MorePerfectDOSVGA.ttf "$(DESTDIR)$(PREFIX)/share/datgpt/fonts/MorePerfectDOSVGA.ttf" && \ + $(INSTALL) -m 644 assets/fonts/Unifont-18.0.01.otf "$(DESTDIR)$(PREFIX)/share/datgpt/fonts/Unifont-18.0.01.otf" && \ + $(INSTALL) -m 644 assets/fonts/Unifont-OFL-1.1.txt "$(DESTDIR)$(PREFIX)/share/datgpt/fonts/Unifont-OFL-1.1.txt" && \ + $(INSTALL) -m 644 assets/datthang-speaker.bmp "$(DESTDIR)$(PREFIX)/share/datgpt/icons/datthang-speaker.bmp" && \ + $(INSTALL) -m 755 $(BUILD)/dat-thang "$(DESTDIR)$(PREFIX)/bin/dat-thang" && \ + $(INSTALL) -m 644 docs/dat-thang.1 "$(DESTDIR)$(PREFIX)/share/man/man1/dat-thang.1" && \ + $(INSTALL) -m 755 $(BUILD)/cable-thang "$(DESTDIR)$(PREFIX)/bin/cable-thang" && \ + $(INSTALL) -m 644 docs/cable-thang.1 "$(DESTDIR)$(PREFIX)/share/man/man1/cable-thang.1" && \ + $(INSTALL) -m 644 docs/ddat-gui.md "$(DESTDIR)$(PREFIX)/share/doc/dat-gpt/ddat-gui.md" +else +GUI_INSTALL = : +endif +$(BUILD)/dat-thang: src/ddat_gui.c $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/libdatgpt.a $(BUILD)/libcess.a assets/fonts/MorePerfectDOSVGA.ttf assets/datthang-speaker.bmp | $(BUILD) + $(CC) $(CPPFLAGS) -DDAT_THANG_VERSION=\"v$(VERSION)\" $(GUI_CPPFLAGS) $(CFLAGS) $(SDL2_CFLAGS) $(SDL2_TTF_CFLAGS) $(LDFLAGS) src/ddat_gui.c $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) $(SDL2_TTF_LIBS) $(SDL2_LIBS) $(CABLE_MEDIA_LIBS) -lm -o $@ + +$(BUILD)/cable_media.o: src/cable_media.c inc/cable_media.h | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) $(CABLE_MEDIA_CFLAGS) -MMD -MP -c $< -o $@ + +$(BUILD)/cable_video.o: src/cable_video.c inc/cable_video.h | $(BUILD) + $(CC) $(CPPFLAGS) $(SDL2_CFLAGS) $(CFLAGS) $(CABLE_MEDIA_CFLAGS) -MMD -MP -c $< -o $@ + +$(BUILD)/cable-thang: src/ddat_gui.c $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/libdatgpt.a $(BUILD)/libcess.a assets/fonts/MorePerfectDOSVGA.ttf assets/datthang-speaker.bmp | $(BUILD) + $(CC) $(CPPFLAGS) -DDAT_THANG_VERSION=\"v$(VERSION)\" $(GUI_CPPFLAGS) $(CFLAGS) $(SDL2_CFLAGS) $(SDL2_TTF_CFLAGS) $(LDFLAGS) src/ddat_gui.c $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) $(SDL2_TTF_LIBS) $(SDL2_LIBS) $(CABLE_MEDIA_LIBS) -lm -o $@ + +.PHONY: gui +gui: $(BUILD)/dat-thang + +# Native Dillo DPI plugin; dpid supplies an authenticated socket on stdin/stdout. +$(BUILD)/dat.dpi: src/dat_dpi.c src/dat_dpi_icons.h NETWORK_PROTOCOL $(BUILD)/libdatgpt-dpi.a $(BUILD)/libcess.a $(BUILD)/dpi_s7.o $(BUILD)/.network-protocol + $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) src/dat_dpi.c $(BUILD)/libdatgpt-dpi.a $(BUILD)/libcess.a $(BUILD)/dpi_s7.o $(LIBS) -o $@ + +$(BUILD)/test_%: tests/test_%.c $(BUILD)/libdatgpt.a $(BUILD)/libcess.a + $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $(TEST_LDFLAGS) $< $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) -o $@ + +$(BUILD)/test_dpi_socket: tests/test_dpi_socket.c | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $< -o $@ + +$(BUILD)/test_gui_p2p: tests/test_gui_p2p.c src/ddat_gui.c $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/libdatgpt.a $(BUILD)/libcess.a assets/fonts/MorePerfectDOSVGA.ttf assets/datthang-speaker.bmp + $(CC) $(CPPFLAGS) $(GUI_CPPFLAGS) $(CFLAGS) $(SDL2_CFLAGS) $(SDL2_TTF_CFLAGS) $(LDFLAGS) $(TEST_LDFLAGS) $< $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) $(SDL2_TTF_LIBS) $(SDL2_LIBS) $(CABLE_MEDIA_LIBS) -lm -o $@ + +$(BUILD)/test_version_bar: tests/test_version_bar.c src/ddat_gui.c $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/libdatgpt.a $(BUILD)/libcess.a assets/fonts/MorePerfectDOSVGA.ttf assets/datthang-speaker.bmp + $(CC) $(CPPFLAGS) $(GUI_CPPFLAGS) $(CFLAGS) $(SDL2_CFLAGS) $(SDL2_TTF_CFLAGS) $(LDFLAGS) $(TEST_LDFLAGS) $< $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) $(SDL2_TTF_LIBS) $(SDL2_LIBS) $(CABLE_MEDIA_LIBS) -lm -o $@ + +$(BUILD)/test_cable_thang: tests/test_cable_thang.c src/ddat_gui.c $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/libdatgpt.a $(BUILD)/libcess.a assets/fonts/MorePerfectDOSVGA.ttf assets/datthang-speaker.bmp + $(CC) $(CPPFLAGS) $(GUI_CPPFLAGS) $(CFLAGS) $(SDL2_CFLAGS) $(SDL2_TTF_CFLAGS) $(LDFLAGS) $(TEST_LDFLAGS) $< $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) $(SDL2_TTF_LIBS) $(SDL2_LIBS) $(CABLE_MEDIA_LIBS) -lm -o $@ + +$(TESTS): $(PUBLIC_HEADERS) + +test: all $(TESTS) $(GUI_TESTS) $(BUILD)/test_dpi_socket + @echo "TEST Cable address CLI" + @$(BUILD)/cable address cabal://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | grep -q '^cable://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa$$' + @$(BUILD)/cable address hyper://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | grep -q '^cable://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa$$' + @echo "TEST Cabal JS/Cable side-by-side room and user identity" + @if command -v "$(NODE)" >/dev/null 2>&1 && CABAL_JS_MODULE="$(CABAL_JS_MODULE)" "$(NODE)" -e 'require(process.env.CABAL_JS_MODULE)' >/dev/null 2>&1; then \ + CABAL_JS_MODULE="$(CABAL_JS_MODULE)" CABAL_BIN="$(CURDIR)/$(BUILD)/cable" "$(NODE)" tests/interop/cabal/cabal_cable_side_by_side.js; \ + else echo "SKIP Cabal JS/Cable interop (run make test-cabal-interop on a host with Node and cabal-core installed)"; fi + @if command -v "$(NODE)" >/dev/null 2>&1 && CABAL_CLIENT_MODULE="$(CABAL_CLIENT_MODULE)" "$(NODE)" -e 'require(process.env.CABAL_CLIENT_MODULE)' >/dev/null 2>&1; then \ + CABAL_CLIENT_MODULE="$(CABAL_CLIENT_MODULE)" "$(NODE)" tests/interop/cabal/cabal_cli_features.js; \ + else echo "SKIP cabal-cli multi-peer workflows (run make test-cabal-interop on a host with Node and cabal-client installed)"; fi + @if test "$(ENABLE_SDL2)" = 1 && command -v "$(NODE)" >/dev/null 2>&1 && CABAL_JS_MODULE="$(CABAL_JS_MODULE)" "$(NODE)" -e 'require(process.env.CABAL_JS_MODULE)' >/dev/null 2>&1; then \ + CABAL_JS_MODULE="$(CABAL_JS_MODULE)" CABLE_CABAL_CORE="$(CABAL_JS_MODULE)" "$(NODE)" tests/interop/cabal/cable_js_c_live.js $(BUILD)/test_cable_thang; \ + else echo "SKIP C SDL/Cabal JS live test (use make test-cable-live ENABLE_SDL2=1 with Node and cabal-core installed)"; fi + @echo "TEST bundled S7 JSON/S-expression adapter" + @json_output=$$($(BUILD)/ddat-scheme tests/test-json.scm 2>&1); \ + printf '%s\n' "$$json_output"; \ + printf '%s\n' "$$json_output" | grep -q '^S7 JSON/S-expression adapter: PASS$$'; \ + ! printf '%s\n' "$$json_output" | grep -q '^;' + @if command -v "$(NODE)" >/dev/null 2>&1; then "$(NODE)" tests/interop/cabal/cable_sexpr_boundary.js; fi + @set -e; for test_program in $(TESTS); do echo "TEST $$test_program"; $$test_program; done + @set -e; for test_program in $(GUI_TESTS); do echo "TEST $$test_program"; SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy $$test_program; done + @echo "TEST current Dillo DPIP helper protocol" + @sh tests/test_dpi.sh $(BUILD)/dat.dpi $(BUILD)/dat $(BUILD)/test_dpi_socket + @set -e; cli_root=$$(mktemp -d /tmp/datgpt-cli.XXXXXX); mkdir -p "$$cli_root/home"; \ + echo "TEST dat doctor local network primitives"; \ + $(BUILD)/ddat doctor | grep -q '^cesspool: UDP socket bound on 127.0.0.1:'; \ + $(BUILD)/ddat doctor 0000000000000000000000000000000000000000000000000000000000000000 | grep -q '^address: dat://'; \ + $(BUILD)/ddat --help | grep -q '^ddat '; \ + echo "TEST dat create/sync/log and dat:// address"; \ + HOME="$$cli_root/home" $(BUILD)/dat create "$$cli_root/archive" --title "hello" --description "archive test" > "$$cli_root/url"; \ + grep -q '^dat://' "$$cli_root/url"; \ + cp "$$cli_root/url" "$$cli_root/original-url"; \ + HOME="$$cli_root/home" $(BUILD)/dat dns set archive.example.test "$$cli_root/archive" > "$$cli_root/dns-set"; \ + grep -q '^DAT-DNS /.well-known/dat:$$' "$$cli_root/dns-set"; \ + HOME="$$cli_root/home" $(BUILD)/dat resolve archive.example.test | grep -q '^dat://'; \ + HOME="$$cli_root/home" $(BUILD)/dat dns list | grep -q '^archive.example.test[[:space:]]'; \ + HOME="$$cli_root/home" $(BUILD)/dat dns remove archive.example.test; \ + mkdir -p "$$cli_root/empty-home"; \ + cp "$$cli_root/archive/.dat/metadata.key" "$$cli_root/metadata-key-before-readonly-sync"; \ + if HOME="$$cli_root/empty-home" $(BUILD)/dat sync "$$cli_root/archive" >/dev/null 2>&1; then exit 1; fi; \ + cmp "$$cli_root/metadata-key-before-readonly-sync" "$$cli_root/archive/.dat/metadata.key"; \ + grep -q '(title "hello")' "$$cli_root/archive/dat.scm"; \ + grep -q '(description "archive test")' "$$cli_root/archive/dat.scm"; \ + grep -q '(url "dat://' "$$cli_root/archive/dat.scm"; \ + $(BUILD)/dat log "$$cli_root/archive" | grep -q dat.scm; \ + test ! -e "$$cli_root/archive/dat.json"; \ + HOME="$$cli_root/home" $(BUILD)/dat create "$$cli_root/empty" --yes > /dev/null; \ + test -f "$$cli_root/empty/dat.scm"; \ + test ! -e "$$cli_root/empty/dat.json"; \ + mkdir -p "$$cli_root/scheme-created"; \ + $(BUILD)/ddat-scheme -e "(dat-close (dat-create \"$$cli_root/scheme-created\"))" >/dev/null; \ + grep -q '(title "scheme-created")' "$$cli_root/scheme-created/dat.scm"; \ + $(BUILD)/dat log "$$cli_root/scheme-created" | grep -q dat.scm; \ + echo hello > "$$cli_root/archive/hello.txt"; \ + echo hidden > "$$cli_root/archive/.hidden"; \ + echo '*.skip' > "$$cli_root/archive/.datignore"; \ + echo ignored > "$$cli_root/archive/ignored.skip"; \ + HOME="$$cli_root/home" $(BUILD)/dat sync "$$cli_root/archive" > /dev/null; \ + $(BUILD)/dat status "$$cli_root/archive" | sed -n 's/^Archive: //p' > "$$cli_root/updated-url"; \ + cmp "$$cli_root/original-url" "$$cli_root/updated-url"; \ + $(BUILD)/dat status "$$cli_root/archive" | grep -q 'Metadata entries:'; \ + $(BUILD)/dat url --compact '' "$$cli_root/archive" | grep -q '^dat://'; \ + $(BUILD)/dat files "$$cli_root/archive" | grep -q hello.txt; \ + $(BUILD)/dat verify "$$cli_root/archive" >/dev/null; \ + $(BUILD)/dat log "$$cli_root/archive" | grep -q hello.txt; \ + ! $(BUILD)/dat log "$$cli_root/archive" | grep -qE '\.(hidden|datignore)|ignored\.skip'; \ + old_version=$$($(BUILD)/dat log "$$cli_root/archive" | awk '/hello.txt/{print $$1; exit}'); \ + echo newer > "$$cli_root/archive/hello.txt"; \ + HOME="$$cli_root/home" $(BUILD)/dat sync "$$cli_root/archive" > /dev/null; \ + test "$$($(BUILD)/dat cat "$$old_version" /hello.txt "$$cli_root/archive")" = hello; \ + $(BUILD)/dat url "$$old_version" /hello.txt "$$cli_root/archive" | \ + grep -Eq "^dat://[0-9a-f]{64}\\+$$old_version/hello.txt$$"; \ + $(BUILD)/dat checkout "$$old_version" "$$cli_root/snapshot" "$$cli_root/archive"; \ + test "$$(cat "$$cli_root/snapshot/hello.txt")" = hello; \ + versions_before=$$($(BUILD)/dat log "$$cli_root/archive" | wc -l); \ + HOME="$$cli_root/home" $(BUILD)/dat sync "$$cli_root/archive" > /dev/null; \ + test "$$($(BUILD)/dat log "$$cli_root/archive" | wc -l)" = "$$versions_before"; \ + $(BUILD)/dat files "$$old_version" "$$cli_root/archive" | grep -q hello.txt; \ + rm "$$cli_root/archive/hello.txt"; \ + HOME="$$cli_root/home" $(BUILD)/dat sync "$$cli_root/archive" > /dev/null; \ + $(BUILD)/dat log "$$cli_root/archive" | grep -q 'del .*hello.txt' + @echo "TEST historical .dat fixture" + @$(BUILD)/sleep-verify .dat + @echo "TEST dat/ddat side-by-side archive compatibility" + @sh tests/test_cli_parity.sh $(BUILD)/dat $(BUILD)/ddat + +test-cabal-interop: $(BUILD)/cable + CABAL_JS_MODULE="$(CABAL_JS_MODULE)" CABAL_BIN="$(CURDIR)/$(BUILD)/cable" "$(NODE)" tests/interop/cabal/cabal_cable_side_by_side.js + CABAL_CLIENT_MODULE="$(CABAL_CLIENT_MODULE)" "$(NODE)" tests/interop/cabal/cabal_cli_features.js + CABAL_JS_MODULE="$(CABAL_JS_MODULE)" CABLE_CABAL_CORE="$(CABAL_JS_MODULE)" "$(NODE)" tests/interop/cabal/cabal_core_hyperswarm_live.js + +ifeq ($(ENABLE_SDL2),1) +test-cable-live: $(BUILD)/test_cable_thang + CABAL_JS_MODULE="$(CABAL_JS_MODULE)" CABLE_CABAL_CORE="$(CABAL_JS_MODULE)" "$(NODE)" tests/interop/cabal/cable_js_c_live.js $(BUILD)/test_cable_thang +else +test-cable-live: + @echo "test-cable-live requires ENABLE_SDL2=1, SDL2, Node.js, and Cabal Core" >&2 + @exit 2 +endif + +install: all + $(INSTALL) -d "$(DESTDIR)$(PREFIX)/lib" "$(DESTDIR)$(PREFIX)/include/datgpt" "$(DESTDIR)$(PREFIX)/include/libcess" "$(DESTDIR)$(PREFIX)/bin" "$(DESTDIR)$(PREFIX)/share/man/man1" "$(DESTDIR)$(PREFIX)/share/doc/dat-gpt" "$(DESTDIR)$(DPIDIR)" + $(INSTALL) -m 644 $(BUILD)/libdatgpt.a "$(DESTDIR)$(PREFIX)/lib/libdatgpt.a" + $(INSTALL) -m 644 $(BUILD)/libcess.a "$(DESTDIR)$(PREFIX)/lib/libcess.a" + $(INSTALL) -m 644 $(PUBLIC_HEADERS) "$(DESTDIR)$(PREFIX)/include/datgpt/" + $(INSTALL) -m 644 libcess/include/cesspool.h libcess/include/cess_net.h libcess/include/cess_compat.h "$(DESTDIR)$(PREFIX)/include/libcess/" + $(INSTALL) -m 755 $(BUILD)/sleep-verify "$(DESTDIR)$(PREFIX)/bin/sleep-verify" + $(INSTALL) -m 755 $(BUILD)/ddat "$(DESTDIR)$(PREFIX)/bin/ddat" + $(INSTALL) -m 755 $(BUILD)/cable "$(DESTDIR)$(PREFIX)/bin/cable" + $(INSTALL) -m 755 $(BUILD)/cesspool-bootstrap "$(DESTDIR)$(PREFIX)/bin/cesspool-bootstrap" + $(S7_INSTALL) + $(GUI_INSTALL) + $(S7_SCHEME_INSTALL) + $(INSTALL) -m 755 $(BUILD)/dat.dpi "$(DESTDIR)$(DPIDIR)/dat.dpi" + $(INSTALL) -m 644 docs/dat.1 "$(DESTDIR)$(PREFIX)/share/man/man1/dat.1" + $(INSTALL) -m 644 docs/cable.1 "$(DESTDIR)$(PREFIX)/share/man/man1/cable.1" + $(INSTALL) -m 644 docs/cesspool-bootstrap.1 "$(DESTDIR)$(PREFIX)/share/man/man1/cesspool-bootstrap.1" + $(INSTALL) -m 644 docs/libcess.md "$(DESTDIR)$(PREFIX)/share/doc/dat-gpt/libcess.md" + $(INSTALL) -m 644 docs/key-management.md docs/cable-interop.md docs/ddat-gui.md docs/dillo.md "$(DESTDIR)$(PREFIX)/share/doc/dat-gpt/" + +# Build an install-tree archive suitable for Linux distribution and testing. +# System libraries remain explicit runtime dependencies; this is not an AppImage. +package: package-linux + +package-linux: all + rm -rf "$(PACKAGE_STAGE)" + $(MAKE) BUILD="$(BUILD)" DESTDIR="$(PACKAGE_STAGE)" PREFIX=/usr install + mkdir -p "$(PACKAGE_DIR)" + tar -czf "$(PACKAGE_DIR)/dat-gpt-$(VERSION)-linux-$(PACKAGE_ARCH).tar.gz" -C "$(PACKAGE_STAGE)" . + +uninstall: + rm -f "$(DESTDIR)$(PREFIX)/lib/libdatgpt.a" "$(DESTDIR)$(PREFIX)/lib/libcess.a" "$(DESTDIR)$(PREFIX)/include/libcess/cesspool.h" "$(DESTDIR)$(PREFIX)/include/libcess/cess_net.h" "$(DESTDIR)$(PREFIX)/include/libcess/cess_compat.h" "$(DESTDIR)$(PREFIX)/share/doc/dat-gpt/libcess.md" "$(DESTDIR)$(PREFIX)/bin/sleep-verify" "$(DESTDIR)$(PREFIX)/bin/ddat" "$(DESTDIR)$(PREFIX)/bin/cable" "$(DESTDIR)$(PREFIX)/bin/cesspool-bootstrap" "$(DESTDIR)$(PREFIX)/bin/ddat-scheme" "$(DESTDIR)$(PREFIX)/bin/ddat-gui" "$(DESTDIR)$(PREFIX)/bin/dat-thang" "$(DESTDIR)$(PREFIX)/bin/cable-thang" "$(DESTDIR)$(DPIDIR)/dat.dpi" "$(DESTDIR)$(PREFIX)/share/datgpt/cable_hyperswarm.js" "$(DESTDIR)$(PREFIX)/share/datgpt/cable_bootstrap.js" "$(DESTDIR)$(PREFIX)/share/datgpt/fonts/MorePerfectDOSVGA.ttf" "$(DESTDIR)$(PREFIX)/share/datgpt/fonts/Unifont-18.0.01.otf" "$(DESTDIR)$(PREFIX)/share/datgpt/fonts/Unifont-OFL-1.1.txt" "$(DESTDIR)$(PREFIX)/share/datgpt/icons/datthang-speaker.bmp" "$(DESTDIR)$(PREFIX)/share/man/man1/dat.1" "$(DESTDIR)$(PREFIX)/share/man/man1/cable.1" "$(DESTDIR)$(PREFIX)/share/man/man1/cesspool-bootstrap.1" "$(DESTDIR)$(PREFIX)/share/man/man1/dat-thang.1" "$(DESTDIR)$(PREFIX)/share/man/man1/cable-thang.1" "$(DESTDIR)$(PREFIX)/share/doc/dat-gpt/key-management.md" "$(DESTDIR)$(PREFIX)/share/doc/dat-gpt/cable-interop.md" "$(DESTDIR)$(PREFIX)/share/doc/dat-gpt/ddat-gui.md" "$(DESTDIR)$(PREFIX)/share/doc/dat-gpt/dillo.md" + $(S7_UNINSTALL) + @for header in $(notdir $(PUBLIC_HEADERS)); do rm -f "$(DESTDIR)$(PREFIX)/include/datgpt/$$header"; done + +clean: + @if test -d "$(BUILD)"; then find "$(BUILD)" -type f \( -name '*.o' -o -name '*.d' -o -name '*.a' -o -name '*.so' -o -name '*.so.*' -o -name '*.dll' -o -name '*.exe' \) -delete; fi + rm -f "$(BUILD)/.network-protocol" "$(BUILD)/.network-protocol.tmp" "$(BUILD)/libdatgpt.a" "$(BUILD)/libdatgpt-dpi.a" "$(BUILD)/libcess.a" "$(BUILD)/dat" "$(BUILD)/ddat" "$(BUILD)/cable" "$(BUILD)/cesspool-bootstrap" "$(BUILD)/sleep-verify" "$(BUILD)/ddat-scheme" "$(BUILD)/dat.dpi" "$(BUILD)/dat-thang" "$(BUILD)/cable-thang" "$(BUILD)/test_"* "$(BUILD)/test-*" + rm -rf "$(BUILD)/dpi" "$(BUILD)/libcess" + $(MAKE) -C libcess clean + +distclean: clean + rm -f config.mk config.h config.log diff --git a/Makefile.windows b/Makefile.windows new file mode 100644 index 0000000..e92a173 --- /dev/null +++ b/Makefile.windows @@ -0,0 +1,150 @@ +CC := gcc +AR := ar +RANLIB := ranlib +WINDRES := windres +BUILD := build/windows +VERSION ?= $(shell tr -d '\r\n' < VERSION) +NETWORK_PROTOCOL := $(strip $(file < NETWORK_PROTOCOL)) +CPPFLAGS := -I. -Iinc -Ilibcess/include -DSLEEP_SALT -DCESSPOOL_HAVE_SODIUM -DCESSPOOL_CONFIG_DIRECTORY=\"dat-gpt\" -DCESSPOOL_NETWORK_PROTOCOL=\"$(NETWORK_PROTOCOL)\" -include inc/win_compat.h -DDAT_BUILD_DIR=\"$(BUILD)\" -DDAT_NETWORK_PROTOCOL=\"$(NETWORK_PROTOCOL)\" $(shell pkg-config --cflags libcurl) +CFLAGS := -O2 -std=c99 -Wall -Wextra -Wpedantic +TEST_LDFLAGS := -Wl,--stack,8388608 +SODIUM_LIBS := $(shell pkg-config --libs libsodium) +CURL_LIBS := $(shell pkg-config --libs libcurl) +SDL_CFLAGS := $(shell pkg-config --cflags sdl2 SDL2_ttf) +SDL_LIBS := $(shell pkg-config --libs sdl2 SDL2_ttf) +ENABLE_CABLE_MEDIA ?= 0 +FFMPEG_MEDIA_AVAILABLE := $(shell pkg-config --exists 'libavcodec >= 59' 'libavformat >= 59' 'libavutil >= 57' 'libswresample >= 4' 'libswscale >= 6' 2>/dev/null && echo 1 || echo 0) +ifeq ($(ENABLE_CABLE_MEDIA),1) +ifneq ($(FFMPEG_MEDIA_AVAILABLE),1) +$(error Cable media needs FFmpeg 5.0+ development packages: libavcodec, libavformat, libavutil, libswresample, libswscale) +endif +FFMPEG_MEDIA_CFLAGS := -DCABLE_HAVE_FFMPEG $(shell pkg-config --cflags libavformat libavcodec libavutil libswresample libswscale) +FFMPEG_MEDIA_LIBS := $(shell pkg-config --libs libavformat libavcodec libavutil libswresample libswscale) +endif +LIBS := $(SODIUM_LIBS) $(CURL_LIBS) -lws2_32 -lm + +LIB_SOURCES := base32768 cable cable_feed dat dat_browse dat_dns dat_walk dat_wire dat_session sleep sleep_tree sleep_bitfield sleep_proto sleep_crypto sleep_writer sleep_feed sleep_proof sleep_replication sleep_utp dat_s7 dat_scheme s7 +LIB_OBJECTS := $(addprefix $(BUILD)/,$(addsuffix .o,$(LIB_SOURCES))) $(BUILD)/sleep_verify_lib.o +LIBCESS_OBJECTS := $(BUILD)/libcess/cess_net.o $(BUILD)/libcess/cesspool.o $(BUILD)/libcess/cess_identity.o + +.PHONY: all clean test test-paths test-discovery test-version-bar test-gui-p2p test-gui-media package-assets +.PHONY: FORCE +WINDOWS_PROGRAMS := $(addprefix $(BUILD)/,dat.exe ddat.exe cable.exe cesspool-bootstrap.exe sleep-verify.exe ddat-scheme.exe dat-thang.exe cable-thang.exe) +all: $(BUILD)/libcess.a $(WINDOWS_PROGRAMS) package-assets + +$(BUILD): + mkdir -p $(BUILD) + +$(BUILD)/.network-protocol-$(NETWORK_PROTOCOL): NETWORK_PROTOCOL | $(BUILD) + @echo $(NETWORK_PROTOCOL) > "$@" + +$(BUILD)/%.o: src/%.c inc/win_compat.h NETWORK_PROTOCOL $(BUILD)/.network-protocol-$(NETWORK_PROTOCOL) | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) -MMD -MP -c $< -o $@ + +$(BUILD)/libcess: + mkdir -p $@ + +$(BUILD)/libcess/%.o: libcess/src/%.c libcess/include/cesspool.h libcess/include/cess_net.h NETWORK_PROTOCOL $(BUILD)/.network-protocol-$(NETWORK_PROTOCOL) | $(BUILD)/libcess + $(CC) $(CPPFLAGS) $(CFLAGS) -MMD -MP -c $< -o $@ + +$(BUILD)/libcess.a: $(LIBCESS_OBJECTS) + $(AR) rcs $@ $(LIBCESS_OBJECTS) + $(RANLIB) $@ + +$(BUILD)/s7.o: src/s7.c inc/win_compat.h | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) -w -MMD -MP -c $< -o $@ + +$(BUILD)/dat_thang_icon.o: assets/dat-thang.rc assets/dat-thang.ico | $(BUILD) + $(WINDRES) -i $< -O coff -o $@ + +$(BUILD)/cable_media.o: src/cable_media.c inc/cable_media.h | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) $(FFMPEG_MEDIA_CFLAGS) -MMD -MP -c $< -o $@ + +$(BUILD)/cable_video.o: src/cable_video.c inc/cable_video.h | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) $(SDL_CFLAGS) $(FFMPEG_MEDIA_CFLAGS) -MMD -MP -c $< -o $@ + +$(BUILD)/sleep_verify_lib.o: src/sleep_verify.c | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) -DSLEEP_VERIFY_NO_MAIN -MMD -MP -c $< -o $@ + +-include $(LIB_OBJECTS:.o=.d) $(LIBCESS_OBJECTS:.o=.d) + +$(BUILD)/libdatgpt.a: $(LIB_OBJECTS) $(BUILD)/libcess.a + $(AR) rcs $@ $(LIB_OBJECTS) + $(RANLIB) $@ + +$(BUILD)/dat.exe: src/dat_cli.c $(BUILD)/libdatgpt.a + $(CC) $(CPPFLAGS) -DDAT_CLI_NAME=\"dat\" $(CFLAGS) $< $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) -o $@ + +$(BUILD)/ddat.exe: src/dat_cli.c $(BUILD)/libdatgpt.a + $(CC) $(CPPFLAGS) $(CFLAGS) $< $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) -o $@ + +$(BUILD)/cable.exe: src/cable_cli.c $(BUILD)/libdatgpt.a $(BUILD)/libcess.a + $(CC) $(CPPFLAGS) $(CFLAGS) $< $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) -o $@ + +$(BUILD)/cesspool-bootstrap.exe: libcess/tools/cesspool-bootstrap.c $(BUILD)/libcess.a + $(CC) $(CPPFLAGS) $(CFLAGS) $< $(BUILD)/libcess.a $(SODIUM_LIBS) -lws2_32 -o $@ + +$(BUILD)/sleep-verify.exe: src/sleep_verify.c $(BUILD)/libdatgpt.a + $(CC) $(CPPFLAGS) $(CFLAGS) $< $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) -o $@ + +$(BUILD)/ddat-scheme.exe: src/dat_scheme_cli.c $(BUILD)/libdatgpt.a $(BUILD)/libcess.a + $(CC) $(CPPFLAGS) -DDAT_SCHEME_DIR=\"scheme\" $(CFLAGS) $< $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) -o $@ + +$(BUILD)/test-paths.exe: tests/test_paths.c inc/dat_paths.h inc/win_compat.h | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) $< -o $@ + +CORE_TESTS := tree bitfield base32768 proto wire dns replication writer + +$(BUILD)/test-%.exe: tests/test_%.c $(BUILD)/libdatgpt.a | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) $(TEST_LDFLAGS) $< $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) -o $@ + +test: test-paths test-discovery test-version-bar test-gui-p2p $(addprefix $(BUILD)/test-,$(addsuffix .exe,$(CORE_TESTS))) + $(BUILD)/test-tree.exe + $(BUILD)/test-bitfield.exe + $(BUILD)/test-base32768.exe + $(BUILD)/test-proto.exe + $(BUILD)/test-wire.exe + $(BUILD)/test-dns.exe + $(BUILD)/test-replication.exe + $(BUILD)/test-writer.exe + +test-paths: $(BUILD)/test-paths.exe + $(BUILD)/test-paths.exe + +$(BUILD)/test-discovery.exe: tests/test_discovery.c $(BUILD)/libdatgpt.a | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) $(TEST_LDFLAGS) $< $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) -o $@ + +test-discovery: $(BUILD)/test-discovery.exe + $(BUILD)/test-discovery.exe + +$(BUILD)/test-version-bar.exe: tests/test_version_bar.c $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/libdatgpt.a | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) $(TEST_LDFLAGS) $(SDL_CFLAGS) $< $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) $(SDL_LIBS) $(FFMPEG_MEDIA_LIBS) -o $@ + +test-version-bar: $(BUILD)/test-version-bar.exe + $(BUILD)/test-version-bar.exe + +$(BUILD)/test-gui-p2p.exe: tests/test_gui_p2p.c $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/libdatgpt.a | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) $(TEST_LDFLAGS) $(SDL_CFLAGS) -DSDL_MAIN_HANDLED -Umain $< $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) $(SDL_LIBS) $(FFMPEG_MEDIA_LIBS) -mconsole -o $@ + +test-gui-p2p: $(BUILD)/test-gui-p2p.exe + $(BUILD)/test-gui-p2p.exe + +$(BUILD)/test-gui-media.exe: tests/test_gui_p2p.c $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/libdatgpt.a | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) $(TEST_LDFLAGS) $(SDL_CFLAGS) -DDAT_GUI_TEST_MEDIA -DSDL_MAIN_HANDLED -Umain $< $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) $(SDL_LIBS) $(FFMPEG_MEDIA_LIBS) -mconsole -o $@ + +test-gui-media: $(BUILD)/test-gui-media.exe + $(BUILD)/test-gui-media.exe + +$(BUILD)/dat-thang.exe: src/ddat_gui.c $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/libdatgpt.a $(BUILD)/dat_thang_icon.o + $(CC) $(CPPFLAGS) -DDAT_THANG_VERSION=\"v$(VERSION)\" $(CFLAGS) $(SDL_CFLAGS) src/ddat_gui.c $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/dat_thang_icon.o $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) $(SDL_LIBS) $(FFMPEG_MEDIA_LIBS) -o $@ + +$(BUILD)/cable-thang.exe: src/ddat_gui.c $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/libdatgpt.a $(BUILD)/dat_thang_icon.o + $(CC) $(CPPFLAGS) -DDAT_THANG_VERSION=\"v$(VERSION)\" $(CFLAGS) $(SDL_CFLAGS) src/ddat_gui.c $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/dat_thang_icon.o $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) $(SDL_LIBS) $(FFMPEG_MEDIA_LIBS) -o $@ + +package-assets: $(WINDOWS_PROGRAMS) | $(BUILD) + powershell.exe -NoProfile -Command "if (Test-Path -LiteralPath '$(BUILD)/cable_hyperswarm.js') { Remove-Item -LiteralPath '$(BUILD)/cable_hyperswarm.js' -Force }; if (Test-Path -LiteralPath '$(BUILD)/cable_bootstrap.js') { Remove-Item -LiteralPath '$(BUILD)/cable_bootstrap.js' -Force }" + powershell.exe -NoProfile -Command "Copy-Item -Path 'assets','scheme' -Destination '$(BUILD)' -Recurse -Force" + powershell.exe -NoProfile -Command "$$stage='dist/windows-x64'; if (Test-Path -LiteralPath $$stage) { Remove-Item -LiteralPath $$stage -Recurse -Force }; New-Item -ItemType Directory -Path $$stage | Out-Null; $$apps=@('dat.exe','ddat.exe','cable.exe','cesspool-bootstrap.exe','sleep-verify.exe','ddat-scheme.exe','dat-thang.exe','cable-thang.exe'); foreach ($$app in $$apps) { Copy-Item -LiteralPath (Join-Path '$(BUILD)' $$app) -Destination $$stage -Force }; Get-ChildItem -LiteralPath '$(BUILD)' -Filter '*.dll' -File | Copy-Item -Destination $$stage -Force; Copy-Item -Path 'assets','scheme' -Destination $$stage -Recurse -Force; New-Item -ItemType Directory -Path (Join-Path $$stage 'docs') -Force | Out-Null; Copy-Item -Path 'docs/windows-build.md','docs/dat-thang.1','docs/cable-thang.1','docs/cesspool-bootstrap.1','docs/dillo.md' -Destination (Join-Path $$stage 'docs') -Force; New-Item -ItemType Directory -Path 'dist/installers' -Force | Out-Null; $$zip='dist/installers/dat-gpt-v$(VERSION)-windows-x64.zip'; if (Test-Path -LiteralPath $$zip) { Remove-Item -LiteralPath $$zip -Force }; Compress-Archive -Path ($$stage + '/*') -DestinationPath $$zip -CompressionLevel Optimal" + +clean: + powershell.exe -NoProfile -Command "if (Test-Path -LiteralPath '$(BUILD)') { Get-ChildItem -LiteralPath '$(BUILD)' -Recurse -File | Where-Object { $$_.Extension -in '.o','.d','.exe','.a','.dll','.so' -or $$_.Name -in '.network-protocol-cesspool','.network-protocol.tmp' } | Remove-Item -Force }" diff --git a/NETWORK_PROTOCOL b/NETWORK_PROTOCOL new file mode 100644 index 0000000..dea08fb --- /dev/null +++ b/NETWORK_PROTOCOL @@ -0,0 +1 @@ +cesspool \ No newline at end of file diff --git a/README.txt b/README.txt index b89a932..0c670ac 100644 --- a/README.txt +++ b/README.txt @@ -4,12 +4,16 @@ breaking out of Node-JS jail and enabling both C and Scheme env dis dat://59d8099399510e23a3c693fb5887b7bba1586f4117f7ef1c5872622ada0c1181 -Warning: this may publicly expose your IP and the old tools are unmaintained + +be aware: seeding this may publicly expose your IP and the old tools are unmaintained + you might discuss here + cabal://cf2caf3a1c4fd299471d315416b6cc3d9c5f7c678046652fb70ede4ff16698db hosted on the web at -https://git.worlio.com/divorce/dat-thang/ +(prev) https://git.worlio.com/divorce/dat-thang/ +https://git.canithesis.org/divorce/dat-thang BUILD x86_64 GNU/Linux @@ -20,11 +24,18 @@ i implement the SLEEP spec as a C header (similar to REST with HTML) then i provide FFI bindings so you can call easily from scheme i also have a novel solution for hyperswarm-dht (i think) +this is pre-alpha prototype-level software + what this means and what this does: here be a p2p program that does away with the client-server model, and enables you to basically share any directory in your system as a torrent (basically esentially) -the 'thang' part is the client, eventually i'm targetting wasm +the 'thang' part is the client, a mashup between AIM, Kazaa, and Impulsetracker +right now trying SDL2 + +dat-thang currently supports serving html, addressing different versions of archives, integration with the dillo browser + +'cable' is a C port of cabal-cli, there is an experimental feature of sharing voice and video, which can be turned off with compile flags. "why? when hyper:// is here now with so cool much better justwerks dApps(Decentralized Apps)???" @@ -34,6 +45,10 @@ I like the sound of dat:// better, this is cooler no offense, and the scope of t "hyperswarm" sounds badass, its just what p2p need(s(ded since 201X)). i wanna be developing hyperswarm. in this implementation its called cesspool. so when people include that particular module in their distros they will package libcess. i've always wanted to write libcess and now here it is, baby. +cesspool is essentially (old) hyperswarm under the hood, in fact the only difference are the literal bytes 'cesspool' which you can change back to 'hypercore' in the NETWORK_PROTOCOL file and it (should) find peers on that network. similarly, theoretically, you can set your own to whatever and have your own network. + +you're typically going to be the only people on the network, i tried to make the bootstrapping node process as easy as possible and crossplatform. + i'm using C because ez libraries, assert is the ultimate hack, and scheme because macros *shablamo* and s7 lisp-in-C because hell yeah i specialize in beautiful elegant solutions to your networking problem. LICENSE diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..5af33ab --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.0.002 diff --git a/assets/dat-shell.cmd b/assets/dat-shell.cmd new file mode 100644 index 0000000..6cc84ae --- /dev/null +++ b/assets/dat-shell.cmd @@ -0,0 +1,3 @@ +@echo off +set "PATH=%~dp0;%PATH%" +echo dat-gpt command shell. Try: dat --help, ddat --help, cable --help diff --git a/assets/dat-thang.ico b/assets/dat-thang.ico new file mode 100644 index 0000000000000000000000000000000000000000..70faf55f78988de90b8c04b39846900c95a27acc GIT binary patch literal 1113 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 z z%b&C2IqNfL*Wv6goIR7Xw{vzj=d|D)Z_c^EIlnm9k#pTSHnV3a?vv`uE@oMxp*fR-{lf>E}6z9 z*SNGQm(JtT6nbcSG^B?cJyy}jpW)~`qZIMclvnHCz3w7TvwCpMsVFOu1n+kYFs~; z>kn}KbNZ6L{ph=lzVY;ZOy5FosKpI!xM2`CEaZj_+;D^&E^@;Y`cXt)<^V`dy>n2l^FoBe<~=H#XtMuG~178|QJOA2%N5#&~W_=yI^tYpb zEBd?9e+K=R(?6L0+1%8Oo4Rw;bZ*+fO$WItiJSg%vjaD~bMsnmKFiH{+|r#}j&RFw z1`K3C7z6%st2MW}a;rPHp5oSAZnNXI0o>-pZJ)Wl3AeB0_UGJDmpc}5#|`c@<<5cJ zxsE$ua90cNn#12Yh(o2@hKG;A|d@(0(B@8>ruwM*s z!SLk_Kf&Nn4)m!jtoP@(536@suG?b>OKnJmt?*XL;%? zPgmz@H=bU`(_48uoTu;bbUx3R@=SA{8NoBVdFCC@y7KH~p54W>cX-Z}=UVgJLY~{i zb1!+mDbFwD`9nPaj29~L!Z=dphF86K^%$?-;?+-#FUxpa#yc{;8{-Etej4Mw8NY||7a0GH@!5?3 z!)xZe)|uCq^4dOLyUuG_ysmk@F0c3H_4&Mhh}YAYAWX1gLN_MtV!~x6l<-Ce-WbCh zKD=>`H=glkdERv7%@Mpgo;N*s^9XMy@n#-xnedi1Z*}3ValEyTw~q2wDsO$^Z4=&Z z$JnWqGGL?~LP}&Ab!LJ5jvzh z=L_%r<6V2+?ZLa_cy}J}dh)Ix?*{Si5#Ig8d#!lSm-mu+zX|WJT;P@u4{% z4&%c)eCWf6p?sLcBw%kxYBZv;wAA zW_oX?FJ}5-ra$8g@`W>B%;$>`zIezNZ~4-UFGumEFJE5g%ddQ8!B=hhY93$h;;UG` zddJtMeBGO`z4&@RU#Ia6_{NcM#_-L0zB$P^Da=sJaAd|vW_U9rni+-6_{~hsObcc< zW@am9PGqJ#GnX;*Br|U^Go6{gnPtMPI?QUttO3kg!K|IkI?1=?_;xhkp5;5ucSHGZ z3E%DEyEwkf<@>UHUytwm@x43W`|$lCzJJd5Mf_mO4^8-C5I=bGLkK@y=7&sv_{Z#~ z%oq+SehSQ_;CS0?&8N-etgH=(#&nn+)>Q+ zV(ww){$yTP=FMf^e&(I$Crf@B$WJTy=@38H;^#j6yoH}j_{EK1_VP;xzc%96x%?Wz zuf@!7$NWLecW3@0=5J^IKIX?TKY{tl%>T#&u)vfBby?7q1+7`oodqLUFoOlFSa5^| zF)Vn~3HfP}i7Dlk}GrzUsx8D5b&Tm2dmdqls$bm&;S+tPfJ^7;_e@x;J z5B@m7AKCorz@H2F^CEx#WN~X24`%TK76-EUDvMKD{E;PKi8)JJvZNPF#|CaIJ4*omHf9Lt{9{*+WUx_YNS(kFurCfEXQM%N8 zU220abx4I=Y;TE;n75^V8){=yFeVxu3dxHC?`yEADwnUq4)n>uk?2I;x*JgLLS(-M>)aKCU z)wOw7Z9ZC?FVp5Hw0XKV|D`Rgv_(g4FbtPL}sfVsKRae@mE1l4l zl60kfUD;e$Zlf!Y)s?+<<+Hl-BV9#wm72OrcU@(YuChs2xu~mT>neYBRZCsfSy!E- ztFG5o59z9xbk%3NYLT|AtSue1Wq)lsQ(JD)mM694Lv8s}SF_gD#^`FhbhW#>+Am$b zv93N^SD&q`uhrG}>*|+u^?SN{rmkMBYnbaA?RAauy2dJ9BShD@s%yN|R!Upd&{mza z)o5+yt*rvJ)h%t6t7}%$HGArs9=hgfUGs^q`B&GfqigliwPxyCyLGK|x>ky=^;_4j zqHA~5wMXdM>vZifT{}tF&ee4)>pER@o!Pohpstgwtt)El{@Qx0w!WdQe`*^8ZDX%( z+H0F(+GeJb}9jEL1>bj?O-DF+2NZVUz`-!7SZioo;YKH+ZBQ6zPUFbVFC&aH(z> zp&J(IM$L4iIl9p)-Kan}cGQiR>&DT#@mJl%MmHI+o9xt0l6BMax@lM4bh&PNRX6>r zo7L6LoOH8sy4iBwEL1nUr<;{%2V3nhMmucK4%fBAXWiURHy^H>2kPe6bn`;pqM~lm zShwh|TTIX`R_GRix<#yR@kX~a)-Ai}mh*JWBf8~N?O0kncGQj@+VO~XOwx{jb*qND z)d<~cy>4}0x6071O?B(8y7hG3dW&uys#_=M)^By2vbs%k-DbRQvq`s!(``QLwwAiB zi*D z?l4w&2+|#%>yGBSV?W(-hwhlBouHi>YNs*UX{&a+p`Cte=Vsb@ns(l$oo{I8Ke|&( z-N{{dI-)y$(Vd&?&i!;}cinlr?tDpi&eUDX=`Ou=7eC$Qr0()iclo8e*414H>aHtv z*9*F9j_y`kceB>rdg^X-b+_HR+ZEmIz3y(JyEoF^U3K@_y88~@{gUpUpx`(gs5w3eY)2`62O|OAuKVrN{hsUoX1c$d?(eJnKhy&(^nh-9 zz(PIXfF5vP5BQ`98tQ>{^}tSg;5a>Sr5?CX4~*6W-|0andeC4!XqO&z|9=#nhdU5{ z7e}e?{oHSRmozmrHKo1xu3u@}N;D8cG^8y`LPAA}RJI~16)GhfXh}+YNQ;u*=lwg* zdCs8)wQ0e4TCj~4TqDEUWH^Hi50T+bGW<&mhtonAT6mro{-8xeY0*Yn6i$nBXwhF< z+?N)c(&B@(IG2_vTB1iw{-Y%xv?Pv}l+e;fv~(0LwWFmcY3UpcHOGZt}XaN}olF<_~?oGySWE?}rPspSZnG7M5)nt-PCZEZ)Cz%?OsVkWt zC)1l``jyNy$gBaGwI#FuWcDAKxsaJ3nWd6hHJNuI^Lb>xn#^~Rc^H|ellfyZ|3((| z$wH4Trjvy!S?nT<7_#_6mi@`{KeF^7%P_J`CCi6oSxzek(2D7_!h%-ppcN6cBAr&e zp_Pqkmy{HO4jelrZw42B%66;V@fth$>uoOgpY#go?!+Sio!nb1CW+UHCAVrbu0+V_&YHORXOdAA|&p5#5AyqA%;9eMkccQkq5 zBJU66BjnSLe1?(FO!8SvK6}Y0n0!u>PZIg$lFu9R`9=F1(*CZre;DmIr~R(9--q^} zq5T(W|2^9Oh7M@Z0WCV9M+aun0UJ87g${(!fqQh|FZnhn-;v~NLcYG_n?k;?=pfO- z4s>uj9dx6EF?8@Y9sEFk9m#JN`MHqaN%G4jzi)J?IUO>jL#yae2p!5J|N7+LmHa1= zza9C9l7Bk+zomdC6flJX)=)qa1ri0$rodAa_=^sYqr<**_%Q|PP>?kR#ZpiP1>K{d zClvIGg8tHxmUN^q9hpK$meUb?Iiwrw{`QF{6-R3OP$5 zX%v!8Ax|mf9fj7S&<+&Zk3z>%=n@KDPoaJk`k0P(q@&h!^c)@iPRGX4u|0IGgpTXc z@eOo5lfvpz*jx$=ps*(t-ipE(Q@AUIhg0}fI-x-)hS7<|bi$cV9HJBV=%hBC{EtrB z&`Cc!d7B~{P{cHf2&ae}6!C*jwV+eu=#(R!il9?h=+qmE97d7$6d6R3H!1QDMRlX7 zu@q%YQAa8221Wg%({1VWSUSCyPP^0T({%bVMfalUB^14pq9Z7}jLzuLnf`QU7oAC_ zGw6!(VWJ5ana#rslxE+y2bgh`a(MhO=v;X5S` zqeM4KyiAF&DDej+HK3&4lr))=tSHHolENt|lak(3vNk1;p=1k6-ayGglzg6&izvCA z&bOrVv+2Aeoe!e(SLpmVy3n64ET; zD0LR4uA|g5bg?B}G^C3zbTNo7ex|f>l;%%qZ|RaIU1~#@2GXS&bjgt}h0>*qbg76g z{h;)wls=Hs=TrI?N2&!CU9O}nE$GTPx?)aOJn70= zx>7(Hpo}4uv79noDI~IY6@K~psR1_Y9(D$bgd0t z>qXaw)3q6NZ8=@rN7qv6+B>?gMc0SXb!)o5m##pOS(7Nsjk?%ZQ#L5OA7wA5Y!Au~qwJfM{f=%lrW-xz#yGmMl5Tj> zjX1hdKsOcL96&dX=;ltknMOB%)2+^QYa-oRNw>V{RvO)UM>(A-X9DF|QqCdDxkNc- zbh|y>HlW*c=(aQ6K1{dc>2@LAeoJ>6(VeMu$C~c!r8~#y&NaGIM0Y+>Zez;rO1XNJ zJDGA#DE9#622pN2<=&&*FLbv)-PNbN)9CI}x@${!kI>yTy8DRk{-!*lJRQoLPI0&OZ7Mg>c#z@7?rQ^6@J$fSaoRPc`qwW)9*6&g~ZBNYZy;Wa9JO+^i;XgC#_ zP|+SLilL$`D*8f?8qlLY^k^nMa-c^?=us{`u1k*>)8kY0_%=P!q$l0!$y|EkN>5JF zll%1KA3fEhr;F+7ZhD$RPk+#}{`71fJzG!DPSCT5R9uUS`&02UD&9@SK~x+~#ko}c zg`R_+ccAAJ=(!C&-$l<)((_yNyp*2*qmuemqD3WrsAMda%%YN|RN_S?NmTNMUUZ}v zGw6jUy|_Rx%BZvnl@6xT8C2>_r9M;|Or=p&noOnFs5FmCUr=cUy(D_so?hzH%W3p- z6}|MNmr?Zc9=-fSubR`VKJ;oPy;?)B4$!M4dUc0hy{A{dsH_#08Bp0mDzm1t?Nk;) zW!Y3#L}l;jbwhgHieC4o*XH!vk6z!T*ERH}1HBnTZaF}0%?{~x{gr1wem{tJERLLa8n2YdRkmp+`J4~6ux z8GW2WANSD5v-I&XebS^){pizV`m~fj+0&<8^eK`)-Jnk|=yM(VtV^HA(`RG)yqP{9 zrO%h?a}j<1Ltk3a7hU=?hrZa-m)-OwjJ{l?FHfkv7L^aA@=;VimCEN+xiOWmrg9f5 z-$CUEsXUa*)2Y0W%FF3%JNi0}z8D$JrcK`_(>ELXwvoP_pl^5R+i&`ALEq!(M=ko%ntm*%A7S((mwvpbA3y1* z0sWjpKNrzY2l{!Ke&*B9@ARuJ{hCR?w$rZ&`jt(;K2T*NsvJm_3#rnUDtA+53{^g) zszy}RnX1N6l?7Gpp{f|F%A=}Z^t&GY?oPjF(C_v1JCJ^-(C-rZT~2=*)1Mji$BO>= z(Vr{yr;w`aQ*}?Oo;mZGCCfsFBV$CV68O54+ zSo1yCTFbRUxHh=I} z(q=V))kIb^SsAi2Vzq+RT2@Z1wzBeKb%*OV;kt%gcMsRS%60#6J#DTxitCwiy)|6V zh3jqOdfr?wj_Xx${f1n>71!^}^=EPYHC+EV*MG?M|8fIxgJ#^IGdCE;4VH3)jocuJ z8(ifE@3^7jhCR69cy4%v8-{bk%iQn-H=4zb)^MXE+~_7Zs^rGH+;}NB_Tk0}+@u*d zS-?#KxoItK+Mb&ZRqH zTQ}s^gSfRVw?4tGZ*c1h*6PSwlUQplYx%L(dDgnlTBY2k3%8lTZDLq^Hf#H__9JfF zncG@&+f&^39k=Vj?bdU<6mIvA+fU{8`?&pm?$C)ltl|!rxnl$FXviH;aL4!DsULT8 z;!dgDDTg~f=1w2DQ#I?fV4cCNGm~{JSjUxhd|2licdo~sr*r4M+&PE4DDElShMHr#y)caP^Dt+|IA_bBF`!?3af;K8~)cpMM5;laK<_&g6T z;K5aF(3A~^vcWPoILroFJVcX+4C5hoJS2>V6!OqoJajk@wdJ7~d6@99IXvtT4|~SL zyYO%e9-hL(t9ZmP9JaRsdJj5dlcvJ@-WyPbSc+_tmJ)B3o@#qX5!#rjw zk2%3(zVg_)JoYS)Q#{U+$EEQ2dOUs&kI&!|9l=1K2) z@(i9F%TqLX%6y)3fu}a-satsJE1ouir$zE~;_0S5J)LK?pp1{lVdHEM!{+o@6jT*C&HXC(iqrPl3l#Rx-(JVGv z%tjV$w2qBj*=Rc(?PsGSY!t~xNo;hTjqbBi2^)Q3qZ&4@$HuMMxGNhEVB^tjJcErF zv+)WxUeCsx*?2D-2e5HC8^^HmMK;c6ew_PBsZ-lPESxWs@8>DQ1%|Y^uSgP1sb2O$V~+1U6m3rsiyF$EI7^^Z=V4W78Nm zO=r_PZ2E#tzp&|FHfzjg9oS5d&Bn6X95yp%GkZ4M#%BI(7RzQCZ1#Z7KC`(dn>S(e zo@_pf&1bXuDmM3E^CN72na%UqyoAlavPCVn&}ED1Y~jfkUTkrQEkfDi6kEiw#Ray= zV2fL9@qjI!v&B2M_|6v9Y*~jb>$7EZwrtCmUD&cWTMlB&5o|euEoZRhe70Q1mKJQe znk^mJ(w$e#;*|^8su5eYVylj9)t#;Sv(-?x8qZd<*=jLcnX}buwsK)BPqx~}Rv~N^ z&sI0ts*G3F=2g>ql?$&r#jD=(s;_LV$<}q*x(!=*X6u1$J%Ozkv$Zu_Z)0mewvJ-! zRJOj))+KEHm2GOVjW*l#W}As@W5_l(Y~#i@hu9{JZ4%h#I@>&Bn;Kp{kXJkM>M&lN z&8t818XaCUjn{banj^d>jn_QmH8s4p1FxOHYt4DB1FzlAYlC@hHm`ljYya`O#=Nc* zuN%PY#`C&myv~u=dGWe1UUz}lcA9oc?6+k3Np0^3*f`WC!ikJneSLvMCi z#SZt`p_&~#vExj3bYjP&?3l)mW$aXso%*rU7a^ZP~MWuTmJA?9o{;Qx0>=+U*4L_9?Twn*<&Sp z9A%Fi?D3Viwd8HXdE08<7Q)+7d0Q!aYOtpkdk$mImF(%oo>A;sz@FcDdwt$Mg15Wz z_PxA4jJIFr?QeJo^NwD;V-fGz%{$V0rycKn#=BgB z%x52S_Ibzq^>}|I@BhFDCh&pHd?0~+8?o;c_Wi~OUHITf_Up-hR_qtUekFWJpAUKQ zp_lC6k^N2CKZ65mbHD@+Sj7Pz91y?(4>+Kj1G{kGDh@osftNV&6(82&!{&TAiVqj? z;XfSIn}eoukR=Cg;-Ck7WIP{P!$)E`xH$)}=HN08S;`@?9J+;%_Tr=IeDpIPtINlh z@Uf$O>^&cE&&SRAcp4x7$6g^_T`fs`D7kP zD2^D;5e^)2j3b`$sbPHTI7bRcT5{xmj*Q^Q7aaM6qv~>$F-KkC(@psFSU&B+rwce5 z96g4kw{i42jxOcse|%;%pYh@|kNC`QKHG!O8uM8vK6``D*6_JGd~OHF)aMvGj(Nnf z(>d0YV{dSr7ROn0TrtOw;P_NdsKW^!oN$d3DmbwjC(h%f zE&1XVzF5U+qd09Rr{(gcIeh5=U#j5rft>EZmpkxfTfY2|ujuiWy?o^fXIOH^SH9}N zS4;Vt1z-Ej*Y|Oz;>_)wS%QrvqEnU84!MA+)Rx026%sD!ovz&7RIVXv8?s3i+ zzFnVh_u<>K`1U%!y`OK#@$G!R{hsgC<~!Z_&P=|ui|^!fZfnk6&bh&y`<(B#=DWsx zH;nIo;=IwEcbxNy^H+2J4Zhcd?``9IANl@FzMshV-|+)Ie&EOt()qz(emIsNZsLbI zT+oOMmTj1tnZ4T-c8b=X2o}E)3+tSS~E%qBdMKn2R=Y(G@QG&X1b$ zqk;TrIzKY!N51?hogZuR;|ctDGe1t{$5s5KB|llfPyG2wDL)l{+MA!wmL4k zg}?sfir!pd#ub5Fk;~ue@V8<7&6dAK@VBS@y(xd6!rwRX_eB2wo_}=YABOzHi+^0@ zAC>%5kAGV5&mjJp$G@0=jo@GQ{40`w6?0{CuAIh|TevcrD?f6T4p%MWDsQg3&Q-tp zcR&8Uf`13|?|b}5_|GW*&5m zTw}#Gpp-vmrnS(kzP$vy_DnQ*qMj$`XEwBc7FnBdM3|s^>1SSBk zKrHYY(iSogvI~+9`3}_+$^^Ne`uLfs*#yB2j%pzdSTYm9o6P|pqZ;!*D{>bFPz z`KZ4K^{=4*Pc-O_24-jwhz7Z6SO*Pnt@gxXmtUtKBILPv|fVN`_VcJt*fCm z09sbi3We4Kw5f|WW6;J4ZO))gDYRQddls}kp`8ltaU9g5N6FFLkG$Fb&?ya_{z7LGbc~?m1f5XmWI^XMI=4jUap=4joll{23A(gHmnG;DfG!Wv zRUcjb(e)R)Ekd_A=+=eq8tCSsyB@mlMfXqWF&jNLphp0DB%?8NKz`cL%%BYZ;$>n(ccaIqtX8}2K2#z6&P?5 z10G;t9Sj_Zf%7rY5d#llU=jx2!oZgpR1brOV2~vSZNi`c42s2|n;7&O`n93o3i`dE zKMwkfp>GTQozRbgejx@o$Kbgb9E!moV4wkm7BCnH14kHy!XOR?IT+FqLpo!~6bxB| zA$u_76o%wr$QKONz|bZbItxShV(1MFBMcjdVVf~54a2H1d>Dp%V0ZyW^umZO7!ivR zMHs0tavVmk#mFFx{Dx7}G0F?0UShN!M!R72b&URlF}fIIh%wtS<{HM7VQejo)x}sd zj19rquNc=A;~X$94CC@Jt_tIIFn&D7+hTkO#^1sCPne*K38t8kkBMV3(FhZFVqz>N z7GdH$Osb7Z%`nLZlL|1o4Oc{qMYca(eQ%+$@8KySG)bW_Q z8dDEo>S;{9iK(wK4Vb2jX=5>nmor!|a)u?Tp!HG5Z_lw8or~n6nsj z95E*ubDm%>FxL`uT`@NlbF(n_9p-6bURTVUj(Jv?w+ZvSG4CYirD5I&{MQEmjmLlM z@n1OpyN&t%G2a>UFJnOuEVuzfO&Csv;cgg~!tfIeE3vQ(7V2Z+Of0m-LU$}YgoR01 z_y7yPVNo+I8iqxtSmc65zE~82MHjK?9u~dD;)z)7fW_fh{2ojCVTm1<#A8V@me$46 zby#{7OYdS?Z7eguvNc#1jAi$*>?@Y*V)=3`--qRwu>1j*SHP$njO<{v4@R*tdJba+ z<1sMa4C5piKY;N!n6!k+NSG{w$!3^D!K4VL&0sncramyuhglt%8NkdAX3;P!gLwy- zFNXO+m>0l83l`I1;Q@;$urz?BBP$lnqu8@tUHBum9U)-+d$Zs!LAPMI>T-}?95?z z1a|jfR{{Gbutg+EtY3=tE3tkv)(2pHGS)xD`X6v;0SA3J zEP%s$I2?h)c{n_Q!yh;fg5w-GE{EePI6A^H5RPZym=4EWIKF~oC7kNPsXd(Z;WQCW z3*cl9Cp$Rpfm1k~(%|$6P8HbD5*vDA!!&F##fDTkH-qzHI3I=cOE~|6OINt)!(}2| z_QT}~T)V;54zB0mng!SAaIM6~j@UQ_8*Q;M5F4*x<2$(3f?Hd-4TjqsxEaH3E!=j) z?F8JO!M!EiXTyCLHUXO&VN-i-(#0kNY%<2C6WCOQO_kW(5u2xA^D1l(#pcV{{2f~w zU`sb_nT{>y*s>E_j$unCw*0`>zS!!Fttr_04jwx2m<5l6@W{h94Qv~VZT8rffNj@dKN71$Al9Yxp)>>Q4rbFkAAJF~E}0=vdz zmkoBsV%Hb!?uXqT*d2vEb+Bg;_E=!g4(y4VS$?>u;ahfizx zjDU{`d=A1V8$O@0zd7~~$9_}n--Z3>u>T?U|G|MFIN*o_@i_1WzS{7e4Bz$eJqq8; z@O_Shb#Sl;4tnEY1P;D}UvK!WgI_%Se&Em$9J0cpKpeV(LzVDv2>&kd9|r$9@LvIc zH~9O)KN9{o;Qs~zK!7d+CL>@a0(K(c6as!Da3%teA@CaxPr~8DIQ$VoqY>nepacXJ zA?Oo=s&Hfsj+{ZTLU0oVk45k*1Rp?f3W6UYxC$X!2pNiy6$sgc5PyVZA>%eC<4HLF2w|-eHU(k35tfB;AbdE&tr6~z z@T&;_gA)^QVg*j@!ihqh?1q!ZI2nkO`G}~Ci1CQnfQS%8T*0ZXIJF0-j^os4MCu^Y z1d%%snS#g?MAbo5Lqv5()DT3?K$JV8Vh~k=)0#M~kJBzVeGaGZBU%tW5z(s=?T+XO zM1R1UIylo6XGY=7T%7U2nP{AOhqG;Pb~w&j;p}FdjmFtroU4s<&2Vl$&TYfFNSu3* z7$Bw}VwNIiH)3)T+YGVJhz&vP9mJL(P7v1vaU&2n7jY{P7l61d#Qj2iYs61L{079I zL3}>qYmm?r33HKPhJ>9+xQ&D|Br*~^AaMW^jghz!i77~YkED7?8iJ(RNOC~ZAta?D zDF?}QklY{1hDZ)XasrZ{;yiF(ALm_h{xU8!$AzJ|V1^5hxDbR37jYpM7v3R7kkT0` zqmW{Wlt83pBIPksz9CfysWXvki`0Wiy@1q5Nd1Y6t#Hu*7Z>89GcF#*#WT2=f{TxF z@h{R^BFzA4Q;=qcv~5UBMA|c4s)tJkxMYY+j<~c1m-gaPEH35aQVr5OBYg(a?T{XT z^h-$3Mf!7Gu7k@xaCrzWPsZidxO@PY({Q;IS8CykHm(fEmHD``1y@esNVLSp30F_y>MdOTfNS+}tq-mZ!?nq{wgJ~d zaqS|m72&!juIu1>A6y@T>oaiO1lQegJrLKEaQz-Kn<8@_GB+ag7&4QPc?+4pkkt`c zGm+(ltPo^fN7hSZ*Ftt9WVc23RAk#DdoQw2Ap0`1A0zt%va4~UFK#TtjeWRr6*nH@ z##`Lf!ObPOX^)$Ga5Dini*ZX6w=8gLGj5&1tt#YnLe5m=SR-dIasrVPg`8C6Ji~1b z+#ZA59=LrQcN*i48SZ%EPAKkNz?~PIZz}Q@A>dm6jq=JC~A(Pb|@N*qDd$+N6}ss#iHm1iYo9(;ZZL@Pb8qCF~1P!WKN0({fPw|V&Hi*I-Ey$-%l z!*^?Z-;eK!_+Ek^3P1Yc$9(+o$BzX3xQ!o$_)(6ZP4II7e$K{E3;cA)PjCDT$In>& zyoR3-@bd+J)x|Gu{OXNgBk^k7LdN39lyQt`!@b);LjlZ zF~^^6_!Evlx%l%6)s0cz1J&bDZHDTtsP;zn5mZN@Iu6xosLn=p5vt$eZ)^OWgul!1 z*B*bp@iz{C^YQmJ{{BHtebi{9rVDBYqGl>;R-k4ZY7$ZN5H-(GQ-+!v{OgH-7Wfx} ze`oP84gU)9?}cb+ibhk>=r0=ML}RgNIEse9XuOsGFL<>@Q(rXgMblq2Q$_QRXnvGh zb)=S-)Y6k$)1{V`)N+wpd!*J8sdYwbU6R_GQhU7AUMIDWNbM_9yG-izl{y=x&RwB) zLM}pggxd<+3a1G~FkcWVcq^hO;vkYPs+OoJqWnb_OWi?I*F);&NIfm7XDszjO1%$K zzpvCcl=`+(-$Ux}m-q~Sbi7$OZ5 zrIDsI>LiT@N~2lQ=%6$@FO6GE*71GQ=ni)#74bsd{nq8M>pQU+6X}(69M@sW2()_oy=p`+TrNw$_5i2crQL98=P2#oNc-;6-bvbr zNe7k=y`+P^bhs}a+epVn(lJsxR!OJ%(&>1-#R?@AW~>9SqA zWJ*^^*SXU5kaT?`-TF&6C+T)YbZd$31kv3gx>=%oPjrh#_osAkDBUMY_qo#DO1isA z_ay0FCOx`K4|D0UReBte9_iAfRC@fDo_f-ArS$ZXo>|hfSbCOAuR7AJz4RI)z2-dcBd}n$o+I^d2j{J*0P;^wE<(8>COF^m#9O4MopD^lU{hO7!lE-Z$wB>AO+- z9+bWb()XeC{VM&YNx#+7?~?R;A^o+a|3vA(T>2lE{zWpNr3_dr1CGmpA{p322CkKX zNiy(@4APcC%Vf|&8I&M{?u)(>{i&kwC;G9X|5pajlflPj@KZ5pCM*7LfR2kJsMj6Sd!!oK&Mt76Zjxst+#z4l*kTK_EY*QIKLB^hxv9Dy@C>eK1 z#%0U+_A-99jQ5lA?_@#;nJ`Nx1jvMIGNDo?&XtKrW#W68)L$mq$fQV_^hzdc%j6X@ zIaj9CmMN2DO0-OQEmJ4U)EzRlTBfa*X)!XrwoIQS(|u(6OPMiHW;n=( zD48WPYqrdam0AB}wt>ucklD{YA*<36ai)FZ2mdOfTS+Po1M9Ip@va(pL zj>)QJVm(N#zllwptX?N;+R2(6S?eO}TFbfuvGo)?1F@?RyKiDoVy`9k24Wv9_Ag|; zo~&Ol>yOC#YvRyE9E`LRWu#r2N3R?EiTvhlUJX^GoPaf=uC z+TyM&?uO#-F77A9y+}5-l1=}~rsJ~dm24g(n>WbjE3&y}x+0{;V&6Zto5B(WnZ%FD;Dp%;ypyXt;9P{yg!RiU-2;)pGfif zA^WGv{)4hVQx4RU1Eb}Dy&Sk9zP-hFg7_{LUt96@6yG56%@W^la@Eiv$w6B= z=qm?P<=}hqt0jIq;x|Y9JjCy^9BLqkhRPu)Ig}*+wZ&gw{5OjKVe!8s0h$spRRXq2 zzʜ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≶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

Eoni!-=Zd9SBToc74 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

ZWR$JN(k^xpAZbd0GS{OB~D1I zRzG*0#ZM*h5&K0BDL-E>j{H-GjJ$5N-M(`(5gvP^wugnTwTWIZtbBf@X&8NzXGMlW zIFckc-I0nUklt;HW$thVO)u$fGB@Kj9=?y?-s0;m5qUCJC1L=Il(apV_X0li9QpWR zFS=JPC~FE^OG2^<_b#1xAyefh;8MHcqvsVdpc2?9j!&C~U`!o~LMC58?8g)olx`;(~{Y zDQR;Ym{N=4dd9x8q&)kL%nca7h}(O-vSCr#tli0qZK6T_(V0pj6>p0tVu%4LW{#FV z?+5kivwd;)K@I&LvD4S*?39#ArxTp$8-0a^yQ?qYX^RYHO zq0)S9e`0m#Alhl;e7;%C13D~cHd%K~F5g~=ig4+C)HPW;xFiCN`s{O>OGn@7tsi4* z8=j6*%i3m0%&k4g(|Ca&DjIp7q-lVKFJyX6DZVL@f1ys;IyFF`(wY*@VpzzVYzbq+5L^Ftm}65q5gsqWB4hysc4rXOl#{VE-_53Wn<+q z4VXL^HIw-(8>KCCOm8yoVj3xCF3yk8`$3)eL{tHA7L~|XF4x|tP)wAnlfH)>w!pPU zW^0wksR-+Fl!U)nG}y#Qch;rcJjGM*o@AXYkj=JZJAZDVDQP3^nyE>dt&sI+9Wj$g zJbbrKzNfM;=FmHEYYf81{d>%+~l5G{VMcuHe+V`qs9c3KzW5UgUY$)dXkM0s9i zr2)QbEA4%-stz;8J% z^)BT`*dgD-FTYlXR_r+u3Y}!g%kX~?8?bM=BYo% zX;l#y*YgO%<#ounA*H@`O~=L7Xh>~*BvKuB+9Ks;-;Y1jym(SfO6h$R*h}I1TKVwD z*Y=Uewg#g)xOW`t0X^p&j|*eu^j}QfflkCa0L2~gliZvw0r!|u=h8%vr%(8^#knl6 z(GX-i9Q>FVfF@YwNTL?NV*v_S(^6(txhz5ea510(uV8K(m(X~c&A1R_P;bafIBswj zoS|DY0kq5V7?PH30#eASsauP@L|__E;YpED$%)A!=F6CG5cGnJWCOrL9@WH)`K!tq zDIKKaO;qclGlf7;53@I;jjN%8ygBD-KQQigBSDE8!xe$rM45P^uV9g@WDa}3HT*n` zK3evu=`47n`@v~9T4M#?niL-eI5d!Q#YKO?|+L~<5XV=!xFtt)JZgT z(O%U!_MBxa4<&>mM5D<>v;(c;o8$|caWPf#!&5soV~GuPj9N~1IwY4-i-5O1D{?G77{9Dc9#z5;Se*MUwJYzEJ(?8yB=uB^d{~s!c zzi7_3OEZPKK|dy+7WN_<&!5|J(r>uvdu^k&BQKLc^{jjp`FA-$%fQ&$H)qP* zjbx27pm-ZLKuT$!AMe%Wzm6S|p8MT_fh^Tiag(CgLgEeol+vQ(Y!kz9rN-!WvF>0E zfxJkyXM3&*uS)4wQPdzv+2~JYszhXpZ!?MBRQ-|rx~3_6cR`YtnLuGVf>~Z&kY8$! zB06ws%g|s-(B{amd-~W|gCZrwnInc_pf@9?-*g9q?Rozao&TjdfTMv~NjkwnUh z8p-b-O(ex;T|ftQGn_D!_+|{)n@+?Uz;I*sb;fX@{l^RObxdhsY{(G1n9mp$H!^}W zAcNoZOa!Ow{Xy_4&_={Eh8+-v^YAerg2k7XWr(X;GnXm8t(iO2kWcRbV`} zGl~6o6?hJ#>CRGM4>*?Wu5*-9Gb=^=MG817FKgw*b>flZ#9QT!pdTx z<(M%C*$Z6Sh+!vl$aQdh!)-JF7oXy7XndBF-WhQBZzE4(x@#*p;-0769?fmB;b|pk zoAbB5Ux$K%m1JV%Q5hqOZPt9VXX&ghc5p~Q-Ttms^{Dcw&RlOuMcO<&CW+ue#mXI zJ_66QP-lxEXQ1HKCp+nUZ;pLoGW|0PpeT(jLXSPPc@x__;TzZwY7Z5pDCz=$US%lj z(;8uyBHg)@O6N5>jj;QX9{5gWnYesVGs=c5kT3YD>3Vughrh0gGMQmf-aITbgk7(U zPJWV$M`DUKGiaaSJU4d1)epoV7ei)hry-jk$ZTD6CywmYS;n$0I|)E$jF5 zD`Hi)HH{EliZJ$T-c5sHa*Pp`+SQG1QCxczczVeMbYc{UtPsbr%;wvE=;>D}EfhAU zcRE{WW#@7CLSC#P2h4cG7ElRIzwGk9j|T>ZKa?lcRosw1?1$8lbXk-wDIRo*7Gk)}r@}&cuWDY2I)JUIUi#r9gYkoP7eN zk2ItOe+NDGFEA`gtC7h`Sdgy|5RQ8p`n&JeCINBUWc(@sL_lsc&|)3<4=T@8Tp{GVEAy> zL}P1U)IQg$0J)IsQg+u2ejz-~!dJGb5Ktc{Y{Xu;cO1Zu~ zV?q|`Lq!L}Peprh6P;}tTC~EEwbD$SEUIu_lz`Fl6o6U*DXuI@X*4|}n!PZml*h$D z!UV)6ikUU-IV2l(7_6GfVaMpxI^Aup4=jti?&Im1W>lzB+c)7$I}yd1L~>xU{#b!U zmr}^fZ?E4PCZqF5E7jk?{8Lb*!*ZIq&G&G$ri2Y|6wx5~`GDwBz63Spd1RGc5>x!f zs6IGW`)yx7Yhu+)qHl<2EyIyQc8!w;HibU%h;UvuXU0VAV3LKZlCkuM5PUwN2wQd; z@kAn|np6ICyK8Up~Llcq+pK z%Fuw0LgbOUEc)o=F?b--CR}hSC=6{c5~3u{cHo-vMSUPl6!N4@hxj05qjrgz&wUhI-D6O!RlrSsh#3ZBH(*%WHIJL*t)ax3^ZThg zg(W4jtjP*IFS=1wJ6-$9rU_#duQrN}wkt;r0AA1nUo0!jpXweSx(S6jYrqAkfAo6n zi9oh|t`;5E*dO|f`K7I=bV^qO+4)R^PASC`3iiwGX3Tm^Hh(RQjMN;%_p5bu^FRrB z=S<#ikJYz!_O}vmc6sTKMWjn5X@I3RofZBDo>RgwQq7+j04T-(@O;YEA|5A7I^Cd7 zXKA3+CpuC2SHvE=p`ESdNFzoas1wBo2h4Q%#?f*IlnQ%#i`GRS64mXdY6Z0cBYl&< zbI$bGCtnXnQ;yPl{D||iF>CQ?t$)AxOvj_y>PJD(zG72p=E-#B;E#+RcX{PxHuZRA|B`*oh7nuOg=U z%AsEDf^?JB1*KyLus0#aJonGYuOGq;D_G&To&>B8*W1e3L>G2Bxx;eB3f=Y)DOonq zEPw6Sx9<Xw& z+%%FdqXE3Rcj+4mvSKXV0(W%qAVAZEZbe)k>~E|MmMD&721{a~^fTWDb@Y z>a$HL*gxMt^@JhPd_rjR$iDYpnGKN5G?P(;8}Z_Riig|f>Qg-9}sCyD;E8zyG? z;KJbXL!HKfDx`>yADDI^FCK}kGIL;I{FyuP7dzOJ=c?D$n@Wj6H4iTz;Lan&PsZX1 zSr;sDZbSbyL)~nCiSsz(j|p_*_=p2S^A;3cPkrS#Rh9VH?{7rUAMlbY*8Y6LJ=U8X z(yk0Ih~xLmp1&Z~Yo1H^=1^{;b*L&bNMSf~f+`VuWvK%6nAE5rWt}_)$A0VWGoT_jdM1Oda!V?IMl2mSB#-8)j6{{9p}<%ba@So8abZil2=!J%XzD zpO84z+yQ6-;pARB8;k15OL~Sgd96#jsL?DiRdS*)vwH%t*F6z|q@Uj5r>xu#(3BC5 z>_xY>ZeXOq{DN6SqPVz1?%pP>T?1UGRfK>0AZ{D^#-K0k(qjM2##LRU+oqrSlFN zL{u*7cED6TMx;0!^g+G8wMAsQeQz4|)lZ+P)FzZ9DrgA1oO5SwfZ-mvwd+1paOz$^c+Q-N$W08B06soq(7c*}_z|ediKE0Q=Zl%E z$Smrj$$JFz&o>)gtR<(n*L~fbEH10{n`+0k&d+|1{G`V~bWUwfhUkAZ_|-pXvACrW zU6qJTeLtnk(a$2<7QYS#C_^wXp+sWQ|+gcHuTtgs%}O1&_&i z=ffswSSN`UPRjoqW}KHbdtI~Ya+l~t-Oo4hnB%TLFGYPaM>tGqJ%BAz%E`;JKLE%r zSDNGQcrk^v&JUs$_tR(O-CB>Q-e?-#XY5lHC;ng3gdd*@xB+0_?l*j_+2~qS)9884 zr{+!i$39!*lE_+DlxCLa+p^Z5V=HU+X*I%I@|aEZm7Hj%5Y6d0r*US}$R1-NaDKv$ zi*KbXIC2@Z&cpx0G+Eb7fSULjp1Gm(HqnO%xNk2u+cE@XuGIRD=))wWgKkuFz+3I9)@|YMt=uXMSv?@0O^Z zV-48;mQxyo=|HBpVKsF&bA1}kv(ElXI(;4B!ZJe~U*bjarbbcNau?b~hA0hb#rNln zdO+X!vjh`K4E|X{0LGz%shXy}@tijx~N(=A*U^znhm`2Ao*x?B$u+eL81eChhoc7uA3 z3#_Rm40;8xAGWH;3_v)4r=v2l=mPkbUT>E-$DQX>HyG&adfBXU3#~a@7!tG2dUU}! zSd&i1SS=$|T`7{01gR{NwL>0MQsTo-^rhPwnRd>rpPg&S1bp1A1Cp`Vt5HcZr5(O} zh!50ug(Og(m@wmQ&hITlsbIC#Jz z(_A@cnzvjOe+UF-jqk7wnV&7xlH+gT+_ z$swUC$}~wO2~tY27VU^)RLZhm!?N zZx)JtTdOZSteZ?7&M;5uL$q$dUR$JCvjh^17E;;7Gjw(fww#jW?E#%XQsZ1n;yN4! z;z5|2u^7>qA3&Qn3C4mG!TR1JzIJ+MBRLCW8!GC;cHU4k^SWQt8(xCjE+yBS-a>|P znuYj>9s-eZCX3*Hha#V^-y7p?RD5-tCNe|6BxuWk!j7gX7a2RV!b&sq=gRDPmFUCAo*622@UbA zMVXa9i51ZqnIbV|i*2+HpK@Ig0t%i_c~ZutsyFY@@;T>1d%=}-O6P`Sk#kSbVE%}6 zJp-WD)xGVHBmRLn##20}Oiwg{bF)pP=RA=Ol0tWpWO&YRo9>Jk+o;ihen8^+;61{w zQ+ic%7m+%CVqf6A=W9u=mkCSgeWP!^q6Mh(>jdCFoe7vvVfUk8DYXeCwa{^(`-2uD zCMhN<0&9X;(^nG5G{ZA#)1(9xIGp$MdXaF*#`Vg3m6D$F_8)3<@E3DfMk7wwLh?Qj zf$=%~dU~Vtq`&*e8d@?nLnUpTA0mx;O=5jNOa}EzxR-k`?T=c=g6*S&h>+)tNsj%d9F{_3G>BT8+V;k5e`b z!L)?h+Js$DEC#_2y4|!Nlm@b0-;`$MOdC4~fyBz6s)5wxwRoJrckx_U}eQ##u?##se0*`lLCjg_Q(V9lN4Z%eb! zk&HYaIJX!DQuPQKV{6Hqw&-AWF z6I8`F^`LYWo>TXSGrAj_L z%deL`NUnM(CEn^i!~Bgbua6#&mUPXr%4wy?duKfZ9wSySV^(nNj0NFT#V>o_w3$ul z&wHTKzFDX{o&Gq}3vMAnnNBHYjera1J&*WdmJUgwyb2OE=ZSK?nHiuS-ECRlLWx2i zMuthKotNf|v4NL#nR*--fsxJ&@mfK*Ge!ZMJ0T|TjRyk~!@uwENn4|{nMR2@NNNTk zQqNKDYROd#T0VIOIfMu@6g6oRhT_CY2<>`4CyoN1&trU&rkt(gm(*^{A^zdo{%GuT=&;)EFwhC<&3^>~A&DCSVFqL~d+oD#4^R12ylD8YLfB zCh!0`8r4>XXLQvGBV%UFrVBwld=*O;C8hpLyrmrBvhSJXnFeRotEM8DGPQ+Dn8y6S?Q9`UlFS1&xs6M(u&nwu2^nJ&w1TKAzU_ zpBy98eaSlKfBG?}p_`_JTM7nrs#|!xlk>Ej2OC`MxA024Fr-sFmsB9TO4^Y>Dyzd$ z_h(=3ee;NEPi)gT**x_nCQd5LiWTJu!`NRKcwMksL%cmr=EXKKJ$MgH!FO>Mo4|dc zrA`3noKW!#Zaz7>CGJkj8D`AsjY`V^KP=jD%5o;w-q=x0nd884QnHTm}MjgP2WlwY)GnpYVB(*5lrm z1KN=D!!#boJe!Mmis|Acq)7+t)~VAseVHs{z1Z?j+}Hi7Ou&gb{&7v?nvH9D-)D8` zGsIBY?t1IIS9WDD6$W6yyB=~yDfQUfuBVu{VHuRrRozUpTJ|TT&qOLpfj&~N|KP@} z$7BpWcJQ?bO?BJK1Mdffs1iQc4Lq|!49%@4g32+QW`^c)F|^Q7jeBH1W7+#wQ&c>> zQ?-nf+zsVzaNQmao(FY1b{r(Nyl_Y-Xh#SxW;t}4)2kL+X|a=9C4l=^KNcp9laZ$` z8k=RKYCw1Vz6fBOdN4tMR)VQD{?%*j)0cNhhbN`zK})IuQNUA4dsTiY>S==pE3W~q zKNpXyOF&wQ%wg9S8_E%InJ;D?pS(w7M-k~!&K|xqbw4(y@Oc6iXFRB=SL2o+d!z8G zkxI5LQwaVY&HB#MjCy?zCvm9toGjVnzL1HH{9LA1Y`;;Ib@7QoZAfz6tm?% z0;_-6#ucySt<<;YU#L@8tH3Ry(3UP-4YHD=X(jVIf_=e<`wi0sslK}V-V?Lr9g zRGO2hD#%JA8f*HetY2KYA(X_#M;z#xsk(*)czFJxPotO&y16lBr-H>2rC*`+b)w02 zDk$`F6Z2F`Km85A{O?+ve&Lzz1}eWS zL@J4Gy^m_0x*uLjJ%B+S*|t_( zi7c8q4fc>o@4}|b3@t#y*6{!KMF^-_<5I^;wcCBDu$77o2o=KyK^bI_aHQQ_nj{G= zD&hkbvC2G6((mGlCEd~5cIQA&idKr!Q?0yI4)s@hQn<#b-z;XN|4d>cd>pGH;suz; zR{G1MU?}Y+6aTvYy^E){vEA}Z>n$x;NXOTxQF=z0!h)9&(*pvA3Yw2vlSr5N-;*_Q zWzrzcvchhBt6lF}aC@UUMJy$_#rz$k3KGfDIZy+{^`MR1>=BI|Vqg#>YAkc;yPtlRw?Cu4QYE)xy{o>Auj5?T$$E zTB~^9dhPf?#b3RnO{%iV$%s-*AQo85<;$QDbr^NbDr^s#YS)u*9L6F(T#7svCdafJ zIm3ORd_X^O{IBou?=6V1$Avb+po$eLFyp{`B8Yj;C9%=d7tKrb#5|{dE7F?b=Fsp) zQS29Rr0^)~Km$T}_5n{d){>4x zeh7oPcFjP|wME1^#<9!vKO^x&bl9i|*L?_SB>MS7fsTc8c60J30Gn8q1YrPkjZo(wkMqUPDM%1`lpN*dKGP6i@i6pu+YukdlwBGgfG{66L# z$Uw=9qGS!jUsqD~8L&whWxrt1ab@Rqr7cJvr^vcJgrA$(T9FBEaJfgm>TU?z30I{8 zR4@2+Ka|;N>Y33hX_LGy+yxt!_t{hO2%z#gNno$EM=2r^QAw= zi$bN4vRUwM>TDqrLXqo8!;{=z&c`A&r9S-=^@_C04(i!11aw*OC}eQ^-VGyZ7hqUA zj&B;}F072P$@(#krV%%^;H=U1|ApT-9N}#rPOmY0!V`M&btyHvb|~gagVkE_ubY*8 zFCEk7cPfJw-k^bAdPSM3zp{DiCu>@CS>sZ}+SLh$5A~xUwu@8fs*j5**>uheO6_zI z+Ykk4@a)>`qcdccBL_u{zV;$v75$ND1zWM)2F^5gxjQj+GBM}4@G?sx8B&w8WxSq_ z-j4Wz^_rcCs>#2|FXo?{zjXRjUP^nVnp;G#rBi#NE1}=99FkTPbCh1lbS!%Z@zWe8 z>_f)|_77yyO==u}U+D0s1r8B0u;z)BxfmBi1MwN}F&ZzUNE?W6U6IZC*$&cnqX43f%FUQ{u$PnfFiHc2T<2HC6|NDo-LEU_d1ov6#BWgPLaCUvMid6HW6uiY1`U^z?&8S8Y$u zr-(?Qu(JaenqC&K>0k8%0TrH*L}3!KR`339XMRc`L7W))Gs26%{gAyF2%&PwI4nw4Y!=m@fWeRFXao z+m&^4?;ILTwbiqmyco)~#QBYDu}nRlGSyj9ac$c|^Se8Bpdzj9Hh4w}`9@QboGw85 z^{>B_3f~O8IZvm$1l|rg!)A3f*+x58^`gp+)wliw;nEx^z-kZ;;vCe z9BhP##9eOAAku)_6Hm~bK;SOZPEG2fQWDFJ&p`kS-x%mYYStLPkIIyQPNmmAmGEzW z?}ifiN-0T~E^qA+)+GDVs6vze%Ooo{*yxKp{BZ_~w;LE}?8lTd}~A zsH;Zqjnn>-&;Dg?K)0gmW+2gNH0(=4Y@z!4$-Hm+QS_zwu#k=AR+T>B&T|qe*vh}U zXsWmGWV}uXAq^fR`Sfc0-NuZ25vFRWF(AFCga?H5Duv3L8^)B6dMU(Er)gHh{DCDc zruDE#wy2YS^r~svM<-*8c+qi9&BDAkQmwUN2#BTM3eHFw2cJ=j`#25n>2>-AO(btc z%&g@1zjwz`Jnyd9QUG;$xlcj(&18#GWLdlDG&93-y3ssJOH~|2_$*5E*1E5#nP6JM z{_<(B(9Q}EXJz$*^+x`V)y_NXFXqe@w+-!fe{2?&_XyNewmi{yqdE2=o+8JI{7Qi? z&D3jIYfW=gvu!_0osSy&T7cY|2Yq?lzrXQ~wD&HVeIzqMaUBycM1JDksTp4nmF>=}55F&*P2 zPGKBmX;^!^M`tVSq4uq`i0T-f3wi)= zbK0=hPeDp5p0P@Ut^YJ$S{k68hYHfuh7V|83mwT$oAeXPrk>)`CA66;>MO~~DWYt6 zqQ@7_3Wh300}90aC7(hFz#>_&#W##UDA9+%WRF}TvVI?;g&8 zQce6A6>N*iVZw4J|J#6Gx{rHMLjp*Vj+BAAKJ}F|DAcPNnP3p0;5U9yC=E^J!TTL^ zu}@KfwnG#f@{~WxkPqeBxtIpla&BW9aQ-&g%Ncqc`iXonTG`5{W`0PiDXkh~ejhHv zAe_MZUqvmTdBW8%M^WXgV=^hH~+t(1q5B_~SIUvfy(p(A$q(BLw` z=bLsNy#ip}2n%Xb;4C8LV$qlzlKlv}48v~it#|G6{?!}yzjfgE`oIElJ+6Ip3+PUa ztZorSf|9_rUsE-HnE-w9rX*BVG3K1_oI48nJPj+UP3Uh=<<#EPb%@$6j=}PZG$+xd z+6Si~qxX|jFfmE_$v@b0M=np@v+tKN6l9c%2=#A31j>MU&c zv|FtM%B%MxHpS#=PF6`X^Js%Jro~PZCB8eJBu_lZfTzDnpvLt<<=SPAW0XMsN!nl9?Jn{lZO&zoR*T{$p$uDWnZ|GPJWE7Vw>Ik^?>u zlR%^l6Pr-4?Qm$)8t6@0dpjExg%3J4bRAI7x!yxYBCh<5d#-1h-Xs+get70&_7XT{ z1|z!NC*#;??ecH|BBzELSMJ^2bFvCPA{9y{X%HZAz;ga<64kkMe|(AzP~nZ7D7op3 zW_dgxntgD~ZHKLkoI}PNKJi(QmHv-;QaWvXB_DZCSgFmgGA6*fDRF=9A5mr^?uMXk z7H;t&vZ~cO*LoODI8n|C16djEh3#ya$LL5;W_xL1;-{m%K$O&*f}#Kqx$T*MQUQmwOyLSS4&km z?pEt)Mu(k6=_{^F)C!Vaaf~(LpZD}5{gqCeehwF+ho&+XUr8q3HNavWhj$s(!f(RGccU4j8p_Y}6!pLCx7F|Dp53CEO_d;Kv=I%yi&*fPZ?Xswwnsr54$xF`xxyxO`=m3~q4q+&wFN8#IJkHE!rU zA^fi(7FTf5M5d%COhF138z=iBcEgCFw-U|D{N`TENQ2@;26)QDe9pxGA)a!EB6V{u zGT6f5yK==MX}Y)NQIzRRj6 z(#GFZyZ=i-;%$AE7OSIY4HKH}fV-ihgul7^BCfb+OBkBLIx0(JaL%a44gXK5o#eRN z{_Y{{#;b};JPpIueO^(8#})_y7D|Vu8*}IS-yw;D1-XZGKgy{TI$%QB;ZmPO^8*^2 zEr~|Hs8<|1mZwxuUUk-OQ(x<*!JOYTeW|-ZUwF)VLc52)YV#@7cWEVLTIWEY!JM>ps))u1WOh) z$o^j(e^tTmj2DYL(-N>tLu~YRu5v6Z7Y#AK&+^0L+7>;rz^RU!@Dxk&`8TE=agVuw zS(LQDyc8*<`~OnMx`Q=Qv7trVIhmyHyfNyj9E>h947D`9s!1aVKJSb*ke|9JJ5+f+ z<*86o&O#e{3YEM047x~dH_{+Qh zrBR)E_-JKR!JHNH8&9SJ@Yf&PJOkp>FH0T9v*cZzU+}s`l4KB)x&Z_%iJ3-g6=eWX ziNw0p@T~m;OcZtDJtQO_16FFX=RuCOGYQuI(*IR+q8gxswlI6aHzJoQU}` z{6i6zn8LgIl#a8vBdR0Kw)bXvAlcNvEa#NYHwZ@B`F4SeTAj27#ssm$cyr%o%}Qy3 zZTi6%8E!&&sd10$zr%`;8dF5CH0ftAo6UX+R9B0svoU;0@iN75f{GYS$WbnV95&Im z+^sRH*`YG9F6nf$Z+qqzc-u|V#U%_n=QE`-jm?V)^@bjryStu-Q!SZicC5!OW;Tz& z_LgWa0dTA)(Eb4-Di|`6mPzibzF;5DQ<(bHjfeXgy`ll(c$ziEZ6(K?=M(B+rI1Tj zq?wp!MXYPWyCU3t%3x;t4%vr->DjiU!daAr0=YRg-*2tE$VJo`kt#9Mx_b+$VVgth zS#FOmb@9P&aU0P)id?{w;YI6W6CstQoePa0)7(u%qCP~n&YJc&jQ6lq0s_egKhw-I zR73V_%(&77dYh6VCI>~Xw5wF^PdoM^&{&Uv;lrq6CVJpdqV zDX^1y4mA--Xd>X%1|E>FJj7A5N4TLu!Lnj7p*@UVwAd{-dqmX*jv-FN%R=rCjH?v_ z*q6M>RnujSP%*!czRW-A?zq(ou#DRXaPDvmN)G+8UK}&N*+B^!r%g8tjfl(^6@{=XwN{36cA(h^G z6x>w4lX(Rk8yiN+p62&1D~#BlTTTmg<)Ffp$YA4OH2YN(j*(=%QxDAE)+htMLvIpy zvRSpJ+A)MzANCt!KEm4Iq4|QfiGJ{n!i z#<^&ZAc5&8swLAioT<6+y=84)+b#*&akSifXOMmRbt11Vz_^PDbg8bLtyNLE_CVWJ5<91PFkAQ%uqENlzj)@ME zKrp$C)dLcpildmR`yLJMXVd;X5zL`>TygEH!h22t00_qv=f#edu-2ZdvQ=Smu09k+ zf9nR~jnaIvdJES+nJ!)3XHV7V*+$A@Q;&ac=zdQ9Qi}Vl)1w^C$k;NSqJxB;P~;;j zP{|$`3Whs~7SrH*F(k)Rt7%_R)ZXx-J;l`E!GZ%a4r)GYG_rw)19MlUx?l1eq4@ZXpOR31tF(VUhVighUespI0Fy{O8@Q6uk5F>W zK8%ltF>XKvHX5xQ7XoYCNM^EXGI211h8?=p;W_m49`!26@jE`oYOcYg%w&yI-yB?l zHp%V>iwGeFGUC5WVe00vi}|P2qF*_6e;!w4`Qk0rPEJFd|E3*G5kh|-t!#2qhg3?b z$6ZtA^dhZN2>R6rq5BU5>AUm9G*LDJ?6gN$mMc5|1i!$0tybyXYB?h;&AFj?;84{yu8dlP2 zaLLFYCDYaR5`V4?zd4Wjq~R$g7#&mmm0mO)G_ze(DXSigm)<1a!h~-Jve&75B+ila zqZ|A8F8e`lrd4IiYEH%w7z-eR5onxp)h?7juW0MR%N*?RdF!1cI?&`CbQiL z+>m)D%jb&^sY{|_Z|Z40y$BZ25SvaHycxJc;A&q$09}208!j{0R(F`EXYVabS7-6)w8^ut3MO)Y^bF)PGJg~%bO!j3_{BvrwI1FG6xpL`yQ z-yV58Y-WCb0W!^<%mwp;370f0q6>ri;Kpz2xVtjg56FQ%SR35yfG>4U2OH8R4N!Ex zoOV+`eqnKx!M6*Xw!>-aP~ESs%#h$!iI17QB#aA7buG02$&C*9UKZtldU81-$!SWb zOj5XMYNH8z4ro23kI$c#y{pLzBLH6k70F!jxDu!+%UHo}HVym)RZlOctMD@f$S)60xw zyjQvUBJU&(O}})wrwb&P(nK4C)KS+Y_>QM|J*TSJe29cUNykuM8BAI=gr@?b)0m5i zVnpNnD^61lUKP4H$|wKHdKCxAK0-u>1SnAQY0UmJmb^YjZ8@=K`GR81N`i{i-HPkd%|)nde*|ySSh}vI*wY~{ zbl{d8apC`??OfR#SF*NE`f9OZ_Mdve1`QfC2naARFfb4h5GYWvKmh>(0RaPpfPjDv zHfS(l{?vEf&swE}PjPzY)67mfC#h7m&WC3$?UCo+dhs$)35C(r0c*-bj12xU8q>F9 zb2X*t(F=x@ejk3Naq#*uS5Z|oz?tdU@-CsCGMSHOehG~2GeE1+^J-3)Bzcxi> zb^P9z2P^RZxuHU*C#Wh^EnBSe6Dtii>ubK3PWtN439mRcJX|1M$aU&ZM`f2x5}~5S zL^^7WeF*@6m$EvUYkVb;DKzdd`$g3Dgui48V;j7qIc9^Z;wID+cXl_fyhha zb=7Jeh8LoAti`!L6{LF>PNwbL#c&u;mUr#C)WjxWF`1fbOGvB@^!d$CwVLjShag$m zaj=vgsF=#A?WkBDfMbw^iZ#?hzN7@B(trZ07A&(u|5`Vce6pbhfJuVQNXw>zOoU;N z)S;r%Z8b-i?5;*dx;oAtj-iB-1{-~BiYfTXN{VYedCmwi^}>nMtndu{FK>a;k^pL! zu2c~kd#dPiuF}i1>IBQgg>8q5s$Zme?RCm)lbz*VJDP`Lj&EHE2e2%K;;g)-*PJSz znt;&5gR9@+o}TGx5Vrv zu~FhKe&>8pb=houdyMu|{?b!%vKcT$sYhQB3)HDMietA0wWJFkCP_WP@g5 zcL+~;IN(23FhOhfgl&R|eWvy90g}fW<>OeF+Db|+FSz1?rpgR2AT4fXm$o5CTqvcX zFBjA=F;k5HAJuq1dUBDVq7>-Dx}Vx=>h?5K+|;mrgx-xxSSZF-mmOkJw6kbY#~0^} z)Cn7&)KsU#A;rcmUQc>Ma0amI>F#1?_4|@&iV8c&`g?LH1QJQK42A|oQ5&pjbyTAA z+!3l<(E6B;JSO!y)fwwH#s-c99WhZv3B40j@G@FHfg)vC)O_-8(g!DPov2vA%8IAq zPy+x!zm`o#2p3T;Y?HY=Z%tA>JjCxg>(lzHC%A5su#}e|Fs5i2%!c^W9_Vw1cO-jF zO37vrcplTL)K2cVbAyeowd4vFD217-t~wpHRS!2wmMJ$>($A>Mvj3g(^DXz*vXf2z zDni?dE%tSd!BQ2`{x)SV54CVn{oEd|0tsvo2i-os)oJJtkRb9RX!tJ03477$`~79O!pG4w=a z7D3{f?2D+BY$BuypOqMJB`9&g3vCrgRYv?ZLz#w_!$)0gaXc3ZJZjHgoPiyVIx>;Z_yC zuxC?aX7be#^U5CbS+&)C`F&P-8cmlu*KCc&$c3otR9Qgd8eL>=SE?O3%A z0XM1Cl~vhdc`*zbUoow(xqxC=5y!)&5iQd5Wrvxlfom9U+J;C=Farg(2wRErSvnQl zMEkC#x%hVI&uQ0&u4+_W5x)uc{n7R;T%(xU6DwivU**=J9maf?#!svozBM754gSMF z)nQZ0b6s+0HO4Izl>g?#bun(&oEI}Jg%UvGN;;wK1#F9GS3@kqP!OV|B#oF$9HArS zjPfF@9|N_Xd@^Clo8V+=YKAo~ds>(yKTB^^Yn9njiK6ur0gRMqWykbrIDvK!{XIJlra3 z@tW4UqEl#dy?nB3i!^oz`YmD`&h=qZ1r;NJNEtFywrwxBijpS${xnp1d!%+^S}ua! z`s3RmVDY@J6?n_@;r*jlf#s@-VZePU2&ABhL*K@HTx8XA8uM!c^HOCo&HB~KO>!UF zLmIQSYE3{VOkzll6~XXo6xOsq)k;0)Mvhb|w&=T;nbI0zkUiY-d(;bsEcsDG6{NkIxuFT0*zG=2~3WMc9qqhR$^>b~EXuv3-}c zVFQK6MZ(9LxC?-+cFpHtqBLfS=Q<|#;^<2~GLZogE%rsZ+AA|nIE2qROepE$bvF;tngn9&1=Ul9eT$ImyEcj z%U!XK?WQ}LGQ8sFNn`Nr+bw0tu&KKHh7Qn1y$W%QxP+`#fDn4q*OcSrHuB`ZlfvcB zSmn@HWZ&JY2>kQ~Yby&>pCeayz0Q=<9z!Xw`r~UF#BA?jG{3UiUE22h8y43D(Y2IL zgr_Fv?E28wsV7y$rMpzuAfUaPcLr(|ylH{0HK8Rve$9y!@*3KCzBi3kN3ZxAskkc! zDMXU95-4oUeVGbjOjX?|lAxe-78a*=b4Vu*J-33=lbbw?^-ea11tw;aFEosUyad#n zVDm(Cn%Biu{+xd?8~Q38eCvBmeVWFhUrHRYrx1>cHRgSizDCxnM6|k5!g!1W4YuJ3 zV4z)`PZ3bENsAnkP-tJ$`!L(J@2v)guesXKcwxk=7>}bUlJc~wN7hOD)A=+s3%ZoQ z>BV!v=E1lMWO+IvFc*c_C7Pxel(+zJm`lBb2#nn(zFYoP(1Bcz`q0LuD$5#v#`D!uRDlTyM$ixk-n??Te_K73rEvIR`5fU zZQG?Q5*8%!w3%KksM#;~IaR+OHE4krbOAX)%iei1dkIiG$O8+unGQsog~eXKs2Fyr za}QLF`DaBxpR9SKu_6wv-9TVv7J5Z==S&?h|2649Ro?zzlHTV)r2>Vy+B(dh~qUb!wp;kuJH9FLQerd*b!T^4>l@Q#+6 zEwaa~Ha)-|sSKnKlcxT+a5nz)l=`*iX4ICdf?ZF0x=QOpVauiwzs|&7p`$TsmHFiu zsq2GRSp4~@RB{lYH7Ah-{y~JKjmJrvbkv4wb5r`={EDNQHyDAp_;fm}cOwN$8Zh#( ziFe|-W!G{S!mGj*@9I;qY)GY6`x3=Hr#UaI?NpPhIZW|;JGE%t;(0}6?MN2@@g+VB zUP@7>*Eo>UnJV1j0K7AgLlkeiBf`BY_C@f}_R+`$n9ig4Gfetf5P`Q8&n4fubFv~~ zPdE>a;bxr4L1Xf6BEw~4B|i)OEuq)7z#R7RaV@4+S*l-V|Mqm0na-nlfT+8;cxypu zHtR(5%BNnuFN?GUpw+w=@lEIG4Z7C@PZ|StGhVz5+Hi}y{+@cYiOB-7vA(F$pgvxIFJaoMuypV0^`ZnWw?bQHcB<FojD~7_%B=z381`As4Er2p5>Y83dAzR%BSFb$K4Jer@-PXf^4a zPoo!aYto2wb{ZYkpHi*fj!{+l?8Ul5GW)qD0g#Iw0Ve;91=qX z7)-9c!6P?0J*3TGId-CuDuPii5C5IYF@Yeqy@no|Gmzs)DRt|VY^{3mCH?hOHe@~I zN{Z<94IDx~DvgIgI#8KRnza^42d?ZLJ#6!9W`tL#L2%Yk%4DPbrS|F`m1{ovai@`s*_ubl{w9(0Z#$sT5YR zlcD|Sf){2@BSOPeu*5QZOa1OjyW=#jr1}*$cMk#rXEd)7=z`Ff^rs*~p+76;V3*)3 ziwLQV56?MCY>_a?Bm-7ofso+F*qo_u57x|cnVp_t=cycQOsFHb(w0(j`{+uN;g=yY z7)O0=E0Fbd8JA+`oG`>4n#mL!CkX~z1u1#PVT(p)Lo+X?DMP=OHzk8F=oAyWg6G04 z(5udlpPLybs8#$biuLhI>dDatOLkK_EPpLj8bfm1Z1DK|hxwXKvn&)}Z>KzI?E5aW zaO`tcCu5~cyufHxzc=%lEwgjN`+^)**$RQdcsv#{@tub4Qjjh2*&8m(ot$w}x>~}2 z)BsPOdWKx~lQn*BXwhkMbQDW-#+CF}%HF+@xCD=W2?%|DNS6$pnHCD%G8!)2flL-r4aeL*oujW6HK!hB}f83k8yw8 zLMynFQ@x2Sg{d=RvBCIr66W+*t9Af!tw~w*d#B#In62BIb$n)Ev~mlyeBxF%HwC*$ zG#rEY9cByHygt1x4c|Nr0~l)r`#JaN(4XWIYm9_>S?rCdOz2#N!RNBjez^-?dsC$G z3ceChH$PT=mKHglRP)68=^$W3tTL9*3aZl{FLlY~C0VpqEgT-X&Q?F-Y&<%b&gWw* zqlLobP8NM~*hVhf9B4!1-z~9VGLi~pV2%Y><}G=2p}$h(%K>0O>PRtTjk=0!cZwecb0^CH$9|)u%%(+0XP<_du-&? zy5_8zW^A%=GJFUD!T9CUxpB?uwX=UuPK6zZoeILBL{)mU0v$F8RDa|e!8B2^r&%r41Kix%W(vct`4Veh)o!oMLsXX0FSeuyqov9r1RL+z8H)_zR300LU<9QXl3QkrPFqHXpaDmP^HwFTMI+NwSKI*USu@PBL zf5)a|KuagL;fC1>pSFK_^&5yVu3-v+ie3kxc>eUHM|0;#He$-dCL69aP(kfNNLzBz z-pV)=r!d0)^0WKyarCxR@O?BX?B4BOW`gE-kV6BuBMT+HK~ z`S>E@G|S|Fu)3>pyPT4>TUPA$+xRAd#r=n5!-#b|+%DBj6Vg*40Pv@$pPA$4uk_x@ zPwVDH<8Qpcg;Vyr)fe{BRHA*$k|O795P{MHF2KBfl>PinCB3Q9h&d{;tr&A}GD-FL zp(y>Gu*fD~IJDqwPJpeJ)dtG@W-w&lkB7RkNUEr)zo5aK8S3+ZVKufwY7umFY`>qi zA$tS6z1bLso>@Ho6AI8Yj|ot%+tMNj_^P?jRv2$~AOpsLZ+$f?fuRvx5~$dw^Xyeo zWCDH+k0+mtCG17uGzbz;@Y!yb9MWtzMS0HKU5w!jvWtK`|5N>XH0h0+d$oOOyjWK(RJ`5>y zLY6qcsmC9}`1{fukttc8%8;o`XNK9StI|%xQBUa*|I1T23wd%9+jg10k~FsuR4Kfy z&icH?Y>+j|UU02dU-2U#D9ZmA8}NWKaDwO{H%Sjf8+d5HRyK5>HtLzu*^Js2mFwf_ zfASPr#rivn&brRtfniL#%rtW~=ASUs`lCP@#l5lF83Smkf5-s(+i?pQwiEs|i}1tHgs^Kdaqb_eLSiLN2eDCn_^8tK&|S}*-RP~3*G-00?yvV6Gv2#vvGQpH8l(^|`n^vF#APAGeF|-C+Lp6T_YA@?BoudVNIw zc3F7dO;bQ@=_yy;mg$sdZj%e#hdN;j((W(fvo{;doH23r zDyGTlpiwB>YV*ZWY8)V!1gyrPPtsxim)NJDidspP42p$;Kd(<4(`&Xc9Yb8AaG9G{ zS7i0_fQ~yG;(KFHX5Ml2H8i%(c37UWa`Ag1)5j#srBRgucTx@ilDg7w7_W`J)xRm# z(Z{Byr-R7gDIHQzb&Vz%=p>YRr&`8LI9iL8ECCG}`?0q`mICP0SNC2FX4@;|eXjEe zl*`Qh)!#>YYZ12Ba+YC}b4+r#WSL>R_{qHo7n2MD5nHv(X?ARh=1Gwkh7dL7?!*rT z(GnW4jTak9+Kg{5*YV)dF4!T`gMom1I1*h`QsP6ltb5xQjQ_<0`()p|e4q*w!$BA$ zsBHN5BvUL`r|qMkV(`s7I$D~^Tg;gUfA42EU$bjPb39)`2#0vU^~G2z)46;Mi++XD zB<-~IgI+4uwFCn|eG*YBBom{S3M%mi3KG2TR(1CV9}4&K!{ulQXs$uN+@iDdy-5*2 zuB@k{tOE&~AvR6x4AfFx;3i3zT80Z=jVLh>uyBqAjLKIQyYR1WZ1zeW7?9hlVh-+A zI@3zy!Zh6qUBki7rnbD`xkBz)YId8U7TSLRHXuspRtB*ZnfBQ` zcUvDIib2SmR^9d}mfg#}0C$r-7|wuaJEb4G>UFisHHGzCf)0*5eeD7&(}ufJ(0rjD zprOu(eCiug8PHX`w0`?mxyH59Uq5vvyO#J4YX>yn99X`ahniQUlb4ps+Vbw@<9p^| zB|KL@QPvGdnsL-hd$e6VN@1Kr%BMt);4$?**+Z-HVZu+aG)%O5qPQ935j@1NCBMJsnxygXAe2_%93h%dp1wr}ezkfd>6U8he$_ zhFjWZy*GW>1f}}sk1FBdB^lB-5blXhBKCYK34to@(=olJL!}}Fv$PdBjW1uLktG7A z=@l0lImNSMvD=(dp_I&KdS8n)SIV>BQvG)0TV8r}Lj)`dL3YGq2(_ZSbBPaW{}}UI zn@+vnwY?<^&f9|YXPR*OGlH=TIel+2pomYF1p1T#J2C;NvEUz~KU7o%1Ym&=g;Rt_ zttZR)0XG5!MSRQ~2H2V;KIM}eV?@{LpxZK2bp4*5FU=O5?ab+Xa>D3^#MF6&5YH)& zRQr*7#w;R8&7L`A8O>h~&`$&l7!dvV$spdzK=mwge*ctqYV@RX*ec{y*q^9ctq7fw z&9tgOs~n_!fU)8Vt7$zrBNp$l-pkLdr5nWSVL#odxUaGv!oJ{}_R^e?Dzc=bG?~qi z^m5T#O6W96AqMKQ7IeC%kvD5!Cya26n^x0}LTeD{Uj9|6cOZex(n@WkL?~(c+9Zma zu!u^jbknT$gz{I*4pr?H=g3Av1VqaOsuF|1@MVbzIiWt%KT(NM)CO{|za zRa=>4zc@y`vkIS7fc=|Bqs#p+TirZLEQuUbUm91f^)h6&niRO1#mKYN zkP=yFNGt85Z`e-f!rxhwdA}undUww(L8J`1N(xQ-T4szITFEj%h_=#9(&eZe)u>j7 z2Sz$Due9<<0lzL>)CZ<>VXDOwW77fkqKk}^4csikNZOEhU`y2jWRoLH6+Ka?ve@tq zD;Mam*dzla)#nGi7IVwwB3KS*VKod?zL3aDQz*I$=s!`Pbv9PM^$I z{3ef4AFmdx=A1TBTYC@*89C8f#@dc5o>$8p-SE`@N;)%ER(7n614}r^wgFzvC9C`; zP{khPHk^lQ+kBZ3qTTYc*UMGxWg%C$5086A@I51Zr}JJJR$VGJhJ$L`d4<_nmbz8P zOCeh0!gqT`@AI4WEm1Q9cAQ*p4C%mHLomR4!p01;z+zmK8gQT*a7uQ&3{1a12;0HE;z0 zfOns2Px{)57xC0oYzFiS5OBcRYQ8{;XR>k#i^y=M(7@niWhu~%Eq(!f*wFz(`#o6z zj`z`DgFV8|dat?0J1kdI0E$bqxG0Xt5q0m5B(yE?IK>uJN^5L*S^dnPwSWx5HRk>B zJ4C_=#wp;_*6(G2Y23?e|}hL!+lONQ@0y39%z3B39Gel~>eY${%W@hu z;7urY-vg-i|8W(^U^pw89>h{uky2v`>eLFB5v*SKJ4vOxNSy*&U9ZMNNJ665wsP9Z z-F~B^<+#-hg4m-4mtK3m=+uvE=2(o!(#L3%!oqPF)psYGRCeh}?VTpaOk~?fP-YcI zW$%?W^?7X;KOmaG_0r9210`qTyR9L1g=Pf=0v?n8PRfl5UG_L$HNL0U6i=78$p-lv zhmLn=8@=;JfOMKw4R4s5jhyv4)TI!hk^4VN)9H zz=4;~=E|w-YyOn~*YTY}GYG&spHhvRtAQZWI9KmPPT%VUuI{aU@F!$?br3l{6W`)S zhW5wr&F&b-=9q5%Es>q}Og~n6%egc%FbCs$Mg8^_GdjFW=1y~@_Vnf6I-x#TL#lzT zH7~YWOlNuRkgkERs7Kf8bPZ#!JiWL0>S25P0b@P+K6VmO`6O<#*i-;;rnB?@%XJjX zE?b^2t9;`8N*;>lvD*uA1wnp@yO1|J#ty&}r>o}r4667}RkAf`W>hNDe?AUts#(yS zcOba0XpP4O+{5+~0eyRcId1Bcf(sNB(6HAuOusxgv0D-LyTmPz)xZRUTc}ge{6pDD zG$$fKXcd7rhYK9Vm-Q=1?WoMof&g%OxUn7V23es>p~z0Ah4q{kVmM)77a&)4ueH1P zw-!IATb_5vU^7o20JM zT-CRks@uMv-&J6#1~Wb$V5o~zc}3iGL8&37!JxB}SfDXD%|bOJRGYddwzY(wH;4%7 z3pz%~*bk``B73w8d2mzIbt*zF3C5KO|7D#On2clT+nG@sKdK+~9jffN1gdDx3Bjz2 zQK7qIJPk`aYH!>C1rhT!{!h-RQd>~~aa9eCtpsY`GLfsHkl~%2RIicppe?p)it0no zc*D0j2%|@Su85uqIoH%gyo*$q=`Q-7m6s~c1vD+7XNp?g7b4=!ng=KdN?>gCK2Uh+GPU9 z6V$w>OVE{`>?y|Khh(;{HmIexge1Nt@-adhZ_7Y6RwwN-Eou!?Vi5{Usi{*uUB7+Z zBb>|2d!fLwGvF%+Vq?s~ah6FgMP}*h_EUL71)SJX{?OW5&#ke9M!@P9ksUhg_S7BH z2z>X&FUti{hPwT#@gb-ozyAXo~FS@ zve2y^S@lSd$yMk^Wxy?=4JTOH=q@f)smz|(Zd2A{jR+IjEJQ-yGTueNRoD6jN%rAm zTzZ9X4yW6t>yENeyh#~QreNI}!jy1GMlPRNU{y#mgD5oxxFz1qOGhG!&c2ef zua|$~F_B^lq;Ri<;W!PC03@`DHVqr5xgmte6=;kN7+tOP*Wh!L4lgNjcVJ>VreSPi z*R(zQMli23Oeh#CN7Bidpq;JI=Up3quy&7D1)lsMyYb4WtO$fwe=ZKXUC28>7&X)>Gl=e*z#O>{4H3Jz;ha$iPm97H)- z_Q#U3ABIX$f-?j|h`t4N4~cTAy2p+@1OK2(1PP3i)@091HpJTEAtXg77J~HFh9c42 zQ^lqisWPljf+G+yJ*)4^SWzPv9J)n>u{LA6EhCH>=F_AeKBtJL+BeI1*IjFcAC#!r zK5Xi@0o(e1amlDS3-noKw3t}Xr45P&CNHG~B7f;8-`pzXcx01!FGM3e=`T5rw8@OM z;59W&JgDI8($Z4wk%Lp*Mh{gCz`_hWI~sO&~nJF)3EU=>2S*bk)lg4CO4Zl9fdb@!`EmD1S&S^ z`L-#_8Ym6DL>ek1mWJP(TFNN${;Suv=m04ZDRJ9wKncB!p%i0Tv1&gwa?O)koXW;_ z%VsaU=^3J9HB15(G2_?2=U;3i#2!DYZu^&as1x@pJ{#@={`cxni*KzyfT2| zC+Y?vt?h}KJ@Z_)?4X{fqPF^eiA;NV=7kU8qzJ^ZF%T)4E4uYB*(XqV-?!=}`*?Cg zeoawiW=p1jHb2hk(2e7+) zc;Pz50|RCV@5ny3GDv1vwYh372WnPvV1`V5?_${Hv*f`-Qmau<1C(HA@&z(_Oej&k ziy^~B%r>jqkcnTpR#<7eb}hW7mJ>_Se3RrPh0k* zMrJH9i~Chv(epCT4VGOM*oo;rxZG$;B^I+={}&R<1S7*aU3u2Jdll$kyRGb^7yA zOkJ$X3q-bDq>mq=C>LdGGU=J`rstONJ7t`7B2Z(HiA;()@4FzSxhj98ey@W}iT(q@!D|A|@UD3|*?i zQaNOPW|)^vyF0zLs(5wYTgcz%Z0KK&8rCK_&rmP29m0s<)g;XKrzxu{ZXFFFnT+$7 z%;hPiWPl!$x_~Mo%a|pd`Y|Ym^Zr=$1aAtA2cA) zsSz{%W!qdl7INWa1t_tMh8qN>htbu(hx(9MR?zLPzgaVAPT>qApQN(w*~{jMRxnE} z$kA;}aeM5ZCe~)tSZg5?+DvrsFqq1raT&lvZQnYTcQ3@}J;DB?p)3OlG>ypN#o_Z} z>b^Ix@x3IJyz&_b9_SdWgVv_1)tgid3((f$rGSrO(7M*~U?Hi#Yb)&a1TM~UnU`{J zA@&I4c|hY@%BxU*DlV2xFumiBgDXdlK&Gcrx6aKkPkERQ)eh^!#Qc=Nl>Q*Wi7z(y z(W+k#g1Wbxo!o+VSXYpb>064!18~FRSLe z^5_c9LnS8eC8IzTRgn7poDGD{VHsU5%>s%{vrA~)N^vl|Yo=EY~1k{0WNhn?f*7iJdm?~v;d8XiuVrG%!9D9k_A-f7p^ zsE0>JREG$Q%8((gt1>Z8j2NpfcnbsEHoIAdP2GZZWUM|-QNTOfB*^SL@l^Uecp?5& z2nYHi12v~nwasnBeiBv~ zWiZfp3{mq8cJqDoh;+&`a^XkYh@1BxNU{!dVm>yE4w4u>A=<5tjIb`G89Lr?N0_HL=CrQ?WZiWdAGgE z;aGdEYQ>rW$c@Vd9E!f-(=+cO0?OtWaK?x#* zk5+i-kM^Dd{WcPkDk4gWp@EVi$vi=eeyk5erP>`up!D$4VJ?S__AVQ-`RIv78p<*$ zye~#fTbtT83&G!?Z4w=I{WM8pAE%u!JZ?Eq1?^+TMmuFX!ByR;%Y;6TjOiYG6@8H- z*iUeg-v3!aar4E}E=Jlya|-9IN`XREA*d(nmf~P|l8q5-^-DS;n-**FfUXS(84f3# zQbi0xN&WKrD5%dx9Jm5aI}t7eEtUxoL9Uf-fnjwU^V`H`iZ-kNM#7onX_wBVZsQQ! zZgWPegTec>79~J~L$m;!=RiYqROJ=D7L4Lp%9A(ZOYuxf z+~v0PBr+yets2G1TwAFJ(>*?ArTc_E1%ds{)u2lk6r%LPSm^TRHFZ;mYf5iaL2&X0 z`>_`U2xsJ;o00KK^JpC59U3?bTgZ!?DAMR+8Zl_?RmE_()-|*mlTw5mQ-5$|NWyy& z_19>w?>0?dq&H??eF5A)XDfcXfbzgy%2V_-E@;JOi)b$tcOYZZRLZWAy`VR`Y00W% zjXok8f4#sBmP+WH;ydtx;f)=mZ?JUHJQ8_`@*sF`I zuVibeO(T9Lg!-^-uJazdU#^iDqa2p+qx7sJDpDLClkj-RXT{LKG8Kpe>mcbTR_PEn zebY>YqG!f@48sKjj;TI*vyDo5Q=oK|D!xh}|Kb%Nta67}kdGEm3*kEe%O=e+MkFdV zSZMjaR>W%#3`FiWlYIi|Bv+_3OBo-{K%!GNW>CMzpn~Mke&63V1A5QzxV`UfT+0Q# zoxRCMooaTnY_n=I=_7b-lyzI5T2fjfT;rXykiJ&LH0e}4@S@Vn{+r+eiY_ih=ayBT;nk@g4A11}1j)dgWscX~#Z79~&w z$U;casc>6H7F2Fs7vOon?wrw>7i}hsofqt~&VZNnqAs(L9eUJ$I{^l(je|^KiItp_ zJz1>D)`X8M${2bI`HYlwH@Bufquz(I=|c*|?XE__3{&P;uFrWq)b!EjY6+huizH|$ z!Xr`qa1AzQo?9tkRI);9Qdpa+pZJVjx4D{4rtuVNmvUVY+sU|ByvnAfO!E$N9B2!i zS!=dW+Bh(f3v15~v%%N*7zMsvYNkyv_-YC}#n#3?%yIH$kz4+6nKUuvqVR{;mwVLE zf*+R$K}QbANvNzUrlC#)`o#X1KZ^zQS^4GojV$xt@STcIL~b*THOEG?YV?}AuL6XK ztjq6JbJFPCfC^fqI2{&tcwMoEY4b86uFWBy+rr@An{Rwyh2Tc)5wCY`0-*Q_;O#Z( zSnEJ=OVYHIe=xr4tU6lhHQk6396L&(-2?IYSkSu|c{jXNNfQh7aTJ*@`m{aJ-)C?l z>bnus!!$U_{D(W{nL<8mgquoOzeLMLqD^j17k#veV#$3>=d8$G2{HRDrz{f9)LIZ; z)KITwpgDFXPn*r zZm9`x#2N8YJkuD)i%%mCeH(B&Hu_94bF#P;xUYKV__R$yy)p^<_4J~p07KZ>zQ z^s9M6FP{`_o&ejU!S^eE%&u`Dl#4M6GxFQt-r~-NRw=5l?{cc*--WYbN_t^E>NLG9 z!=||N75~=>THhjXCZmHgagDZ|$ifXZ2G)hBP{4&$db1gOyIN8Uf>V*00i3vo(CwW> z!E~FF7A!-v)GWp?&F1S3G$a?)*`_Mm@d9Kv%}VMnTsh+S z#ws~d-eiSE_B?HcDRs){wvD_`?Hq^0zI6$#2Iui(hVVO_t-C7YZ^zcHuPRizn zd|RlQ$SkG>E1AGnUT|r{*%z8;6_I>ire`Nfx`Y@n@Hn%0@9Y<2JlzwswhTr$&TO*A z>rnB<#6qKCkgQXui(%03R1cGRKOGJl>D@N}C}HE1C#~v-Pjl4|U+t5Er^*>B+Gae) zDG0qW;Re@=Z+iro3yPt?KwS>{&0M{0_fXO_MI)s{`cL)ab*fQYKVx-gm2vCN0HrDe zp;lv_(FtUq%&&vFN*dV$d3LUvFFxbqMoO{Uod)Xl=H7J;5hHE$L-xig@3lQ|)nEIs zz14#!ZtE%PE~&-5i?nxe+H;rcRo>{=?#_cbooA0M=P{&iue@W-)o{EV2a)OUI9ERx zUCe@npH=3)hsCo1E!r{u2-Qb?Azd>O%$i=lc51+ul?Hno>rIm=WgQ1Zt^@(kTGB<| zh{QXq27&J9=I@ue6nFJ%mtAf1RHB`2CGja(YLA z!6hL!FRk?UH9y!<=jyH5EWtu3JTT}a+<%LoaP^#c_18VB{p=`g8y8Gt*t|hiu@*$G zzhOF&z%DSPfOT|T5~9IPUb%Rxp!ujMvHIph%vh)-8Z@TWk9r&VG?GXsbzRZJ$MBxkw`YZbOLj#(~9iGh-&(w&hX zL=nt*!d#}U866Lvn9O1jonYE*n6~zJxjJuO)BCddG~2ja0v&ToBG#pyW)dN4vYS?N z|M~JC@=-m$6cb8L^uW|(+G9YM_G38>zDDI&ZNncLW+cEUo7*3v+tOLUrl^u1Td+)4DIvnUs z6uWI6-;)M4cf38U-SQa6_(D_s-w{{TgrfG`UhIN4T=sQsFyVY5{9XGC<1*)zsWoiU ze`4DrD^A=0OB%LAotA2lqK7a5>rH1NSv3%?v~aAEMbJ!bNUR%=Y0@0}%6|r}%4i`p&6GDt8*9YKUBU}$8B)M*6I8(kt=y)KTW6RRQ`dt?J?rjf zkt&lXphA5EO#}7>s>Vzs%q02ET8*y7pbI-+gF+$xxBFk1PR{x&uVl}}6l1~vW?y4$ zUl;^J@g1>vDawPA;vmO8rHw8<9tjWZNO$cN?)DI(6{=Fii%{ppVy?P9V?Y;0924Tq zellfG1~~ATvh2v=W-l1#1!U0|f8Hgdq=%ku({}1^Er$yUiS%^H6z>evbd9L`l4uic z=P=CkbmS&k7dLhHszJ97EuRfq-aj>StKMu${hS?~lVC_V*2T{s>&VN~x-zv-jG3ML z3JD}S=`7m?GmU&Z+2e1N3t>7Ql}BRFC}Dn*cmP6!40s7jC1nA?&v|TAr)@vu=lr78 zqFXdq-{R^h^icXqoh_uH^!;+`SNeEq3O|k}@L)E+u@<(hdFtBcKB%Mb^^cZJ9PSiu_)#Z4R~>)0XS98WQQkagw)@{2c7qP8XqXu6g^$WyJ27i9eD zN;r7oho_Wxbf0CWv`BY6CB#{%8~3viw^9PRWEX_(@&#u^9IBMrm6|7w$0bt83|#kt zD&+k#+z?}HCMcSLJ_(mf{jsE;$$riUb+UZupYGDk`9CC($Mckz^Age!#OeS`Yc+e^lWAX734@hXPW1{LXFidSE7`!f!f-V3g#W&H>rM@KB!Wfe<22j zDjhCQ{8GG{9wq>*#uOw)X-!#8>fr~?%Uu^hDXCmWd-Z{26Sp!R(IM$}ODKi^^7Mx- z0@D~$Y0A>vtf$#!&leyc85j|{ev-kMUnWv0#o@R4p5Qwk%d{m&%r2r?s1h4_$NT4M zSWW{~2VT<{*#5Lk^l9v<`;nC$tCV|kt&DA{SCVehtiMjXwv`E#hS5s`3>u9RqR{=h>6Y}IDK5j880_xCv+icpgo0v9e&S;@4o`YEa zF$YY~&1xbS=gDmx`|B7|`iKofgkl_;TKVxSKFh{l%#~R3RX3!364^7_G|SG7`@_EtP~fJl)s#BrptNR?XF5^)2< z0y(y{TgjY<>d{Uor!)BQL=;2?R%T)w$+hp!Y4>w22UZ55;zUCbRMQb^%G8Ht12mdN z@5NHeq4H9)c`^7k6g3k<(}rPQ69l1;QES8BG(f2@yr^uu6xCdcZb5#DlQNJyme~e4y-13{^E3&bzAN(xFk)Qw(?s8T)`mh0sw0a!OVBxjL+e zHq|5fW8%fcoIDo2q;{+bKoJcRk>3)NXan^E78@4bu@h62L;o}h$M>08Iyu!#TaX%y zJ~VBQSujz$Zv-lV6bmP*P?mJr6|>1$WVmkbYFe1dha$0itbOI;t+>MB0-}aj9M|0U z#Qtef)I6V4BcvO%EWGH&Og#eBMJXuSON5Xe<-?t*C6((_!GM)3>}Dx-N&D+?vB+MX zpUYxl^s5#EDwmdf}>6sk5m! zvPEonOuOmg$^6ehb(HRR_lk z0ZnNNru>9ubw2No1*%XOmRg-CGz-TwPQmC@MjWLH>LlVli);^lbvS59`4f9tsAp=J zCeA0p&%#WsUVS|U>vVoRTiaVc^IgmF*=KWbcB%?qy9cTh{u4s{rJR(PnaL;yC=Y5< zcc8`LAOVxeP7Ln;h$v>Lrpn^UR#25OR#r8ytncTP0YXx|zr9+Om|i&LQ+teKd(i%? zd4*5_D0LG4{F2Ssl2ZR&?Txy;B!-ILd7#but9CUa=kaM8q4H6|HY0aBHYfV?bL2AK zy%jc4BuAjV=Km7u%eN~Qd3+U?^+Hn?=(xWS^p@;ircb~mBork z>337hk(w-e)@Ixkr1Afcbb?Mld;@nEYZpi=l2q;O*44)2McYPV?)gt+aR# zX3QB}2B)oZL4jv@jTAp?j^w7ow!tWjS6y$Vuh|z;xdbV2Q5q0Q zxX;`*)BL9P;nJL{S#k#fh6fF3lxXji`votUyNe!#g4=F#PPw@U zR1uFLayC9mNCHTR%w^3foeO195O<S9++BGzOh&S}H}voHOE%K?VG`MTKNIAB74H_^pRT&*q?BQdZ%1D^U=k>TJyIqjt7V z)gpJOS$EP_YzV)PuLCnn$Z=A!t#%yUO?g)iUE$0};tr|j^4JF*Gfg#K^Tv%g(Ah_Z zc)QbCosNfi3QTZaN|u?SwJbi0hvJFo9BQAU5xqPtSHK=t-(Q;8TNqnUHybXy(wj(wF80~zv-h21o?rQ z@w|t|2y=-!I~}L#Q2_c-4tF+_J#xUD;v$N9K|E3{lEq4{A^j2NP4tnfd>bT`J~j@1 z#B+Ebl*BuMK&h5$e+%z1+N^;mTyWTGS^A^R1#JDOmB~scVrNf7xn+Jm0Y4!JSp#5Y z!I~D4*RFk6M^vu*TMP8S!9_%MZfbJfVdLse4)Wsbu+SroanTQ9kxo!j=4aq^Oy;sA zD(B(9YIKmuB$CNqYnX`&{>XG8%|#9In^dcygzg!h$FSM=kdd4Sp~hl6^f;`Hcr&oX z=;s}88ZlpIDP-@&PQv^VYq{NPouWdcR>i@H<Mb-#)ar>2=TyLL51eS~l zC#J-73`|R7O_7fzP|wsJlXy9_|Cq1{$wGU7%!_vC=!swwM@lJKpQ_tCo&UV-QFO&Ns*DIuqR1699B(pTmr{WZy_?aD4ncWTzo>GWm$&) z)m0zczf8uYl>S}q>nj5L_TbQ$B>3)w=o2UYl`dFPys@l1Iy6VfHRid|GD56#z%Gjr-vJLI&l;!xALPO3?e z33jnKWug(13L@UF(6vYSZ+E1tbomn*1;iAZjtZHvOHD7ou(4)x15SA6#z2ulqjr1U z9JDPB=PSu07MatK*3e%cwY<=pS}{tRr86$~$nMAuo77t)ja^>c;^1nFtP>&0I*+NQ zNZ*8sEsisaQNxj!TL<_&k0I@wK|eGXVm_qH{nnDoV2oWQyfdVF6T^6ch0GB!xVLKe zN^jn>Z|#`eZ%HQ`+N&+=g*{}>Lpo6ie8?1t7~wS_7f+NhAJ}K_U~awzaC6d}3lxM- zZl=NC&;gZh_orkub4A>z4;7(W!5SOOfvBG#Yn-a*Co~BXLRg@c-owni{lv11>)I6F z4VJCm+H|6pO!1=i{!N_Jx59{hP>^!_(YAqvT4EZB2grP~+{KjkQ~blqMa(;5W!U#~ zC5j)a#WJT=jW0{1;f9w471REzbl%^@X|0CF!CWcKn>M)^W<@${#=V(_#2~e02+h}J6Ahpf`IVow+p@FfTVEU?S4DhhWvY6x7y<-`Ipnw&KiK=Z}+|})>KGo}| zR|w>sYk{K;^gax!?lo^^{fmky+`3_vwm)OY1LJ?#^2U>1P@dqug?xwj5lLotriFAQo~3s(?2ksY)<03xgvKu z%L$NB;2UXqPhZU8AF>w}^?f7vRT2AOo)m)w(m$KO(tAHt-L~#!--rc&gNH;o>zwq- zhwS-RZPfhdR`sdsMXxA;_`mewH+mIzh~O&;`hq_;DN-v62`P>-|L5UjF7K&74SkpP z5K58v$~aloR{HnD`yH)5XZ_Bt3l3eP!0E;&ix9OrW))D$NBU%QVlC7xd&;slJum9i zf7f+?q|!=;yAlx%iGX_S2f^pv+xoI6Xdt96;h(Z#tC}|vXl)CmG3;n(tk1`L)LJ8T zdCb@Tn4z_^k!Nu4z#hVTUBb2P+V;l1)gaX5Shvl|=w{&=FKF&FdOEh#wDhSA zKZFgFH0)Ldq7POg1k(e9l>ft7Az;ylY_@A(Wg|lS*@-odC-TL`!edsh=<^qIrGzNV z+pB$-CW4Eb7Qy;>`&x}M3U!QcNtqSRi=f!VaSZ*_#og7~z!nS#{AAb93Z%L}jAT!- zd(73f&4x3)6MMu+@i%|Ujg(b4DxfPMb;fy+)4%JwZ|cgMY}uWt2 z-p3~j0D)~pK^##OB4J0tx5(v6?R<8}HVS1sNOM60lD76bQk@fWf_=4qi`P%@j|9YI z#fDJ~1TNu&Kf*5geIj;DEi3CJtgrloX&rk}?lSz*Tdu;H?dC9cH3?LOD%$U@(BSOl zs4f^>E3b^YGHuUZyRK#JSDKjx>DvoXy>I`@&nT|(1A;-KHtL5Fq&mA`303aZ_Cty` zQ0ATzJRX)4jlOzD$FM1qyl74yd*gd9;}Tthllb?8v%(S`qCR9HWtreA=FqZEXNIzp zeajz-rAdNXy^f*D>*V!QqT7}R2VLkBPPa|SA7ZMyrur*ui)TN?$WV5$EgP4a#Sj|f zTjs#D8Zq$H<7RWuat*gb?{ZN*)Pd(t0_hXR|LT#V(WES`sfCMRtllc7i#s*{ECDrQ36?Dmww%xR0 zJuroGH4pC5sM}J&C>ih{+4sEv^xr5K<#Q3ddTo4ubQjTZ-Hs~&Ysih+Y(w{?uDeTY z^K&LtvtPud?TOIDIjh2`jGxjI8$;`)zX4I(rHorgk{>y!^L{Kjn;X-)wY}OL$~5OU zOD0@30W0&cql#3x=x38XB`u>Ay;X4GNSt)+>6o75dw*mbYaB0X00^_hzI#dkX+UDM zJ0r(E!16XBOhX?2Rs8@+a2=Rgf2G$)oSDWP6w6le5ZTcahxmi^@=oHLX3;@iSIhZ6}Bjzcd_2*>U?3Ps|<$x_H ztU`7jx6Q04zJmPFakgqeCST4^sY=v^#o_V~!NRZR{Pbe1B-c6&Y%L#|(B{NUW;koy ziM4ZhpsTa}ubz@sUhKK})Y|6TBk3$$N`Tu58cvrFygvV!{N^-mTY<;6&GmG<)z6u6EVayEQoATrnkm&l0Mf%p{6k?OM06A{@W5a^UU8r`JyLFrW$ zjxzi@&!#Kx-3(uw<|ei6MGt9qxA4Nu<^G)i7r;ptq5@7zCK1cf8&N%~&iJClVp%c=UGhLepHjEmO&bM4xHr?%PHutLP)A+qRtpS!jp308o^m zv(h=KA8VEt33m;Hy%dHapk8?IUY=|)v)!c*udDG@Vz|#JD!rw)Clf^`wXbS%rGP1n z9-tnZ)rGLt%wmm`Ae&X*3Tng32|gH+_1cJPGtp(bf`O_lMm6%PW{|hlAKQ<#ouP|) zs0er3z}vrsB1g^*^(1+SzVhNPRm62Ildn%Py;H@m+O^i2CJy0ycT%DvpEi8;G;M7Q zb}X&Xjwef4@h*HUVgXzVf9KqK+F?O-R~%iX)cNf@nqZD1=TYIymhdInhbL6P_Lc3B z2;hQ&)jb|V=(7;yNTJ!_9{Z^AO;>Zye?e?A{qJt~`95fgdlBeKYuGi;Sst=mgsBUO zF3H;RI}%Jq>}mZ6ZG;+loopsTQMf@xySf0zE)ufrulEWguxxm;2ac-xbu%>2?`z&R zW3NW@&=3iKr44YBiEs1JRm~dJ)CS`kGqNN+#UywSx9Fg9m5V4Y{bYE8je2Q7Ymi+k zW|8{DNg)u!)tGh$f*~z7FD?Vb-Wrp@l!$mEw-x=nsutB*f9j^-xy_Af{FO>I1oe$3 zl^zpHsyDGGY+bUQ#^j!aVg4KmL#!1Tr&*srr>(!dbG%jTDZ>!SFQh7E-6mKd6D9rp zMnn-KK*}Yj_Y7D4nuOpu%=U(98+L^>xl}pp{T`a-JPY1xULFB7k%K_lj9}qXZ}fBO zo@&Uo`n6GE20&+K!VQAYRo+n}E1a`l)!_m*w@mdhH;)-Rjt2s71aLTC*#9&!2&Y0Q-B%6u{ z49nCm)7badQZ%-XSJ`{rRXTQ(#8W+%08^_r^i_IggpY~aIbJUg7lgVacs%I=fp$6r zeT1;tr_(r>FP)f-)%INpp%i(gMU#C}3X0s0cT!g20ie;=$IeLMBd{ zoM)-=OjKqtN&q9pW-K)?v$U|d-aOTgBPHrX)dM3>XPfR3)n+;qRrXMte=Xn;Yy@60 zIDkPgycPq(dh+i5aYH*|u>C64Du%?RXyuHPzqUi#q*#(mv4!m{V=-;_r;7?XC6e^+ zeL`o>dA#^dNKD^a$}qR{M@?ZOGHF&PvpSyJvhxhvwt%Ji1X4{0XyI|Ta(s|%rEFLg zQ%bV>Nj}zt-6~3Q5gK6HR5Pm?U{VoG6jDQSh`bYlshNBjF!PK}n5j>>O+1Tu(t0?F zo%7?@N!uT9Y5(^*!F|nOiKx)Rp0&4xsb&icocV2ST$7KG>+RJ{bBkS5->04Y3s-H7 znpuP)lIFZLrye5H)GME?a@90Jl>U_P-(6<$$?B&tP-oMO*^?HpvxwM~fsySX2~hs! z`CG-OkpuKU#r`QHXws0Ht%j%(xmoE#sm_ymio|ls**TG!2Pl4id??+6#7zx6wB%Qn zwuGG3Hc-g{IC^0aVN>wEpD_Q$V5P?;?kAb2ClJOs0T?KK%G;(PRvEzS>d>eeDJg}IGcDD2J@Q?jQJ7=|~(n6VM1f?W=4>SL) zUCPOUeM~JZWuldNc%$5RMWPY%W_PbC#VpQy&%8+~e`B_w?;E!S*n*L;z9VsxpWIv5 zqlNVnZ7t?2sNpEdCZ_gLlW`v#ftdi6x@!cTm`(pnxSLBSnW@7{+25 zZXn3AG5NpNefPFvc->fmfEP+)bm9YXHLh<><2}P0IcyeAO;B?C6c3(+GDp=oo!zvr zotPw=gtEhT*jUi_MIM+l)}-t15yj-7g!88?)GF@4NfW_VUhYc1^f~{jJ2<~AOiCM( z81}9h({LsO@QfgmEg=Jjlk-K&81#c%SdS8%^zRu^)Aw;kE#J@j6@6{qM&}wd*nzp) zQRK_gKo^Zv+U>sb9)3<%)u7f}_KdXKEmfFzf>X8NVooTEy@4#J$>5uez^r(0NL`Bx zP=>j)@V7!Y%mtV@oDrf*=1qyvE@*~cYaOOJrat7VZZ}A^gQ|u*D+&GAAp1XdtpSo0 z@EN@Nln#UF0-^>UlREsZdXagy*;f1$IE#!s#J8{sWVw^lH27VHYQG*!+as*&;}pFy zt^@E{cG-R@ab51xMiAnn8{w3#(Yj7~Or<1=?hgfh;f^X&(gZGkOl_|z96C*ds)2e_O;^qnH$tpDZVV>w58$u#SJ@@$jmrM%t7#Ou;j=}xtB z^ZpsW*y2#|#UDSVpMnwoJ{HA~hxqSwNHLw1eb)?Lp*#>|BW97SxNK##DVg=R#lBdj zzr7%aRz_vnQzT~Y?x184>Bv;&796JMNyUqB&3ia#`UKA&Y^)KGQN|`LOUWRzEQ=BK za+l}*RBOs&J5>wN-`ml{mf78>&W42n7MtSnb+Uud&;vCDJ_ZSC7}K6uCMX7DYAeXX z|C5UcsVMW)RwBJYHJorx0PJ1Lpk0y0GL#lV$ zx%J?s{&GHz{n{HFI# zmaSA1tsnu;*L1RYDPVYbu|V&2x$kSMcLCQl1cpMEx!>KEZ|svMq@K0WdmoT$X>_|x zifO&JXfSx3kgYzr$Xew=b?I*qMIN4POp-3_uPA8`KrM2vv zX}XZy9o={|453TdM$LeYdaE{QvV9px;50frw|W9XGSzh4fXBin0aR# z_I}Tf+M6k99s6u^8@}%-p3#|FZc94OXgnSXQaW4{W}p0JS^P^vH`1-@_m$E|Y8rX@ z^7!lG!gV<{71RyCLSZ5`{if&*C4LWQh-V+rQ?_x19<+J(!6#v{;(%SPDzZhWh6lOJ zy$Hf{)oCN*y? z&}v3nS=}&sGI8KDXgC{JTfyd~gO~Q>TnX9$b{3h(7-v%oq4v8?P zT7Czu4we52@$5k8L^kvUPC)dMRGA5}=tQJx`FE*i)4h-NCLW?@f5f^~QcLYJ#{c?S z8%#kFC5m-AX}m+D>fAa?OJ6+n3)Ii)>MSMc?Pz@WKouzGeR6RlK`lY=7k|9^y+2>d zE9t-V7Sov!(ObQv3vYod_uug%a|uYzFDOKes41u&z<*|e2w@*{@4t8xfqt9xN8peO zC{}PX?(A%WxBn94%pf4t^VMTR%7s@ZVx)e=avF=XIIL<{PKWfC_9~C*G{c7Qm4)Un zkLcDDR^pLRD#`s*+zD3Eg+L~zFr~398H+t4Oo%i-_bStac42cc10JSS0g)X}&Zoj$ z4w@(Eyvw(EUgE#bO?5V~DOeLqIf4{>$A%wLO}W|6`47JQ)MD98AxG_{m5Q8HyP7P4 zE&3CFJF)^LN~F$})XQPA^FJob;>&AU?)5EqY!3Nb?{7LJO@WMcqZqlyq+B2Cmv){8 z1XopyO7QFNoN9j40t&H3>GSDeJ^B9CO&Xnu(@ zi;8w2OqYU2T2n^YB>{=aEgQ?9m?mML6sy9LB7D@oD{d#T zplA&&IizN=@DVEz;1RLYIeFs^-4|ziizxM=(8aoi9#7ZO8}#e zLzRXIurSv?z0+f*xJ%uw6kB5^`u-9?d|^$29|4uq&gOm_QBz&IeHsNru47N#QBiN| z<-szAG;Tfc;Jx7&=k}>o6FXQw0nSkX4ss*WPPo4aHKSG)cG|k8?NY5!mjL7uNmzE= zn2HpzN848$Am%%qyF;R(8IPW@#Ol4=*TJzd^2a!aOb^>gTZ_wIKXm%@ExGlFJ0Qn@ zJ_7Qmv6`@tEyF4mYC{916#R&k}@v7%Fvz2YwD8Ud2yZIOBfJH&k6 z7omv2ATkW!EzM7Cm}V0yi+1F5q|tJ!01z1Yf}gx@-Oer5Yski>*KF#hcvr{1Cq(p!`aV?{OF3;%odE6sBALI-5YoT1NNt1c{#kEZ2fAN`y-6pS=~E zL7&u{o0!0J2IboJ-=Hg1-2SMvY|SLayY#&fN&%%cHbzO&4U}=M#uI@l8=exhXfOFP zR{>yFuLXc8 zG*=A{oYI!&LKkGfE2*JRVO(m>!4fv42289H+So66$*5UIpf$CVOs(F9kunDi=%LEh&bnxUiZfzbuf)Gw z%tq?T+cq_3&3b1o$Z1wuG%;gsOyLlyJIq_V`%p%Cd;KE>YV|AtuB1WTBD1}=UQD{F zWjK~|JhT*7_nH%7M27|4H8goi)b+6qh<2=FI9bz@O@GVL#p=}nb3q1hbL3HV6csAR=Qigidp1ZK%rrNpFdVFxK4(+K5`-RW$5jQKanD#>j))*F+b!Q!TKAe}5 zlr#lZ1sem5r5xiymMdX87|0D&*F9(^w}1kkW&X@5HG{VC2)|<4C-}8B@o!iInVJOwFj9vQpy*Y*<{(!pTsNn`!VF+`OF05ZK#1k1d z*`dA`%?9t}D1Y{df4(jMP-0nkZoEONk5sCfsbsgUQkA#6bnf=)t@_z~`UGG~+n4x$ zwP$dR9q`EGT+;EqRTtV+6dmXmlF({9Rosvp&!0Y@)*}e@>iKqNIsKyazac5r^{MWv z-BSm%Z})cYH2k*xpB%xMHbY&1KXy8E7OFz)py6d#1V*F3wf{)kV>b_!l-rk*Kx`h> zUwxfM@StzL$#C9S6A^zC!9(+UYo|EX1GO)y2c1SW1s`n|h@d5~zd~#MNtS1413Y(I zo-tYqclAA3iwJ|^H;p%rN-s#Eh&G0Os)*?4G$M^HghFlit+P9wH?MTaBM;Q@#t8CX zHon0t%;|zj^ub({DcP=GF(EA?X2nfwji2LZ?6^W9ZCj>-LYc4W24(i6Hk+~!CP>&^ zlBozrK8864lUOP5{gzVNnHf1)ZH*NTUM1VT6m56w@x!R&IN0M)%A5NaZ*eiAn(cT{ zYQk`^EAp%;FPv*oY_Fuhq&Qw~9gIu6QON_PW5}6-)^N8|L z`c*C3O+qX}fyvo~p416{$^)p>MxofiUiP(WH0tA*j*5^$s%q?HD(4Xqz^M;i6NA zlF@#se)&MeC-0s+%_-9+YpQ`jwdyueBLC!)6C-jk5j1p#TQO!uIvc?>D_h!f14ayU zhAsh0@C-S0ejP0jj96=(EZ5UwFawv5#n{kzCT?#USJQKJN1usZ2;J=oU7hd=aBnb0 z`?}$%gh8zc*%wwI%cx(ip0k36xrCcRU`Vhh6yTroUZng6<6)FP`P;4PKf{@D=KZLON(dVgo(kyGwx(AwX}7XhK$t7GeG zC1Vhh%F!6fzd9KAFQ@)^5X-^>u+Do20MD^3qumOy2dY*J|5YGD+a1WWTl5-}ZV;s9y zX$6}t+(oRXfxu$*;=!1-p+fs8J#e|t%U2c=A)CaHYpR_BIyr0_`2h#<(Ij_?h_oc~ zd9=}&FqEDp;zs-Vw~EFN?Ir0L&(jUi6o7{ByA|ay0bu^wBstgbCqb~3C2y$3MVt3~ z`X!NILvQ6*7?co5hv}G3fO92ksK54JXJ9d!KJMFg^=@~mqTgc4KKmLc11wfti6q8y0eqDN3DSMXU&ds98GYU3yCi8v$04G77?Kin$54==D0D&;jnq5p>tg zxSWL3Vk&&>!CpdvN@x(r3l16Q8?Nv3r+yLNd#rHJR%1{U$xT9){xuO=RKO57uNUX7 z8hha!fNMm3TWCrh_h?_43F~A?Mm#k4_y0oQe!$@&=6xLFVWz^ER8IL{(_yDNcP@3Z zjua+ti(b^}@-K9ktguh|CQ}1suMOoqZazw03+xdi(`pM-88sz^#`Qx2 zor$gs6gmG5>39_C%4{3lGU^CHA};Tpwcwk}UPG{%+M8&|A5d`VX?MJ%)=S-QDL8xn zTiEOz8xY5~%MM6P8~u{^i(USSc2Sc;0b3$tU%%M4*11XauTyFReQn zz@Sg0%9vzjqR6bO8E5m3^{H3s#u>!vY+-b_#Y)I4V}^N^yxH*&rE8ZUedtvfG{N#W zSEeryM|#VxkiF?5fJ?P=CVCy*$aaUzeoqa+<1r2tp@$jDsXPgCv%jK{*(Rl6mB>Xkv>DXZ@LHbgTv&^!329^3A?_U3&(L(W93kg8A7b7iPeq-o#H;CaE##D>D~e zj_fUE<<{}t>ZS*eo&?fq3pie@FqiiPH~^IEi1lucxxhU+^z9v}r@Pup4qvGxQ{S?@ zNJS)2O`N!}qzI&0iQ6N~l=bijpTle2UD4X>9I&J~a!2R4XxGsU7$jXf2w83^Pe$q) z<1JOHvX+~VzAeHKjhIJKA@)UI!hUTt<;`vp+kZ2pPvv3?$2={?XrwjXv&(>57YcUv zJx__8`FeZ_CC3XhrArzEsbj?PprvC`v2pUY1>^f@&CaEODwSi6yaXKAvdV>r%~tg4@u79_1z ztQAwO3W|vn0GNaFeZESr#qhnR0|Q0#Aq9B@=OWHjS>x>xGR>o6<#yCkQxI4 zD`N4%C_u^Ewh*0g74?8p*HR58H=dMXjC8Zzr5wsC6-$wii-9sN*B;NOJMd-5L}TY7 zxz-xv7V(z2z&x#o3rEe&)`}^9?x>+Y>3R@gj6^f@7p9*swPRLRlEtWQtHzIG$XXl0 zir~|)m6pD>w1s+}6pyBmY6Q87EHWvnY5SCF+zTaKw9ZNHF`EpdMGUh9(;~@9jfp{N zm1wDf86HYlc(go_^BwSWD97a_kt-5Z_ zw=2SmEY>5v@n`m7_Q)Yu1SgJ;P%5qK>JU#JLkUR(6DduMP1uitp45k82{_ajb0e`U+=>rL`u^%b2&mcJO%U=d$A;Z|Fj5Q-w03v=NM zt3}q@fq9nzaUL+YWHF#D^q*Cp<`UrViJW0#YSpBnF+OKy)TPZnnG<;~n6>7X-6y#l zN9xCGm7FG!7Ka&yR}vS;U5>ZPH_!KE+1{0B=%;KY0eAKdnvu!m)V6|2tE}`@V5u@D z1d~=9+#14F4dDw2HjP@l3{RNNF(fsouJ^K^>dXHhac9fkII^v6(pQUZ7XPUO1`KG> zARxfNz`#I2KtP~ifdT>o0s;mG0RasfG#D`8{Hgc4pS4OV*|L-L%s#L8p6*WKSoN{K zKb}R1^Ue})K5*FmPxP5v*2!iQlAx#bzfIjU-O4I`F)A#m6FOGFU!?X!$Y1$4Q3T5s zPo)^T##X(t%(BXglr_>T%hM&BTFRP$05U@=jYs0Z3*{*sB2xo%LpQ-|9T_^?86CAI zvs%g`kHvK12$Pj{s^AiB*oDipQW7H$nwM|%N%QDM;6Gc+u^E?A306Z9=kAH`f@nl1 zg~HO(_rwHpGdfU6_JlL|M>Z;4X5)3bP-Tzg7>Oy=(v$`XYNgF$1R{6$vWZ+;upoHx z)C*`_VH_yMFlO>V8)HgUJ53Cs%|Q9u;_vS85_LZKu#>HX<7fQi%hCrhH zCf+8G*E@e`7qdz~d+tIcXR907@7`qwAFx&|LKl#$%f^=IYpTv9K>^gCk(FypuR=3- zkKcFrx>F@>k^2w>D~LGQsH7D;4&muIopUbdWU|k5Ylry$^T-t`t!c?{yEN5_X+|{Q z2IYQ@oX$Iorx`4vx`8yAPjW$1rZv%4_$5{-Q32<(H!6gv7K`|K_wJc^CAY`l%H@Rf zc(gSoP- z0MMWi1y%R#(@~Jneyz&ayGR3`;QI$S)k)>NVospS@3HEBc9EvZFkWYy&I<7~>aSuX zo~;)${sdcIc43I>QMJFY7l2X8GmZ+m5wLpACeQt1sBd0#PXL;ez5v<0oRsvetmLvM zU?tj|s16^drfkazYRIOmWe^#?xSv29029}iGWk0EP2ZjBmaO2NA|fHpgg6O;z=%S6 zl?wTnIHWU-WX6IS2^{kxufj1@=Ovi`a-g(+Q-{(-+O}BT3DCOnok| zHc|v0(tvOV?-{5~1zQcXTtR%ogYe+%oX^(j%8*KevANpwddtyj{B^BPF?u%6W^X10 zJt;atBehws*drv~`f^2p)#CMg1UQ-XMu_IaE1^v@D+H)%u3x4!g%xn>ok1Gv_^qa- zT!ot4+VJ(YSM@Bx2HnQ?jf5jwh>sN#8puQ$n^uvO1yYNk%cy7yhaw}Kf-_wPKy6x2 z?0OCF(n@Jt;@h8I!l5w~e~?`j(~&MhAMwHJXD8x=T=t^;StwPdRAOVi`aU+RiOQS> zYZq>5*M~pDU)8iPh0m=hY6+S%sbLcN!7mDEv7{U#yJ3kdj-Yp=V1>*UqO4SXJ6YQ$ zbf`n-2c{Q8<4}$c=tu(^XYMmuM%tjB6=MED8KFAuyKt6N(h)$Hp#jjS2ha%d+X?rr zYvl}X;wVjb`FbZ)sQobXG@&J^l}Cg4NVnM)h}q!}F$wvl zzS{50(?fhYK^Z^jrD6_S{Ll(1*dbT**%LUcg`D)arf$`mfkMY4gDqaiHOA1*jWdE& z6ESplp3l((`GdP(7VY2-;s-;@cO%IU#ZcFgvith_?#P<;O~W9a0q7y0NO?toR^iv- zxII2-m6>2(SE{0zFIc0;{FNy7Xwua&-#k&-5u8-APC~4>p?Foe+vBqH=bix{A;{VX0)?+!XoM`OMYUq!?j53w!1$fRZNs5}`F^PPNjToyY8#+=`Y;kSt>5tOQ%g zcfqUxjLY^Bsd_-m>$~`@EGILaU>!lM6GI_&`?}5c_@pt`z=87~3$$wyBo{_XAP&gL z#2*ONUe@wS?jLPdp(aGG`k3=C@66cS_Ke6@Nu5_pqK8=hL8C&`2$3EICtW-a6XSe* zoMFgA4gYi5{9FkQmlg4qJ@xjvX)$15<5FP%${bj$qB7AQzNma~{`5dj)Ipm)b?t^a$hyn@ znDb3#{+Yn*GI?clYoIC+RqjRt zy^YGzRtFh>f!;q52x-CZ4?i$a^h+y(*u1!JK}gJ8X0DelmNLJ*4hN*-Z0zNkdRQ_nT)ngTMRD zH1SM1q;m8+a5xd>jXr@S-{8$T;sxd1l{ZuGvPs4pWOYK|_#AEmTT_lR?9Jzb(mn4H zGO>4<_)}Yz!c#r1)*M~=Y?=My8miF%Q_!>RCdF7vycs$U4x7>lGWR}6Iewkj4?T55 zsC)f6u%$7y-Pn(z>85}B>G9W7J&wOi+oaz)=;X~lG`*(OFz0dRgmODBSaYT>W9~re$%&akCx7~^|>v!R0We7Sty~fcBT40vVgMf49 zJ%6)Gaywu+{tm%@m;L@ghaDCItwQ)&P8LTmcJxAW&YsVOu`;gX6uN zv(%v#U=evFm(S*IRX|O;jhB8`hBf~(NwxYE&U5gVr-vHjyAlGUkOtsNlRI{VnUYks zdKxFmD7klwa$;5jz%8@a^UlTMU0H&FaW))AQdPwVZ}U$I)E8hgx{37?k(mR~Y6!;G zH6y@U$`-uTao)v|z)`H4FUUxlCcNyE0V=ZN(9w`J?9o0SqT}(er>5Ea5!d?2sRP}2 zp#>AaHj$MfTGgZ`_-JhwEGtQX%;dn#yboFgKzPG}kOZPZ_1%9cZ30>56YgoSp=SQktyuDAF@Xus}vNA(?8sH17VUIrb{rqJa`=*u!- zdB2h(7)@x3u>VX)A2=-6I45ff(-}ZVelEIPyJ7p0p~`o}B-bW1zjIVHnsg))?erD~X2UNfS9?XxK90T4ih(-UVtDmXp!9z8lP42woAlweG>49S<;6*UmZ2xhDpFYo{hf8> zvtiF!AN4H&eJO)7F7nOJnB$@bX+Zl?DKvb-8R8161hE(qL^?fp>|9BQwnCz!ZB52m z9Bru19~2TOZ8QR_LZ#}WLNPvEu(BK1B8s5&{={Ej)3eC~{ zWr|kxC`X*KXKqS_Zx+74luBx^QA&f2QsM$!KARbRQ60~f5q1?8`h>^zrv@l_bjZT_ z!WW!PvCy#fWwsOA8d_;BWU8<>#ICA+%khj#4P6>$)|uAf;F8+vC47$!9oS|$IrZq} zr{+-yz66ditC2P5n5_gzJCDeqtkep7H0-m75IW5q0YuKphfno`Kq@K~aEb}5#A%=o ziqVheK4!K%bs^T1xAfqXO`0BhE&WJIP{rY>5~`!kEI0GtEcj|e1z=4>J4c`jMJ=b; z5+qV-I#Dz32OuJKxWVc`)5vdnZd*fs7o){c@JXJSb?}j3hT~Jm#s;wRC@S8#bw()` z&eXXfboII5gPj3*kC(K6=Y00KUoJ&(U2A0!F|=fbeNQFH`)oNEB`9Jn4()wT zYe4cQArdRC1mtmjH7lLGFdxd#IB`qpe#AjiBHo1CM(ie5f3(~mq-w1Z)S&b-+pfK$ zeKDuqyDs7{H7k6MnNav9-1m{$4ut@AT1jWM$AtRpMW)?BVdaZ>?_gnN)U7N-pjAH} z-UA;<_y?LMfn@;7H9OsBNa1{mlq&X=FOqNZ$V)Q^P?BNQ_I4Qe>AJ61M@AcH;*{6eryxuPMZOnA)%FEG;_B6kl}!Gw*F|F17T4{t*) z5mDenm50NsvetK8SF==Sr*_twhqdjRtd@b}M2~I?6%I)*jG3Z#KkD&(>qEDv%g|o= z7|$BNkRi;}Hsgn<>x{#h%*w5wBQLykbMg=heljaI?u!?9mhrYRM5ow5suo@^sbP_` zB^kWLrv?as9_dfxL>s;)>+#^PE`SHD&8{S@LnqYP)zr=@W>AbzV0j|jLdMdIl7m`C zB_D|VIrdeyfv$z0JUn>Y_&e2e+<~G7l_443hMoYK*cvP$Qp-UY!KoIW*VIKU0!ccn z3+O-NVsH^%=o|&qC7?b}ER{Knj|c9fQ%Q&lwdC7#R31rRjNu#gXr~~H)1M%GTf|D^ z;o$rO3nwB(x9@I+XJ@x-L@9z|E%`=8vZ@Ey6q9H%52j)S!stVdr*3>eplR; z1*p!&4#&QSW6ak^x*JIeg*J#Pk&mEHLRW}DQaIfrUb)mP(Plx3S&D^sD{i1pSH%QH ziK3UWdHtb>8dgP&3lH8C;=jIlHMAdI7Z@)b;>Grb$x8^yQ{EhK6>YGl-oTKBX&n=| zX4w61>P}QFvIhU+9&f6XWbQ2aj9W_s{nFJlcH+6iZU&-V#M@k^Xko%-+K*g8>)0is z3HbCFPT_3M1vGYKHhtU1Kt9tLr5w7IZhX5v0@}JjNM+g@-`A`*hm$o^B6l4JAQIMu zX!`a{B{-e7b8MiB@e)tNs}(Q0yj7J|4i?mkMje+3z@X|>L+f&APuKWXk?OFy;&!>l z9EObaa7H#}$rSdQlP5+L#2$QTOl=py_%Uj5^s)+UaVi!gdX;)l3KG?O=fASlI|*+W zDk?R>Vw&jI`p_1*`+`0tU?(l0W#5rc8Uk_WSJQ*g)b!U=S6h?Y*{U&tBy(mmiDUv5 z1H-_6t3d%3PSR2?wyu0*8U54dV_tMM=zaT~$`C)F=M+ZVshA?*3g(b^zBMdrmI8$k zqCmt0iD)e?z2p8d4`IVRSwm7R-O|ei+;scg6EYsZ^Odj>Ne}xwME7TBDuP%svaG+@ zAClc@I6Wx0Y07ooG`N$zR)KB@rR!8e}#p893 z?&cpe1vQl|MNeHFq90@aMO_HQL755kS-qNUup;Cm<@ECU^(KF5EA|Qe)l=2w{TNq5 zgIq%0gyAGWhuBk{JlcQP^p;}v2W>wL)Zx#AiAo2CA9RVv_0RhG=DSSAECOL2O>$4= z{LHd3XN0xMS#Ydx4ONg+1e66TdR5st+PZ518Rtzt%Os_N*aNw+qG=0e+j<00zZVdG z-~Yh0ONZ|3X;^)1uG|ekAhJ?S4HGPS^%j-&MRlP|WA%-D2|thDbbB7b5b{-D4c4M% zxW&%Zczz&9-`T>?D(+r}H(fn_wNqn#73hoqv{X0!Ac zCi*Du!iiVv2Lx9@O2$PXG#7@hubMBI{UEQKkFv^QACDP+XdqSjKQwkLvEGU)pt*ghaey$T#+belyg zcMiDSY`8VFdDF|oev25b3fG*BiK6ny7rEZ!m~L>YUaeG$*1?X(w^>k#U&Kn1E3)iK zXCGqqs>&Rq(kr)Z~4Cq5rTRh>t1iQ7D)K-NAJUid*K@h*We z2&mnMA9Zo#x;lLbfd9+A8~#67)mfbKCbtW`Vuj&@gzUIZ-m+BcBh6=0YJ$K zBjKcpaqVkGkHfftI&85QEZux;_b^)DH@akG^n_85C)P1dpz3$ubvvx%=Z6nxGpZZI)3epB)YmmF+NIx*8A2waE9pGR zXxA>)_a@gFIeRP}%r^>UTh^DH2A>kEt86k^ABP%@_YmF+cIaov@w+kQ94RZDzx;48 z`3a&n!{+fzr(h6+RcHpI+$?*qiSj7FWaxVcdA&IERRDBE&?;pF?U57qbj!>opq@C4p9~=VzV| z;a23P$#c=bjjP?@c(0JEf#yXCL5BhSu#xXq=8NAaqd$A%L z_kkWqLWo+2cW52k3WNu<==u=Hx(<^%nv`GFu#;;-wp7Kza0pR3vRQJfQ+sBx!TIjl z%uKD+9^q9+NXTYU^xMX7=-Bi}f~S(IQ$H%^DboG+QL92|hl$zK6?N-Zl4}WKIa0(* z^LjcF_oxeMY$3SLRoJLnhIvt1SC}IUg>XlNYoG)Ze^4#ouC33&e&F6yv2mZJ&q9nIG|K$)GC*$-4R|Y-PV%_f`hcz-cG8ntd=c zQ1HFJ%?sU@b}sD59;)OhDJO3jg8a1xmh#yr?v^9>&<&VV?^obo-Fj?R@&0F+a2SrNSSF1+A3~x!VE#&SO#!3WM_eq+O{fe% z{9@@mTEpD4EonjCv*=-qFg=lEY^7-QU6!h2m`282w|z*{vEHVLDVA7{R%x~M;7hOj z`p-`na#VqU1O3Qbu^Dr2z_@U&GzLu-!wH)T&V^x;22+Gl*&4IKshz)Jm~27CU)zGj zJlT1#ad({7Ri@+8A`W(dQzph3IbBj17GYXK&Y=^TF4C{pR7Rqk znSy3kOdq;>q;B29@DjUKCufkcM1iuZ;xJyyFIG54RLQv2KthB5K%2{VkGH(U8$51A z3DZC^Hblp`NdolPR7fU(N1)mQE!alkg`fct(5F{kIG&R}lp3ooM_w92@@DbThM0bZ z@6;4X(Xm#QhS~v{f$YDhSv#mH!rkBijg1CSFC=H{-4X~?W0jNH-<=mfPj#gbf@@bR zYkKj@22S36Z&$1_E@Uifc%)9>rog;8>~J1IPo~$SHVGLFX~7SmKw2cPp7^=K9YQ!x+#Y9Ec0UFbsJ5ZXdbU zPZ}3KSLi)@o_xKH4>LYyQS;a>d+Pkz7);!QUhVb#E{pk4j!Ks!f}K5SHULvFyATf+ z(_(bA`~}OG@v3;JvdLDd%$6a$NQ&n+NeS{H@!T_X_>SZfML`Bc;TI`1h}zPfRD7H1ktbEU%P!63B`T6*>3uU=J?W zos@f;RNPcNPVHG{g+rg2n(fvIoaQN>qu>=4qRy1eW^d^DeATpi>O*B6fVX?KJU4L$ zt6^i+&K~!!m)w%_i;{U_!SZ^CNdrXknmkeAg& z%HALF?W;v!qVi9{t{x=gS1Lnx)ahTXHl4DuY&GeP`T-wWPUE=-NiU^nCt&vr^Z-9{Q@R zFk8FqG~2+|p%*8jxsXAg=?o`Qm%Dx_XSSCh67WN=rrq_l*nKHBCL5mhPoF~dX=2oE zphtfQ@eGz3;H!2?RLU_tdl4X1zc!A(w=G9j$IB<=WJ6V)bsu6q&(#8fMg(U!-!n$6 zX_WK+sI-)1Tt7NL&5m`ex8BrSDW}Gp*7=i->i8%*F-Mo2GkjW2;l!AiCa;8($@fcP zfXR&vOBIts+>2(oOToPSP!|Cz`=Tlg3<_Zr_e#L4I9>|5Z5UWjUu(Ow^|Y$&`9U|eRCQAWL=fzSf2 zN1i=!q&Fw3Y)rQNq*Ai^l>fdpqc(-ERDxdfaY2GWT zL)_b`1M+6M7>AgHmrHT|8&3rELeErxPQI|r2;3@mwvj9RoRphcL7&3lw!n{>obL)z z|Fj{LV;+=pXc3(+u^)*V@HVClor0kS%pj@)2zbmh(AC=L4Z~1sj@VFtlo+3AZvA+? zz66b@T&25cvH{n5Wsn4JyZo3aZfRZQRV9u zk$l9=EG?|+SplZMo%W6NnL5=e<|_^azA7~R5K=nSUr&?1d$EvV{Ojq6WO*28SUV+N zmaps3`4cSYrSH-X1Wrjrkx!%qNs+5B*7&beXs?x`znMr1W&B->1;;~8z^oj1v! zX-2gZ`HB4yU#N!FB{}pkYgVmAo6=aaBX3Dl9Uu`lzdE6evhS@o?UbTJ|+PkOM-$;(| zmH1p9K6Td(U|w8uE2@ZGXDjVi!1MT2lS1mTYutFtv_rUih{plcOv>3p+f}A0aQjj< znNe|gmqj>jl9jZVYdoX!0+(Yt59Vc~QTBILkV%Z8eWI}jNW?jxW;sny=H|lN&1+x@ zW|l0ZH(L>7Hm-X&qeriTf;fe_t!837H9gzd^NAde4x<$R>UWpB;iIB z(od5=m`k&g&PlH2ZLxxPT&5K>C$6Bi%0}(W|B&5p}*48(-50UXDc8tc}n+vxe zB^wtiC>a?wy*g)TqjbEP(9DVH;@=uHq$)v5Uil}}ZUGWQg2~MDNHuh%Epi{KPZ*mt2;^HRkKs9-JAtd;v8U{k zii)S+`XW}6%%B_CH7f-3(KYY8R>7@2Tdaax8@20WrCio(gc0fRCEpuABIDSVQ^Uem zzE_PCYi&rLsV9Dd`jurTC}<5_R>Nm? z_}5w1jl0%HI`s3#>vw%Bm$l-l zcTN7kbxKMbbfrL?GSmlnpPQ!=BS99qM*VoK-H7MjI4k95SB5@UP;k1wGVV2QFHMqb zyMB^}D(6;Q^L61B?Q*j|`R$7;2JW5Km1oF;YW4dKV4`p~_+KvKRO~JO(}~(2`TSyAVbNP; zPFq?GI&*vd21nyIr7OOR7u*4W^LF(Hc za3SP|%T93)cDR9jo*}4JLA+rfrnm*?X{O~>#SM(J`quX9c{a?u#@^0X%-HEu7}fJk z6&o7%YJ>U!nN(AT`85LN;4cbzK~b5uhAW%!-l{eqL}t=jrqMbIr)2FT3YC_Gf+ZmZ zqM?kjUC6N0Vm?y+f7A5j`JwIt}%l<3S=V%fwe`G;&Z^ z&sm}2Q?!W7y)_5$h|tty2c|pyLY6q+l!JDuVvk}_BXb(Dxhfe-Th|B`04!F_{Db#} z^CxGrIIR~O{bTywi#L6ACL>b$PIx<9Z^B(7IyRJTl5}&;*Ypjo&!Rqis09 zg?NTuz8zQOW<}m8SU&Iol4Ok>1POCLKr-?kvBomizI~+>V_+iyS_qE~L<{CtVS(u(ewKcoY<Irxh;Owv^!LfL7{zKISUKz3I=?#kb|;!T~X2 zv46Fy)==J3FX*jy#Kc*Mh{0Edo2;o_U3O{V#Ldm1mNplYl^E?ip zP~%wGAa4NQWyshMpCy);2LtBO-LHfSwDH?&P#K!K6~%{5^P&GEZ+0D~#5BHbd+50$n3F)rCW?U{`2o%F1hH@XV>gD=RE2mBb$2#I z`vA_pS*WpURx@)9zV7a4PezOgty#Jt+o8ijm8iBgwywq5bZcWHb~PMwCmX3H=Q4E= zJB|WlkKS2Cxj~;!0$;Q?rBq{erMQuwBx{$lo3~aV-#G$P*XgS0JRy-5p_2&HXi0C?7i_11@)du;Q*jt?f@DxkuN*R(gyURWtpIGA z+BTJ&IISNYBhA%esD{wCx!y@?UUOn**ZTg$XslB`3y!sv9tFeerz{)frMt_ltbYhT z(GuSHg7O5YlVsrv4z|c{zl9GTLj3h~7>fBZ_2z@GBGc!v?IL-7-aVwH&Ftd0k-|XZ-AAsS<9&x&gaS}fEW@9cZJyIh zd`nSqH+MLNorWI2QiRjAp+7}U8=w;Vmd-wK(pwDq_Se%%NK%ksW;q?{)p5#=&{8S} z)reB(qwCc-HK~I_%pgYi$XRNeh)V|);CgPJ%)0b!jm4?pZ#OjOm5}S8t(hJm&gX+y z>0li9A8WhW$1tg@rym*&|6$QauLf>#z1Op8nEi$fq`Mr2!srCAPcQbv#2qzUxu>Fe z?Ug#Y9uwULYu&kU24jPa^X`R8qsc_^@jPu3Ev878)P+0liKgL5dvJGCAeX@-? z5}Trj7}+An0JEk4Hwj*YD}?Ro@GIJwJ$^ZLUMf&+tCTB0DUSc0TWHC!73jSp`&NAm z;S4~v zXs-g+?tx|`@%)`q9f)K(2XO&`NumGG#6VM$neI}7wp5!0s* z2}c`P2PBQ=QCdx!(4I}*L3OygEa7dpkFQ9aN2ZTZF!((ED!wid3z)1?-QkM*#dv7E_ELb`C%QT23AZ57_PUAR*2 zMWB^%n)STt9UOtu-Ne`n7eWGW@%pXYOZ}fbHWRg$pu6>_=}hpo%1hzct>~4!mbr>C z4sD`DL?*i^$710~#Epxwe$RWx=ttxkNb9iUIfh<1>nln2s;bt#4Tjv%p2IFr!5}EY z{EH7f^ACbuV!M9KZXu9)y1@9?dIjceT;e=w#QVyhcMC1Ji1;O_B=~Y&Qb8E_lRCl? zRk(qpNpM6MFog3uF-jz=uGiti!Hoc<*;IejKF2}e(6sOVKwR5Lb?LWMx!Y5{nN|4B zU6;T5ZU7P2>ZR$ZuIO5yVs31HV_Py;c0dZHD#XdEJo89(^JZ<(=EnzNz8PS&Tdr01B&k5lgRD?0T{}$icno7#m9pyJ zvc7QCkI8hF9XuxC+%u5QXfMpBnx`v>Srq2FIdI1S`uW#WQ@>+ejQgx_L@SjUrRH`s zLN$l!H^U`UEWwcWH`0qq?Ur;lksoJjrE1 zN`(rK{kjmN%UN8N(1x-71I-nc?*tXO_Us zyJIR|1?E0rmw*NB*3Ws@jA}vVDDKPA$2xZ~%fVH&doWSuaylESlTH4!EN246e*`F+ zG4G%9aW87UIEZ>Z+y1h_$+L=vOb<#La=c_9r7Jwc-gOs34!@zO@6ykQ?t^QFd6f`% zKh3oDN;2jeYc-*e_ptZ!+lIF6NjKM9CP(Fje}K@9zZ50CF_k<#*aIv@{Eevx2xqY0 zJlxQL@BowJ+(3D#jsaPKt`Rsi( zZiuG|>cIKKhl?tElm;DgJ6#7P%~iTA+jas#vEksHT!#6&{&(n}fRoB+A_@bm$w=1D zv9mE&ZXrPoyfV-wHMGASKf6Ap_lYCz2=$|^_sj<|-y<^Sfk3~9<`ESbQMrW8i`t!T zOsKrv#;$B0oCg5%A(wJonVSqT?|jH#eWlKw%+18xd*T#aDTiqHHmYh*77_Xa!o^|c zKf$EVV2}6wU$uA*VH)LG&f`*JOA<&ikN z8jM9$=_7GgYx-9mC->+R)%-Ta172Fxr!&jUBR}VrE!El^D;qR>O>((eQJEqt3&@{H zXjZQ?ufV>|2thpL_X&{@nFN!FuMFtS zp)x)m>2*$2HBb$v)wyx5Ak|VO-3d#Gw1v{`sg7g7P{QcFV-_uvBg-!FqZLa8GNEU{ zReOT5#NmIq)cRdvbxu#CLRj5akYMtm0gG$S%Uk<|9tD4zG*5k=NykkQQ&kDLUivk^ zdi~g5^oLpN%DJm3W>oldcD|Pkf|qvQcf*yVQlP8=vy%vd#6BDpe^nHhc$y^(x~r<& zjl{rMqTyMwLgpkdSeQdi$T_vdQG(~Cg~e$Yk$}_X=3P|Xh-k~LtKP68AIiJ(kj|^P z({mY8poDG-N#Vd@z;{goR3vt^CvRjn!(}d!qQF|$*Lk`V_5?daGYd@t7|=6AM>g`C z)2^L*q7MkVTaR1EwWfZOxE8jyC&zfzPEk>(oU_732g?@wuR$Q!l`Sv3EB8cs*RXjc z*~&c0y3}nU4SE>e4``O~R`z+6xibH|>#{o92-8iwwK2W&rG=%wXdceAc2}1*~FuE3$I!#7MMGjUnQ64ZP65GLhqiO0uc=JEZwgs5SQB10UE0bi4_sCS=cKn<1t6H zjcY5(Zz>9A<~6Mawc`(sUV2+8Rlqw1k$hzbO+K5fG^yQHmtLP`{>f=-UKR>Q9m=dA zReo%nW$37dZPm9ZnqhaR8r|d-5CfEv^O$#yEfYTDE?fe`_Hq?!Oc*EFSfSi5NLXi~ zTB9nw3R?>qu$G#cZL~+7sZLX8Tmi(ZAdt1;R=w(r_xY2d?fE~g-m>Q( zP*9_i@MJgjj9dZ_52ms*YD96okSc6OheG@y;#;Ujf&@eACf`Umfz;wOMJfTZe+cRl#37hkZeb znW?eoyiAb;C>)YzFbwEQI8rqTG4%&`EzG~4-`}0%sfriIWbI41 ziE%fD)7M(N>FvOAY`b8ywE5r+>cXS_og|oRA>Rrs#}{RPn|lxw1+S+Njs}y0e&8oC zI7B-DzvpT~f2XWXLL%U5C8;lV@RmxBMtg2Lcq5tnl1KZQe`O5cHC-6$Fx1@;>Y)RI z)2L4Y*ql<0aZ;2dk93>W3~zSpQ7e=O1P&vpJ9P?xIP;=j5M9-f5VetP?>z7vCU`k@ z?Lj$z>m=bYyyp_oRb6R}=O+2s;u#xkCK;NJfFtuWhc&wI|T-Y zNW-a^0UTy$S^ zg2nkDwv%=M#)6-&(2jk;RrzH+#L$!NyY$LwFGbSO8MAHjs&6AmdF2GS43m&%HvP}a zwUCsmDn;se`FCpMpq5Me%$`(jK%fdKKNT~Ls2(yFpLR#7>a%2g(|tV}=%XLntFFec z;&xMZ8LS_WaT?wmfT|SdwU^#vB)G6pihYyDSeFHWOhzeA{zKbTqf9T1E_={8=Q*o$ z*AK%H{L-^xck@}Cqtdrogo9sV;Y;7L*I9GZ8`l<%$9?RNv8G zVkJLLuFl^2j083clDHtIyoBW;b$qy-kSgN6%$iXp>wMN5OIn@mDz(wJN46W!Ry&y! z8IQPTKGj=>lXz2_(Ie65d}Chz#(H48oM!772>>@)1?O+BfO9;x@hnL(b+I#&*jj^hyD zB6#Xq%3Y#@Xw5aTsA=KBMqdq6vUq5DUzezEpSBH`@_l*SP1LMbCdlXT)+g?RqCz?< z$m=@lnnA=eF{&W>ZRzZ%1>T#QM&f{PhnQgKQV_mDCkN1&{7i2apd}#j4Y5cPC9SGsRg@6i35Y06nbbwtogpZ_1TB+v z^s?T2MsVmqPE3!)W@iG?uJYlL&>gFl8)Yr_>nN5W* z(-MqMQ z@qARHGLfEzIo5G>pPOV<;6PC)JB>AL6upS^>;5WItk0wTbYvtO)L@#~q`%fV$mm<# z92d-{7|uJUkHKv-+|OM?_56FTO`J6fqKQw9y5@^#rv?x)Kpa2geT80@o?CQh1ZvFuBGeOJizO?80 z7g|Eq;b#K<;H^jIBH2d^+Y^2gF`gq66dfYqe6)%g91bZuov8isDhLjwh;(Y#lsk1T zX7P<DgQgh^MU-`6_qohZppYE+NV|pMnjqp zoGPH9&r#RCO?HCwDC{A4)s2a!Dxs10c+va`ncMt5=)>m=ehW-+QxS)u&jN}hD2mwi zDYmuh!)<_}m5k*~Re_%vDbiv}^%8ONEd$^Vk$?4=s$NP!(TxUu2c# zucN}^5(OPy^;o8cIfAG`*ieMhi=e1nv*Y+LJsz<0WvMYiS_L9*-zGKI_|6%~H!qQR zYygGlUpWgMlFOrrce4<3xdM*L=L*TR(CN@P1MJUWT!>^$zz^_3;M;fTITBK55mfp3)YW~x`&+NN1*W3Kxo9@1x z+VFR5r>t(m7j&ZiS(0|Z=C*@ot-O@5P`#~L1Qd!>?!X;lxAT78DYr_h94q36=cvm0 z_;KAZ9R$SGRaq*=@p*k(gm>tLBvdVya{AY9NW_VQU_PXX5&NVd#v6`V#VB>vV7Id z60NZ5WBqjQj-m!p~hmL5d2u} zpCI=2Zo4mGSQ4%h5+~}A#8FKc;wVdsn8N|2VqkA`GIsa}&t16sS(tf}kG?iYsBbhY z+5H=oTCxXQ{iF~J*z|Afw-o4+#b)G+^P_X~LpFtIHL!WMo3AViAg2PrIB$qH4!uflZb+2P+yJ2!N(L~I%31jPHDRajDZC&h&Ad`A9DH38!e>vY zA(Ti=n{{E%l)^VLp^(_mto(0`P?U}imlL&rFD{<;DtM}>Y7Od5A#LtGYbyI4jGQYo z^s_dDo1ta_`l33#%<_09#eZf}jdU8D7)EL?-w#zvgqnJ5!$9zm2lqNbsUp6kn5S+- zsGix3{TO)`bUlTpMIQp(COH>@m^sFwr&dSwa!oFq6d%nj87#rnl!B{x8rPi>vi+b; zSY+zYUP5K@aC^;ArtpHW1ysMPgUei9!Pcz-IcBADvvOzaXt02xgcBDoX?7Rqp?Q1@X3mlRiEAUcHxp?6b9RsJFqiy>Vm zXYKw7A^mT)-&^F?M9mtc^!CZC517}p`mTNKH;V$e{-ZN^Q6Qx{$pH_uy@?zShAb`z zqp#9IP8E=J5!vj(6CBG>w4+pOyauW#r#NdpyRStqcF|Gr&W5S>)vk^w!8sP zrAEst1+8#p*6!lq{8Y4cHPT>Un25jm)L&o1Ii!<~y^xJM(w`vM35MNc3%|8#jECRa z)UPi9rq-#b5$O6fLM>;bnXR9aCt8wAA38l%Qgq$vJb9(LDpm1PZV!gaUsMAf<#8F? zp_Hzw#^3GgzGxD5AL5iVU|z~%fWIB^urxDpW3p=gmL%7|;dJ$1VG8%5tJGF6VJcFW zoREU5`X=|cg$`Q+fuRY8_~}6K!=he;6CW}{|iViiyf$`5x(8TqrB5AnRE5cY`~99d$8IGh{R&`i6w zJF2DojP`G{_QwdI9)8JR8gz&k?yP_G&a>t2V4)b#P|V*Wt_Jn(psEb_lfwuhG+Rs6 zP_Ux^ZVeEnY+JC-Tz!g6fo1dRkLR0`Gchq{tWxC1UM@}OAFv9)RlgSCS@ZjV9*0py zL_+7o@1SL9IjZ-ULwzN+K#9<>NUc&Umlg>Yr}g5P!uxUD>peO_tvyyk7(SHQW!mx)dq;KS8qO0!i|vdVV4{mDr7`w=VU$WYv;SK%CRO81r7WPG*8%A= z=CEs3(rfyF`V?@%`7SG(!-uor;3ZCAkd{VUBlb1uf^r|d&(>nYApAsCj+yUJ?4~hb zwsLOAIq7zdOt0!RVXJGu{zzn6&|+HYed;mF$yrDlfMBjC>TroSKs&5Q*O=vr>T!v) z+qWsm(0iQimanO1;4{$`#kJl_puGJ}f*QQ%Stn{ssi-_|i3!HZz@!ip#_>c4iRmu3 zH+;|Z?CX83iG97Qv(;_O!Yl5EAzqQ@%a%;v+@GhK6!G1F6k1){b6}>%_4^SSy-fhH zM)BJlC2&<pTfBRp*WI|l8yLysqPc~p|~iNLP6b-*fHj!KUtJgPlfI9IoxY|a5H zK=6b{bXuUD6WipilEx2j_g}bHtkk60!8%@mZfwPBbYlqT-HUypJ5!~Ulz+7ATc#9g zAvg&v9^lli(pK)?S(4&@T$w`bWjU7#&-2O_7}xI#L`K6W?vdVeRFiNN{E*{A45pqh zVZB~Wu+ob~acj--e8!+}89%(Vk}f9ywxJtH+6oeT73E zm>C4YzG#J_^PQWU>`2q0mkg(hR z)%wgIt5l;eBi2Kg!afxJ_${AFv5)rfKj}rZrjCT*;r6PQa5CK-qjt654%s) zwVevjAUA|`GzgfE#ta5+h9FPe@8WqroDbc_qqV$I+1=7wm%+oBz_E|7Db_umBSG^B zeX}2X+@^3q`h)MFxLCF!W}bJPYKb(Hda9~QhXe460lOoInj~Wj*dk+!Zaw z?nkL0{F{y%K&F>jK2c>^^V5qaKprTU6F>if99RSmC$&KjQ9`F&%LYF*Vd&bUR>d8t z6@=7^89cwk7mpq|c2D2)P>b;lQi*rEr@H&PPx;Q-(7&*~3H%oZwb5Lj9~&FXOHltL z=psvrnk}MSXrhjzV6}f?CFfn+oqz<;%`Uc6CO+4^n36*bQ`=KmTpmsTsRb2Rz|B9J*Vz5C-nCW@E{mlx zCY2GbVl!iW>YJ8Oz$3eMki5$(Z-bQj*qp<$sjlyHz#%mou$bOAo7pVxU7`GF)6-6X z5gj;a>hTRtH&pc(01OTfL@kRct(K%#I^Y;2PwByaa2~f*7M}h*({Mj^K*BNdv<;`& zc3+Q|e5Ftp*tLz&V*Og~lN)HMi}!MK+XKF&hA>gnl#7}yafpr5h!Wa9PHjKN>f6dj z{YOX6lP|G4YQX0JLO#Vaw0X`<#OB7i<#{2%4T_GtTgJMk?B$8|G|p`Nz{VKxmW}F$ zorj~DNP2yxTMxe$X!4*ay9%Kh3w2uI&%IJ-Pf}k^f$f+4tfM?$a{$a-&0tFICA@nB zwOg8Se&5%KaGK#lulf*m3{}$*V+Gy`Tz!h@%Ga`Vh#d5K-%#jj6 zpgMe16ctA?j`5_A{H1y>y0F&P{p$|9KZX~9D_Q&@I|!zFaSBfvnDpaX|CT7uzyxD> zF$qVu7B(q?)-EPF{W8n)NF|LB2?L~a;!PeHD;(>oep)_AATS00{v(1xC8oAJ6P?MbBS7k9Wb1ZBz~U`UUBvESY5iYdbRCC%4B7 zAe*Uro(T=g{zuEY$kIMqA?AEp7Kgz$v;Y9(_ZZ*jv{ijyIX-=Qs{2uZ?X3{j3VuWJ zX@eAjyo4M5Rb1fBk&$^<9R6aUbrkpXRinwehcb&cFzH5me4R-fZAN&|Jn;?ta6=ef z$-y(>Au1B6%M)B^81wv_Gj(Z2b_%8k3w$va#`J^(>A!hp%6>>~;2;tMGgiI=uY#jk(6!ExKv=6NE54+u?x>=?HpI&xhr zKS+jVp}J(vvHxUDW<})i@>Hv+S)VOEE4@cSg1XSA1=*l+; zZoM#-l4&rAlAIDqWKxO4$r)}EU=^5=E|0FbW&QL#|8>O@{>|rG(z<2F9IcdKTy91} zJqvfdHLFYVusSezN)Xwgn3bSfs0@af68+}@jj1sHxw#Qew@dPA^q!{EfpBBi9XW^g z*XO`d=ft;@u%Y%ALVi}uyYJjXQI^kFKPKySazB2iJ>rHZJ5gj&^ZXj)1{I)T*` z!ZZTI@pXTby}G?6c-`<%PC3$Akn{|DQhN@Gt~c`945B2vc`ZlLVZPXL0fPCQ4lWld zJo4R6u zIIcqN#UR65taec|x=Bl!y3DQ_RO;Rs11#l1-E{?HX?GX==E8BxHP|7hLl_w1heTR} zSk7;2k%Yn27bp0+hu(kTL> zTdZ;`x4f3t8+TK#;yzD_T;VMVOz0?+&m4--3+e$T)>w^ycq|xSIiyPiD6owxU^kBw zGiy$x)|L-Obt$H8e8Wq=-8Cgqy7M`gj&cYUb5g#dSCfmFc-f|`%rsW*BiF7VF@{0b z(38|o_nJYiN1GcbCxsnZ`OHIGz?&SroPKQ*&_c7&|MP8(t60;Gq!NzEJfNy;IA}~+ z!V~hVbk;sHZVB&D%!Q~u#YDv>o=Ndj@VDCV_4@}@&8-#M1%Q%E$JlQ~H(|(hwkQ>K zuVK^1IY*BUQ;24y{RHnOHSr%Ao>vz%geMTg!~_sq{9)YyM_DL7wG~1)PrT5?FQxAV zcG{w(S3g>@Y;lPjhxR}5C*mW8_zK2+6~j{)RJTe50DN|wtJR|}JIsNXRSb3YL=zA8 zCn`Mmf-9b~wFtWwz1-JzVNm5EEJDMPvjA;jysd(7{f^L`D@RvP;7MKhxaB8`l$iS; zZ$$6L79JfP-dYIHzMdIsi>gHlv>r{act|9Fh@!Mj<@(qJKo&Ph!P9b)4!N}c=GNK< zATht29Uwj2ju+eeQh8hT3MVS@9cE7xGs_nvFPUl&y6RQ)P%%8zi|y&mGZ7TNCV?_; z!Z>kBDE=LPJ~}guxFd3nr^}kiS4JyFYmS%J=FPnVIpNjD{ya}eJcpw=7!>&9C#{M_ z%HBvc92mS;@h1L)=v7Z|y?$4Dn%E|4F;na+!8+cur)H4%eIYnIVR|B;gu55~du~0Q(_QeJ*H8`4Z<1`$K{XKYEYaBbwG|W~f zABpK?t#4V+-OKGFZkBQ*rEUZ!ZUf3yH4GjI%@oZKQoxB1i5O;hn!1x_-CzcFU;9z( zq56MeIzxV4e{aQetDA~ZLvWBpsD6x2nqgEoEY$&1rp2a#!AuohU+~&EBx*Jrf2#oK zWSNqe&jn`AJ8MS8G^Ilj4UK0J;c66&EHv57x1;#lRqgzGq|%icp=m8_5j zyk|QhIbvsv>gE0o^%1J{7U%3oc(NKXQ*scua3=AUG&;p*B`Gu?p8NS*KKbZ$;E13*Aq!HqU=2wr8(Rs^M+`v%7i1+T-UaPHJo7IGJnQWO6tY%Ue>EcZhGzcama?b0W`k9lMPt zyD>X&OwkSE+}37i)og?f>vPenQd29dcDcdZpMAfTA12}M{aKQ#Wr5XqIK|e2+@i4% z(;%PS1V=^569?DiG&J{olQU!XmZuK-m0i3(!Sx;A70x=r8*Y-nM`fBiK~9QILq;0A zB*r8mCuMAIg~+}`+RWObzN-#&@oXp0AL*0SQd_v z__k(sL`<8m^7v=ML~7x>1*%~(oMxbwAC0WDXf3PL>U(~wq1^@1!$O`PYDBP(tyO6xsi zppqN4s;~({A39tesc(bummDB%*TR>U-pqs-zD}nyRL7=4#ox~J$C7MI zv3RY)r`^oja|2~7#4O5o#otD&b3%_eqDOZYUyM7QL>>|cD|@9Jc{MWIWOiiut-At= zo7s|)ZTqqc?SPFYL}c!o6UXbNkv9zxWs3*dVn7mUDq_-W_kSvV@0G=`&uVd~6V@Dl67hqrfZH||+rKX*tk4SbKinuO3qf)(4lO@NJmBOm-nUei*)aasiY7`Ov30%} z60Z?)*o5#JgGvWVYj?#RsWRme*qI`8LW6oa?(GBx@2yR&o|9 z-$fLNbX})G8|tQMRWrN3z_l4Pq^IMgCKT#W@1|UmN>cCZ`|}P^wsO2`T|eEOoa!{U zUP-^(8ld{emYrFSRK0P@z$AMUwV3Z|2gSd==SA(w2}fG4p>qnsBUz;UCC#I_g|OEu zHHyhw+=-1RI$zqN?)prNv5xey>QNkoR~*q`Ofse?WTB+RYQSXJ{3*O%$CBWv>6JZ55Q zDH(lK#H!-`)vKn3{KT+}CFDBx!r@K(B};1gM}aJ&56`GV>5~?;aqcawLOC9QUJIi2 zeZ$eCiWv3l`jC6Y3ndzk5;90dYj?LS!ESnirGw(r(JMfvvH}DY zi$Y5P!slvPz*+^VkVPp!U(tD(kGd6_2pDy@=$lt-o$cyH(Z`;t@wAd^3`i%7cFwvu zZ9jY@C6v!%V_29T5jmA4$^MKK2cFYGF2QTj{(UQe9_$j6@Hh&&ko^+Y##r*Y7$w3% z%=J0v9chW*tRdGXp(3U5kV%54zVVrbl}pEg6!!?nM;-Ch#(ITrJlTEz)r7 z)Qf>#g$P0L$_WTcN7Qk(B29FWpbTx_EEARb0v9a2#{F10TPmjp88ACrn0)#_p38=_ z#V=HKr(i_*LKbtJF?f)}Yx`uCVFOLOUg;wX zlqc!F3Vp4qXtFyy6?c|mHCLmXYeU}%FpVt%N04~z$RsoMZ| zTE=v@rvk`1JKy^=E~m;$=Q~DG;4)UxiOBP`cyZ#fu0NZK4TBW=YD>y+`;a`u%=Kf`SfKg zd$%ykYahb?BCxkZyu^d{eeUTCd_*7;!*vC z`cw_q3;W5}w(Ke@(}EDX^u_k#skoaA&vNJ2!&4ZA{le)m;VEVKQZfVwnP6lDCWuK^ zZYV$9@7y6^?mq)`4!@m?HFJ7Ng(WpJqw_*TR{*7Rl?l+7)y4V8_>(`%90NhAf_x7< z8zkBdmV2`_1Fej)I(@lj?1i$81#czE;4Iyz7i|-*j?<+6o>#K{M6lNqyTN(u~cG&Uw`&B?Hma@snu7*GF^=tsBr6i#0H4)c~d79N-dw=*0YG@ zmPBhJlZ6nlXUy9VvA$Aml}<_LE3FiIKou8yZ+28Y$6Fn`X6SeVAJ}Dtqyu9PQp!#L zP4U?3j-qZ(40Ga$5;047bS0ZP(z^RP?5-uE?b=VM9Y6r3unfA#0Pl6NsRroo0Pwk<{bi3zSq6muA7CdlH^zw2)2H61CVo?1{OR zC2b5ffHhlGu}B>Y7w6lwY90Du7=N7U$EJe{;k`Gxtt0*x-omq?&seIIOLW;xH`!@c zo~ApiF}G()>QU)3t`%s#Bl$cPGHkxem0iNMNm@w1X8Xh(nQ1w%Z>BF_9fu=HjQyaU z3MV4bM|jPKJdeZqE*AcYO!|n9KJBoHKPo66QpP?o3lR#;Jnxihbs&=_mU90c(H2f? z;ml(P;z)LCzfSEop?J#nnk#uu#uga+fy)}q(o@<0Ym>uUy*vkRz{-P$DFN9xw+EW! znoGS}c*g$C3vvswEA*VHxw%J%Vl7>@jv|NB#!}gyq)nuBTKlKI2&(PfR){|83FhW( zdyt(>x?V{J8EbH7QvveD4A@*pqT;DLK0#O7gj*g3N77+vHaypl0>%=^$wTGon)$dZ z=7Nu=pEm^Iq9C2&$I!gQL8EZrWK$`{_AwfFwP+>pHk4!tH}}2R0_fvw^q7m;1)J)? z>FF5cU!Dia_ydY%HleGugLipBiwrg!R>|^*8d@`czbk_HQ;lBUKj;*z=CGv^7HMyiUxLsTvi&Xtrm5)2r~it8z)HykdnN8A?bpn3Qe4&g ztk0{90|I|8Ej?yoZ+gk)#Dc&`2}Y-~91CKFp^4KGR8EcyONof5P4FRLRv3{Rr@TN| z=LRet75Aq;K4~kPJ;#RYNjNYuu3{CE9BqgB3L;ngg)sN3U>QMRci=qYoPwXz*29hF zo<>l2Nt*6e1c4D#wI91XP7~Y8GF~#Jd*i&#JAyEMX&GH7#OhKgmi>;yY2B*kFQegs zBO<8A(YzmAaK0!0q2eXY#`qET;cbYA5TDibY{nKk0E96@ug9{e>%8Mz;R}E6Z+#K7 zf=;Fda0j(9cRWK2&6=~I5p~$nN%L1!%%fYhXf1@a^x7M?oKcpHMKgmoEUaRVxFbaG z2aj!82}`e9sOqw-VPg5qNCkN<^SPoNRq>f@e7&;u7Lv2DI^vJeoRzC{UC*sz^gb=B zbUrOmU!SSpamQ>^0Mpg7)GLq~2`!xqx8Ibn6GE3qAk0e484x6QH{x26s> za?w)5Ael|kxerhEI8Sw-A|W zc}=1$8d$bHWt2g4&GCpHJ>)RAzDroJ3j&L58`Vh{Ui;+O85sp{O7oIdPgnPqDkA|s zP=%zOi{P$M}M$VewEf~^eFTFAgPWBLOI?ol@X z!Weh}mNvYUjW{_ucMM^&cp0a_8fXkTBk6>n^O1m*o;XE|N10@@TKK87CX5olhnX}B z^P+&rk`RxfR2ErLtYo|lPHZVbuXvrHy)ZoJ&53m%s@5?4;b%X@!%Jyu6{ zLoa6z-H^*>=SfeVGI?#sd^iQW%-q687q=uU^4fUlDKk#mH#5$ z*v+2EBcTyAmyrB9Qyvtk;(q;#{lg_+t5iSm7Pts+kEVh$Z}`!pL8K$UBRl1Qx*!Z~ zq-DnStUl}*Hi+bok{)7waSS>{Iz`lx@Vjsx3=2yN%pw`KOV#Dr#iOJp<*P_Esq3rn z)-JZCopS;8XbCyj(3>0In;cwN4h`*JktXdWY3v9vMJ=offA^7sgqzs#Ljh?qs;Hp{eWA8l0ZyNraRYVm`m}L;ujlNvp@5+s%`FFg;7?Fip&;1gt}a2bEy}ztF9lB!G+|r*)*iUKnVeRZt;VzD(W^$ z>E5FZWp^XB!$bPfK4(Ka#B|AkYeWAO_u=bsDPqS~4s%`%nU&SRLlGkYfEgeOAz2v< zAxyRP7ch3zh23R9o;m?wR^E@~x1l6bMA zm<&tFO-v@^#_DC|im!D$^0G}GTK0@4#oy@W$x_?0eMJlhd&UWwZuvcvLblGKH{=KZ z((VINyS=pSXFZ3fn(U~xYvM_#zWi$_(>-)_nU!349reP&=8ACl;tCd08e?W}0H}-} z;cb!?v*l6C>b8yj&|pDo80pH5=lG%^mQ=8jkw}aWQnjJs!H2!hNCi-OgW$tyMwQxT zOjnR)T1;-+mcYG7~F;XyZ0a^tSVeR2)GcIn_6l;ZemYL@wA#*+h z-b3_kgd5!|aAk{;!y$+W+-YrvoG?tz5R==GTTLaH*0U;^v$B;021WN4m3S6gjXH#DN{MOCfP zGp@V0prUQDCL^2y3e0{Nt_eiEEsIlimK-^nm!P=PBi!Pa!ZBJ*x&6*C!a!E@{M zfS?|cxB{0@+)B)Ae~P|bOBhtMfSowVJb0A_>tv-=Aj5$HIN4C#JY&DlLpdbYgTyPh z#q%k;K+@C}S2IbaXdR(2H@>-AfP!WZX4CCmnMV6sl@oll2-a{G_4(N_^{ShKq%0xg zKqQHR$p?wZA{|1A!49>0RqWXU2`{^jHd`$MA&vP9-DtOYVSLFCY`$OKaH`UEB50P( zN~dLSQ6QBns>AuV+0(XV%zUlNO|GEg6)C#YqnkUL0dt-M`-_sb-0qvWP-KQM-t-HQP)3%acLAbgK z=oy3YZzXS|faC}!J;=uiUd-rdnXS$RtTT$Inw%+5$b_MtTwMbeD50x@8{YHnoAazv zJxY^jSqYc)(V>GT@g;hujreK$DY%-c2zs36g{n%$(aw=!6 zaEN_>kfnlKeEPN{Mz+jZFF` zRFR2YCWgKdy6`Horz8p<5Vle?n?##Ys1@hEv`>n<^4S?YEUH<4(O5SrrY6*{T$u;l zHlM;w_a(!LMNdAJ?`4E7xY00*QBRW)^lVPXhhRfR72?0*I`-b-?%4*d5Gs^ zecGcJC!E`UIE-`FDo*v_b|eMBrCwUH&#(_&(@)}Q5G5OBQh@M*| zpoQcVt&%GfU*>NKI_VwzLoa{6#FNvDVL{CQzlJiTL-!H32BCdWV!mGu`VQ*^9 zTsFd$lww;D){d;3=2TMV6{Y^6B zXWw4*QDikTR~YqC78#8rORUxoMhHjI>*)4 zTvZlZ4ASh!W-mA;g-zmFX;{)lF;ppaLqX5Y9DFPu`fPD?+I$*Xcwr8!Mmx%hS{UfLewDxD_Pe1SK_L*vm znLyrJB|~o$#eXtR;Wr+62_m)-CmekaL#??O)M-UJQq%e6H=T zfJ@gfrhXvTn`Ix2us(@`L=>Za$nLr=nYhGWAhxei8}HteK>`mF_(ljm`Bw= zqQAzb$Xq|zE39?efY@dII%IBa<52xJ(L)V&_w~H94zbaCIc_f_V<7hc~S)BTPJm!q)jW#4Vz;Kit@HKT_BZ%TLkksghvD#zCEh zL?y@#aro`8!wYMYP|SQym8MdEq!13$AAD{6Xy#oNBdA0%MWv@Q5vv&GV@q&=Q!iDy zm^bcUUDJvc#qc;8&e?%X@p)t{%PY54#pn3r6W7*SvBu(>xc|5%hgJLNOk$G3Zh|qp zGzWzkU#rhNyQ8DmLNNTY9kH7m+TwE464035-Qe-d^-blY*D7%TTyyDTqoKPY&yCzz zxX$tF0lo+{i~>H1XfY!!+VBsks-q+`3m22<#a8oAGX1m3yj>2%41F>Zwmd&{3qcY@ zM>%UBAySaWKNAqX1HefmZaV0a_&!FpI=}zsnwYRaw?y6vjF(3yUgV{9> z>;L=Y1tV6WXC(+13w}}{WrgrCqcWUK69-?t{XZf4hle#DpX%I<7{S4-9nH57X9)y{6X*rysYWGiLZJr&)Zuu+~l(d&A;4Ic9&5de_BV=Pi5NLd@Z}Df!(8DPX z=@`%C00(Nv=osW3dZ#mMCaJlsS|L6jQ#Fm61@WN%-32PUD^sW;uS@$^rFf0G0IC^a zyDGn9xt@g}_Q&sGo&!o|yUE%?M>ETke|gavOoE4KnY9q(L)VQxeZ8wQwe)38>m#?Z zurHHA{x7tEu_+roEBc zoE?tt7K$ej5*?G8tqq1776D={!`H|LMzksH2lEhnHgZTR;pD?rM4r6$HA|P7Xr#oc z7yGw4NmFITyl^`YOg#paZ$OLfDWywh5Pr;yH2lfw~E!Ra?GJ z^J))sG#%<G<_L9ZKX zNdoUZAPdKKjxPV(YVL+cMHlB?)9%l&?Lfj_Z^?mlZSvIoKibZfy>Vq}+hnd0+sye> z8*H$^fB^vk1_lNO0s;aJ8a8MkARr)MU=R=xFd$&S0`H&tuKTHK9c(Ex>+ossm5d!* z>O*y%>#5@RHORnFe+^%3&SEXTasMhE&wDQ)9HlPOIV;*;XL>4JxEuyW zyu;UQ9d7@BiE7n1hNpv){GQ;URLRQU>jnhjZLoKrQQE{dvIWWJ`J1a<~AL>C*b2%&Fz7QNhGSQ`@`;g1eWfCirwNu2`HZI!C}dE0GBjtHBDMg=3} zp0+KpN@xW4exVF!IZpWX3=@85r>JUte|fHVceEZ@XmO|u zC%f~I4(-^H459Sq6)Jh3@i;5}eO_W*4?0+PjpKh~G}%1LbKz%Ym7fYY_vw_Zlr*ZW z^kK{X;Zjh*Z3jskr(6anB(&2xd}}dWf{XnDqwq0UQu_9xr zOO4veLMK=U00C(Fw8fpuk#&u~uq(o^c3`CM6`=2o_`l`vwg)Mtk8>2vBDLvyym=@U zyjtbf{-r_LzAp#CJCk{n>o9F!?U$moM~0hvI2#c zdEuq7I`-%(B`Y$gOCDXv(>>@Ss*)mSG6Bu6LtaBm}i8hFF) zjNxTFUk)HMfc7!G6d~qcrZ|T~Gswm)V%$=o`Gu=te)W$xw=m_o&%H0YZMh3XzLvd8~D+>v1UJ&KCQh5B~Q~G|d=QxiK-tLMM1Bl{=_ENuOT& z(}YxGZab$tOJ-CG`{FIxpZ}CKu;MD8Nv}vi5b-XKL&Kw5fj+XdGYc;i1m!Ez-%#Q+ zP_1id2&(B>)aw2VD~=jNq7Op?_Cw>|-~C#ibT%p*0=0D@3mkNo-|~cD5txx)W-pYu ze`BmBx~Sw;({HyAHQL-!)m~B7zIQO?4WUP)cuoxKv95EVH|=21h6l?x{UgPm?1!HK zZe3&qtqbr*5}}p2=1ROeV@u0qz+ao=KB*Sx{D_?iL>ftn{dwz2;_v-B zJpJBO9a*=ZJg#pKN)e-~zTSOu{y4-TUWDil)5!5C-$)yC!~0_-V?gQnB4}<0UP2No zeoux^Q0^bd2xLR{>6ACOl)9nJ36Kq-*r=x%^DCJr z=~u^vu&;PN07kZSI6IOcDAM+#D|JkzWN&9_?j9O2^Tz%{=836H;_QyDh@MbzPImD` zGe}j-(!4-AE7|Kzbp6veMLj~i0h*qg(@m)+_9_Z+s|*M4P|;U3W9=R9b*b(I&?=2! z!S?QW(&wby92YfYUbaZmJkk{W3m;UA>`Qyce@Un58ZR+k9yB3bKc~;R!9K~*cJgyh zx@w~wh5IeP7RkhdU9kn#66!U6ygiz^0&1aswM#MwFHbYXvjR-GWfA85&gq`UDXoD3K4_k-XTn)6X9sp`>ys(?e zySA29pP&$l{2cdEcBo~lv{o0IpRilD&{W?XgQ|U$Ng_eAsIX-4)@gNa4RV3tD3`CL zK^(t)DyPO;MnGdDvJPx78~Cnkg24}B+bjcJ4+XuzhNK~Iw6#4Xe5DaNbGEhB=v$NF zV2yrGZR4^U@#XY*gnq~?EGue<1$PBHismsP#alHoCTnJgPeWL7pX}TeV3F^93QO?n zxqTefs{z{QEjsE@D(YbXJ*pNG)+Y}h7X|PFWx;^eHk*~C)pql0-iIvw%Du8sFNh*E zszRv0DeMOoA%}Q{?4}~)WNSk?KV|n%>O+@$I>yf>w8s7h&5gCf9{poaxa1$S^8=|r)hsCBh8(W`q5duq|woAk*u*4uDQcc}J;i*I4nnW|M~ zdFSYDy4cixOt18dy1^V%KS*{O5f=z6J3mJKs`N2v2b^WX4^dgDZE$rl58+_dyca8Q z9}V)tD{%dlRC7O|?&$Gz8VRRvj;x~ENU{ybbVzMWW)oma78Gj@vphx?36A`m;{g4e zt{*|}1X zw<;bNRpYV@)QG(0%U3PH*IO5AVr=RE5?9I>5M-0GH~g_y zm_!#|_8^(lRnO&;$CpN+sG%;jut>{+F%1kv!S|FWkT3b%o_{1^_F9h|^2~?^%)|>4O zCDPh})1iG;9X$Qt=2K=5;81pUe;*)2g{UcXu^XFq;n)piAo)AQ-BR zRuqtyz@lP3;c^{O7D*SZovS2j;!P+}=J+TK+OyKJyqcYO`YD^%&(TE7xqnS^Uyh?8 znCv|Bre|{8Ip2!Pvm)9J@}#t`0wQA#6oT%m@27v|*#oG`m)eFo(96l=zuMt!399Y^ zI5xj{SF8{ zRG{2N$l!I#c5fR6LJa5pCH-Or^0eWd6{vr+z;!lq9b&o!tJu=R z70v@0?*n@Yr5rTGI($8LdKJQ4TOmunhs6I2?F0JL$mGQ9MW9m$T)p0<9-jav}i%rw`zOLrGi41nV1ST>IY8UE8@Y%f1H?#iOtG z`>%&H$RFf&`Y*8J6}@zB@-9YrHKoLQ7}SC<%EqqP%s4nSI3Ne0PzojzCWh;M-p}jg z&Ov{EetKNCAuaPzUsixdTa=Ak(=>fNWY87F4Tnu2=utu#8{&pIZ#BZ3F~qx#;!=2@UN9Mdg*Pd!+&QWFhBK|EfrLqS>j2*_n% zWLSWfd&$8vQ069wu4=xdv+#^8={pSpAjUdX;fr!`X&NAnntV4s$&QYpyu>3Zlk+(A z3E{MbE~Fc!2ct3)(?$73Dba|Xlm-S*#0C$!V$-T>N7GfXSzx7PDa+NQL zB)jY9<8|sd+2$%<)XJUU`KyJsQrnm`z|;{; z*@;?w7Bi>*D&w523N;m+?|KRZ7_DrNH))GP z&Prn*!hdlM-kz43DRdd@ew3CdT|S)BjfdhIwI+t8a!N|{KiUk?F9AF0X7SDJ@vZU(4bKau|BGasTgHS=|;x5)I2iL9F)tDGfj{ACrdymc1&?>F7cKP-Ae2Oo@tee zu?=iM9lYL$6Qd0RA>yxJm8ixmamB@F-AL(9-39euiXu!HLu+JXtCqW1Rnew`-VfaFvPk ztjsnXbM0vvk=b6pshoIIS*={3MRIpg7pC@z5+Cb0W|@eQ=r;OuodtH{hB+4wSH6ob zT^p@+q-I1imD%3yG08h73HPb$jKZu)$g~8HT!+C_-m%0SxYj?Eco-D1qKeUR=*W1A zB?obicJt=?)%Nhs0@z;AK*BFi)`G%`+4=x*sbja?H0 zSUznpA2a9FLq7F+(%q*}h~xdG4+`GMDz)aAZOj)hm>NqQNg#8iZ3F6RpOtH$MDF^~ zV6>ISB@}M-^in0s_u>hrOCN-m2D~=)O=gQMQ>RlZgv`FzKDH;^@9}DhcU32Fb<5O~ zE4EAZvI9EtoZ!jky0r7UaGLbWdl4avtfAMAbO)!x47dn@eG0zF)$WNLl36-zrfLC+ z2&|{IZCCafwrtTB&|!>Mp`>18PhN1_ilduuV01;x+2!k)ezfTt+wV~hx!C6ZM(nXp zAoNRd;1N~`#{$65wUmGJ?wK|;>UfM0mW~SfwQ5P3+MvkiFP@%A5Y>h)*o_QYvGJ`r zR%McS9;P|c%(*O)g;z|I54n2XpS|_{WruJWYaF&+Qd`VJVWqW+on?bQ9Z%7xi8(ZL9LcC0K%#ag1^7Ft}En*}X-v zBx0uaiX!SXXI+D)`HiWn+XJYr!x{U>NbQZzi}>MZ`on3f4j zJPxFm-f=DjoMAj#o)8bZd8e#hQfRVjYHOhAXJy8Y;Q+b_e0r$XpklNu z-$A4H8nnrUm#Y6~8K8&$$u7;wTL-S2Y2xGSxw!l40-?Vvo zevnoE5)TIHH)L8P|7o?irNb{aTib@%H*+7bF3+F~CJi#qMa}tD`J%bu#3(oO3V5o? zqNW3;5|~u$9!we*EZxJt?)QFPkB23IE92L)f5q6BF<#Y`nrpdoYVnn+OXXQJ{$2=& z#<5%lA79L3+MMxpE{)!zI zI{UPyPEz_|O?U)*gAJI%?J?0Fn@gWbIsJ%G!bGNQM-N~`aV$HR&DAv?=9BVLFTCIX zN$-dFp@v_-`lXMm`7b}eS-ZsUZ@+lFoe$$TAMugw7JvC$tC9cSC>|DQf7w=}zaQJ< z(^enffAQ-Z-G5>=%l#J{6IqSoAI`jN((0c3FaGY+w(qzxxAg;7Un@i1TFCX+=2w4Z zKez5X4d53y`t-Z2&3(MlJlfReYLz7k_pBfA7nyZ*9N$+Y7n6-Q9G4cUAAcwz}o&#rlT7{k7FSRxipe?%!N& z-uju-2Ok&AKRA8jfs*b&@GG*p|GW=s_sKgWU5(@M%d7FOZ*f0><$yo`O>^Z)AL=e2ws@Lyhl$6Nf)cvo{+&Uy9a{TFvrm|w1L z{P~NrY^$fN@AU9$In;+g@;HBUy65BatiLvWaDS|ix7h#E&dnchvGc*dG=6v8{B1GD`%gSR?f%C5%j~z^ee&<$X?ctL5iSP5y5aQVZ%o0Tu}ROyFcVk1M?Ou<7ILLH(U4|D%nW#_@Z@ z-kM5z{?6Z+;PjNmY^V4BbNv$k#1j2D#Bq^dLo2Y4FGO2H47ZCs62*;i#eb0UtM$#g z&_!pUu0H~mb-99c(8PAENF@8?9REz`pSs!gkM#3u_Z@X#c{+-xgi8*tTdMx^<4YVa z#k(+h5cTJQNOg(ErgXb>4CMCn(46B{fEDuo6c4qYu>>nIw@Hp_2IKysaUf9fpY%ga z{(F6h@2UEBIcRCdpNBs#@y~R+j_ERLCgUIJ=S_t}=7sUxdO99{e2M+>!ME}{{E>bV zkzk##lkcTmntZvA=?9tL#BAKhGvA*l&@U{uZ+Cn0l2dw1FP*}iZ#Iyt{Cw~0R(Y?N z?wYD|eHhyME5qIV)Ntv>q)!Uxp|2Y=NGWxrj<3;&uzv-m0KrFAmbo~bSmu}GwEH#9F51`~-_y%2*Tc|L;a;~x zb@`%f*zV`y!r$d}eeDwdZTqh}f9cNc*Zz9omufOYynJWG_1LGEevn(cI-P7S*^c(| zoZgCaRztNs%R*#%I8OBccB$A+0&9P;JGH_0709RRZdU+;66uGRA&(+aV7zQhAZkQGciRb6c5LsjO)xg*2MK3K>@m^^kS{b)KmyG9rM zwlT)hyLQ{;%rTn?zx?hezx#N>Eg`uNN2rZeryDfjG7v9KDq|$wf=hodSM}ToiExvP zIYVvhIJBhFmWx~77M}T1etS#jZh)fUyTi`|&}ubY%f^sH`l!06=XHAw`Et3&>c!_a z(+@;jQ?nPFFsvv>1Vew$mPl&&?IBi-G1VQAYbG383LeV>_`2lu+WnEnE+$K3{8kIo z?A95y88W49p3~bZra*jQjdW3nvFoY@wJR}_aFbBdz@KTw(>;#i+! z=NlO#vC2Y6UX3>29d~_XvxhNVaygb9No9jD3*Gg<>HkzUzZr*Drw($uGv`Z^7L zOt-p6Zh=fV4EiOBrFIbk9&XL%7t5 z-m?7=({RZ{UuV_=d{&1Qagg&d3n@eC4sFwgM!nnYDyvIl?=GgH$z88>MInuNi4Vza zeby@q3PM|c(caqrrhK>l=i$Gk)$8&-8IquUAd!)*LH2y84`%^?Tm|#egs)w1T~OdP zCOBfVhHifa=bsx_M=2t09KyLQXeP30M}`Aa{bzWcq-c>-djH4!9GdiT!nL0bBEFn* zeRR}N=01wECNT?(Wtb|#8yq_9ts&aj=;r%pN{Pe=)<%B%E*%b6tSB^B2f z9=$kNK3cAvy5S`LZ}&;*wXRexRqilQW2uRIFIfpQHOd&9Iu5ne-~lxg4o^-u$h%GK z@1(Gw5CLjZPhQnuhNk|SW?lvh{WDeH+Qat*cm?dH>s}_Q`(CA|y}zjX#K#G&v5j~z zuJ+A?sky$<}C;>S~;^ zyZCJj32FP&IEWSZcg6?EH43V~(;%MuaZ`Ty5bO}Npt)qlcw=5&Z{j?YGy8A7o&MCijc1v)f|3-idnicOFs7T|HFR&_&CHGq`Guv z7O^d()F>uC#mXpQY&9Y}43U*OUp9Zf%b9#t1EtkPos!ln#@L?T)^^$OGG6C=ih(pZ zY9eIIDgDQn*YB9~w=^Ky5No&ek`3`iI$iCSuvR&-O{2Eyh^b@ixzIOI1skmb3b5vt z!X>`y^pwL~X7!d{1Vm$$kHIBvn%N_S89;lT>S|4oIO#ow=GjgftJB2G3*?~F z)eqSbzgMC!pU02biwgB+Fc4k2rr$@7HwBC{)$Sxw_Cx89bKY zrL-)z70_aI-e%dV+dwK*wFTFTRZZ~Bno0p>80dsd@9A{G&2h3?b#LE&SJ+HGjXh?p z(XEC1ULGPQyL#XyjfMbYPb}0UZF>-yWy#vf%!ismv(!NLTUT&(QtEe+{CNQStGUGSoUY@Y^k|2slTN3} z@`Fx?a!AVk^q#8cR%OfdpxLYnNt&*Dh>@LGfQoIl3M5B?V_Ya~Aa zMOE$sYqZ6akO_{rA`JZwuV~dR&g}X408W5+P#5b5>tPu&+r7mL#G>+-L>;x#s3*O6 zX((hr@_WYvC{%M@NYT-#9Mihd(dW6!x3)Pi09hM~3M1_b_*2OoMucM(aoOpk_JjW> zMl5(MFKCSo;Q-AtW$HRW`G>t$-(EgD*1$+mP(tl<1SV;5B;^-xV-M;w%0+5BAo|=< z#>b`h1N2;9#9!gDBN$J`4r;T!L9rqEnqS`1#b41!5~XBjIK3Fo&*pRk~Oxe>hovIp<3%4Np(M^_(M9Ga%PEt=)O|1#7jbs?GU}- znMZQGp8)^^X0L;fm_EjzA~P=~>VkF_qz(T9t_;Lxq51KzHDj!oP-Gm3)^>XDsOqFX zYcv%2=;(sX!Pls#!Ieg9WATEQMya$;22EV(qq$UMG}}@ETgFeXmi!{u7;d5B&726b z9CAD9xr!Fp5(NQ=;YVU%?@X6Or+k%)C{>Y99_KC3I=sf`_axSF;M{;@0c;v7BrHg0 zyS)vy75f)jw%)t~9vkE3Yy84ba{70PQ%K~YR+SZwypZCxi{Y|T7J)|hi`wHBJ1BuO zpt?6kV~7GB%G(U+A&yV&tJX#6FpFc^%;ngWZ}{5TlF3~+zCB7-G*8cZL?fktAY3JBfQ zOGTp<;rqHjf<(JHIKu2*EY9X4mfUm2>y*ukvVR{tI`7P1e3O5+1l}}new^dggK$I$ z85=i}x}#voQt#A_6GqNC4#*wFu4Z}IM_5~^K!VJdTy+=u=HnMU^e(H|N>h7+`Lnn0 z!GVsU%0i{wi+RZ>z#Rk1A#mVKGwIy{q{a{QVg=(u0CL~ zxe{d}@D%_`CtZHI+b&ZZZ&V{wwk7UX%Oyrj31 zEip7rgvT1Hc|)xKR`)E&U-qKQuguyuid^$+eg%Qlz{_IyH$!zfPWw<2WIm+(pA_c5 zDUJ}7ZxGI38@r`PyI@a9*iru~i{O(#f%~tch*?-Jx=Z)H{aW9o3>OC^ z0m$O`$>P^Iq{rw$uo)C}gEHx!PHxOv8wbd@xqLH{`o>P4}OH|iZ}=Q4Eqg>5sx^5LBoRm785 z+Dqe!2=UdOlM1$E%KFnV0E%V<(@yxRsYPa4O&`uY?WS3R)y4q74oIYhV3fu=&taqnIdN$W_mp?P^y@LSMMF)KW8ix@tdvrC`{VYt*RCidla@Q*<0Vi6 z?K(We!p?QvvvRlP-Yy_~Lx5?&;*WZXhu3iY8o%&!+M_>x;kTC~iplfyHx}hQg>+55 zPN(#q@|!&%)+@)D8vm&idMl>v9p%_Nm!l zz0@A!E#c8$hOY0|xPMXtOv$y-z7g)~yOJYp?T0miyMvugf)VkZ&EVtN6L%2nLd(CTiRr@Eh{gU~_ zrmf$YvRyfi-O*7a-oW&s4TP4adiPGm_cUF;_!=!SozD&NaPn$>I~mOAwmZli4>M@P zd-r{Ut$@~j5x7>g?z(4LhKcaiDgyP<^=fRV8{gG64c8Ma?Om?lzj<@krWX;M(@+gh zv~9HEKJJ=jSkHSm{nPR$|JG^V+C$h2WSh@M`J%;+Ttm)zcbC(TAH&0*4jtkY3`G?~ z;I$f_PURdeD16STQZ)S2?d_kyd9tt>9WAi%MJ9AcP-2f3Qx=5D2780#fSJ5`7<|L8Le^rwPTJYWC}gZrnT&i4 zC)+MDL^)VQ-c9Tm*)s!<6E2H;-yiC?;Vd6HpBrM0lV$TEw|sFnPg8+Q(oewll?YJd zTI^#T`kk*su;UXcj0}y{al=IfTNXr%QAAt1qHFC>d3+*-z3>dGd3b%A_yOSvpKb!$ zoiPkg-`dysa*OFb9@~%Q#%S$k8O^mkzh!fjd#x84-hk)RiiZ;|S(w(SCtNkr4 z9Qly~&F^Bj)z*6biP(evFj?rACG)wAu&ew@8-At6f!h^*p^1m@9k%%|dq=BD?f0P2Y zoe%dCwe)z?q8&^f!AK4lV*Y$#gWLT{VI<-{_?F3d{!rQXKmC&HFanK@_vrd^!K zC7_f?HfF)TbrNS{Bd*uYD((mFnQ!T1YjGxifR#IRMUq%L596>1T2A5hOp1)rUCBI# zM{KOP(#J*=-(`&sms6Yit<9`IQdIc{Cue(eYS+8fdw+8cf4%|4G&Si67No5TWg3Y%t( z^VU4fj-;Pw*-F}>E~p?heoBjW3$?A<-^~fKIY@EJd8%Nc(?ygEdr#AyDzOiKLqqTq(9HdEV zhS~>y*suda&xwxNsHxv<0!bJwML!??r8N-i!%=m>IGCXkgT(U(xo#VW(41w|zm^aI zxoS;@regca=~~zh;X&L-gXGzrHC=5yaNXn3u=p}I})}Yh7C&0+&A(#4=I<*s>(%>; zP2Aw{y33toLMs(IyC;W52Zu{0mvjmdUSZHCnirmn`B-rs^)sipTRI|q@I`eEi=`$y zt0S9p}XBjk}r_GaDx&!@_Uu8MC2cfUB~ z960e!T{)c=bvC}4)YNbrFvH2ua+->-+iIf5d!=Ah8ebvYBVY*U4*$_7)`uiidBA+m z6*udJ$h$mQ8&fe^k%sx8)aDVD;X6emNgRSYcW(h_SqkNOEs|>U#mRKaR%^1y9vGI1 z2xSP0(sa4AqSPZUPEJZ42Ev#whB!eoA1m|5vTwQ)CjL0>CRFaLNK}CqRQ|U@IfP|= zkBZq_rdm;CG>ym(_SSkif!-s;gAQNHRbKma=@+KV-p1{(`fIG_YEtbbrU0|SC$-Q< z=C#pG{eZyNr-Ku(l<>_OS0-D0Q6qxPU)D8PZi*5eHU?f~<{z(V%6>v#aBkRgQ#4(D z@H*xhh$Vz7>umH!VT5i!q0=BP~pG}19@oo_ruL-61V5`v}u5X^t^IT@wi{B zGnWIEg;r9(svTKW6K-%Clmi7DoA{ttPzhGKs?&VO_WK`j2#aFA|GUaBc4_8LB5w zSEd-o2qdM!T`z_0YUE}3(ro;;KuYZ2ny;8^B9C`o*GuKLu5K`%%m74-*6eg`twx!b zJwIUYD07rEDSgYFEYq@Zzl>6cvbb;kOk~xi+}*(!fM3*9u!*Hof?j2ltGb0d{*hvR z|H_9O)!j0`NyZZXCExV%yG2^(4KpmqbUjyVr$kflS{&1c&HtL%oqTvp?0a2&qm=&< zV42tNP9HAUBujOzt;Xt_8)fk0QaSq(RXh4L02viZ=Me_g=f>RATzipPV|A!|-(-S* zkibSJuD7^4aT5C_klu~}Tb+&)c&k`?(8H4AwQm#-@Agtu3$iB@D z*UIHIUWZfB_;Wan?d59@^o~;N%4L%3_TjU&5f)dB>G+yXw_Nv_HbblfSX|2Ad?dIu z;&XBjjp^cTJ-X5B&LQRK?0h7~fBvfZ4nJ`fy*aoUVBealW^zxlUyN za`s2STga9Eq4pC;YtgWELRpzvnJW~UF6outPO2JS4D5XRG<1@b2RV`ld5#CTNqVeH zCit+p=gygi;!-^PPi*RX&f}=m&8Gg^e1Tc*(Z_=0EY)o}sPLON$0btHKJ&LUivBs= z;t6`29h)0RBZb2^oeafK7AFCk{0Oo0V|vpLwvr0%MYYJE#?0`M8qc0&v|LRMapqY~9k*l6 zsPOIs;waT=%qMm7?rHQwvgrthl{9BrGM}pQSdGPm)`fhO!__&SQv(v3C8{<91qw}3 z!sOn(0wBkwBUlu@qE+ft=C+%A$i%h*M+2=IytiH7rm-0HurD{ysaqsYkDR;(!j|av zju1&P;%m{X0v_Txu@2P`zs0xs5}<+pa>$o$&XC?ygLf2ndK<$SGfsuJob1=DRSfk=-OSFTxz{ltAw*E@oDULDGfEKqjYh#dj zV>X>X;~WSb##B9jn%uwt3@-QR#g1(Ga@&fMj#R6M{nB>hMO8n4QrWY$2Np`T#Y`D2 zMIP&yX3jNHD(9@Mg~(N$bTj_c1@*`SysS9-?S?sJ^xUpm1cC%th{L_38z9Ipi==P| z@7X#AkP&g7VmExBW2Pgxuv=4Jc{Hwf0Urt)6(~^?PW8P;KfFe*w?klu!i*!t*JI>@pU;|(lwi^u&0RxZK6V&>zTpe;h-T({j^b@b)W32UY+ zOQ&mxc)2N^j#9Us0h505noiwyJQLtP)v{^two5XdewxD~6#DZ>>am10kP0e*;MZ>$ zB;BPras|xlew!(Ox_Sd8>-eevn5!J`tETQ}%#T;D0ecMNkN8Z$p-+>*)Tnz8CJ?1K6eZ z`IE4rb^ZIeepCPSA8M4IL%5_Nol=*_kh9PNQnPO&o02}p^dZ&uf9`56n(InH z@|f!^HU5(QHR+qPg?&=!!Dnt^4IzW>S{0^Q_jtUXwUh6h8!(&=pFRrx^i%1p!ZICA z7Q<&};hnKYR4My{5W^k=AHY2`)v@>n0SU5v_=q6gqnsGSFVsqOV2)*0xm8$=Y%0IQMz&0 zAA=*aSilM=(eE4Vau;YVNmDa9xzLR4f=)y31XMg1NN$aV8I~k%LQ8Gy7MMlsWwO*S zo3@xq(0K*s^1SN}B9aA4-Fls?^Ov)qd-|Z3Xu_sRs{5)ZuPX-+XRY(_@ms2iHmQ!G zWC!mZUv9z=gucUZYpK==SwE`$^gmA&0nOC;oNFQ$TNpM4zNy{9+adSF%erkh?f{i) zOiI>3my1QNa_ows#CygMRqS&o=6&m=!SG3#BwuJc1KX3Ft_Ey}d5@5%`L4le9l;^a zO&Yet`m}gROOV!Zjvpksvukgh_#6Z1w-y(}s3-X0o5(DF9z90k6lByN^#<4C9&Gg%g6Z^y#Z822x@|ZO zPeNBBOGVxU(;j%zl2)g~3TBSoIQ27zO;=|C#E_d+{RMcctsQZ0C#s4mtmcA-q{?cB z--!Ng)ygRJ1;dT-De4O|Lue7bo0Om_nk6?KK3{xwBPv>Y>Uc8MG@duW8Wbk3JZv?A z*^@8*)1{%7W;V45yq1K#YnSSg)W5}_i4GO1yR~5JgDg}t$`cAr=uTX|BC}yiC+wlk zU8Pj^Qp`Ai)>*LeX9GE%XiJGMm-wE*E7v{Qe0>y0!ReV}B@HN^Mfh(H&Hahu)+;50 zXw@A$0a+BCsj1eD$ocu!8vnvcZb@ikB)DT#!-aE{T$dsz2$(Xdzw?BJ8{YUOj7~lR z+lf7Ae0opuO&JA6a=ZliUak-67{BJ%15ussRj;;YZmG$~wQRZoA;vZIv`89>P=`iH zR=?JT_T6W(mY8Ij9IIzKGe$M#7kdvxK<=s*e90Y?q8k>LNGGT(wXiVcw%Ar?`h=Qv z!J%Ei6>O~p%Gkt4`gt4hr8zhvs*>OX7^o39rrqWN^N3k&pVON}C}G+;e*H#B9kRX( zT68*iF$wiE|lgc?Jekh#bEW z?0nee7OX(RDXoXn{2)nS7l+{IqmSwSK+MYCGksXVRGkCbDmJnl^j20h^Gese+$H$H7lvreXBE0FuAlI zt(KhPH5>fpxWuO?b%0Ttht6tXb9)%5S%J(#__|;-8#kWaH(WwSw&Q!n6`WeBm*s3t z|0o|OS(iA)A_-+{>{5K4*IDAvDz^m5t>hA(%zv!c@5YyxR^T}e1@aRKOuiaMYoDwR zQs%STd3kyUVV~NNqRJ^^rNp@oT;wc%|h`sSoWXTy?Uifu?~JD|M|i(ubo|R&AZRKPEiU_QJ zT}JFhGjr36qp)p?-YG}%@RDA;V|ug9g&X#sOm*klhT6dG_WjNa4?$Tnv{8W74t zO+|fZ`iMe>h(9M1P95zBTMZB)Nlw#}ro2B5`AEMu*in&wa{$7Wc<_KyA5ta^G9*0s ziyOxFkeiGjYQyLop4t-9D@o11Se|070?i_EnAQ}x(%8gT#VQwGf-QQd8YdeRFSqga z&$&NCGPhK9q@AHg!EY?UqG*etEgWttn(mnNWO^& zFyQaeIYm+H!$K~{M*hXETjJ7mukO(kyK7Wm%=@dlVyT4mk>`2FhT5(X<5=vvO}3Kg z*MPbX9U`4d=%)kMbo*#99`{K&qv39(MhF=*R^+}QtW4#`F=l_1>-A9${6+9Mp#~D& zh^aNbDZ?g1h<%FSO`d=dCh&s{8S>$hKCfgNg&}*1*FhavOx8PQ(OF z9LA4CQhf7l30kN*-Kfs3BrRD&>Bbg`_;TvUb+IK0UcHKi1u`_Bqo(f_p9z~}&DKx} z2D1f%5uK`ir+@1)kzKeDns= z)L8wuyFmR@6S~+Gk>D(e@p)if+(g-yI+0!^T+SnLE}ScyZs4N88rzl6Y{T5z5F>-n53 z(=+B}&MUY#5BC?vXk*Nt{Ial=rm+z+>lI&zJOUBIU_p}5$X#K1^K1|f`H~E?Az@_j z#Rlk1M2z?_*V4u8*`2T`nG)!Kyez`?YPr(BklhNX8^G54OIXedp|zagkZJ45UUVEGcNQZj@RN$*@%N# z60Aonh`*#4I`9zIS57f)$~TWAK<;uOzopO-XE~;-tA+U<+TXjy!t?Z)jx3a-X(5bK zWPwX+IG?#Y+VgU=)50BI_axb#inOQZYGC}KHpJTa+Qo3{ZUP!zB))7N1atX^<~jA< z+Bfya%7|9z^pdOQvSf6>$M!UZcFq$Wa`7FzK;-g0&)v1faZ3&S!fEM;w~!L1n~|Yv z=kIC;YA}1W;JWkt;lApheqBz0O?nsgYgI+y&s=0ITJOio`ivTwChwgjqOu}hm0m3cs9P+95Otd`OFzF68XLQrt=}m)x*>pAlVZ8K!l8yz)MgmC zzX#%>wEGP44X$xiL-cdHhbga^p`>^qpg(~B%Iq#Gh z4cbdVplokag{3lARAO{#fSWY#btL7)dpjE+G4Beu8e3fYR8j(J`8bI#q@K=7tpe$n z?MJ0}IFs2)EpN%M>E(S}sC2*NbWU>aEpfG7U76%aZ+j9siE}7CkJ4cmL3&KdeEH1x zFC>jC-DJ5QR4MgOFZD|f@WMqixl9h3=< z5W@BEnZQRR%`};t4Bs*}#Y33Hm=3kIblVFc%j0X;!kuw{rnqc6XfZ|j6px5^n9$Qp z;QuqGVkUhmIT<36O;aENH}c(vLz`$ao> zuq9}cNMF{ClX~lL5*OBz+>Q~uVm&&?PFH}v@C81G%eMsyaX1nqUT7%-+4dDLjn&Gv zwPj zk@KF@$y!b6EQN}CMEnnZu;wQZ^G;ohCPf+&Dg)`M*#tn*gDOhE?A*`XK(U!;^;9G( zx^drk{+t^1uonnFOf|;*!@Jk7-_wE-a4Hs8dlz`p1c#F1qWM;)qZM9J&apPcB_;yQ z5AglW1z2I{hTBbQff+wtz>-vPew)*f0Bi6JACi9+myod7-D(WEvVei^4UV*Dj(tJ8l5sR{3CT7buIYW55`KL-A zAsbGmCEQQ@0IyT2ZjR|)e#+CfpR#{X;qCkqK=PUu%UGjW+D~jUrd@d8y;XNlKitTe z+hAqC1sI0uv-S3SaYw2>%I&wRhjEW3rE*N8*ds5)0mEWx(*LPWXMFmvLb`JQ8z{=g zh?UaQhawfUj#IFZOO(pc%}9bfCMS z{KMvZ)kHV%4zmw6x#7yP7(&+p&M4WC+faugba_{lyPYh}-WaeCO_@Pw8tMt?A$OQI zv^K2N!3Kbr{b2jXD%`-Lp|^CY`RFuzV^;z}^Tm-3gYKgI5}`sL|JtKIOkKXk^EyxK zrlS#SJfQU9F`|H3o|fHO*C))RG0-%UCj~D~#3gSh!>qi284u=T)GxsJIJk)L76c6> z_SVY4bZi@FCT0o>9oK;$OKr}I0%4ZARm8HRJI$Zn++90VmFOk$b>={`y)(8s2fA7_1~Dz7wNbgi{VBQC5$Ky zgAtXYO+7;R7P)2dBT;YZOr6K0 zAM?je3})SptJrGq;HDOf5`P&(#kD9&>+8b`@ugy=k{&uKt{Cs8+a?9dr0k1mY{N{4 zrNU`4Rg^1ID@ADlR@m08JP`-+++-=W2ijZi@AAq5O1a8UC9#-M3BhZ^81b-~n1=@K zdCHa)IC=VZ44gGPEyVCY&Ero@p?hZ!D8^k(W60g{jC@s2<_^8C9EI|kT0z=y$%9f$ zWWon)Dci{FxOXe_mKV~uzJy9$^nF)UAYM4QBS8+ZSlWHalkTpYSL)=nS4rlVk-`u8mcbA|imVL*}R;^zF**cY1|6g>27S}}A? z3)Zi2UJ-bDPCr$ntarKR9BH9Fj#)953IVgVW@ZTl!Rquy+}M4vvcTr4>Rxu{l&|2H z*R%pl(?VrxH@X%)ofl?_!9H0=2fykmzk^o~#7Qn4IU+2UJ-$m8<2K_kV4HL#2d#77 z+SXG0AF0%0>GL|fc;p?MS%np<3kW%X8;w9AJYN`>CdMJOLcJF-@P+Dh+FKi`FSp*Z zCiHU@1a0Uo5u)pl-w~=PCpqjgjVn$*e6_T9@6ArjDS^F+78NSIE1|(HgjW=wDsA_& zer+TiP>%?AEQEq3vJ25pdMEtRs}oE`zS}w}4zo`~T$gOo81tXaAMt1N4H4|KboHHI z(1R)S{JOiTnAPx9H_z$qy2wRu_M%efRVw%c=X(+8jqSBOle&!HjWA}X9Fv_=#;77p zWVc3Enc3}fvHpqDI>PKUZ5WM{Jkk0hKvI)OC>zNghE@uTY6 zMW1eKvAwpn3nZ0Ba=&3k5LgZR^*WnqITmHyNd!%{;J|hZdf6N-mv-j34&+;VPf0&Pur%EwZr@Eo;GWF(5+-%8 zA#wiJ5;fbK@5v@ve^nD_k)@za;9%!?)HqSQu zQwf~k!nw5@l1pzg7FJCO6WFWxABr!ZXRDvR%i0v;*K8tpckEBil3ywk7t`qVqHTQ` zqt12Q*K3C-BEOQ~i}?qD5^!*F+=?|UYeA7F{RNuql#O1)oi$@Sr&-)<%OOz2#F;IW zwon#~K$B<4l*8VoCK&|5zV>lP&z62p@_-v(i?$=m1&&~DF*d^9?Bm5tfD{uPMqHwvEDFaII9qP2RBE25AmM!bR#ni^YN0nI z=!aJ*XtI_R$iIOdlFg#Pbqo-!tRpY(%;uwY02oMH07yxW(>)hI$sYA3ju#hCR;oAR z+IPov5|%sNEPxJBVMxkr0*#a#~s%5TAWHi?a3z@9j&)s<=h{7Ymt840?*um0m< zB=_?L^zztYA&`ZWQhXHv#TE1xQ~|_4Az=MmKH*@2;S$e?Ul_W^JcRZbHeeyct1Fj? zPvABtS-A|jZBBbm*%U2lLUZg?`g>?SvBDm5p9I@}w`_|$2Ch3(Ut5%)kn)-O^zw5(K7v>A^C=A2hH->;e~p|1-@ni2tr7@JEV zz&1Wbn$6q72x=jehAi2@(KO4dk^0qVnJvFe)dG+9jWv+xqp01jY};olv}4TgRrfR_ zFbgjiy6p4eoUS0*OjNp77>lCtzgJcvGM6}ThNXHSnb?(VT)j!%Z~-dndaDGG+DDl= zal05!&TCZTOB!;MrZDGpKK*FUya0TH9Es`4v7ATj@Etd{r||cxCu->3qkm&COracol8!{Lzh ztAgdN`*K-PnKLz_cNi5co|nMO(1qslzKz#@1=k-gUpsrs7I{qE#)?GExg%~fE8G5L z3s+sO&rz);`yH5Xx#HZuhcbrrvWS+%AEb|v`g#3Lu(0{F3)#B%Db~1Lyio|b2A}1* zQ;v9dQn-3g`Z5CxPINovTTl3a#cirnY%Ex6p_SAWd+T$v%i_mM-Eo&6PcY+KJBLyc zri?DaZ?vXUmaQf66l;phl6ZK>#Eo6me=;3W$tknvy8)u zL~7>TlSo1Cj!%VOThwTQGM&GB?MScPZ%|ITU3WEV=X6WYMD-VSr=OlyE`aWoU}6qv zsLF9gVk^ieUZx&9?t%QbCW53T)v>3LhoAI@SOf`zAN%iFv6KweF&wn2CwFY=;_R;84>^KPNbU(OFo;NHE{2HJuUthHYH7rW31A8oHE zh-!g5G{yTw^8sfb@H=^Rx=_@paYD0_(&mxvdlvAKC__D`)Q`$4GJovV!R`=23-{H^ zw`eOpM77}{ITCSpKJCI97cl`&&Po1XW0)e3YfQJ|J!!YGvZgU4|K7e z1BSNOg|>O^ai>#!4L_U(&Vt{!!7b64GprOEamKu`16YB*3eoNoVybWQ%EHB*c@_l; z3%g4y&|3#yCVkbM^Y`Q|x0XCsN!8U@9BOw%`YFD0)}1j@pe+uov5)a+iA!o1#wf~= z;Dc44Y4p0ias)G6tO?GQ!`8jc2!oY7Tz@mHq0SZIMGW&Tuw`qjp~7r%81ujmC8_qx zZnH6U^3Kk0f-Wj^OD@#Aa9ng=t6;jxo0o4ZU5_hfOa)>BPaqhg+J|U4I8A zOZEf7*V$PViC9Q)=8T@szi>t0DJ;e4HJ9t8Scqz5`PfW|SO^pyMYoB4HSu%ypF~K5 zQE5v3^#uE8EKa$8o4f~!rcz&8^+H0C6q4csH{C(BSbeYe|AGm1{nw=0=g zc-{!XV+PAAaM;h8|HLo+dmVAj?FlZ&TyQ*CjA8d?!^&Hm5mRtuqaUjC%Keo$!vhX9u z!2k9Bc<~}*CC3s97Al#&gr`x<@IDfjra&vYurGfV0<*Qn%ul z-H0%2{4hA>9RfGoItpe&68gMvtppFI*G(NIcZjw4PU3_m=&^0_i)Y2axM zF}=xL@4D2)deNtiB?A;Ktdrq4#;(%H2gCO2kb-FL{@G`tr?sOWGs&+3+ zFq{CwgCD}Yga_C-O0;1++P>RKF=lE$iu@`#V3|lBJc%WQvUM{RHpcp#MkEMltaDRq zanwgDn?7eP8>DO&Kw%;7LNN~suS#~MB!O}QU(5lM->U9uPGM1)fGzF~=;jxGHDz+x zWveqi`I9FSWTx~8o&tTD=oJgsu9SMdYQmzKUC`LK^CbU5ep!7Ql-;5nGg8*@?Ho&r z9hr9078EGmGjp%`e7(ws+s@{#2xDp=VMPfwS)K}_UDE%bV7U*+t>GBK&zSvd6^C9TK@)NA^9mr6Rr0PCWw`rLzh8}}- zHUNaZez2+)Q(5>;NDDEiaN0@yAhl-BZ=lsvP#z8Hg6;KUKg7{2^+BNsF?q?tDQ8x8 z{|0k&bxC5mlw0&t*`-4vMXoAZ;HK?a^=)=3pKU4(fIE9(09j%Y(6S9y*FD8>wqTH{ zQZ`g61KSa1%jKtm^kkb}<9W7S9~izJ^;;iW99xCDbLu*)MPQdIn4%RUSguNJV@>05 z0j5|2VQMW9CZ?l`1Qj-I!Z*#C9=85+<9cq3j+f_?TO&E`ytW2~QVWgwGNwh6NJW_I zVFe)?JwYpG_FlRCeTx&Oc%DmMc%UwifYZ`!a(h|yY>P=yz9F4#0fkRmLW=Qp8)8#g zt~5)jBo28LIYkzcP4(}G3%GK6lN*3g{+wJjk}-$&)K}@H=oo#ym_YkkjolXSBNTz{ zu}UbczpOPpb9FyzcpEfbZ1%ZP)MRNmbobNui zlsZscy@>yzaQ3K@3S9cW$kJ7!C+QTHK>=!H3}3sXU#N!jtIg+{tMzofvpC$MR%G`xyeWZXmd)mK!X7Zf(E2%~@?}mGC|jNE83&bdv#yze$}CX@UTA64@Ivq;8OU z>S*-Atw)uJ+6jc#iM6fN#V&`0MFM1;m!#9<_QQ)v}7|PncUgtx9rAWCQIw{yYT?A%)O}9K* zL0gsS_^p$)%%7#qD!IlD7FP@lC@JMObFmXDm8y2FO?^f?fR{sw(R#L?EQ!TUyt@!* zUd0ULeCi1tPdThCJ4uEyN<*wubyK1YAA2AHE>tc2a_$Ay0~a3Arb@5XOQkrWtVSgY?1G2; zx;Byu^!*lFRh|S{D%piMbF}r)?5T~Sxtqhd%mOu8YWgNWGDF;Qv0vlyqC%w6RDlMy zlgr@ohS`0YtMvSdETx>wF4Hh=jqFyo`my*3wC&0I3$8vOM_Q@Ef=xAt(?qYv@BO;A zsZ+C{McJ7CRK-sql|9%Ci?K_sR~;80@Kg9+IhJIQ*p_|9@9&sQxn%L_n3Sw+n<=8w zv@mTXAKA>xmTJX9qGCB*s3khv?hhEcC4FQ^d3Qs3PE@8iOgHom;UHUaShFg&S~w3) z(q6Q-Dz{WBvhRiNL&_|`X7?+>(_f#^X!)GZFh*k?hZf}Fn?U;2@G29v4y>+O|o>I|0kNIBj=Islq+Su}!5_!K<=#Jp({t>|PMl z+LUQK%;{{dX|XOT^jnj7X9)bCtuP+0(4v-BN-l!}kBiGuw?*>8nfM3uyrbKX6vx;t zvW_SVj!iq!n58I@l}Tk@pBB!fOQ0YS4SA}ddA$3ITIr}uW{B?2OstWNBd1CyCfl>8 z@`#9mKFT{o^^7r0TS{WE0o9g#(1+)h*kBNEGb$%FA1%@avv$V+J#losE?WYJ@=^iz zfgiH&rPoSTqEv8mWA70UICf5Dx{vW#^RVd;z;}xgzFSwV%yyGA!7u`7k@U^G{5%~TbE&PO5Y!y~md$~Ep z`a-=8w*Fb;uWEC5fF@}vju*cZ$>XFsxOHcG1djcC5K&eX<@+rhB@2E zwtgMVywf(JD)p^&=lSakO_~Z*Gm)2XN@`44xsxYoi@9*|D3CfV%i&Zi@_XeHDc2VZ z$ZVzyOwKpjE{b&7uCL=|f@k_HU?ZB#dA4cfsR2nr_kC+#;#*VELLN(Wc#=*7>TTNr zL%?wvtPbfH<<>JWhYy$oX#K`bA+YyPnRIrhBTytG4~%YVn!08o7|G-@p2=ZzV&Pf{ z^TmOnMaWKCmN|&9+)UuPn^*%}Yh92IADa@OICrjoL6!P{j5noY*D5;DF@MsGoRme(m5Lfx5J_7uU33#ifMDq5SVfXXZOD!! z0j5-w2v8(xf>ZK|!fHU{MCGg5@|DT)vhP#HMol-|7d@U1W?SjZrEI0#F6g>MARXPp z6cF|UsmR!*)GrMV0ANb!ke*sU@q0Z8KTPQF8tTwX(X89#Wghxh)oFb$ppjTwcOwt||cP1Y}%;~ETc zq04m%{R@J~O7aAdB2So5SjoOw8W;PZDPdH=PznlBaa;6C1VX-xRk+8kZfp+4Sv~fLA z5(k%;X`5w7_~Jy|Y3Ku54@-s&>dgL_&?q;DBL%^#Dm)aal{bA3{ZUGhN^>xz%!p8L zaM2qqEj&MUkvTWyx#@dauB=0l_$?7Cq%40Wq~gY6}3*rTNp zQ3%5*e^P=(g-g>%RS8ZZQdkZCVVV1O{j|xfU`coy{Ryd?#k1bxRaarcR2h>%PHkls zE6n~WjK3LG3angruUZ4g1uaqDITz=QDqa}jFND#@T|Rrm}*?c@65PC9^{PX_NrhsyjG%-32H#=;s zn__GkoY?dj)0*TFkV**`P_TkUVbSe3?{jOc#&C-#l7i(P_$+h9g(Nau3v3tKkmU}p zGY2`|#-@SkX=0No4R-T~Ul?sO-r~dbNB2PvZcwtf#!*rR_JNKlTgW-<<81j~^&FUH z$@i^N2ay0~4Vvey3PM7DA2z}mQ7MTswpr-gSwOA?mkf3<=|H@$g^bRL2wnmrYj$(b zr+^@&%{?WBOwRGs(=+`X5rV0p0r3&VTAZItNGgxq>nV1+zPJJKNm;RKAA84iHwVl@ zT7jsVvOiYOE~Sx#Jr!JEul<$6ynCR3*?}Y<>cITrJGX%hGGJJpqrBk z8J7Y^sM=dkN>Bey@7+-4r@DKIG5?>Sc4DBt62C4M8S+<6IGpu-^QGLBPB0}!?k$?({5SGm&g`YD-z*vw-*z3cLS+l$>Q@~{77|m=7Qk~QtLD2|KO5>L7gX|&J1sP* zHGa@3KhL@9soC^41Z@tX4PD6jYdCLImTj%w5?@`tbe?^TIz0mbWJ8$ci#qgQRfkX7G*v`Fc#Yq8IRdSwPAF{YBYlf;B+RX5somteEWvIA zrz)>jkRp$>P}Gg{I8{O@>r}w=fVdS8;@(!7d_Q$L478x;+pzeMaOkrf+mBS~x?MU2 z7DXpT$_mfyV4`xG@>5(yh!Qomx*CvRKU=MY6NrU(u8n6|3!a4!)VnA)+3GLY7KzfsN!iE^I+kGs(oGA*X=M zokdIu3eG-}`+1=zNiun-E?uG)lLev?vT$M$a!`IMWnLE8S~?^Yca}$Sj%OWbF9ekx z)?w%~@MQxp+p{B3WTcNEMP-UHVC@R39xw-jWzp#<0bZ-yF{`6hy^j4wR>sbP%*n*e zNXh#;!HWeFlp!D^^06_TNt={mfgnYjARr(hkT5|40RaI4fdT~#3>p|14A@`lz3wL>Q->;d&)Wart68gU*FnldWSrxP zp1`v?-#id5UheFw;zFvlA?wgdPx zt}0|3nEMU!)X(umdaS1}boF)A)<=*Ek&<54(7V@iQ{CV%Hq9YCtTm5-;b34nx%WmJ$^E3>mPU3e){L+(U$@SYh0UpyN2ast1{|l*&+`m8R zkZ2+l(nj)4?5I{uK*u?du%&PDcj{;#TpXAtgRFr28%Q$PkkwwxCFj!17jN;wY7~8c zQsl%LJpf?Wf#qFAkUb1ZvuqG^zw>pY=$F!W5;HYzo~y`ACrC?=ZZZsBSFyeRz*qLv zYFF8{lhOa>Wy4sia5;3N0ycPc)FhBC>Z|@_ zy?YJN?MzJ}o^b2c@VE?5o!Vqkc2r5mb=DX93V?HluXG{Tu+a31q(#C6A3J&@&P0(kMKW)ej~qH=&mv1ONKE@z3YxUH1TqHjc_hY)Q| zuEBEvGp2anO9fbY>_FK?$HkQDdcIV| z@#uAyCftOlG#pkx9?dL1Q{EC1NcA0__?836yv)wD-?b4sScoc3NJ!-BYfDI_hfZVi z_OY%mn(}Pi^KmRCekl6Rin>NFKc{{VvF$3n18e`8LIq-R5KK=l+2Xt9vlSMy;eV(qL8g#d_;UXW5bEe%|FZsKV%XH&cc+` zTgf}yiOR(oe4$o9Fq+r75)VQo{|~bQaG&2{}#e}enz<-oH{J`1s@PTTVD$Pi9!rlMvU#I5q;@E|ndOC0ZQUSl}0mmpCj5|vB z%dk@R6^rtaGSjo8Td$(B+ZgRd-L!~>SMm8IAHfw!vlYn?8}4)Z&3pU9!Ik3GSYJyL zeCK^`7l(&w9s}MHuPZ~NmlTSj6%Y@;s^K@8y7kf$;PD7RDnKz|6LhU}-eRTXS7~A{ z;n*v0G2fGFt#}l^u70IAZ`0s+A=3v`F)#GI8j3O+cA_@ZXYttR{p_uY6imR~s?e&( z;W`NDSw-LaG`4C+Nl~gvQBr+5%ad*XIK`4e=$;C9W;E+(J`6niX=bJ=PFDq2WX#~a z%3Y|nw8C(K@hwZZ5*tlNVqRtm)MRA;#eEihHD#Vp};f^RiEoSIZ#vGCq zwoRG7^ldO5RyMUMSkhrF=TQ_TPD0{`#EaBla#vT%><@l_L=xG*i8fU-W*6r&MxQZ8 zOcjl#4xv&1UGYnMnLyti^3K*}Cs~A#>O^{3dMepD_JP$diffW$toAXu8%Pqo1h zT$@(|sI!cDOV$#--$0`6p2!DtJa4F0a#hkg?k;(p1 zp|%au(a&puoJSPO12U7VJbtYgeVM70%;&d zR;$83o7XYkX51NFnCnyQ=@QY~K4Mo@Rvr1?)W!8`S;w=jsjZ*=doc&!V;`&sNn}FG z>NQut)=_-UBhkUKGX(#jNcsCFpJED!x2@+uk!4u$y8{2~y5sH+$Bk3|?!YUdCTX`* zwW8g{h*Y&zu8Z}m!0 zb-O`V0jk=2kuU1E1yI52M(2NC{@ss6=jMjT+ni#9^3-%rCGB~+3Q89vJ;5U%94i;# zN`;24Tg0pt->{Pm$H2w$@q4B_D)UI-grcJMzo+e-s@~3boEt_;g=;uk8+%kVmtTAKnyatOgD2gjwT6@b_D?|a&yHmY}u%PUx z>!5ZD+et!!&K?NR&qQ53RvOJ+9A6sC!+k8unX*PFaAA9H59C7DEK;BYJJy|iAC>B; zSn#vyJomt}EQfvhu!?bSqY& zR1Iqg;9`+BRV~pNsS0>p+DKFtr?h5{t=*7>jDtBoJ8}x;SVgV;Rs(so54h}2ikc2U zNt|mWuyYy$LWAampqWGLz;?E6fDA}iQ+38&{zM}eh$ZlbAE@Ey_~*n@7mz#v@%})W zh?R!w5dwD>$#O81MrX&FQaobJn6;^hDb8;HjjjQTs=16 z5RNt`9j#&fQ;y5sW-MES-MIYa3@;hqJ6%Yifs#bKB`?oR5`-XYqB|F2{0sBRlsG9qIRKxS%F<@vmU)5i~ zeuP2Uf?VZ8*WopW0pRfuyvy?!H4yo7U4s4htG~uCGE(jG zafwNA`QPk7Uv@y4bVf#6tjXcV+>(gZ#|pXJoJRVbecB2>bcVU6aDFiA$~JQD!Ke1` z*`+VJRj5rCDoGye(4N7;(Q=gCVf8M)ded0Nb5F+|efe@O+p_w&)=k7VkMTSaT3`_F zSFdga!hLO$*3Xx}+yc7kNt?I-OeA6HU?us7={&+f%Wi3qqF=D*WOXo4K|E0m*OQj?+ z|8khyY;Ftqnx^A67^PsjUvYDPyQ(@Wt*#!){=@pd=zaj}jlJRP{rWnD8c6c47UXfh zR`cXq->I7Fo&~GkF6&#RB^&s!kFNuDCZu;YK|DTwA}9?x^DRgqc(Q6+aK5uR8)Fn9u8_5F{2C*%db#5tW}xbnsMDuW6JA|*W`Gf>Ieg5YI<6i8MMrl^x{ zjp+aLkXU|!_Q^$qQ>_cBZ5?f_YL~IGZ|4y=pDOM{cjDQI9EL#4WZ{tarSXr0*Y3MA z?+)_68YO~Gigi6!VVmYV9%WhE~NA>En_*c)8`PC1h?Wn_1l5nng zHy=XsUCe7E;~&KiPf6Q`rh24Fl78FZ<^S`JTrrG;g9&1&ToT3Q(`dljW zm23$<#3Oc@kDyMBCQ<3g43T7Vxo@Zj;MqN%5iLq<=WkAS4ErywN!K+|yC7u`b0EB@ zkj1Vd336|Yb)70GKD4(vo-kDMty48wb*gLqZ$WdqrnOOA-tFFkyEK) zj*$TT5Rw&=f-E}e`5~xSVqzB=GAU33O*JK{nxAFgSx^V{-Yq2q(DM=QGJi%Xvk|Y0 zuQ!*Gg_Z^0M8&L~6x0;;wj;T)$If$QmR}4A(sdrcg*lGdi>!H<3mcHei>{Mx|tc)Q9j-tlrSd@2I7e zvX|*`V^{d~X{{t{+D*G!XM<06U0VJXbZO1WA*ihQpF3u|;<~XbUh6{`-{SkYw2hmJ z8kxT`-+kpXRxgd&oHjSkRnvyPZo*e=M#H;phN}L7R-5bQm$mfBp4O0mm@&HJ1gv#; z4*$e6I526Fd(W56Ny-)(%g1-m_bxepvCq|p&8Mq34CD43X5_epIkze{dcKWrq(4%q z;w%%+kLj|0leKXT;TZlgUnRMruZAR-S1bY2@UOS}{R>Q&`qYMh;$;}(F%2p*sm_fTUoU~7V9C{@An`EWgDu$`p?5#E9}`dU-VeJ9V~UC zYu|BJZ5o&@ZOq26LrYT&AZ&kso9+q{W7`WKx3%be+kz>{U$l<9dy$#KHwEw(y4=Bt zrn$z86mnHVF#By1ZpFX&pd4gK8L(qBpSP=Uv*kqUw67|cfLhP^Ya(g=V?Lg$d84RU z1!)O`O=_vT#6^^eWG|*})6+_?Dwp4A@}e3I5{3DOQ}BW#qVf$b0cr1~7H}>g=P{nt zjEF8Vz2skOJ#2+ZhQh^FPFEeLM4U~mTk?;a7=$1l*j1xMwE(A<@V~_uEeU`hOA-Ko z`Gnf~Lz(=1(@-E=<>QP|KWgi{=zmcG_D`%{HgyEqB|xzMzquJO>+3Gp+PQ3&X{)fp zErw@lkY19Hy%p3SlEoodSzPWVHO*Y>I8q`B%f?EGC-t48bf#=-sQ{zAMX?Gq+TMA% z0CBF)XYg9E7!$)Y)60gbBZdcs7y7UN7{ipa)M+ut1HG4)Xaq++qLc15u3lF z4pTU*I*R~LR@vNK4~o|qg=nn^g=q-|9UW2}PA^38(yWju=Yz8|b!tC;k%QlJ9|z)s zs1)5(KZ$hhQ8^+JWf8y82&!Sm;NjB_Xdu_CwNBe=@-e9D4fwGU=BF|`pim5Qj zq1hgv!VkHtgKdYd`~mHaIV|gQr}Dwus=Zm{R&1rA zj`S1LpkH!VC-PX+oIqqT{cQihYHBW>bwsE;)|VgBS33aZsgc}Ga>6r-hO(RHGHH#Y zJ8rh{+L`;S?s^)X3U5pMEJS`TU*_RM;ABpp?1Or%=vaJJ$NFy4>66xhH!u)MR-Y|_ z-HKn2>VzjB3WZIQswPQEI6}p*>yNQcwAK9fOxI)p-gR8U69f36d;BkPq%F&S2<_n{ zJ$Co#CF4njfN-TI%%RpHi~Y@Vu(e|xt~%0$8i@l4b{rBthM!_xw)UO2X zVC?!FdRhrnQU+ngFV^^jHAWe`8B8aPYg+q`TZ{Ft^uN{}#{DpKRibYV$qg9igPYIs z6UR0LkR4CeU-8%7Yp=~gM9d{}K~WtBLMnumu6{24R)d+Ke);G5Mvs3``oSFf(bH>_ zGoAf7Ub;gQy7yf_G{ALl@tCUSq|&oTqdj$pw`LBf_YiO4+`Nke?QehVmUi1gin+B5 zGUYs=V9}#9UXo4jk|(!fdu1_XAL$THB9u;nJ!5CZ{PKnHWx=*mH4!WG-4e8Dtp98^ z(oQXPi6K1Lyi<3mL7cLy=sjEj1pAPRSw%{F6)z)}q`jw)?7Zu%2dQmbeG|enrBJd# zUYm6~A;n|iy$_w~`hsCv-2pla54w5lPCr{6=}UXKcdA`tpeyoeisTAtxY@Sa@9m47 z5)KR6jA!vHR#Y^r~!0{j`reu zJ@=K5i`-gNQR!EkM@i(RIfd||^NGcqpBhg}5huScNrytei5)wv0t;8Q5YT?iG$QS; zQ2|Eca3T9+Ir=Zn3KS+kYyE*&r80BbNFjks!vMH8UEz~xi z74uD4p+tvuL7%#9WVvj%pHUN%)L@DaYZUUt8bTmeT#yIslBxjGKC2p6F^q~~h zbIHQ!|JLVf$d*p>60J_15OD6s`DWdlqIY;MDEJwrI)Ak*k1?e`BK)m!~kduAD6d zI6Q~3ZhTHz26N?yDKRnc1+6+N;>2(J?bz<)C5M;fVa_X>@0sF|NKzsCIj2b|2+ie* z^(xjG>EI&4CCZxCyBF)tdL`A|ln8xY``!G0KAED{_%FC9R}?X0;B^h>g72}6b_&D^ z#ZZCFRZrVICsE1m>rms>uLJ!pNi-QiWL@2w$_d?tY^-H@arkcTbTz*%ReT~Rd#Ex@ zhn)YxRSvAgf1Zy`vo}BVWms{y*M;tPE4SXb;rTyy3$)9=ICNDirSl|J-(V}+%Tr5F zkkwgE76V{8T2;Pvp&OQ(2O_uv0fP%qWaF$BNEKt7n`vd74wE~mvqM*ppzj9n6H&?Z zrr|?hWJW+=(_p)?dDwmlKG#g-pX%qbSSW4Dn)vC`1B$8OPfKy^Qh!98Rc8){o!3kn zqCx+*=qU$F9b^a>Y#v@p=LuN~>^ zsPt*2W^Si2)@xt4=D^r>4-M3c-w>9Gg;|j~hXk;|aw@l6W_dR(?l> z4Hr7Je|~DO8^RwtbD!tU5im}sC9b!aXq7=^g#WW>;mBorjK8ttqmb$nE%gn1pu=LR2xAf5-jgqdh>^?`$Jj9*R;3Mi@#T`kl(lJ#!M3$(OHsA>Cx1~V{0{OdzR-`sDd7MLY zXjc7Er(T}ah4u+yD#+w^k5>B;16>i8aOw?QeM2XEK7?~>-CKUw9&AfGe~YN+dGS{Y zC9qe5iX4@FEj(Kwc{by3*G)h;OY_ zatw{GZCIV%6ds}n41+S8*2{KR0dtD?%VrB=)wLc`vf(Y72UpG-sz55i&|O-%bBE0` zjr9sVIX7zG%*@L~$gizop$7dDz(Nyp;F@8k=fz*W^_r+Ni4mMK6~rq!)3h1LEj?(R ztMnNoi5jQO+{6KB4xtQt(9U~G7zg%DwW%> z5;D{?53M5%A$m%5NK_=l3a>?{!rILCe?5m*MG_Km6GgF6Nq6*?HUB~@>_r5jKet)Sz-y2xm5R`!Ryw#6grHjDr9x&~%6?M5`&P3gV8OLh z(v;eyHMELV?V`MhD6eRBnuR`LLHRu3;jG9Fq3LZfLN9R{+uFO>Zpt=Ctga7u-yP1k zS<#;Q{1k^nJIZ2Mi>2XNNG!kvEzt}f!pstzZJL33C4%_c-9xBtU`f3MDbHW^h6Iwe zbN^}}%JF%C&?E0eR@!-hmfl=)RLnUJmc5|h>hL{U4?Le5MfsW73ITWIJs_9hRSW%L z-gq-{$LBZdTB+Vuz38N9GYI!|-{aQ|V$3;FZ3PFP@73S^fDF*6?$&W(iJ* z&~e*Lw<%tyw_Mt!=GD@ATQ7kUZKpU-)FM;<^xg~#x>MuLyD5I$Uc}BIJzpK#iMR|x6O)Ld5TmoG7rzV ztxzK>m<)v3#IfzGAspWH>03IYU?VMequEaF(jD3su+kIkJ_xWB_b<-q%_Mjb z5Xpi+m5nAV=kmT-+k?EjA%@E^w{bvkL9ud{Q?)gK*@bu<;2b>bdc1q`o-F1_@G1k< z$h6DSi39GMwr`;MMTGz%Yo=9KCKXrp>fgC7h)H9!l*4S6al1N@r(})~gGeU5zBa*~ z9*99Iksit&+A%o&AtYBA$4ZJFbeY zC1FJ!c=)8Xtu+%cO`6LH($0&1PqaNisaraA<-&j%)7Uzx4DOUPm2P-Ge}h9IN31g3 zEF+eDBMN=dc+8jaE${?9L%g4HZlC;qc>NXPukiNzUj6l>%C`FN%;`L7V^4m1z?*-W1BV5ztI+`GSHxwT#%`Qmeo9^fkzu*()B zOzQz>z#Bu^gkelNaN&x^w`AkYc?k$RF0(c3(N(`)9RJ*?oWvJzulZAHyL*OR8nlmx z3vZe2LEfI-5P)}BI;N36U6W8e&?}ZT*8TbV#a)aQxkh!~6?uL8{D;!d(+UeH; zC!y;Nc|Ot|rV%^b2#f2Nt=;>}=GD&%NbB`gEZcHbdrx)^U%%S@R6T794_|PtW%rC% z9M3Ga1#(Tb&NS~a34bm(;E+$_iOP#aFfAkEYecMr<*__`_OO5UaqIE7HtnQ;1IW?p2@j~E9bJ*YkJ@AhpMNH_=5 zffSUsHM9~Hzw(n517Mz{Z&XAfQ+GJVo5r+KH0hNcQ+0b@0-pa6+2y0i&+J|2%;lc6 z&-?hUt(wEh^-n&{w&5y?Y zRxifHQn1XGM?UMXb9ir$;nu#PD7`Dsi7+><6ql6iZ7{cwCH-r;8(rAOLAUn|=q%-bT@Fg6`0A1C4twRQ>jki#DGo(CR8Vt~E z^XzZLrlp$Duo$f$wK$L>+BfkQnw9`9T_ecL?7t5Bu<7B2Ww0^)hn}lnbeUhH8dP)@ zx^YKqa+A7Hb_%_pC6jk5BA2OOTLVc-x-Mh{mNIpTigO!ft+(D1f3x^ONV7&GxYF0n zb{(5TYC7dDau>OQo$}Mj1}=L&R4?a+BiN*4zv_MV=(TFTWF|7-?Z*pRXtuffVM%oU z(q^^|Wq%w|O3K^jX}@GTgxf>KjueJw%ViAPF*dQ{>90c61mw8PD=c`;DJT|>IF4WC zPViBt#-EZV6boTHXh@9-s5vPc!Q8~$nF!i?^o?-IQ&P>IRHek{^Ygb zF!_e3ah-6U*^gJ@$O+ak zDGeQtxoXcaUK{1ldxwD`N$S281YF!2FcZeXkcNLNQ2q{L>&uMsHO{s~(yyg?-E9*z zS#s@$q3ysKlZ$3YkMuC!9bxg89gANtZ{7>bs~(6+!?c>_>94PC82xW5{p8c6`Z?7h zq)CnMjS{DIB0G9Sa|u<+D<3Vdd>o9r?{EEIWh&gF?MNrj>z<74bQvFXHBp}JEthe6 z0qfk8M7a2*eN&9e?xiFVu{P(?cYX)CSRUU0cp=Anv##TkUV_bH&-F*eICSwwLZeib zt&n9eZCurai6qnL!G`BU+Z7CPqkc376_r zkns`g@RY{&kHOEy{o$>xbjS&h3)qugMtYM5>uLNp`> z+gkw<_VmHCrl7{?w&a+%`hL)*G;w*Ti7 zmZxuS$;9fh{;KKGejLx1PV-<$*CJbw0q@uV{0pqEYrGsKwRrg%Mjycv-`&KSw_kY5 zI4Sw#G80LnDYClGzJrxiH~lsupjN6Qs2pspvLDC-Y`VdOir{Xv=kKc%L0%+}8IVc% z-NgZIJU36mc+8E?wZS(OXI94C&>eDsvWR})uG)H7mm|@9b9c&}EyOv&))qCbhigzL zvs==K!UUaySh@b-bbImLE((Y$CoGjH%A)+x^``4ADyH~HH zsk=N~pE3AJMW%R_>>CAo<*68`(iJ(n9k9kL5jPu}tqDKk@ym!g=Gep2eNL!p=HNpa z5|7lSTS997r(K)Xw(Bu_Jh#e9*(FnvV=S?!ujUecHw-c*4z|$l1}_v@k~A_k8e?Ke zG)tBa{T+G25}izT>ha$zb32>e#kR65g-(J>r}-I~+6a;_U-{z?_*_;=;TT!mSS!ga zjd$WM9SNeS($fx+Ra%ppq4bPF;Q{2Htrla@D77(5CL6t_$~yh-%<2x(d!P5|~1ahfB%FCl?`yPfgC~HmjXP}^LZ{lUqF?a~T;FL5AG#o8zUQxUB5{p|2 z$D~Pw$-3b&n=AMAgpP-rnTo!<+F}eP?r_I8UU2&41*UHZ2WP;E1QDD&EsaK3K1H(P zwwj{wBqKrPB!eZPumF*D@dE=y}~KW(r^2b5mIN)haDDZ$DV}NaDHw`c*qkSqqq4>v$l5 z{gG}VAH$)ZR5gYX1nh#V#)Cx<77=HCB86)Z+P{5G41~qcm#%T7w^Q5^%WEqQVP*6` zHnQI4_Y5X?H4$>`Rz2vuAImAd<9Itk1;tIOUEoEmv~tD{-BuW=X`%6V`1A6{7`r0R%vv#>r|EX-gr z7@pF@{uNf){Jg0DzrZ9y2%Z{?tu1#l;a4WS96QaBvQ|q(eLO?5Y%H@%_PkDJCwvc6 z#Xx&L3Bah4)5 zxX^WsuVhAB_{u70N(k6|oRfkqSyA3r(%ANqdb?Lf`93JWAeu+wC348^==3K+maaL; ztD>-b5ko${RbK1dB*W8Q+bBE#PoQU*=Bb0pDEyaiwh)+fKlQx<$E;30z%gdsBb192 z?&#Z&mE$>C54kDOC|yU;_@Sl1z?HYHHgtK~md>XktA0(9YrB>>A`o(G)p)9rlASOf zqWYwpFj=JEUYC9(p)AAG2V$97z873B-dC?wIqJ;FraBJh^;y`>sT1>&>Vjb*fWFW- z@qerI{67(tIswEPp|TOijxNQLt8>b?H0tARs{8CVkO}DxEqg z=W3a3ZQ6bK)e_qmL9t2-++qTKpYFIE-R=jVxsQ&fF{dHMopETbd)2+wPTxR%zs@a@ zm^zH=e@2mseuuSWo|byG9k9M=N|xPeOtz&*i^Atj=T%3Dy@~TT{^0i>fz(i*AI5!i z`PpFgMYA@JF1z(~x~P@4h>sY8ToEfcMwp?*^&}{J+J)_gY=N}8JG_c0*zP_}YA4VH zkrd@j6vl{Wh%GPv3SJY*s1n;2)Jk*3P6rI<1|p@Wz)dU#&_bj;Vqg^&xEouH;qc1HpNdXiGA}BnX7Axh;$>INU zo1)gz|KZVDXF=Y&L)|a&lyMOU-KK_mcRZxmix%AJVmu7M_1L$LFKZ$$m5|(<4C= zl9_pHh>L}nx}4NscAHOF$1r|F1+umZKDlos>G5)}4BeY4S`oxc3wD!l#?Z=8kq!PY z43*RQTYG|}&mmm??TO@xWsinoWtC~ovNy|j*XIv-3PobE?#-TO*;7CI_;H7P=RY{s z%1+AcZ^I1t2lIuj$86IXs_$f#QGF>ASX@o$8lKK=a6vwSoWhMNmX%!VVp|hjSEEM4GGdS-?b6pF>Min>m|?XK%&tyb7=GO6tz;|3_sfRyHe8aqk1&1e_;wvk@_< zsXUxvkV^0A^h(_B>7qUD$!Jf3KMB_z<8@W9bq$t!R(E~7vQnk#{%A%eM#*})56|VY zm$^LlYEjWw_L`c)0>SKcoSt}^t2!}=GUcIrpw8vXPv}B(w(6X3z(uV4iRf$_gn6}6 zB*#A+Z9u0IrX}xO2dPlCcK2U!Pm%Y|Sh~0=<)T_l)p={x61CIK6KE7HXrJ^WOGXvS zNY#}FR!V(qdEC@q(F<6}8Twv`9*>@2YAR{Y5Y63XA8I00Uw*d)8y8`$nlrrw#}F-? zH_G-@M_EzVlebe>9$5|9#66hZA4lKzJHr0Q9k04b3Tvrp zqz_c{jl#Us@qa7iJbWXrsSV5q8f;5T?R-@BFeWb2WZd*_AHgz2J*>~fWivJs{iFO2 z_L3=2S*u2Z8UN7o7nWZrHa{xTGulRci3ep9NzOxM|N?5^uG*5a^<%n>&Qfd zT<#M&PM~*Fb-oYwv}Sj_c86m*b7Y+4?~A9G9F+IwEtD>T6w!1YkU9MDzr;XnB>=C) zV{D6RT0O*SDkDVMMRks^u#LUSN4TZDsqF8^-j0iwKJV3P>FuSw^gF5WA0@N2&26AA zPjPK;QF>ReIHbenl`buL6$U|jIL0LHw#!=G6?pBQ`gb_|{T`3MNVf0Rb;Xmh|6`-f z-}p@aFR}JEfOo5IS2)Scs)KCINv##U|7$B~oV&cKXz~WdB#X zqK*-Cp}d5`qtI%+tF)Fx6 z@PmS}CMVN;yPH5j-T+Bb>Z%$0b~&>#y4VHL^`>MRJ1MLYSW2Fe`blh~GCyDMMDx8f zKowA|0^v&TH|19ALr0bjkG);uC{c`lgGw2#QDTzv}d|sE@ttg z#E+y_ezciKa_O_yY|{EQ)Kmqmy@Ad&@mJ~7LaJU`4m|EPpbR4`Epw&_UuAqN>L?sX zcAojjI`y9%;+dbNOJc|1k||+3%JAdwqEQu!d#Twq_yWgW!c-=A(4g^ zLpRW&vXvX^J3-+V9VVBi30|qU9l&j$l=H;5QI>Yg_ji40ZtCSIvS-vt1pU!^xHZ?fg zJEwj&IS#GmiR*WHKD(fm|C3=v(G|C&o2AE)=vutKo&Ualv?;6A&T=A0d-vppTKA16 zzsVFhZe+c|4Q7J*mNiRv60nT%RjS|QY~#eBR+6b~W4;JrS6&o9vZX;TG$iE_=yj-WJJ2r; zR6W#xB&fWlN>{e9pelBAtnWA5tKvi_EsblFu~PlK+u+dE^Vsa!$V<06-L5pewukzr zLUKGmgEbh&d9eI-QSGM9*_f)?0a{ir(duH?ugI6V7LThH3H^zX#jEpSq5_%BW^Tfc z#c+E-&f$;!=}++nelr`})}bQlRSFkEftPnI^W{MgSrX1mPv_tFdDe=I>ZM-D_)(wd z9e?`Ss@3yNIcIO@e6ZLF)El`oQBqBKXyqR_(v@S;gdVL8WS{-Ek*@Yu;yJ|X*F)W) z_P*cN{hNO`iV3S|j+{qo z@MVUXB1n3!8%vm?TMIOJ*WYpMDg>pvl8-~YhO?oyXh0Gj%L()7Qk8be0e`>bs$bNj zZI4)Q^gCI{Sa!^`Y|*e$G{G`pXmNr|J!HHz05|2OqHG- zm}qAzxP&hi6k51+mn$8I2j3+3OU(oF^B1d#$4Mh{=GYrj!}s$TwgWR10Xm$ZT-F0V zwS`k9A1L_hCo^MGwER1*fGHA@a&+R0K6En_1c|$>kJst=*XFb&rF^slIBPEgL;9tX z;$yq+Aj%v;rH1}2j_#H|u341IEH5qO8RI|-+_Ly1sWB>WG=)f1&vmJikk#0U_4s_( zbQApa$N1h3avPc;(yEU=+Sl@wYeQIuWYkNGiz||VYXxD&jmMS7{peVIQ`s~TWr?l# zU>3fWJSb#_I6%V+j)#Vfx;HB|rR@&Cw?n9yp|F%f2-g(r9wPNL6BU$Pm-l~vtELmA zf21#KMrWe3j$B;{463hz6v>VURX3HkmikcIv?jFI)DJv_379my)MD7y7k49C{}PX# z)Rl));o2JF;vchWXp%pd4^l*{+z-x4H9W6ZB0uTjqV3M8r^!C zKERY^LLBb~9hi6G_s@T$wvTL(fd+;>HB=}XZGp$IPJU*CLOQPaGb%_=#IzU#O3W!~ z7cur1LalPhAzWmsCHs=rKV4)1>b~L*dex2^zhi&4n=+hrVlJ=^-_Ea!qRM3}U_UYw zDd)|-9992{6sPbw#7PmUb@8kOkdu#X;idu{!e1Wo(eza~Tm6A=gB|&~wyqLBKl`~m z`gAhgm!+k}KRD7`L5(LAbE7$i?v@}$XHR7@7_n&=c2y6lZ#8nUcBkTDM?+UiOipyT z%nU;a(Yv*;?l^+t+BGd$%&L1)jCQjGW&O@@KwA(!rTT?B2mlOqcj1Ss?>>yO7bo3y z$KhYY;iuLBU+B3#&?iMInsELDW-Avq}*XcXI3KduDGI9PLQzYd4H?E26vUoEQiIeRs|M1 z{JnL(3j%->>Ql@or#b6n+Tw0cPUTV)I)>3m+H^-Rl_nIfv^eC;eH zrJHo$3AhFZCXL!UPRU!bD=`|y`#JRPE@okJQ51M#zPZapX!cpezcgs(s%oyLi>F0s zimu$)FhX{+EA*nM&idFdbnDOKypgnF99EZr#{8Ksteu+-t1=&I9pYeo##gY98D^jy ziVx7QmAl^tFaMHO#Qib-we*I}Ne8ewo_;ztv*x%U^r|3JQy+;XI+DTq5qNnr#439r z2G~+p&1M?Ioi3Iz`3JIzGy;6Hs&zskGE;0pC3X2#|639u?9zD}Dun&gTI+`Ftm9bK zCr;Tz;h0&FWTWr5Sc6?QfO0N%xzxfn!{o{d^F7rV3kgA`s5e!AvR$^T+_CJmU4@8q zN;V3(-5485qMPyoSFk`g{5LamB^g2!eAo*A%BlReadyGN z?p2GTxo8hoWex44q`ggr%ksE~+;MHjm?UTNT)7yA#$u&FRxT)frl>9TIilSD>3{$u zcCoVd**su`XuoZ>_OH|)D=${{6csAuEraQhJ`6~vs%ynoJOzgwhE+n&8U%(-Y3sX_ z*~CC>|1_jgl@WyrsnbQFkE^M}dMKH9;gS)KOHHor{jM#EdX4J=YBjD{-w1MMMz|3% z$OK$Ht0yeCocb)tp$XChGfU%a=*2>4kBN$C)+*&o9`$jgft{p`-2!6J8p$To9nZ>0 zQ$L9^xW0x3Vz!A1DyRf^D=h8){8t+uF4oIsb^dakdn)n#hZFZ&|6j9;#aD39bJ_V@ zPa&F8=JGjobR8=7-Ykl~q8Tg7w$f~jrXBHt= z7emfpnZE!bD9wk<3WHI`Y?HbgBx11xNvm(PzUugPAUi${FBOzKSua;M(g6Fau`E3=ePfeCP*`#LTbC*FOgb*y2Np+V{*-NzNkLR+#V+8p%2&4DkM4pN0b+edxjV$+LDteX9k^Cd<*w9w@Ixn94XV2{~)$5#9Fnrw%m_= zdfBy6scEoIOLF+`4GkG33Fv#>Ku%EqZiuq_e)3BHvY4#7v^Rvf_0O0T+O6e=vw2pQh6RG?<`mo- z3%`tlN>WS`lR^{7e)YA>Yv0bon-^^0D#rp>`C{!9!jG(LC<&~{2&cml^~^2YVilRk zeB;?0>(U35u}h_qJp->0DSJ*V6C+i`f`Kuu%6^yPH7_$5aQk?JZ9QDFHVkkop zeNg$>8F!rY@ffGk`g|+)-s#|NI`J*MRjd~iYn3j#J|7DctfB%W?tzrDuRZlh$bXl- znvR>SRpXKhHXeZ+Zf4_@+}!?I6QJ?RMHQm_fv_Iny?41;d235uQzFVu-ZoR{7^DWrf+5$L_k)EX+WLdMag&xV;Dym3YVR6a-F4tB3Mn za2*~h3rh~CTqZ-;v^r0Vjc71ZYtHy+WhqlcF1In!B}Zd!db~`uRLrW$^0e>+LeMis zI9mSSW~`>?tYnD2Ssv=SUNWVf^p@PHm07c#4m%vbEp;*rD&tafs!bdy)z2PdkY`+s zMnFh8c(8GP8^u61X0;?a5@{B;DlYYm8q)Yn{zD5X=45ECj${#$0Fk-Txs*yq;fR0c zHTH9dn z!p)fkbL0P(n18b=eMqIxMx$)wae};RS7#=rM5aJ&e># zuB==`JBrICRskM*i6dV4!P7nahWK$=H`{@awlZHGOFL2qKo$ZT_hNr;>S=Zsmigp! zEsy1^`{IsiDH*?&xC8F-OgveZRhQpXsREzvG)%`8EuPm~!~B-loYTngmI1!Xo`+s3 z`4E?3xt$FC^oTG?#@aha;XE=kYw;b%VR=KM!l&k3#%8omCmK7z*h7ZMtmZYmPFVn+ zP*f5Lkt+6?rd`!q9qhb6HU_JHa8j}u_9Sh| z7fM-45ix556zF?KNAa-BM$V zOi*85AccqDtEi#?V=M&ekvc~?}Wd3eyY{=Q#!OFi`ipMVgeOu>Y#Q1R(Zov;$ zqvR>=;Bu(tc=WCAEYxmVEnLkaScje-jVCtd4K?m-974pAQ1W|PqvUAT?lTVu5KHUL zaw|Bs)=_Z=E6w?rw^}~-7>d?2W_Dxebh;{71_8PEhP7r|$NDYa9Q*xx+DNS~XZ7jZ zZcmS?3Sbe5(eMxXcW#b~L6|cq$IX0TtT9&)M1)%`bY8nbnW0Qdy;ZP>eWzbyQFB z5OUj9zMJZ)Idytkn<&Ucu|7UarQdl>1ia$DD(9>*zQCsl_vM&1y zChosos}0AAfo5#B-+m|=qy2-f^xjqR`Wl8d^+TgRt;?QCVc~*^>U)Po-g_0X+3npI zVi!|DTh?WZ!-Aa&Tsz8wZ4jTtP#6}}L#QHpt87p95Ebi+F}`q9LIW~|c!ej(#l$p> z>_TN|6ig7E)<)ZxCVsqXUcqAJhCXA@5c|}i|MmKY6y9~t-nEp?shU<0r<};6sP=|T;^wx1=CL!@ zT&V85S5K$IEU4t-e%B^wEZoul;ZkpudK|0toSRAxzF~Rjvb>i#SWEcL@Ifs!AYuIx z-^iy?erWtW{QVVw{`y5Nv;Ur9e(l~%w*vN|h%lS2x!UY^`Fs*mJl|+)aMGxYX}Aud zJ&#GrAO#3vpyy!z{N=jB37b>}@DdJF+nnRnpDyZ|md8pZ$8b?|)(M_sPjt+;zNe!q z>g!lrAR`1OC|F4E=t>%(eW{uHcuE6i0@*G7Kw6z`_hwaosT({9)yfFSi0jMKf8zQY z@kklZp%%%myh$ zw!|G5I(n*UK5g?0HwwRlpf^+qPpdte_@*MgbB8xDk12(YTXw%D1U$}2+Y&z4JkWqLn?RITUD`eVnIfPqq<~&LgrNYP4_}*RwSS}0!tniOziq0ku#!DRfrlJ7>Y=jno;quh?Yn>~vImu-3x$Id4ak-;N znVN~&(tNWY0r#rI8blmCzA%(bBjSovz=H+<^A#_2%%SqF9>V40Gg56xXX^OOzaR-4 z6dFJ3-{?8#R?Pv*c7{OYp}VFFCrgAdC7U+8^z*w}H|>fz!Vl6f@FVWtXjUPPLUI7hKY#bmg2XJ?C!=*d zj%`dcu2>Xs7DF-av&9goMv(shF0K&*gfZZ>74J&1eQLEZwmkN4vYqlr;85Zg?bjp@ z(6kW00+=yQsJn23DU!+!VQjmrKk7*xzYhQTid~OJ<5l9qp<=yyO~FnJSFB^((pu(e z7(HdeAHokWq%Jutp`}^tq_cM6ewxI8zTz=nBOT$v^1+npBD6Zt)QuIpz-MFAO5xNh zy%*Ik7-rGp6$8O&`Kz&95|85j`VsoqU-9+*qwvR{zZ8t%=%#q>a|`U-N}wXc+Rk(J zMGmG7nds3ngeI^Y#GpkyI?HH?x<9&u6?@vP+n@j z2l`QWXs)G+2@xOpRZv*-Z=k7k9k5MekW_h|!+ealIi4qNnE58WndNviwM9*f5-r3( zgu#6YV{;DkDPHGz9xSj$6 z^jvFT{NOnk$&%&X&5yCEC(#hwC(+(`gB07Rr#;Ti?1oD1K%p}(xhTZ%~e_>%r+G9kjLTY}j zrsS=~#-w?&B_$6vd(2_a*Vs-aNPKkP|ckL5sJT^%UtnNa3i|^EDNhJ%! z7gH6Vmw(eUVbea|y}{x7pZLFooNVLs0bh%O|E-4S z|1m*er`bNG2-)-+Kfn^Cezvd&RjU6_d>1j5JBE?6^Ea*ayP}|H>&bPhPchy?yw)7c zzN(LhTYVJ%#pM#FW4vAC2@al=FX$xdKSZK+V~MkwZKZ3VGlagec#pi zdH8A4qMT`#oogKDDn0)ver@lu?hdKYqIvwXW(X(tOmPvtmHdvUn1^DLq^_;=k`&YL zR~}}HG)cePfd)S1q_znN$Pu=(WW;Z=D1<37b`aWTa=9?tH7=Epue`eJ@59-piWb>% zUmkzSj@=clRRhY=ANmbitxr*n)FlrspmCleMpn7Ho zA`%plyjwa4NH^Yb*@DYxzdvuvv3H3ej*6M7zYSgko1gy@z%jzgxH_O9VWCS%3!cqK ze2L*b;me@`n4@D+Gg4hBA0+G>6y6vE5Qz9K=GY}~3Ji_Z()4t%R?r*uZ%2G+Oje>3s6)t^0FFvx;{~&q!(fYiBPU}xJ7o~{6 za+1+JOK8X{Aeo}?w4TD3S$n-EE20aNpwSC`RlQv92+lCHXWZIls3bl5GpcUUA4v}~ z>RM$0{*^uti!k{h7W7~8@cTQcbDc$fZsTH#Ob{`n8ce~^kd^u!V#NV2i@Qx+j?fgyH8)@gd=Q>Z((R2e1b(Hykc$z?U7xW{DwHF z)koj3pxEXCd|jO=8#zYQlgO?$Gmp!<0l$Q&bvID%bM6krb}x1K6))3BRz+GeH}0S7 zpEsYAt}=9aW{uMMRe_X5TL9g3At-LT@R@fTL`JIWb-C!J8G~@uBz3bkacJUK`r9_u!?W0s3I^M4 zR8qE|6E39gys|%rUd??b#p5v?%^2Uc{Tsz1T-HBWVOmCvx80GU^LN)0O5@~QtUZ#| z9tXukq;-}x#>8mZtaM77!{SP04(7`#;%Lt`xXDc&A|*&- zinOyA$Xp-duNu)M8?nDeOl9WdGcXW??bf1wD|aps!)!^HN~ENV>FQP;V#u?&l}Nyj zvFz!$Uaa-87*#!;3Av7{Rgxe?Ps^`Yyn*mjUKbeRLef*$&&KWN)U~5)4O?kKPaIbF zQC|br3?|Fwk>DVq)P-t*7drQXwM;bnUfbv%sCy+li{`xFhu{P>;dzeu5&$D7bO`a#hZJ1qHw92_qN*v^S5^%R^4|u z7>I;oOMzsSh9-2my2B(|&i%to1(%0BarhI5oG~ zk(Kg+U4bBszb;ZCebn3p7AdycRa^NMDUU3Qm*TH!0yQvGAG(^RL4{)zHq{<{4uXpp z10rCycn{|YQa~SC#a^gbk%DfFiPqk*L$Xv%QyNIPNf8KdY;G?n-)+^u3Su*}(Qaf? zpb(yTn^s=z$an-l($J7UAb0`VCAl2W?TohcVhs*eVP~mSs<6W}PF_Igko?H;g*bj! zM%P;9w6*i@mL`m0%pfh9qgzpw+19XG6$0RDVprWW(@UzI5U$GmLB;>-&^NR62dcJw`&+VDI%~t~Ec63U)Rr8|;Qgiq{R6Y+b zojGZ<)F{}+Mr8ntiyS;DDmp{87=@I8fTtJ>$*tb@cga8S>JzAfv=&lYJcG!<;%BRh z5-N(}DmP-YHti7OjW#3}t78E`*^z;TTbj077mJ+h^M%y(i5v0objj)6u6)x2*mB^B zuo^k$R*m3?n_E#yJ`^mH)I+QcILB&!{?jKIk}M_$#6Q{1M-hJvO8uLfRGohKfb*zs zIb~A9C5Dq9lpJb58~M32 zU2n0yVY|)Une?ynFAGiJGSug6FyAlpJd@f$sIFeeyLZ8110d(3Ik(-d=-ZDR=xD%s zS8SRjM}UY;Aaj=R4rEyUzqoVUwukF}h;HpPn7dWDIq1bgX*aXnT!+j_n@Gx;vSW0d zu>|vhGR5AJe;eM`l{}xEQWwtUNY5!F9mQ}D0l?!vpBN(b%@n$ zWz&Y1^771}ajPpkLxMq>%~FQYJOT;bGoN7%Y;)Lrs7hCvpj}GC*>Pl(rO1%~f80_A z&tl3Ol80>=mkG;=hR3U3pRr7Y0dK_0X7IW}58%ES1Jah;==LjVtMI6!MtR|xK1K&~ zmw+sB365#Kx%x|zm?nO$3^?o(*4Lln6UcSI+>5vnDXinbo+tzS+lY%3hU^CoWGG31 zi<&o){5Uz&(ICtA>i&e@bUmIzis>zlD%2D2u2m4&WlXjOBkHc5n-qsL z6I-V9k4Z!VG?##TmM^Qo-ZG?gL+?ukn1QTz?;ICXO{UOL17xTgfvb-E*a=gPnfc}8 zGy@^2KvNmd1kUa`9hTQ#zt$jOU;Ukg5qLIfL z488}_E(+ zq8jykwG>uYoe!3Vt96)aHJ4r7h7xzJ3RogVW!lGI#=(;Of{p=0P8=DNmZB1?*Avc6 z6e?XjLjSCZ%?@(b3i6m-lZxT2Vrf$P$9RTrn{D%2>n!7|UA;B`A0GI-_JBflTd4_n zuag4bOzQtiZZ9WO*u_JDLM;kLA`wc#50x;H#~p^Y3FAl`3L4mI%b~m~ z`Pc#b>D>ylqR*A6+JnvL_Zf+;_r+fP)aZkIN=b3JBuqQ;pwL`eLGbFc3akS`YJ)Jn zI8qy|dl?UdQk z)nJpFd({Hzt6*iZH|(ALF7y`uOoqpl-$9Mkspl=UUaj=&@qk`hx8a}Ij$rpdw5u40 z6+ThUjf>MuSTmzs;VIk8&w>6+tt0QGpT&k}t_A+eo(h!WFX?lScH z&qGahh4{`2u3!v)4OTuzV3Ek3k#;Qnag{kR}@W*c$?kyD`fNC=_fXy`EAuE2;D2;!Z~GG~aR) zOOVd)WTe{Y!CY4bSI35x7=weQQl|4R4OCT7v6;vM$eW2vo*m)1EeA#j-DCjcG_~1t z4fqz{Eikg)zb|$#yepr7pJ}T!VSgF|e1zvC9i{GN-M5@dSg4uWIY^{Mz$^UP*-)*9 zCU(*8BudxtSgowBE!4}CD)%&FQ}1Vf`%;TZlW}>AxKZx7s9Q1dW^hUob?23vJ`Z7j z*{stiC;wH)SvWnm-Rmb3};8^zquiIMBo=@@YS0)cm=mskTn{ zHSSF4rXQRwo^51^ z&fxdoxj_vge&d4v_20_(G=lRsDAG^^yxbi`k*8FL4~saUNE%^L4W5ls*P_Vjae% z^}st3Uq-y6>=BJ~ae4hGeyya$mlD#f!JRd^nkt;Z6?qqHHn;jY}Caqn_+3LpeBfh4=oIz(T*93zlrKq_S|9@A5O&NP(Vg5#7q;aI7 zo!9NGMaB(IJ@{6S!&&RkNt*=353A08{Pt)3_3>KR^Di>Dqgsc0Qn&jJH65a%PV7Ct&u08x6zbKYB_In#PPH2f1fxiJceQ`87j`2n2mk_X(vP!b~ z_=w8;PXNh8Q_Y$5h_iIeSvqDz?G)Zx`|mY=XqIdF?Qfxh?5Dvu$C{+cX_tQ6O%E6= z+0l5rt{{22J(x(Ng}7rg^s(;g46uL9?dt%w_`&x9a>CuA3{7a5ScJDlhyc`s6*FdG z@-eN>Zc>2}R$<|e9yngkH{r(+@uUbf4eR@y#5U)_dTAjmq({3SefRNz^o&J{>nZG;Jx8+1F{h~~ zf27zje(FgT^YOj5U>q~yw$g<$K4vprI*W%*6rV1lFt_A@R`c=FP8B`d?XqG3g(`)V zPu&p3inYlS(u%L%z7BQMHzw-m0ZF=D;H;3?!1e(E5#I9v-+QesGhiiTbC`>LAsmyPLgXN~h2lMZ+RtmJq~E>Jn?4UeOEn$#%FDud z(sQc2r+z%}<75Yeb-=Sz(;yxcDwVT?T0DiO8!6XcSV9>-Vn|vEz*Fw)2w`+uQwtGg zsw^7o!1>L}x}eB4nI5qz(#mXYa{%6?bCJR@1c8UJJEGz#oQQu;vCpbl(Jk`9bB{r1 zadYgYImF4FI7*C>uH&aRA9>@1RpUAqDrgAJk})|M3d6ZK2i_y`M8qR@P4t6&C4)Gq z^Q~))+obZbKf5}86>~RMUAXEx&QImVO84w7gT@WFB-DztTVk^c;X>T23d8$|7C?R8 ztvx=)=8l5gX5!MB9p`nH4Et>3+XfEKb`U z69uzP|42H2T2b|hAZy#Uq1*R==lSGQEQcUlM&sw9nFi@-UGvn&Lp_L*-<`jrr>;q4 zifz@OhH9Ycsd-MCn{WilyXb^s_FpB>R6_l_eI4H7`*Prr!MSj`$l-S@kj~sMWQbdr zH_kQ`Jf-M1cFj-a2Tta`=9cPJg})OWU{Q>JH7Qiel{(7Xq1LJWr7c{dX(p%Y-otDQ z^%aFewsAs0QFlMKeHK&K$(jJKtnN%-C>rlnP1tQc_z#aT4+kl4#SFJIn&VL7Kx(zQ zm>E|(nIY_oULe7gFuV9dUAV?PNSoRV#xj}AZJk?TYT~C_r1T%jIT4OFUCVY;3IdH> z@X;9JI+`=p-z#HN7f;eJt=!^$ZoKHr?h#@-Fz)c6FFgr1y6?vV4Eco)6?_NN3S2`a zmCAGA-@2R0kwBT$9n?{6X>G33EupOlmG05G#DzxjgrN6Y5Y`%e+JoxKUXOP?2ZN4g zGnt$Q%)1U45m}mU-OwSM87Os%BE1!z7c3u9Yt^MXXcG3>5@UAILm-GKt2Rf$qBnZ}vR)4;eisM4o zi&BeIom4$Kxt+VGVOZi7=6dw5`#8$pq zGVh`i47kk^;vv$U^nqFqj7Sk1LrF9BsYr@pXIiqZSFlp#SE+jxxcYZ`MU@H!nF;-jz#!3~$m zL#eaa`C7YEAFA7>i?sRD8teP6cxidE+CbYyIL**qPI1oL;@hCxXZ3PiNvlh>W`mHi zwq|fAIQA6>N&Cx{x7F_$GD8r1*4p=hBEkWz%_!C&2m2!WRtk+>g~9tTTO0W7^&zN~ zS=bEH@u9fWybI->C*k30JQ1B-zQ-pg#6w%6rc$2?^V_o)J%g3KV|!dwyadc1npege!|$Pe#~3y3lv~#j8hhny690VdHw% z>3M1|PqjJY#=&fuZx7!zdwZ<%mAQum9w6Vb)|YP4x@$!UGKq-GQAgLAbG&YzF6M@q z&8C5L+xmb2h&;Ro-+#Ob)HB+xh+ki@D4IO2+VJ#?Cnv!GXcU>1B;Hx*9fZE??hVJ~N>=W#p?wNhoC+IroI zUB_z8#$iKw*5wQi;~}il4!}x9*D3;n6sOO_C5!dK%hbU>Uuhib*+$Deg~K;j_{ZC8n*R%P-)&NwHHpFUSRFl35O32QzVtM-b)7N0${S*iv0TNUgB8W?AsEk6Pm%tQ2$dPY)xS<5Y~1Ro9d8 z>p)n6;c3gMB2yVVAQ1WVstQqssv!&>JHlC=10EiOT{*zst)+vzAGBX;{&se9lxMmR zvjArv1aGcihnH{*Z#F1uoYa7kOucRY0uyi3gFu{}S1|1hqsdLCSM)=1w+9g!6vsbt zM1*p_sK<|^ywlDM#Lt->jpKlNhlf;pL`%wuGIMfX$+Wb`_~o#~go;OEN78mtKN<^` zfgVM03g)ECSh-1e|6PB%Ai(V(l(j`|SWOC?2`BPJksW+)Am6uBn_lw4Nq3>P5@H_F zhhl$;Fp&4^@5Ka2&&a%?m*_h#qG5TFlUh=haQSCXf{*hOZ}_~k%DM~2olnZbVDo6Q zfM>21K1EiF&86g5nL7H1p`UjCJpA;y+;{sk4GJ}fA4AiaGpsA-NqSYjsnByn(+t_SHH`;8?cXsI zM7UuCt<2e2kl40FKD6yQPU+M<)gNYJ;(YR4{)NN;|42Jq?#Pj4Ym;6UJFNVtE*LOi zKm!8<1A_tu1Ox;G5++C>ARr(hP@sT;L4yVj2An_ju4hMNDpe)-%vrC_z4o~6s**Az z<8#N3*LWow^C>O&<>E&5Vzn}Di8gRds-m13x&Sd|==#+wDTlZwL!zjPF3(3Z#@g^V zvjC+JXaQ)3_5lEKsI=a4Ep3{4{k&&uY;KU#T265B@~i4-$|F-+Gk=_mZ0t1$Ngu0c zNN0E#x%4*>R8o|IBLcA;o9X#<5W_b24?IwN5$)P$*vY; zZ0)W)-mJi7(?XSu+VvR2Fk4|*wv06Ac`J#rg8&iwuE@b*>ZP_Ub?U{c@Su{EHdR7* zDC&)$xoPCXGb;wg7T0 zvV*0l%Dw=c$4q0Ltq;OwpZE6VJ&5hda^%U0zDm=Ejo2;vs}Ep;(PMM7KLktgy;`jt zt6+d_E2^hXlB?oPl3cH(cRU>$dJ3#q_xRICA29&QyAM_~uPr5M^=iv@ih<4Jsdblm zqv`CDdyVntNC0Wo-`qL=fLJ?gahCYJc}0Nb?(=~(?iW)k6;D3De{`Ym!t*7Xc+8Y3 zie`>`iq!kr%B>xR+X_-Gh4@2Ckim=vFMWY!?H zgUujjTYP24ye32u$JA<2Ybu2OuBMqvm%Sc^O*nt}bI{7@G!5NQK1%YL*~pcU_s5+Q zqo-0SCgp9)QT>&pOQqkyyI);Uiv> zR+R=H`?%M-D?^_aUx%Ew3`^`sagQnC#+)KD?UgHvt1}o|S2v4rbe?!mD#=UIHwJ%w zstBYWgO%0YvM6dz#6jBw@>I9k7HFoGXPiPmtRs^Z#+s-#sc7!TpXH+jc`s6PpYb4Q z5iC9DRTfh4q?K9RwWQg~OPaapyqfwpd_?%lI);MT0RWevGY2SV=b(9JmRq3%)wr7w zr2*!cY~qVWs!e%r<#Q1)IG%X&>F8@aHwkv@NQ3#F)r;+Qk~z z%%9wD;l+h(mu{jrOmC9{Y+C~l2iy~O>&qV-x_46D;x(yb@wZo{%69DGE)zhVC+6!?9{@y2BRycwCJ}2wvkzt%U z!JWplVWnNKX2*UUx~VFSP1&EYPC4)T5wK5NG33 zRvOmDg9IQ+HY0`Who@J|A%09&V`xv9)~cUbEOCs7V$%@Md%jKF{O2YR;%F)=apHw z=kWBgoMKzbrtO9$_?Se6dEgg+EKI$zCDD8acTkJ9%6v-MXV1^mpqIy&MdJk<#0E}zvz!l&W#&;h%bKt3T17e2qkcZw%7LWg=C!eA z8YIhGanvTNb4RIZdTt()+j)vNgt6?fb-nRM*21L1tLA*$!QeN%{bMExm9Pb9mZwA1 z^N4O84tH-ViLkr2*1?96Rb!V(WDSQfT{#QBhGX9|Y8t*9+gK+09eMVvBOP*Ju**Mn zagB-Lnf|t#3>XGIt~}f9t=&Ig(at%w$O1w9d&k18i2_ET*;=jC@N0#6p0O05qkGI;ya6Dkj8*k_AwNO19R@io zBql(BtL;!Xe*MKv9-(R^$B4%u=?gV1iP?H~`Nn%*qKE+u%-LxdT$UxW2eNXYVTLn9mkY&}2n>#L^yo)~D(6D&a-UB%3fm_^+>*=ThBU>w?;Ri_sph9IfV`6mj>a?y%`$LbJ}<$F~y zM(efCt`iK#G}it_6XPfCo2)!%E*59ww$&5578t4Y35rQI)xn_d`LmR`&DP&KuI$m~ zXP<1U;!OXLq6CR(bjP+zKEX;`ovcMQygh|)+gD4_*|>m^1Sn+_Ol^*Mhr);$HIAiB z@66b02}->YF!fwFEQp_ZQ61i!pJA-_0DdC-r33rk8597|d?^I`MUQ%Z(}sle%uLtW<-f%_t1l)bt`Z;v=TOV@6s){VZd!(Y+@?=)}qFA zEQZqcivKk+FtwU|lV#Rn3y#ApM$d#ECVvs31KRSO7C!KIzfU^%%_n?F>u$94)hM=Z z92@33J?4n!xXTT+{mb4R>qXt1cWF!9J#Ou?^V8;^K0Um^x92@a0GCiFO zTLdsn=6SF=Cpq!N!o6x*Mz(6)S#fgG4pTkKS-jXTQ&=gv2{be}OU2E!iTaI7sfu#8 zr@F4o33>w0xRV|A9JNP9oC=0=;;Q(2$$Ic!4CABGs2a)0Q<}y=nmQq2i`{^?j)mj@ zi@n58<4h+SlDo~rWEcVPX-H>d^KyXjucMZ`vT+!d2HM_j)lljtwxa-WX5EIN#4pK9 z$O0gTeuI5d|II>S3Barkaa`A)g!}>Ecs3-o&tCntuXntKQ>#Y2JMEjclHuClbx|5v z%mKu?(veXgYK*=ps2R6p?PefGn*Phqf*32`hxo5b1kZjp3iJp7{?VV0KGU< zhz0Xs{Qtc_9bPc*aWSj{lcXbRJCVP6?m$G2O#(!`eXfU{-*Y2bs^P`?D4wm?#E>`d z!_((=|Fo8nG?XZ3MxKvG)V{{&=;`9kR09M;KO;bOa6W}Js%nA0p3n+|da zUQTw+VxgM7LER@|!xsUCc=F`+_nrroFSJrJbi;3{H{Oc-6KhGxX|OgpTTl%tifQnH zO70x0qIB>CSylgL|7J0ZpN+o#+@D${|Hbuy%4bC@6YJ$Rm2heZdo&s0=#AQ&T(Hn zg(-nFA5`f+35}NPp?YPV%PvO@fV!`q;@E7-wIyldpdu#mTe2?vpE&b{g!w*x(qKNQ zy79&gTB-)t@;RKPcW}w~Akm@AZp9=liYT^5Yf5nduJEn68TrbGZyegv){)znB@+au5R;@<4BBjQpQnHE}9P%tgy7*fyas-~8t2P$=mt5oGR;C4 zixFA>u9%eH+KZOW8)oXetBh&rzw2QB5H93mxZC#`APLHG8Rxtf5c(bEj?zNuVkBhDrWaN6HvVx zgjl;O8iaUoP`qc*J!Pe>`{4rl@ds(qMKh49$as3btFU$9DOnTHIH|nG75nBWoQJSH*I6ungJg?fj+CfTVb|o=p&y1YY37L+}vZu04V6_Nb$Sf5bM0y5KT%zmTUY?BoF*Oyhf2?a4a7=quQlZ%lOSu%<&SLu zO^R1K09l;xEUo;Li^z~MuH~k<7|!PIO^)fA{*Yo!nKlBzacA+}m$@L4FF4!)>_ha0Kzi!NHsupCV7 zyG{{b=uFSaUAQjd2L>yPW8q!o=z-}7LrI!uO|mY9Y;GGR5ZcVzT0J;yu0KzIdhkE! z4X^1(u8^Bd#m_~7EXCP^kndmGnIeA)As2K`0_xa3`oEuSR6QBQr!OXej6>iMx~-rmm-`W zLk=+;3^M!+bxA3ZBTeScTm|Lds`%z!FtEwl@JW`_3=EV-jGW}% zyZ*fZ@HT%NWVv$6A@G-)+ecGi4F{QL`6dzSt-Q*KkseABSR)G0^ge`J93UvUQ&S}w zwyrY3WzH}&hrPb1oqLG5fO1Ss%JNHp)qQG-b(cgiE{A~}$-(?n|T?;q&32dEG1mO&F16Qi>uXWLI8-|%a z4;I%2!zZo3PUl7Zl7xcicyDi)MZXvr=T2?+ij}N}Y18J`pOg72zO3@!ZM&AV;$70O zcG2@{K*`n-SN2K)X55;g^IPRi1rtHgJ5mF#rvZ(+#V5o-490a*U*98Ad0utfj(Qyo zr%g(#psUJMG%nid@_5H))OjHugkCkO6&P6BV?*@w5C(=3=V0*>dC8C_-obdQKY?!GQ zD)Qhsng`u+#f@6jXxY5 zkCO|8FS+3 zYp$2kLu8_kj{LHchF+ziA${}39y2kO(p+>DFN*zDP$t5E0k0Cq8#k;+Knv@6x_QEF zM;q`vf+FpUa#@THf+_Wj_x!N1eCXzBu-JN=Rnr{3kEc^q6~TVlf(~~DDM~f46SAWR z*`7NUV4jMr03ykIs60ox5$TtYiUbm6%HZO8iwP(^RV)Td2}DldL1JdwD5!qMyj^wU z<0@=_#DRgXV*{|#BP22Z-|C`cuVCO?v?EOpP{U3(RV zY3$VVClsXD{(ni?mUj1gxJC;$$EV{OT;B=F|2DdH(vNECJ#U;UET) zj8vhPm_m;~9E3q3fI*J#n&8OZIdY50lG`YG11U;7Jo-;fL&(-uJ)ta6z0|J1xAaeW zul&#@^5vxxqPZTsDSuFlQ4AMYF{ug8I3q01Y<-;BFyaFB;!7m3EuZo^I_M3HyE?RgMqNqp?4F zIlF-eN7a_cXG?f=b>sudva2=TzJGfZ>E6lkBVDtH%SvQA^^;xZ=Zsyme}PJ^gv_MP z$9VU4tS%R4jf=jsXTRHObGS0Qzj^f;0fb&4HGM{A z7ysL&=l_!zDH#TNyoGvOHi6>OG<4sUMdZ1(sGvQSE3=KN{*7*HTZT`R{(-*Wnflb@ zyoTtzH;>@rpc%7jzXjvkI~=2C9!`Ig4{tegwPx@qkYaN@W-ln(Fuu8a)n$@oqFfC}e1|}{= z&e?P{Iw#fL2~MjEkyi@8knf`^0?*dLGFC;O9iQ)q#JPzibhV zmd6iHQY-_+8nq{cDytsA%qpG34;(%izfe2$tgF)wF^ z%1Dc0I?E=OHm-vn+s!GfI9&zOZX-3U~jbn7qpk)Kn;IUne#EalY z?4>oXTcq(Bt5Um;`sfYzkdvmvUv{&qBe6kL)K;y}$b4zmpG80J0(eA+IA?G8#tLSQ z6e^u;` z4Z#4QAn(j4k$}0t3L2NgwS5gzbP7Y0;&krfFi`6-^33c_Y_7{FZz38vSV27sQ2-~? zj30iSbSi3;nf_%A4aU?!bK~=?buwvHNZ@1pXA(s^qYF(%@~|Aua2Y~uHCWU%M_S{+ z0JYy04MR`S(7q16ngaDPIgb-MjR?2#;#;kWa14>uNQH|9Fc!OTUh~Q1ew!`QDO`}t zP%`HzvU6NW*G@Lv9M?6Z6}5(k!bx+E?AN1`UG;lR(s`{~kHu5(i!uGKtSpFx_VJKwmKBU%MtuFqt_~o*#Y#PmRFn}huIr*CZbj1G}!mMI$ zKQ`<0{lC0uZ0PTqCfv~+hR&5weI~5m)q|_LPI77@Zap1`yS41i%$=fb*U;Ox6_G zDX5rm|C&f|#h|WjonlPzg?x}H;*a@k2CH{g5J_@2uVkx6IN!3frRLY>!F@EuC{N2vF zl^r)qqw=Vwzq)$VWD^L&Tf9~!rfb*{$dAu+JB)n>l`jV77`{Qn=j|ay))D1Q&(Lk& zrS8&>ss{HijC!b8m%=UQ@K)nJ^0NaxIE2N;5QFee>rM#iQ}M-Lm1omLQKBJwrNOZS z)8VBCFT=Y}^Hte&X0Z8Jl~AL9od}WW*Bt~D@vOF`QgZrmOy^Z&tk7y%n3p)n5!2yl z&i*;lp=pTYP>PLxLj* zPieb2$gWT%$zRU1NY?Xb1zH_x>kAJI5+Uk@LY*S;lz}8U>0NW}C=d8uc~dAq2w8jz zF}yl&;VIp+jpL`@?lF9m=j^3tb1kmLQ|AfaH6GK*ey+-bDt(h0ntljNM5}}1xkXB( zvYrFjE#)kw6f%%%oGo2}Uv=OWFQc5-G3pyh=P=}-P&BYNbvIa^EBXj)Ermmy11Lfv z%)TwbODVET;=^Dh*|wVkqHX^TENxR>=5nYIih3c6afilYa}TNXk7RRNU?WXxD`0+! z`0TT}+%Gi9M$sHuVdPXOQvsS3hE_>({5ON0llc0Y-3>0$Q8FCDaXV&U2Dg zt<6VOo2&{K8wDR*4*cOs+w z2uY)I9^vgN1yywW0cYbzEKf3o(Qo7dWT-Z>o(|f%pegZMB$`Sw!8x30BI<6oX0Omb zwQ2{PtsT(4=5U@A*4B~u^8BZ=oNId5`rUAEfMV?8vY>gu0zazu00>g(gZ6fpcfjwR ztU&jcZ=w3PZlES#Q4U@2Qf5ZgZ#eKNaAxekbBeE=8!T6$8!YnmPv>SJSqJnlAUuya zuXPnDZx~LZkC=?x6kB8Eyf~J6sG4&I%ArG>b@4L4D+?tf#JnZ-YFsmjt#7XM51^R~ zn}9=n6NL~XH;ZxE#$`EiyF~2jrL1;_qWd$GQ9$2`E}pgkX6r0l(_pPJ>C3j#-B6zJ4Nb<|b7NaZkF%*1S1E%K z0o@Py>IdiLeecH5eW)bbU2%zKwWbkoU7ZHw>lDZZcB5SqM38*cmhpLP%h21ng&jjmixdmXlpqJ8PS`(_nC+aSnFa;N^B(<-%2%+SBA4Gpc39o{W8o~F~Q)EX1t zsHE~Il)X%yW=EEf&HvpLnQ`X?@8=Sh_8dfAY|4C=hLc8DgP@ntbdQ9efnwcksod_= zPt2|1EBpyzpd`;XR?QNzI&&J37jVULit|zHJF7gO%z=}&xrC6eoxm$f-|>{2ySE*E zZ4!R04F)U%HO%<3T0RWdtWkJ#Y|dTXw`G&#RL5_7v9-{9aqjguS(acxXZ;`bhQ@`2 zL2ZZf6_`&@9+29^=IAk+SWCG*e=~gZfALeGOT));_K#ft^KfdJ`>^(LT;mzl&upmB z5n=HSw{iQ)D<;GwdT+Ah+E8vxTZ3g|%zKkH}G1}gmYF>ohh<@WT7e~?}E|NLJM{1}XRXo5A3-@_D>Nj6jk|U~k z6s`7<62aN3(i0pI`3H$>HDmia9P{sgeSu0{8Bsp*O9T%pi9Yv8CigHg?#Y%K( zO*NX{<_&E{#YW7U^jVfX${`&UdTS!r0)4?9yF=YF=?f-z+CMIvHV2<a1(rbMA@o`9@PFx=;k*s^Okv@1R70jzcr6cLWdpc*IY# zG(M-(CcW}s%D9B^=1J2ZwmqtsFzE17lgbZ|ZW!*PwbyZg4{{mHdg8#!lVrD9yj}?b zHF!nwZXY2|Opqrjf9prHbhJ{^ZH5#IBh$3jCvbSGt;wG*!zMjvq_gx7>ShSumIk`7 z&_JS>O1VppitRN@S}y{Uh|Nz8s=-g4gv$F1u0iWEsDTyNz={J#QnF9v`1?zyJ|gYZ zLWbI<#zj{QL$IP4LKhbjI`*HA975)~U?sFv)#q0Gkb36QpGbx5QqRB1E>|Y>qZyw! zWaEth6H%Sg=>j;dx6svVUL}4pTdlw(tn&Mz42sLX>8fJr{6|bL$j}V0w}+NxH_fPT zi6N66�&B^FC+Ql6FNu_`Q#*T2xCrK1Sd+pRe#@~_U(EU%d5o_r`X#yYd+BYzPp zfyX53!x#tLw(-Du>jNZK3lqPF(T?2ph>p#&=G9M(y+6j0JRaN-0BQB*!m&0j51 zIJ5yqD{PIa99-9S#SmAEtOGrgI0&=qlC63%j2eUkMQ{GFF_`j^rZB1D(Ww2Ve-kQA z%#~B$^t5kS4;g<=cEcOCQCE*60agQ`7m}QftmY0yX!JX}eLKe}Vr`L<4?6^*3SPKz z-g#T6s}DV!3IwXQkagWy4pwrCHAD;W&KQ8E*u|MuCe%zr-q5m<5*Bse64gha@vxf` zPzNSk(+GNuQilUy%&9Cp=_+lOaoW}?GhANS zsHYT}Yhd}(pQrcru9rpm0^ia)xj8-)7Z#=}ey}ifAiqSM(-U=6Uw4iodt#Suv#pCa z$LaJ4i0wd-nH;_rO&I4dOSVh!<<@mKgd|6P2S5s>0^4KxDRNb^F>_B*#{`wKpSPNvyMwSaY2RDh(=39~|IQC9q# zvw(vvz2=tweZ21E-NE9735x@0hOJ6>XF2lVj)wIs4)pJKD--K1-5Afd+AlfhQnQpY&qYABVHV~6+kj%xyN z&N(g*|BSJ_}5Ky9XOYPNGS;kTNbZY|)lnE|*(f&I5_wU)LS*r*6i4ktUW0`*o?X$D zA^rkh{o_9&dNLiPiSmyb2HT|px_P!kWC;j)q3bAKMVSI|p^ zrHj}dCPpb)ZGTv#%%Cy>gSNOnyF8-7w{U>CV45-&XjD3W+ zX4@5Uby)Ex^E}jXi%vSZ^=?1d*5Qbp{ytRn^V3L?mc23csVt(wWsOrK<&T-OKdP&l zwwe-+U|EIbQ=%3mH)&=@`_hpKeS^6e;pI%_A|^`#`U}YXsE{$lY3kHsOPI=1EVi2G zOTDPhkcx0yfrZbd3=$(iN$#mrGAW9ASa;Q@&@T>Rv}2>odPQlWxg-!5`GmD1Ev->M z*ZH))o3V#={o8u$zs|`VwDKCEf_WlC`=tCoH&SIU%Z{qgZNpDyC6%oP!mTJWpfHk7 z{-6Nsf1rAy<{V->WbS78EVN#B*&->dWm7fA0_76dFmzUfGB3vLmSh>E*c2n4BZY|K z_VYvd#@Fn#T7rEmc7wh7jG=1&yrp=?tH8a``6vLEmF5mMXx8}!h?Ohw-FqLdc~GX( zvx>E2e>FqfblqtU=cn-P{www0s_pD5D)cTKbBk-2_|vkhR0uou4%2*@DcsjUs?x%E_kG zL!z>nqgvMZ5`RS-{rC4dGOeRZuse&9I1oZR&LO4u7XE7#CpywaV>|d5EAn+_w4}>q zO&$W>WriuIt%4`8JvnM3V-&WYKWeXBrc8AIDrY9kjc^J)2S|~V>VDS{NJ$MSh}Wtx zC8JMS)8s)?h%`fpxo5j$q@LLB*zHp$PHp%4dnO^&e(aU7n}|2s!D9z^m+2?h;T(0l9lC;OG7ap6?*-qm!e! z#8l;5So@OCPaFI`#Bf!&5gn!KR!q0->QjZxS%}<}U@$(19N&SN0Mx5rhj}XWSsU0w zI*m$iyyLniY2dIZ1DMc*vHctF?*?7n|HR|b3n-h_mv+vybxMx{{N=quIH-21dc25` zpQx87Ro;&&yrZHs*2|024fxYSIp7r}%Mon6hNd7xNFyjP{e-ByW%b~JUl)rH_ zG^Yj+wUB4jh{eJtHB*=TPLQ05Ih zYf?u+>uPDCf=sTGP7JL#V^RO37!D5-NXQ5^bD;^8bOZ9xlfBy#WSgq3GDuSwKSe>n zcH+WEL)1=!atIZXn+6E!QDHZ{qbij5l1@gKT1LN1F=0cjCiQj>1ztK&6=sZ7IImag zhy`U`U2zL!WD=)Rv^=j!j#9IlWxr+3dZx+RYL~Jh|CQy;ACm zT!;H$`i^(&UNoNfBf6U*1S@zc&E<;M#yGw3d;hK@a+{92B&C;m?}0`~j>0sbW|}_D zNKf=datBc*H-BJ}%aRTop5lD!$5DxeVB?toyv6q)hgF_+tx52Jla1^oU!m#m1C8~H zo#|E4K*V7quY*4Hs02Yqsx>EnDs3!N9p67x9SF@glANsCh^`M(nDc0*a|BoEBKGHw z$HM(AqkZa4WyUz%tP7t$3|~6=;K*gKj0#zl7(T}^yvCUu2=pO;Yd>a@+=ln3@QtrH zj0X~F!90*#K7+7V?VMz}r?CkW68R7E2N=Z;BZc>#Ko3F{{%2p|tr3IU7-NQ;=@b(`w_RdhLpmRhjp z1qHPyD^tHs8x6fHcZfYgPa#Tgv2_`@yReDZr`)c_VddUA2xJP|*cgkUERhh_BclL- zI-BzXh=0Rg%EOl?VEy#?vZSVNN)Mg1lDWYi%2?4enonX{NxNZROp-EM+h*zMK9Q;H zMkB`W9%#q_(<5xY+!|kXJL1cvZ8XgznUolAq9fovN3 z2-)PvK&vgN0BKd*y-x*d)Ni;G){=*B=Xh%Rn*_J!P$pkm+qm9VUWnW7SWV}=8hN;6w-MAuW?V(e4_Lk(_pXp&P{|JNw{p z&|cHCbr}aTLZacx6GhT5423@siQOJmPn@OLD^5(4Q>-%(K`;=yi7QbS2_}Sw|Jj$) zUPt9K^i{5kpQf!fh0M|h>&Xldy4l>(Q}g4kixez3DcmUHubu=QHH)95u~9OYZ)(uI z3NF=8TPde4x=Q1Na)>~!amzwl$0s_v9Advx9eH2~-k0!*<^HrI&PwT@3njF{IY zx-aRw{s=On?iJ}7UvX#qr>=zG8zXhFO6Kpi;!h#zD|fZ=I9z%59Z*91J7`jf?p+G@v3lXDk4j zsr5SU&mDOX%a*Na7fcHzv^f;*j$LJTR**5Awoj)r64Ku?N&WEjl8RMyvj7u#=;43k z_ZMsXRs3ODGsPZM@2e7^VL*2koc|#Px&As~7{GGy&5TA3Xxf^s4CPM+OhUE}N$COe z*LZ!)q(U?imIQW#yt)q}^1`O2>J6c~e2 zw%CS~VBDBegztqnaM5fT(Rqs%L@hU4pVksZeUHtJyR=VsChH8Xd3ATQOBhF{b8H!% z;XJmb_BJzvt;W0^DKbL|?y)|`aHF>+(U37miVb=JnUhaJNFRo{7$bNKGNcTfyDC0T z7AE_*u7F*Mo&6u%jmO02s-c-T?aZOeMpychhs|{8?f~!VRB)8??=rfRI<#HkNsa8< zpO(7q@NQY>4IE=(cm?;V>tZqX#3mW?!>tFRd?L-UB&ycYq>$9 zD@)NSDKm-DAQ+Sh{RFJb(kFb{5dA?ut`G7;Z$0%~WLAcQ;{Ve0Rl2n<~j zlkLBp^+Bp2jlkd;uZd#dr?O@q2!7lavB8BUcwMRCq~$ANPbSu-ww zt4xSWH8;x*9yjOC6KC7p#+(GT^Zm6dv3#M`4Aei|4WaGQ*6x-`m13Gw7mBl?c0{`T znjtKjWQMNGFc<^6y8z%We`Gt2%Al9<2UZ`u-+8`_eOU~;Y*72Y1dj2YhKpKxAFU%w^~`O$#PG#RJ+;K_PG{sTAB+Zm~Nng0T`mCm6HHup_X@_$}OFS_N|A zk1zra#~tS6s#$f%H>>`i?%7~CAz#i zC`-d+F(`Jw6*Jf4{JGI-Q+5`_mEN%zP`1E&aaJm=ti?Nnv6PKve>Dp)Ms@h{C~_>g zBothx$_Z4w9$mZiEIy1u^IfN?wPV?15u@&X1vAfMf>S6{G%~tVq zmNgA?D4c6ur=;W;Cds8OD4~>7)_5f3p_HK?yf$YF4Im0ahL@lbnPyMj(dW*sg9JxU zv21d4T8r376_{`a=Mk;7a!1&PmyL6+NBrx8A)mC@}DOC#NVKkw!^bE z;CqPa-#j|Gz|Jn&76%?PLM`_m;>FSxlHW_{V!SpXPE->eHib{EfI>6WlI3-SA)kt? z6^5s-KdHtI(++mR>QdQ<-MY@kp}Q8&PV1;^2*|cMD3ro+7cAc?lYsk0+?PAHSlTuWl0Uo`*H0&yw@W>vd=5J% znL*O`fFStRr|MYr7axaKyBxmFxyG&pyO9oB+OM)j5WKRoi#G|v3_CfW;U&(E9Gj|q z;pMzHFG$4tcUr=1&?~9rviLE7&w_|C0{LJ*j-3&RcZ%r?s|>}i`)aa(D*EvOXQkpz zdu2clsPGaJTdfdCziCa8F0re{NzhWgk82a(+du}ON_dPGFIysDw)RnP9Q9vyod{3l z2N<5iRo!(N+9Hf3^m8y!j8{WW?>-@pq#N31ma7Bk-A!6n*Yl{JARgIPo}A1@Ed8#j z0_6f?;y6O%@^v#mLz`hKcfhyD7AVEZ+Hvvob`~NYxVF|ZiH^dc!N(9Cv03S6vA%S(a{0jkU2+w0tnP<{(} zn5_oY`(8=snD(*%HlR99Vx<#TAY^)+6?+N|Y&t*gOYzdHyh{pAs`@>!g2)NcgJRQB zjOtXqS($_S+q5ggQXRckefRT6e5G}@DvgfDwC)MeDdCV5WvG$`-t?vt?O}cCxNyNo z4Fsw3V7@~TcUI)(x4K7Zz~E~&Ty-K*=Ap=;1%w2Df^-dWIsRD3Ahoim?!o9cxmyW9 zi-O1qWJ~X;QkUAf@eCAYkqs$Vq z`75^DhP+{Ww*sC$7N2f<+pCvEA@+dKMW*o>Yj(($#L4w1CwGFYOvNwjP(D9%Y$ylf z9_ut`E;{Wh3xgQw2OzDu5qbW=-nk=Q>oz|oKc!Bb9zx2N=&EN}PQSq2LL9~~0qNLi z`lEQ>{LIvwcD4i@E)q)q;O{dVUib|ckL6nV_-3#f8Fli+K|$j3lYG%^EEnm#uJmjw zdW>W*$RA{>2{C-c*BBVkv}I!K&D!qNe-=!LSye@vtnn^dch=94T00984kd^IkwNJ9 zaPw#7(-$LI_M6W+F4M(6UR>fuc!8J=rKJw&f+=$1D{$!sY`aaTj^TLS1$C^jPli>; zKB=n*nt?)}Bc0lp2A`17pqEnTvHa(lZ8;|^)Ip$asZ?c z>=UFQ_)nFzMOUJbMU`Kcj{~0_{e9`wBIa6?xHYVxLA(QMBhw;k8!g{WG7S?T4T4

!L`pNs4=7-VhLN{Ny>e+mtj-3Q-9<#K-@?WGDqIp_Y zN`(I`k1JT7aeG}AT6+_h-qXDePdn^Xy)2#z5qN6EX}UVUpxZ*Pfh%5;{=7sn`mk=q#_?X1>I$sY12*TvE;oC^e40rS>v z;4>2LS`6aFJnB2~WJ)B8rjm=KN-Q)-Gfk zvjaA>6iyvYVfEDGedlU`tcU(qdhoZdNDuU0EYM?~uI*jLve5Pi|CsHB`9d&I>r_*& z7&$BM5(Cb+>>OEqap+`E1-UX!afrC{e%d-wpjz4#S`zULg!iV30~IyTr;zjvN#5u7 z1KcZf=$PT#kPN~4{dNI^H^iIKw&Mz8q-6Dpq zB>15Z>x^;3s@Tf9AH@Zu)xXr&29n5ANR~?4teMN7xcX~1^8kl~;=RqkwWQ>6&_k@Z zp@2;-TaO5hZt4v=#4U##=?%mRBrf##qq$$OkZGox?mDq+kn`fs778a+u+9x19zvHH zw5_}$ADixDy8)OA2jm#s1Z?0^If10R%N|?^7T*G_2QhU$sSRP%#;qeuG2AhK2vULTHW{<75 zBkYDgkr>(#`jCysuqK9UYU|iFHg3c1Te`T(k#WuC>dwya6`L4AhP>AjF)`^-B@a&z z=rhIN1`a0!6=;U{O}h)6+H=wGn->e}MuJxTms|1ndsz$McLp?dZ;w-Cl?N|NG&%LH zkcK~(;+H2Q@v&t^)J?A8=iw&__2Y@dN2`ucX>E}r6ceFLEuvvmpaKf$=f>_-E{ANp zN^QDuf9;(5X(ff3XtYKU5h(%Pk!)JzHiHP7&6a5=at`t+B8My=h)}bWA>p*MTsmBx zJk(cmIT&k@XK!*&+drE1V8W4}oKc?mfh-CSBytJN2^b0$PCUeM_v5f|(sy+cU&+@} z_d}jDx3|hB5TM@YKbkmsfXGdmaWxU(XnQU|zvK4@Mz;crVRp2ta8EQy?(At{~1oT-E(nA;e-SuG3yqTL>|{ts#Dxmh7Mm-O#m6A1g2Lt~9+! zoN>RwHlBY(QqEL(fDqvAgl}LMjjAdbBh(QvlEsozG(X?C>s3dx`41e`!P;3+ADq68 z&wQ7yO@zhb?o~L1#=U#D0wwyO!`tSpacHlzIY;ZS4fG_rmZ7aI>IDC$Vdewqtge_ZMeP==wnF&pvK6=&Oev z=2aX|L(BXDm+(^#`Pt@eIrdQL17v@df3W?$H3|(E$;Bwl>vu~+Tpi19iolGuuBz&! zLXiC{E1WOSI+#|D5Nm`%@t2V#vmbUzqJh0x&`=Trx0q)(sye}+_%X+uBGA%X7@t=N zt!GfzO*iT)K{nImM{f=k#!^i^{EgqEw#KDJrW^~U_Q?)(!5=2rF~-01@P&`{^cHW; z`>Tq0b^w&#%3YDJ2PPGkJt^5N&y;t1?E%5?QMLH64RVeu4ArmHwqHUTrf^njnaj7p zL`co*d4zy9G4UG-{#NJU-cVU~8B1JQ7&{sUi#$i>TwNlhR#nn!FevseJa!Oult8_j zKrKEKZ8(KB(7*!hwTn~+<$dPPJwCj2@6iR%?6_H^*t61@e73)tc}s=YWv(&gQ?LLGTlbb1Ek`c{DAQZyd=3l85O&EkRJoks`i>*@%fBy^|uIFcz# zS23rQN!E74-5sTxD_097miEP5QyrT{cu*{1BRT*DnbxK(lXx))Dm$w+`lL1g^ZFaA zvDdD~k}SVY{|A$lCmIlNIqI9UHME9okAHXV_!<@sz36Rb`38%BdA_r3bh~fAmNQhZ zIov@e@}5Pp+(U@zqZXK}W?wt;L|*c{6t&w$%LDL(rE#?_mZ1-^S*%vve_C zA9E__g`0x#ND^Hx_`J~9Jm0bJMo z$)+ivOm11Bw4?@Gi<|YBLLQ%ih`ifuWARSttto`524erklMEqU;{0LY00S~1(G6~~ zpc6r)U#{N`-xH}NynE-DL|M$qVBHdl6t>>dKf}2W+$9G#@zs5DK1%JU=ufe`q{7pB zi9meioJte>(F|b{^v`4T9KX6o-ma-&I z9NJTVD{NNTk2s1s-Ze)xw%QBlo|c8q+rr++2I2@4Ziu2m05dbCFfwuHOT>E!AWXl2!O{8iPn?ZvAm_gm4Y|b@rT~D~7`e^6$c>7t01Id3Tt|1h zHE&)Enue}0P$2r5pT#~HS#_6K$zRPK$xukIDjYu-{MBePt+ixnfAQy5d34Kf5tPcm2 zr3Hn?s$W5;$l>4=UR^Y{) zma9|+w-}&NNmw?|90K`l7R>AsL4FqL6S8caKRZ7$_paDODTN}#ISUuMlI4O|y)#sk0lqs87(hu6G0pnbcuWE?HMrjnvjHzu?8xu5el zKV`G{Rd~HHi3z#tXW{bz>h=9>V7R%B*GOYr#rSSKF*&BrEr6&G^Y2|AV|-EnfK{)Q z>X!x{#5IC);WW~XVPd}J>hwN!+usMAha&eurnXXIW@k3ppAqOJfP{oon{45Iz>URpA@X6@BL}sl{<##GoD_nl{NDBvHXeZ=lJOwD*1X^3 zc(>ma!UhgiQ4VCLtx+UXxZbS!X^az~;-Uen>i4AS^##=Bhm>!71aXyotQETwi8ph0Zr~^L zbR-kvb9`)J#?h@3iRyB)wmFo&1vh(#Z}iAq$wP_{@;nvcC7$7{Twzzz*c^sFMoBl~ z<$Mkl5(q8jittUKUsQ);3HND>X~o!qkD8zX*3phJ-}bq$ z?x59tbn=yw!cWcmtfEHsFE1C%HLR9v7-1#=x1r3#KDWr?G z&$Ulr2#QCkAZ%mO&qQyRoSJX}KO^y#j^o~3HP_(G z3BZPA`)Rc83#8Zi7pW+%F~{ipLrn_zWXY>SeM@#zwDwD0o$;cZ|8T`abSD*qvw;0$ z5W<*Mt7}-kL43a@Zi~Jh{$i514k&k~gCKH$AgXLlj6aP0LI{sqlVXZ!gE7GKIESZ8 zM$n-!JVxC|towxVW(lQ3S!Dn+rd4dQLVKlbUY1VD7^zn41<99CI>vd#AIptL@GH}q z0gMh6i9 zo%I?>h-K3^ZOgw@+V(6y2dzqzd}M0nF7jy^iP@4Q-pQgRiOm^szttnf^KepC&O=T; zrZKssdx9Czs;bya9;E^J6m7gg%2*h7PfJObQn`+^nk32zV>yD7F5I(4Vpvr@!J~!N zIno>zq7ElT93I1-^J97-{yhB=<0p*E-^1zi`-=kNKjN>?zw9PsjK5Is?=dVRjrX%* zg8R<@vVQTngt#(+Y3VKpzkZrOnPpjeQrLGMRRvYa^wj(({$LuwXNW(guwj9_#AUod z=lx_ow-(7e5e0|{$O@G^Mv7%h|Z~2@3Q@(Kl${%i1>(^6) zu@x+DW#Z!18++d=!rHT6Aogq?PfLG6)ySu}VkxF1jqiAExyvA1cVBCkTf-dp@A&zh z5M6AZL+i`t&yX)yj_u=a?Ou8|X=|SnE$miMxcjhKSZ^G9D(t%Km-9Ocf(x9Zl@qJ9%=Id#jID+?6)|*RD4ZQW?z51oS z^vbWBFRZ^5czJt=rn{WRwm(=SvvJw1Doh>Y8%s?{c3}pYUA*nn7pEe9Skr2sJ$R18 z!Ql|Bn20%q{!gvrFIEu0^7G~8Y2k3`rT)bp+CSfxpy)_j9RyjI9a;NavA67(d@QhH zUv7ID>OsHZ()+kzJ_+$Wf zv}gn0y1~z)4i=F?-|HL7r4zc3i{`#B)_>+$+SwP+g8irH@w4ESd*we$Jm;tnC2kKi zyflZrk}gbT)#m-|5qwmw+kS&dy3G`3s!vXr7wh$EeHfpgLOq^p7+E9eU+&%!NgF>o zWG>UWf58JP=4#vt#JxE17R#r)?49aJ@O>UFU;Z>% z6Rla7mNT3@dIVp(6h_!D6hVh}VzSSGD+jr#cJf{VxB6H&Inp^i=mtY#qZo{R%&db=CgDJ?O!OibD7$RtwUPT;*p6zdy*4D}=j=S?cl3 zeD{dJetgZ2gy8gX zDf#j4MaK3}yI24qt-9UI^d38X3>TpmWHH2JCkJsEB>hQINl6%^ zq>?h9h9^_}m#5{IRU}F-`LRB6zt!%@WD?9-CthPvOn&U{#2~?LKAD#khfYRSD_!pU z^Ur#>()xCma(zlaYc&ye1vU)>y4(4DQ&z8EQ533YF|QrRYW_<)uvhWG3i4aBICoub zw>A5@%}!hF=z~I@NjCOr{!{)BYh?x9-o((16b zq?TK9(8_i%WIC6QgKrPT){i2HoI-!TVPfB22wP(t(DR;x;is-fy;pDP(ju4cV1TjOPCjTn54+v#QAOKoEIZzdc4h42?1F@i8Gr2Vv)-C&gG|GcK`2_d*3oeq-}=1&*DU6s$J?O6fMl;sRo^Ttf^H z-dsw;i|cWNw*WR&k5w|-7mTpWTRBKaEj812j5GLm zD&I_iwtfBEb}u#I{o$VupI7?=|L@$jFdgt@%M{}JyFU-7zw)0hHq)@PIgw_p9)zuX;o*S zna)bU`2W+&9Uj)-`{Fk-{twQ;TB$(5I@7)uq{!WRjd)fXft7f2#Z}B$9vxboBbpAY@=ezSY zuNpxy(8kHv{Px>Zb3aKSw!Ip&f1lIq{uw?md7^Xxc#H3h!gL|s-j>a`#T=QpjVL(7 z4jz_}VlE=CpG-7V9+&Q>l%Rm_J*2ZftdVLyJPvw=DI|L%&*fpJ9G`|uBFWxxNAP9Q z#&`Ul{Q2_y=kOU`2D+;_;Rlwv2-r=!^G_`t9?&xOq#?XTV1ziR7X>7~i}JaLkHW{^ zZ#C0RMrHh2x+)*d`s;tz{j5fW>5|~p_)>K+63cqq zaUt4a+so>tQ?lN&%f6FI$q$v4#@Jg?iO)G+0lTrSSd@jrHWiu-?W!$YcrE$t*=OZ-RO_m*sFEKO9gjB8e&T?`J!anR7dOvwI@GRaq#Lu+rdc7) z-JgL2Rl9R$X561~3jJ_xFH#u^HhEJz>75n%;=Wv(<&Ti2pjl^v4i8N261qYD$`>2L z8f&mm7m}F#H?uZjF*gmjw&Nnz`NccgD}zm`+TLX}r-yda2t%)JG3bvW(oALDHD;T< z+{WcuO9N@;pBUb3SdC2;Hh7BEd@0x=o~~y6&OJx*#XU>H!s!fAYEf!Dcz$e(Mx$=xcbwP$lS@%;gp1Pvq3B&)Z6D|9bqqe41KTd#HB6a1EE!xh&+eiM0Gn z++@W%1M$Py%yhCa)~+K-W%dSQeR_y?H=G)EZuAgIdsao~S{<1bNg(D5eLFVYH{i9Y z*BcCSIui1sRe1Mp<;|<#5fBx{P~-=PIw#v@<)J+$rjmG8A}QY=0MKQdayfz05D|je zED0ve8qMYH@zw~-KGiNKK2bK5b>f|=*4qlrv3YMFUwfzt?NGB`c&ps2U$$zzaS^&~ zPGf-Ye2!u6SjI1m*xX;jN9@PAbmJ0V!`Mv}Gf4{%iWeG7o_pV9ksx~*-G|Qf-4+b~R0HNL$_ne&phSJMO0X#Gccn6&+5ofG4=-xNSe6BK7;1dMr`-{J>Y z1Rb~X05sueuY7mHT3Ko=lu961Kw8pu`Z zPsRF5_?**4gJz{VI76tOyfk+rBAb0Lwt5p-eBZ+9Evi8(m$@vz2f*pe$aa0M*=6ZE zzw>;!w3Asi+uUvwR7g!pD6!@onNDzug9ACWQ5F~QZu7wehpwJ|PC2ETg#M*&A^65`9)8L+_Rz`r z*RDHSg6(l%Rongmc}T%7eB95olra?3moS66ZZM>_4;6j zDEl_mf!}xm#-~JpK8H&?9){`QR_v`_bTgmedyTt`#9cGBCmuX}`-jITw7qm;IxGLP zjOx+yqgo5VmkYl!mHgZ}>9sFju`u;-F zsn`^|HZ5kl8Yp%Qiyy~!0B*sz<9F`vL1Bp;7N9;_02{yx%sJF-)c_(1o~yZ;ySl2k zjl5R<^j<|ellP1_IQRq=sb0CJaCYy>mVxFq z{znTSrQfKmuw@T(&YHtWm& z;7KnT&MLp(6mQ>30;9+-rHUWcuADqF@N1QUHwwwz8T)(biE+8h28zZFs);O|xdAn^0QlK+QP(Sg98J@63&+u&OY9ie2L)H-^yl=p)O!^id~g>5HlSVqFe0 zF^e7#w8eJ+FhZ9k+~EqlFdrQ1yY$pTHgnJ%EnFNvL|<$TbftS5+pnJJFeRX;%X8j?|li4#u`<9d+?f@0SW3Y%X$ zxJ_)6IttXw>TadT&`nGjE^;V-F|{ghvAf*KELIYnWstWGGq|*rTH0lbO#B^qn(Fi& ziVJ(ZJh%Ne-jRNWr88~ZUxb|h`laJQI>sb(*lfPb_~HP z51bss$f<|uqr)ZJcum`b{J`&SU2*z6HUV+|FqVW*%(AzvFqhjv|Fm5f_ zv@f?yH~4F<-Ia5%iAQaJYUf$qcRb?pVrj!!i@)S<{RFH-fZ%?5!P<|@w3^kQ(=huh4oHX=jI@Rv;fD0oBzR%Ymmi7}9f z^BhEx4#{dSJSZ2ptu`@%x*HgSxd3vD0wFdw3>wYkNtrqap703jgRETBmpfE{tcDYk=axeC$7)N7IIoCbe3%*TIoH^lLJlfFm}NBQYHmK?^KEkN*SGQ?{1PM+&E7qf z!6qS%HL$#|6gr2G4)Q#-=DZb$#vk!}TxjrkZBO0g`}Zqeq}(R!iyY3sIVdwZlklew z%5oWJ%a07b+KO5QwU6SPrE+@CiIqwS@ABq9n)LjK7X*YL(|O8gM@#)xJwSg1DqO0w zcyyChsX{}UA3tmeKg=2E=jO&F$FH0*?)$j1M7d%eW7jOR4GN6Wm6ISr_5JK>{ZI zJiW<3_qG$&tbA~Y_Am}{p2^BGA2fX9>(u?PhI05T{#Y1`q#aUAK%&9~L)CA?=Tt?# zJQ74QsM@(%JEco2AeLsjw2I9v;$gz!)VASoL8D|>+Wx8AYJVCshrMksm&Zr?`UD)D zmoq^U+jAHAP(HFO1QH^lnv5bMBj&uB1HHzsk^iA6qI8dPGrRDD3aL_Luo!8R@mZI? z`BE(@-sytw?q(*N2!3i>m4<=_A#&Nl)oH|nu!ssAAB7kz0@#7wvM|W(b@N<<=A4G&~Dtti*vN+LBqN4E^nmN!<>CYG7S6~>f z1yg8?@~E_5Fu1yx?}T1cVq^%j@@`)X1ksi1rWh&oNEnqErsYis+>YZH?-17h5|;iP zpo^!lVD=X~t~GAvXCJ5f#T&2(%QKAh9Z7KF{>v#W!==qJ)AFTp$(x=dT;fS;qheax zoSJt4pkpq(#&}IY!j475ZbyzPLABMAB`ReXBwG>_HlPou_cg>@IA4w`TxML4Q~z^I zPa%~$Vok16q^cHPrwS3G4%@)}`?VPl6GoNlS0pi?al(2xJd&Qr)5r;VIbWNb)_lTp zKMBL_XpWC_)0vO8*^Hoq_py+<8oU^xsXTy66>==h%W)y64!LXYJwC!3?sKQFjDTwp z1NInL3QIa)vHG|}1SvSZ`ft9Y@%Y3e;mk|}yecyh7^u#jk47*m3x*5o&WFP$fBs4|&LEOFly+)4^x-+82|Iq)GOh;5= zn0u5`(w6WjckT>96S@SdI9n`ju#zzxj9-5U33GI3zUNkU8a3h8wua6%wkXLjr^H7V0P-e@ z=fc<~)|mkXz)jC#J?|hJ^1tTTytQ=wV#*XpGof#O&gX>%B+K@d0*BYzRrsc_(!xoxI0@Z!?$4|@&7_~7Gy;<@_eTA~`36^F&0Fzrw zXxL_J=!^avL#af-oCgziIo`S#WtJb_3EegNpo@!rWBh>YmFTNyT}fncyOtSbJ3LAJuTsYD-zd371}yY1Hn&bQ>pms;3Aghf-?%!M zEXJlY9&;u;c!Iejw&0~1Sf{fpxi(&Nt?c;KYNWq+KJ4s(ICS_)QHAV#eMC|KJ3iuD zm^;E71O2LhqCnWQ@Sg&M!4;ipq#j~AYdxz}qb|>2*(b{g*qJVN^!M-;Fn}ZBZN)n= zs^0vM`*SB>#msMD;Tal?dQrG+X95R5GEQvhE2Ey3^5c8M5WWZsLSQ^F-EgFJ!8PlR zN4H*oz8)N4b{eGy?=gg?q(A#{B28y5bZB&^CtK5j-h01Q=xs=s3;$CLCtj};-@>Y)u3hk|#FkrL4ZUb@m0ezA?aV+DO z?mU>jV@%v!VNi*zHOtf9quA`dc!V(~?r#4=uh)xhQ!oQeqFgabM43%X?&-+H8xMjX zx6*fKdA0(ZtFuZqe^45doYNSo17J|^d@w$rII~JT-kw@{ zt_lux`R#g09GTW5FO?}i)bH|yZNfE0KQ5tMM@}gRC6hnM(%oXu?TkU3Aw$F|UEfb4 zkl&~2{icqBDIq8z($ij~`$}x+{vU5|tL8Yev}=>TTI^-6liFZ`0Rsj!C}3b;``GcK#TdYiX{KXJS(5buc!$6OCI&YLHmU~2W z59jmQ_sV5eIhnIjCDoJgY{@;srC;lKr=5V}&7kuMm{*}AFJ>2A(92jD#SUOxqwkbC5}SQl_Y9%vi_viHXf-?ajplnIj z%*_-1OF9*@00t+lZ>Y?1ws=oZm(p%_g+^IvFU71-1-qhNofWQw$&)}~>Cvq$DDVP#0PWjtcs8d zb(r-#BQA>uv1EI!+1=!VPR7`BiF;NwCFjvI@UVs_)RIG&5#~ z)|~~ErD6qVW z+l`a;vnJw1(+t(gN0n=lPb1s2^4Kta_uL{hF9%vd^`YzVc#@RIiY+-YD^h!p-N)tC zv|aS~PTG<;J5*)%@J;&DH)9049AHos-KH<*;NM(OS@^*5VWP96dR z>1wrhE)~7z1MlvLLp+IwW?6|@6>bHksfxFXq0?1c7K^lt+2pQwRN5D`w#u&PXvXZ2 z!(%H;j2vonUij*O(d`={UIr>?-7L?-AM55v{FoQe!$%|NK~^r7aM9(wfhF9%r70aG zc-T5-b{|X3FB`nbBw7dI1|~FrzeL1-T!a{xNd_*Px^Bw5YT0$;82(f)`+SJKKpSot zS0E7vaR2P`c1Kp^>WT%b?j~b0(;{3+naeuU$|uk6E&&SoepS0pvs#=YbMim@L5dvj zEp~sVYp(K5Qa`KeR*s&+5v^rlPuiU33tJQdYdB}3W}b_x_lUMl<$10S)4*Epd~uJw zCz0em^rpN>^b)07Zr<0D$L_%bB<*F@ow4r@r#Q#+3OvQl%qQ~(N^FS{<<j<|%`@K_oy;TYMcPoqdqyB}EVGXj-Omjm7MsPg*m zMao-{?KG-DGVN`+1X)(;kLnTrZg@G=_l2Wdr4siw-@TEb=9Z)dR0WsP;EMaeS<(NA zHirngLLGN~D*8UYa_iactO`QL4$X8Ri4n$h2^HK0VgMuhK|H1l0vDvJU3JM0)@;)N z2x?6yt3Yzw9|;wR&xpq5?apGBKapf=-x*&(S7)9;Ju;^a5-uWDz+W{Y6sMoN89iV$ zuE;vS_bIwHE@gyCi#Ksimo$!Nj}{c21dV$U!~49ycqo8duoW1b#%j<;?Su(9W}#}Y zMyOCVCDvT+2KT8^E-sb~=*~VMt8>t%P<1sm*q5OhJtZsc=txa$yP`j>xMQxp$97p{ z#T~|>Hs?U)k(HuQwavO-(>}e1c@vf*YuKHltOb74-k>8HMCFQ4J&Xl$efJ;`g}ZV` z*s}IrOB?ALO(KLewj>y>h)?JuxoDACV!owM#{!4vjx9w}Ib7&XDIYglbZ`$rq zYR|X2e-ZG6FlB*EcykJk&O;|C-awy&g#TtA4oODpTp&vq} z7nyo}cK~+Pb`R1Yv=6xJOe*N|1h4Y{f_n~g_^w|*;^%CWXgy`vV-YcR` zyC`Qm;POI4A$wkS$Cc^oKk=id+EYV)oG|u_7W)eeeYWP|+G3-bJ1_;8tk^HkLEggo z7>99*W#FxHwqx~1Nqk;-qybSv+t9FOzVbWHv5u_#U;ve_@7k_SZR#J-?oSPz3+Hc^ zCQ$2$GdTU*{1JX-{x8iGbNTyi?&V_>(nS|NSaHkPZ1<=v2r z#IiAAS34LR&epAZXW+SZ))eK#S0RASiF`;K*Cm}+FlR$Nh{W<$K0o_yv4?{Jo* z0!gfbCPr=-*}e#eY%;>nXLBMfad;95b>WfaR#pF!IhrclxY4yJ4YxPG!M7~M3Fv7{ z<))Nkjg~i16y>hQlxQ8JzUI}%A>uYT_kj6662_6QBwzNSTgha|RO@|Mz)F0&xx#Hx z@&Y6`Wogx}lSF4zL?JaxcP7Hr4Ju2+aA< zs5-N07%urSqLwzZe z$rF}$d2x#6%r~-Flr7M$;{+WBG@Sn}L+^*QaD6!&A4ht!Q-`kUe)(=>FMipM5PM3< z7N-ts8l|aMO{cusb6%gteyHQyyqNNDVLD#`yV-Jdhq3PZ)?G8*w`Lp^ULbGh<^Y*h zxY#>om{|r4l|060oW^APdkU`k|Dx4`amB!J9y`zQ58xx~&~aECx3DCVinZkeO7B@H zi6jfEQ^|l|`N6Vt1;viB!}d^jAyMZ&;k)Ds%Cn$8=ncHa{Kkw*&)0_K>GKG=VOnrt zL<2c;C~cely`cD1F&tD}c_nfQ*=+o3Dp0NZxM)R}Z&#A%$qejlo1X}+nv4M++@>_v zHFmwr;|~vM$W_Jz{ z(L9!?r4|k~Bk~2FA)(NGX)=q^C^zy%X9%B#q|1qD2R`!z3gM~ zqfD}wMOvGCq*uko&^gpyi9zPScdjd{=&R}uAO_U#2}RE!IIXJBrRRauxGIKq0pCtNg2mmk>#>8Vd?h03 zh(FDPg<4sV2AvhcB{_JGT_2~m>IX{uOY8&fayQL@{N;rt5dUrC z2k6@GDmx;|C^G7n>yL({H13htT`TTSkzmJIxU3FwW($INSxs5E0tAI3s4rEhF4AGp zU^tr!w@slw-%W4iyYyNXWv*Q{+=HlHzVMGP9~P`>A&vh_4bsj@jS2 zDzF+TqJVR?4S+jM%O3TcE|Bfa?}jLgxJuRO$H9Wri{zmA;!;%Vz6>H|=UrPQP=ipL zvH-BSzyKrx={Wxb*-N7GpEdb z6$@vKV$B$tda4O)s0grxdGpQDYmW_RFheyS^R!6Nx}A`cJtC!LfRCv2if+d=)%p>* zFB<9BW69)z{45ar>@c$;C+2XW8vu@N?v1*0m1o6p(R>QeQ_Be!B*mZeU!pU@2>%@0 zFjUl%ko&AjDEJV+Q~+wMBpqAI#1AP4bcyTU;{pm{Dvn;_>qTu>Eh~c#2`TVZW!)*m z2*|RC{m2ADWeYkq&1W?>;Bu0AsJ6&yOk7YSXY@D>Be}AO+}KOrqfh8>q;4pKMMtoD zS9fd6z&<;)uc0_z)t`0ZRt~&^!xO68X;03B%;*Hyp>CJyMs^6G$UavK*2dSjXL+A5 zzZb_d$F?t<$UDl=TSq9>h%GIRoY>b!kOKJii8Xg~*gZieT8HPUv2q;rz24n!HtAy8R&|?IUQS*DEn*=sqi$wLg+EDEr2(Llk#K6=ZNZ>ai&(t3 zVn#m_B*wC3Vo(WL!^i!vvrQ-7mSEbYof+36_xf|3_K(y(Ri9;5=xz@>sV8vQNA+tBxEA<%yY40- zuOb3KF3|VHxj_@zs@q{>18As7hdQjj{5d;-th>bNGNt{8LQHb+wP%^}3y0zCW$${T zr!NbjMUO)@V^&dc5(C2PDqJJUhF>5X)&AWy@~e0nc202;+B}r?BYjL?N!Szit+WCP zt>V(>TuPv{8MvLMC^rxPfmXzxZ!|)7dBIQ5Rin%)J8DGWK7JjjV&%w}5W;)&hyH5e z6YGPx_!C(R5@HNTh-WDiwdzOkNyr2@U--t)w|MJBycJxgxq*dsruNt#(vI6PTh{^z z%?96PNKR*xU?%QYiV5#l0`R@ehovX!7z?+l10@qb+D%vK8)UjZ zIM}6h-kabdT|2Zt`fc``AUQJBdg{f;CVWpX!Xrk#>6Zxm&XSpOJB()&2YI@Q{y3J+ zaBfyipx$5{NkVrTd>BzSzk+r%%d-}z-il;&BX8NZHqP}2+~M9&vh4nio^gFl%`z#; zNGd;Qw<|7IY;bL)_p%l{k?OZOx6jV;%}~UR%>)x6PL}*_7es6Z*n%*~+61$e$W_-( z%>e&kLg(uvWmZsTy#^!C%_vA!-E|>emeNEos3J6EOI9hJw{hK(;<5z1Ib3Mq2Cd4$ zkLne!W!g7ZEtjUCaD^Ubdvv=Da_dT(P1bFy^dimO#ww}&CWmV;yyuOn(9eh`oL&Iz zzYThW&ji%a&C##pY*8bYGS0`&_2DZMZUg0MzO`d72mMNJp89l`0B%yM~ZbM~uffP8rGK+|E^mQ51$@ z75v%6es1iM{HWE0?Zn)~0@!VH_*NDRFlzM$GJ;?o zRO2J3p{4z5jpcrYd;3jt5njmzdE~30UNj~7Qxw1A#wGTxeoP^TTojoGw+LY@QS(id zRdx_s1L%w}o7z<+0MpnTHfifyDLM3}fBbNI%q3pj5WkTw7#C zWJSpmg<4Sm=3}#y7cD`tA7XVjvv(7}-?b~yj#b;V`w;-}Y|2s0^7alPj&&La8`TKqp3mOm@@88^B!i!Z6i;)gv!?XYdIgB+t#@Npe>+cBmB+kTrh-p6A(olDGlYbf7!xS#x!?)#UKBUFBB!1uq+?^=ufbp}U z`xyr@XI8|d=m0#7KBvD9KjMcm76lSAYZL55M|616!LWXcxPj!N5w@%m)^io|-3EX` zT5q9Xceq=8k4O30cPiRkvcRc@CdP{9KmD-y1d69ro?-p6vu=J7ymdxxdm<%=e_Pc$ zH_`mn^hWz6Ay|uoI3BygfhYD?JYQ2Q^?T>>oCd_lG!U^o!*BF8s)tBf>qsK}pk3HG zKF$TL3HyvcYg6GZ^(QMh)IHW#yZ(BFiNtt4d zWvExWVu5+n7u9v7m9%klF2xV=b}AAt#u7!PDv!Or66+Z4&h-mxGJ31gN6P4pq0`y& zo^oa|VN&Npr8Zb_{dw3d{jPq;bggdR86UJ=PGpOcKn*siYZOl%Uhwy<3gpTwbY7R0 z^t6twz+uOVdP`5xUR=jbkKZ4w6 zR-757GSj?Rj0EZhZ)gTkB@iox6vR|svkqql z@UlYnH2PGVoC|gr6&fP;pgk_b_IVhIb{VIdK1YkEg%iR3vMKG(wQ{co_M43G&kp?yg1uL}nq*c{TUS z=BTk0V^YC53cZlK5SpX5P}uSIvDDAx@LIV0J3trNKSo|!)-KTsRcjl?pcK&P)H798 zQ$_KXob2n>B_bfW8E%ZfYxZkjSr${Ej;M5Uz-y*R?)xOdI!|fmN%^kpG!hPPN;nVt zF`w(%QKt%`qB=8Ya&|%G@Bh}f6(DhAF`vX71g5B6x4!5C{m+Ou28q+1>qx-?skF+ps7qFkD7Y zjT1^g7&^mN)K->kF+P&I=6%QoKlC?#mJLXKpE1*OIP1B)y+Tp!n)ku_LcQDs-@}Hd zr3LlD!z`U%5`x7NnXpIjk{gZ4N~75v@~-oC0PiG0S6Rg=>gdcvM`i}CJ_3#JGY?%g zn|J65xxM+ExLa^1%=H0>E*ohkJa3gurxJGq(G}&Sxjmi`jjD9eryWEBbtaZ=lV+f8 z6Lv>i2!@B>NhOO_4szy2<074q!GlKG4||@P!-(sfPqp3mHd*zI`0HR?6-R+0wa7ei zV)H6!cB>RWvLZX(SK(29 zUpw%?j?hnPhK3Na_0gdf0OD^dY%Cr}&6vsNL#pOCmearji(bu=T|bmYg`r-xAaZ}@ zIT|X7WtxlEQbsVv-LG7g_M|O8+3sSIz*uaZ_$E^%a<;VhWFeU)#5%g=Y3vuc zz9ol7(;{UHhj6>JVku}1<|d}YZq_3?4O&E#EX3)|pTjHB7M&M}7v;1%eOrC8>Uig{ zJTwJU=<}~^62CfQOLQ!^-#9L(JA6%emtbyjFAxKkR@>n4^Y zoa+ant0(LMx{dcaU?WMYgm}#iK$eq)M4pp`-+8syI*> z6c@4)cQ8VCtO#|F7Bx}wIP6gXHGsvgEL7t0!ZEAt8CRWXlTMlz`?xT}@0UXI6)VDDSwiCKs6o+ygf@HhF1oVOU zwb{D;=ahPJYuVP&pL|u~PtAxV#%5a3nAHaas-28gc$I~F!(-M5!E)(L1@`;Tke@SU z5o6xcx*4k>F7AoFXN~UM?P`35j5@oK=k)=L$2g5(DjPIkY*vNzyC9_?d_y(p8|mpw zD)X-NhNeUU$4K92B|nmaB-T#4I%k&yZDki6Y#=wyMaxstAw5CimNTmO@Sd*~lecy| zE*>o?ifeJG(vK2xH>d0R=go!~(?qMj;S~&Un7e$ z#3cNPDU% z2+Q(6EeBtf>y2OMm>CMCKDcfdpx-fb_X4o0-46eK;LQ=bOpZn$mZkBTlZb(mp2J84 zsis(2RsT5T%B-3m>a+yDS~l!Ht9_r2p#GN4U$7=;lB(H!X46trt#2$G7ec`QzpnLA zkWshKaq|i{X@^%#W0Q77d>Z<#hCZMyyB|pzM`ECDdt7|ao&|LyvvzgWM-cjEBc!f0 zxSQLH53|MY*rZ+*E5L;C_?u(zriyoHb8JcOoF97thaHBj44$^Px<#uej`HPRjby-YK<{k_Df5g2ld(OqndreyB8y9VB!C&7?>@dY2W6)O`WEcmqysN{etQYQOan0MwNQK#| zeAg6Jm)*Q#8_)9LbEZRnHmpOsfgzW*rr9s)1U>A=a(nd-8GY zxXGxN=SOR-uFVmxitVgI6qYEMv*uvCyMUE!R!OD7z1y89Ew-JT;+aytUeH}Qbl|o^ z6UZ2e#`&ZRz|fNLqX8@qhGlILYI}eYXj?ogtnj~CIZj*r? zI`AZ6=n~88>7vM?ggNAZ=hX`{U*MXeVVp-OGPOKM_IBk(0t3TP8tiO_&a~8)wYJ+72efd5j*aP zojlzXf+b+s%r_DCSkYojS_Cr_2Pf`p5gV9HSKCgmGe%utU8`}$t>(pxVJ1!eKB^K( zHrkce*|k8J@7hpg;b`e~U1L98En0v(t_rZTC~R;$4&MmaR{?4P*Hkvc^aWK0KI0RU z!g)S~q^G1~J-d0q;NmW=`a?f-`6+WP4 zLN`DYz7WO)(FNN0rm`c*k=(4gZ3%Mn(xwBE()vUxJkJ;L6HbpAOG3cpHJ&8X-HTwkI zr%Q-aVhrs_sNFq2Z{Xp~jHdy%AQ?$k z$>z{55STpW;?$@oG8d@^*SLNphx8I$=F(HnxT9!u&oTsCuo5T(8wywJa!aUE!myjH z{*2>}M7TDBjN=H)lejDq$Te8w;lyZ;yVX~=jRM%&EO+T`+Q6+jGQG6zf`YLX z!=_v;9NRfNR4k2i0`mdG1ZlsX_B~&c8@tB4u>)Ijip$a^&G0FDQpw!53#`Tnw~+jU zZkh(`n?RK*GNq}|3Nw}2=`w382WHL!%9w``&h0Fz=duFUY{7kKrDJV#JotgqnK|mp z!&AhGv>`E1OCP88|@ zCW58qrjR14fx38PFH)hElmbZrnW8~Lq5I|_Z=u%6V%tKxS$Ik4&YUV@Dkt0B!dfz? z@zpesgZ))y3s@xgyX1e17@R;9673RZxAF9#3TPCHJpZNe=xTqh5Tj*5o=r%Y!%Bt@ zw8jN{$$qbYV@5;CGcSH`8*j<7V=lU?uh|gt97M-s9n5`Nb+2N6w3V(@0E$t{W!F8nJC1zGFOsSGe9gZefU0NGY-~;8!;Kix;7M8G9+mD8-Nx* zU#U;{m|%s9L)#k1h(~YHAFaI5&9sUYJH42)tx_P+wz}=U6aP7O_$?Z9|3+{+VGie3 z-JZlI6r*M@k0$qSsF#nqXj2-FVYdmKON3+ZnU6)ZBSuYa99p2g%Be2Lzk2Q3K=DN5 zfoZ3r0o@vv(qt??cE=pXqrA3{-dUIBY_lklm-1HLTwuee0_&Z`hO1f`}i;@d+84K>SPEeIMPpD{T)`EZZPj-vKM8u z6b`xlTc(>u&$qrFZ>-j=^)Z=XI0246{Ae^yh_@ZJ4Hj~|SiXUIyBxK!c*0l@bBjPa zs~}k(N8!ynmZ+;+wV;#An$B|yp&kcho9Rz@J0+Eig_zp)l6bV%m!#eN&3}12hDHDf z!Qo^zIM7E_0Gp~o`|z)So4S7`DK@5dYHHSJWAk5m0PYpo-eE(48r=+zecM*}%*{!R zj}#otaBc!FDtS@X{;iwSiq9o8N~D2g?3J|;t`hV1$cSQdA-$2Y zn}QYL)+G6s$STkgL@`fUA)VD0^i(Q9lD9%TOH3hF7JhKYcV5I(3Wtm$ zRO4yzIH3l(*d_H=H+YdeP8wzEICEWZV0_tuT1L&6kGUpd&PHOM08P!zv-t(mQ7OW~ zaW)9Vt5BV?SXYN+^2i0WBKqXHXjCcCl-Ry*e_H?LXa!`0e3#)HzVW#~oZ_Gyb*oFe zb$aOBZ|R-KX!C5j&Eu1Ci|D{`11U~=#Y~o8HakNk_Ni5X;lmMdzTa+qPd8qkY`mwK z__C;NI5#rUn{s9Fjpf8W1d>b@9;GsBCrWJWO2bS5nY?n%GG#fz*2SGDwa8L zLE*p-6AdekHdQeWy@lb-GEvyBHO#rv9mlFS7oW?Z#-e{&afUMUv=`pAFvgM4lu|B&*gvCsVo%lo{8YH}62|5n=8L4!G{^G@&sU$_?i^;% zil5^-RhQEtjORG7@?!?sNlJVxOgF|;{NHMPxzO`hyp~?DQ}x=GY-}9KFwPnnWNJ!a zcSz^>K3w#ri7$mqGnKIfW$2R(o=6^E3bgkhf)0@CIVQa@;qDS9udzMFOCp=z4Y}O3 zqx!3*kn8&p+EW^^YGp(<$sYOq`>%3Lu4b+Zt_}zEfj_^?kCxiJ?nmcJWA6GmmdUR- zqCOv*_km+e50vhkd^-QC4+8g*8#u2(tBvio0`*Dd`&$Wa6{+)v2J7GCFFski*Y0p> z!av%hcCzwER#D)cBztC2Zsly%cHxqDq3_y;{r{I_v|-iX<`@jcR|+IMHk0$_d98Vn z`*;33{1y6IcOC}@a%>NM-F|O|LV=$h_}H9fCp_1wl4Ctk}Dgg(6mE1hrja}SJOXBZy!lZrTC+q zc(H0uQb;{p_R?%th($k#*)0c(VpQk)(Eiis3UdB6e>ruB^Uv7|Bx(-i`HqL7?ugvP zf(TIVfm}Q`YtF8$8*vjcCWZR=0~dc28c`7;DV4Uue9shceFme51-+n0{-gS7Dba_{!hc>7!T9<2Z<@o$EKgpJh3^HDpD4EZM=J{kuvdYorf5)M;I+}m{opyGrXUW zj+0N-MmV=ydXU)aNuRpm(W?mfcg-7qNmci#TEN4RI)ZbvRrCe)lumlH+A1QYuN?N_~?ljuM~nE=3s{|5-ghQ zI7c*LGzFW5&xP{mbB2vEW~E#kX?D5WfM$VPwGLw(s{nj~>m2ca2JDr-ONfQ_GNoU( z`bt}H-Ap1dFONKitVrQ%?0Q~*;jIY`i=k`Z)r{QSKaPpLz4`X@=VHTP%5-K!{m(z& zLVpIHo8#b`O`=EzSiU|`f5rFE23f5~xf$dzcdV_FckEn>4DrcyDGgNKaU&OxcD<@K z&DyK^grhbje>}K-9!U)qUd8+3!7u1!bL%;x1gH-d!pSX=v}t%HR_nVNIVPaSsvpKK z>aQ(~Elb(H&FyVrAfYyrs(1P7@=U)Dg)Nukgd-OIqX}#HHV6HpV?0*ttXwJf7wb>K z&5R8Bil5TejX@PjX@d%SVAt(XjW7Qve%KcZhxU%%E75N^+#Y;x#~+aPwK`@Hi2W-D z3K!NtH`O4eG=BYlV|Vx?{Mda za3h)+nhVU?IlnEqNyrepOk_^P`m2`3Y!;ZL@Jc=i{*FpC>gdr1LaN$(cM5Oak1+Y& zb4YV@_x$A;Dq0DrI=)4$-Ym?AOdocvPw}S;{b&+maIRo>z(sX!1gq|k+D2ouoarV7 zEe=hUCX?`Tka166;q5bV^sIMZtCA{EOO1Se^mWt!apJ0CwpEH=NJy*a>+8h4EUF9E z>E);jyL{HZ02Jf+N|qGGcdhb_b<1W*ObstPOer@U)WBmLU)dTRCa#~9f5J-lDHcE& zgD^L&Mk7zJ4NSlF1+{}DGf`$MS87xBdSPg^L(DB4C4lm+>{#kXrDUWT1w7Q0F<6{Q zT$LnOX(Qd=oLIQc+N7@IdBgYT*8TrA6pADXphxl?dEk`HI%|VwRSIodgGQlzckR#K z6M3oP*r_9NXCkDxQbu09=$L`xZjEZyL^O8}OPnelmR#jhZW70X0bT8}>zYm~Lf0MI zpFJiVvg_W1QRe)yDkWfqK>%gD+N?lMS=;X}g#uKlIj?@|(C%#!yS?U`OYy)KO=Q zVzX$j5Ha@Z>;3Bc)WH6~O}9%s-$GShHQU7kdP`E63wP0OaVS^@VjORAO+{WVY|E(> zm}vhWoVn9FoMGwNX~70bnZ@C1!ja3*9~Tm2F*h8EsS-idfx&C*N_H2*90HzOJIm|i zs%aW&78}am<5G?3lA>GcatYgaKQj{bBVi=mXrSsVn)%(yWFvb!DQeieJ++$D{OizI z1^tQf+>S=Ps_CCRLT88qfr}>b5O^m?V7Bgn%6i@RcxoGubOZ84Y#iHG7kQc zMS}LIjH3YYw7j-1i(kEMF74+>65C26Y2?BjM!m2u9dK^06-U0^Vhv%$BOP+8?mb8W@(O>H%iGk z!LNJWOc&u3c*>&Bdf4Ub`?_jSkL2*-h#KJd*`H9Jq^J#vONc0aTA!6S&Oe1ilp>gf zJ<&CS@4&iG0S=$BUbtxkm4HH}=RZ zUld{c?}u18wiz{9v>`Y^5O6r>ky!I`)TvEqG~Qmq1abJG-VC(t{>sr=Ig^{>MTHji zgs{n5{*9xF)9@JHmjVYPh)%LWGwJwUO&|Zvkw?v7lC^Z4IasOSxhVmP&C$L(x0!VE z>W1nV-&dLpBa z*b87>tQJ#HuhxO5n1610q91MFrhJnyY5&SrEvMd`ay-c9f|2pjQ@iwS6hFun*~L=v z)-TyfYQvr_rosu@I8Lg9sbgZ|3cqBRSpp~VsoUEL*&uHgnnVOD;U+}QMS&Wt8u1;2un>rG zMdFQO1APJm;)^tmuQ0z!1uoD}EjS7GJ+pu9v@9LKCJ3B2>r zx!s?Jd9|OqA)QQCRbVU%>8*;(t$oEsCo2fbAnkdON+Xz`%p;p-3iUC*^~>o7qPEPV z+&Yh%dzX!U{fNgj4>XNhZ7wBwtM*lWvqZ(5zf3$RwCrRPey#)qc}lG30B5O>hh`c| zs4TBre(HLY?wsIhnGW>*!=;Y466KjkxxC5ejO00@VsM@{(K|7C4?*MxO&C?8bU6iM zJgH%og2*cAf|-^>ZHA@GQ8ai^Ov8o1x=SZ+~)cwpy zVOjF+{@=8UmNk8*V5s<*@0_;U_OGvq^cj0f$IN}R7fsg{VHnrtNfrS1L=eFnSq0#K zcoNNY{yaOcxHAt-u}{k>GRK)6WRGX$g<_GoC=A4)@$Zizvl$A1Yd3XcCl$Z!?mX2UkH-b(e5K4- zDpX;!An)~UvMKQQkY)W|#iJqfDdpwQg>5^36Ohf4du*vw8{b3Q_3lffxWePfEOT)S zO0C}IeET1giMe|AN)a?e{Sv+xXRBAjdQrXxP`6&9hMFcM0=EX7+^sP#Sz()HD-O?GOH`MFU%+|2ZwUt%Q7hMDkx$kfd`XkfjL zo8@YcqKdGt*?;|{xWEV38S~;Op$i|%x#_&#Qww5>8UVt5a zLiks4oX7{vDx9Ev(Hw`Z6-VNzFhWAWiJl6;j>w-QDs5+}ocfh%56cKj!YDF)h^IK- zk4P^oRu-DJ6rw5Ey%U4Z@1$qav_RpdDa46{6s3l3_on-?YJ{U$6Iz@}wsan5$ixUP zg=Xofuyly1B6|SLf%L2?R#JG%6nssv&1iBwH z7>gq=qpi)Ty?tD==j0cnoAiA&J-YXI)SO*Uy6Wt%o3Lp=~UkvVY&H5BFVq{m!v38%R zB!9D(@TRiE(jmsiyKdQXpye|_Q(3eQ6gAODZLEH}86XLETDwpSOitTC4$9*-8EHPk z@v2!A*Mgh)fupNIdhtYipp~uHRsDgmluc57v^*yd>KX@FUFz&)+$dc7!q##yb;Af) z%7T{fHMSxB_&ff1|B$MPvUrYFcfKlbdJlpx>dL2rWLvw)gQ2pZ%@86^&gDH3(-`jfW6DYCaJr!;>f z--H5O$}-FftO#y6>tvG#pISSuMo}VPtHp6Q#tFb4==&!`Cr*;aDumpotPZ7J`|59~ zw_+g1s;IbZkV1+xz$2kO!=TX_-LBiu=5ML}wB>2!bdC5qsSkSmNK;m7jV(XZ3;8uY zg@U$!P^{j|dWzqeff@n=7rX)d#*d222La!7`MFNAUc;g3hbq4ClYTN?$V*kKzp2x< znjN6t+^fB^$@7NL8mm;o!9kp@89MLnJktrB5!$(hxLYM&_XBmW9WB(7AtC~_Yjp%| zG6r&>4>+lRu}hQErK{Clv%HjjpH zn?o#Rw*K1v#cG;qTsXmTSj2P=>1-g{tH(ZDp}oPzp*lr+$WZQ6L@qE)zq*+<29p%u zEIEsIbxSOtzK86}P0v&p?c3iPTdXcI(4gTmA?%!tCj#|U$0hI+Xl@BYdOhNM1Y#>} zR~ATKbitIYOqpD_)Wp>f6?KlVt$>d#)B2578e8(c8uFC=aoYve&1@ zPp8l5Foaf_RiiXA_#3+gzz(KzpIEQ9eK~7nP@0Gg_{o&cE8auWmoa(+0N=m2M7Ucu zyq0r3z5Lm|hu0~@pW)d3WBv$A8^h@q_YYK&j-JQ$feC3VsEgg!rCaNV^9!Cxu1$($ zFSmg;cxMf$lh$A!aQF~)BnA#N+*{qa_+A|me!XeFXGuZpFE1^K(|x>@I2tTd9dsCw zZ%x_2-=xd2J^Ur<@wdQG$5xQA`@43RB6`w)n)>XWVW)xEqjZl5%Bk&Lo|`{2D^}q2 zBp5U!Wk{;3Jq|b(AU&li9@9@p{XJaK5J@Ng*~5x{Sk%K(J{Ib-#KjXN;yZbc|4V}X zB5mw!mVLIp&o7yV^4ZirA6KEMCj-=d@?92+XYHou5~iBuq4-bP)O`kbcMm9>Hr7Bc zCfH@R-KYyZRhQR8m==1ucWXO1DZ*GLW#;5|CNR7(9 zs6Lbm({fiB&o9$SOx4ofc|z=3{gESu(~wVNXAq$;-a%Xio zr(B7G>*qtR9+%>jK|Wu8-k8W5qHhlO&!Vs#xO~=JJm~ONDUKA}5=D0n`O}Y|yLUni zxaxGxip?*UHN%sbYfn-R;@*@3Ioq|vA$F)bzpo_r0-n!(=q}@=qV#x{`cW@WX5yA% zii6y1wgy$#Qgn==8}VgkGE_rznA*#eyY9-~f$qmF-5}YQcPF#zyfc|hSkM$xOJ9;c zw4c}&@m7h~sfTwAq3z!4>MY;QW*%>4v)kX>Y#9X8OVi?jr}brsb33T!iZx(XpT4it zE_TKng@EA|D25p$Hqk7do=53vq<}=XS6>ah;-@LT9oXm9=7*f z&66PK6a`08*v&L7I~LkY`I-_ThbL3pA-5#z!8_Rn7~}NvBmTta&8>Y%RsGHR&x!_R z8a;X5-6P=uC_o_Nw2yHR=Lp>Ns&s+=05=?66y{u@naV};zE z%X{qpxme4_i;Gz*@-r#dc7|%Q+EO7L`U~3TKZi5A;V0HsOFPcF0z&vEn8d?Fe3ug7 z4S!)=g)y1;A(LI-FWtO=eJE1Jk@L0df*BaZq{ zi`8Y-4JE`>U!T_N6?wBzDYE-FS80Cv!};`V3{L961cK-f{Q~g~uE~JtD>d3K`%GEd zt+YzivdB8>Th<^wVMSG|CE|1Chf{*lcmb}hG)_XAZIBKN7uSK_JfMHscqUsguNbycp@q5 zzo5}R9R7GSXpPB7&OE+oODHrYT`W}7`~YJ5kY_CCkNepf3PLhm5pa)*+G9;-Dvt4; zjyUR1#NHU8Qk^6J^YNpdUCB$ca7{TgNO2GGy^*{aABGM)~@rO6eD?(dH#q}ss} z=&0Yck1ob=*1c_aHTybE^las1x3UN~8#;AThkTlk=a!c>#d#Xfe>r1ZkvKCMy2IGk z-y^l`EOV|eVFU1Nk00@b+oQVteWL|ZaOSf*;obwJqow$loqA3_xK*vy?@_fID*doR ziJe&fPG(ClX!nSFh3-~O{Y2N_8*|q0UPV+NLWH&r(22JBPP6B?=ZAzVAF*tTtag)a zOMtF;U9HD-IV>GM->YbO5)4u-hJ^USR+J{ZvJ4e;A!$HKhC?_|6p>EWW4~Z%29YE;kkBVzp5k1z*4v8TbxB-}iP zp4@BOc!9`LCW%xTV9^it-drvP%+92uy`*B2-?0WcJzOABj&XK{0YHNGh!o=sIHv#? zSj7eV^tHw=JppJ1Sb6-+L@u8_b?;V~Db&!n!mugC57Kd)Luyr{mlwG4^3MIohV$n6 zs!?2r$rB3<(065q5g9A6?Xe%-kUeBiHp2Y>5;xOEDT~i5g?QW>vaj}reO#ikgq}EZfypQduuEjsPw}4e4 z4scD#k7@RL)EqCIHjYC0}r$` z##E)?#}Q}d7jy(8I@0(vlARMZHoaN<^m`-*T9B0*T7@F>( zUitpWL5f4bdZO=5=jN>RYm~rkH!|d|Ga{76>k`KRg0#5Nw zog;{?FD?`s#ZTvpRBSqS)Zu#?*DsHswYQ34sLYdYTCmZTX;c1jl&>L%Nv^z0FtywY z@qR8k;DE>N##a<-!bkT*`p-{FAQFeF>TguL2>SVWI^74Jyg%(wl-pMpqnDYs*bCv? zf3^1tyS3K+_Dveo0BO;d?a3JLP5$jg?JTaAoqnS!{6sdZRU7I=IFLaM3k>1?$zk8y zVPiPyeqk9Y$8;FIgrUQAWxjdKp0UbYyBx8S=V9en7EAQbNAq4W8&1sZ_~{%;9!(iU&Kzk7+4Cve5dq|4p54dLl_`Gk2or04$cKgqr+HF4wj4 zU>`qmZ^impy~eq(Rbx#$O~&ictGFHA8KvO5^`*P(>sh(oBt{74*0L&mN@m9 zXocwyQ%{Pg$xIP(4^(?y`ZH9LccE3bs)R22apD}RvVR+~1-A$aIi;kA;VMU`9GbEP z>a~{xyK#uegks_?Hl#jOISpw8wxmyn9h)%`^cf$Qs<^NAB(Z5UW#+{fr15f3Wnf z-^BQLjYfehZLn<7-}DNpdU3GAVawfnXq(3Chy_p#V*Z)p1x{8?!G`ESF8eIL=z8O4 z)F9zJ=V1Gv3jD5<0y@>=PXYxQ*=>c51|*KbvQQzWr>=RT4#{sVwBImaN)(XyAs=&i z7xN83PB5UA!bS~(|4xpNCZ1Y7y$jVyXHU}&9AQ?9)BPlp;TJZsm!~i zRkL-#rupw9jKI528!hOgNrDj|5>4a?4|`EHpP;H=kEd0{@``E4R4s{Y0`rg!8x0XV zbF%TiIarpo!!OGygXCj|;E+~Kto2SW1J$~amri(`&hB&IXFlr;$YyhGZbEdl#W9fN zyhN<9NzgCgIG=%8@2y%EE&q{*{I7+PRj^-7s`LTx)Z@Zf_mj=pV~%=^lzg2kC=P|+ zYe9)~NyYGRZd8X_E`3Cu()m~Rg04P{M*M6mnfSG3P>~7$8sCWru6YF2 z%8#G6x5%70S2jpXkY#AChExSc)7Kfag9shC)+_UV)suL=grY>YglbM+c%0N2h#wb; z>M?P=s6g;3PkR-QNlR>z4D%~lzj!$PRa%M0O^Uk)A$?gH<}#>npds^1(}aO)U%LFu zHf@ic$k~LSnZ{4#Bo<-(@$1l@mUi1T8Pqom37K@!*4dw|`GE~hCGq>FxLdCuN1hUm z(Dd;TtJg+Fm#jeRv)#^z*N%n_Ym3<=6{Q=p<5dX&<}V@vplQDYMAcF+u!sAObJuSz zq@v&uzq)!L-SE~Qk+7eCEhVIy9q;3gL(w|>>KOJ_ce2EMuDcExEx2dvd;%o)5!->4 zlgFhCbBd=bq`%sg$4W42QqP$Lc+tPQr{Jdb*MTLCFKsTF?p%cXF$s+q%WpsnQXC_T zBqPy89N2SP27IeumKuuFTy#5Hu8K%`egGuk+=7zN?3iRhXseLiS|rE_Bmvc)+^Mn2 zO8o97ti#>?oJYBQ=Keq3I~PWZ&ysIa*4(5=*o&nOiSnF0q9Zat)Uq4w9aGndIj@XQh= zD5#$iuxxxaJwm7#JA-0tS^ogj>2Hh~T;xBJ>sQ3}d*NT)gE>RQ;0x_H_Znlr>(0&s zpm*tI#JEJ-Ebr@?fTy3oh_1*`G3lf_*V=%l$IzF`hHh&TqN(YbO`Rb8>4oYzhPk`Y_P39B zH8V>tGxYh@mHMby^KerUewfea)U^VWl*XJHhw*uVwBAjzJ7d0fOt>^8xtjf-V+{Sd z4^7`S(&oaU0d;HDd1IrJy_w*yd|BB#(Q!8ukXnwE7V7aAWUb}b#cB$)-57@I4UiXb zGn`7WV*Q&}HR%EHLc?VFb$`#Jo2=iBY_&cfbNz(Sq2;~_*_ihloxB9#5p)7Rq9IQ$F898m(ziTAaa6={0 zPMb^!O;%oX_p*x_^0fe6>Tl2WmpvMm-0j1mo<}jYpimD#=@N(nWtnqi;yzo;+c6HY z$`jaXUwz0^B|6Yb*%rTOT{~LA%^&XI-kiu_2|cduqnKyx3`ASY4(({WN7}}u38VEr z<1s}h1~jlQ>PS#dM?T*a&Wnn)0>9klrDATex1tqENtP8g?G{cqOu2)g9g2x^zIjbT zeTgWW%CCOTN!Ry<0gL4AvQn^U@f3QuOLm-HMx3};^1sl8`&iS;E=PWI80;At?QQ6y z9LjAgsaY$@l3tb|*k~Tlo*EGoseH1h>)D16(`%|`?oPLg4V@e8^7(p0N@i<%&1y5R z)Sg|j+sD`VPkc2X{?-l4{Tve+1i%Sz^5uGXZ$U|ZX@0(ExU%yU@>b;uDDg9?Ophsd zeK<6;D$%W%^E1%iQwk%YTYq^DlldwM_Xkdoh+-+!2J#dyCZc;ZLgoFSj^x4Qh5`z^xSA zvUNK%?2&bR-aHMiHHU;3e@=}m{*KM5)cbvp@e-mXjBKStrCsQ9;`4kPy5;`of6;es3ls5btTMiRK@)AO7~q?VN5#)&)~C^ zoLK?K@V%5OKCunoQ?HlKDkvr^Hu>z)ek^p#b9`!*HmS9IAv5XJ5ksKN4=plM)qbN* zi3KR^r-BF~KYl#g>|GkAlzrD?bGKc+Zb{JffTD2KK;d2kog$gkdqFYi66G>1LE;O{ z&wU!PlA5SXEI9Vcc$6KG*JblN1;$9zqr6G>Iojn+Cf~8Fh7)&;Ro_@61N6A3X=^^j zUAN0)c+rJHkESr;rP zs!be=s2`y33Kzp1F00FByAlbbbQ8G?v~an6BAL<^;Y4G$R!NP0JXJX%+~PBtj}F}d zn>2L_nc2T|M&)US&z$1yk-^21bb2Ser#4a=H_&a`!%%(MA#0sVL?E&_=C>HZ6?;-b zlGFMXH`R{WBk?l;-bgqTi1yOt8eD?v*fXAC!9f2*-AkdLA{z)Bse}6Zghsw~k~XZg z`+9&%i=#^cg;S~J-Zxbo=ubjP?#3Ea@AgD!tfIXC?apj=)rex3bckB|tz1@?p&5oF zf&@{wDE9XsVV5kAjm=olVQP-ubZSFNFwTne%vNr9poq6`>fjCbinM=XJM0}VwZwpO z%_|6j>51l(;hzJlHG{4$YFxX~=^q|8f+8|ExIvMeRxA$6LkDv$W38zC0|auaO*mQ| zjZEKx_-RmGEJ_*Mj1+Y-EX)@o{~C~Ftvc~h!h=rsbYG#86_RjFNmU6 zhUzLr@I(}3UY(>^nSm`mJ}DGRV-2Nnfl;&pp zCZ;7@KbjYhy(}87t&`WsH;plx^3Xm|M^YR>vF7ab;~Cbbpubd!#@qQruq zoDDY;QY0BX3@L;bFKI{c9|ROPZ19Dwj?Pj^3K`pVc!QUoKQybcF#k|UVTWHASNN81 z4}{ooC`r8ZNY^=@g->B67bUqz;$99*`k<<+utm?NoQ2a$(4}cz2+2}@9Mq_JLrm5h z$-HbK$<{t$h_e0?XQy52d z4M61zZ`Ra^d@gSVv@q09?Eu}XQcLNECjKu4Ka}Y9eHhKZg($TX8tYPyzjI1Bp%mh^C$w2P z(JsX#q5q|Ti8inpZZ(1izJH%wd?C*w^#4WtXfjmYaRT6?`Q2% z)%D3(tnR8acge@fmdqjkOSt#C$1Noy)ifma$Mg3zeWGB^p6)_s1ls83hdIS*<<0nu zX4)Z74dvfhV$Tz9lc{FUOfXZ$Ajd(~Q7AmDLTPLnUqw@QDD`^(;M!l`s2mLlAUCC) zTaTQP@|!p>lzdWk?X|wJe6}$8ePTpY+FCkvis-|ig|+rz8{0H@Z5@YH$A3fH4WaJ4 zGbLx9t>*O_kdLo*n&(A%{bdAVewJaRkT{I{dt0wj7r(iDR9Xdnv7+%5h+D(8R9vP6 z0`p{U<>9UBDm^Vw?U){ymoSgj>2l~$nS7xTyGeZ!0HDc?{S6XG?;xVFhKqTFybl6c z_p}1;bGjUb_P3GEuP>Pb{kIuinbmu&76yiBBh{EpScccLZ0Vf=thyb2$}7FN-JK(7}@ZkD6xMpS1EcptM()rHs4D{+hBGN_{B>>Ex-DgkivUWEiJ5=C;mV6I{QQHmX~q8Q>#rFIqjqQg_36n1 z?b)Wz=i5%Oi|{7fw^zE|66f)7PZQR`pZrVD#WBheSRx-;@8gJH&CGXB1EDc7wbFac zdX5d|d8EOwO7@syrdweRayYBww;Q<3wNLoT66^_br7WD~wC)_UDnSn52vg9aP^3nx z09p#EuR3*Q!f);=3tv=lDUtiGkoswS1KhV_Ty;Dg-$MKdr}G;I%W5O&ABqneccJ%| zli*(!1-#)mK+GBDn%|sNxvz>ZtDCuI=$rQ$-x;maq=3&lw>(AhD&u8(>CR|)y64UD zFxSfe6<0&?nfD#_l$1gk;#>CMQrcq!1||x!xH-PgRv?*~=02+^Q6FPc7RZ(5(5!o4 ze*IB8phGy+)3~jsLGNYr*BaBb%Z}mV$=&QKE)Y|(_3M!^f6=2pt9n4X;NITREW|~j z-DO3cl3CxjJD6wts%C`r6LBLW9K%$8cmZ6HjQmw$_Z9c>sdXVFONmohmXtiVm5cZ- z?g6`~*Ko>a)Lof;eMG9SFU_p-5?%8nez4Nn%)xD`R7E*gF}-4pRti`J20qMN@*Av6 z^+M#c7i6&{gu{S;l-$h^jaoNv-J;?rz!SEu6ut0SPQm;w(AgvND0m(FQa&n1WQT6 ztA^}D{!EpEJqfBxXo23(*hRSSk6{p0MIPj*it-otV0eM0p5sJxcp&n^d7vXX2p_3* zE7rCgOMdy)XHCE{0N%dGkeXC^v(>IU&}j?QNDQ6jKZ=B7-)Ae}3y)%JWV%#%(!erh z+hxSn%8C+@6QOamVF|f)ir9k(E%PJy&CM=5#|0TO8I_&FJy0NGFi>$?_|I9IU8y3$ zU)>4WF2yBi_bIBU&i`{`dxwNRVpA!KZS7^5r~!S1d0s+F(<|)M42yyc)J!ZqLq$b; zQ-3)a5@J>7Mgox296PGL34J5I=c0;lqeamTXtV+1aEHf_-I=zDHjrIRN%bBS_*#~> zaFuKq*1WlyEPZD_fb+nshIBZ`nVMeWTwkW? zl%$I?ak3w5e-?MSdCNDR4fq>w^YK+K!_Oqd@0k;(unEX^EpbkbszP!|ZDYd8NzDN&wvs1aU=#^_A zrXm9qH_mumLAt*F;G_q4=Qvq$OfQhBqC^xeyYy^z0K$ATO)0D+Yubq0eb? z{US~3EQe9FkVU4Xd+N+u?Qo?)=C=)O5t&&x0It#vLQ^yWJ!PLq1*<#P_u@!-5T6le z%+!izU8rrb3WV6))(IiS@!hp@ggmC8yKJ_9Sv-~ojO<@j9Jlk>p&;JYtJUPcsgZrv z9M_@3hZ2;huSFph0ou!7nFIn_n-PTVF$>;@l+RX=p?e=o*gyBhnTv@g#sA!^Q&@*x4_Y}py zq%oerud1)?{1Q;FY|eIKaqw0_?n9pTlm?u#bzbtBzrv4CFLisK=T*LWAv6%R{3t&uS`OPWxW9Zy0w>cxTVnzIwhsvBoe7^OnpIfDbw@M;z9paI7(!xYds}q%zwQ{>>U1n$&_M$ zb&*3Cgz& zu~iaxY_bn@<tmeA^+X~<3ZRNwO96XfsLr~@8HfNxD2s*y6CHF)7LhYKmN3o7p#W04>589F3| z60Hd-Bjh^FRxncH(`l@0FWiMo^UIs7^-3WB1Zqz}b`0b_etKibU)#$b5n z?xuj!b(qgDDON7bgM5!LoUD>asqh8;%zU$|e$UhF8p4&Q`2(P)xg~Qc2M)-P4wpC; z|HZ?7wa^@_PD<)*#YoIXz-&RkRDD+#dZmi?ExEt^Rc_KM(M?5+(=@Z8ntvR+NIDQd z>!3GzET35tnR>ZClBys}*VWnauS7%d?4GLB-4P~Z>r+z)1nys0s_9J2+qG{;xI8lAX-)q zo{MU?r~pEEDGYH5R|iTu+pJ_02UHR$p<95Go8xP#Xba@&qiLfmd)e<+aM-^#m2JMjEc#>#6ID`G09A17#uQ)i zYnf?{Qi#7mPOm0amv!sHXB+L}Nz>*>FC~P+nkEcGJz4^Duz(_8aGFc;)W5~!Uudu^ zZebb zb>5n8O5_cQ7iLg1vd0%sDCnD7%dRycDYBm^uV)aK=E;t=w`p+6}@HdK*w%WXSok#-Bz54GmVN&NfQ+s1E287I3{PRvzcvn}%Zy zLV@g9d)>c+i!f#|R3-(;ezhaOLQzQ!iWQ)Di3#6xGfE|qdS3P@!B^mJ0P|ti91jd~ z^PBJ&i6POhsFTDtR*YPhtDuX;T=H6cmLnCw4{c~lk}N8Rp9$d-{NE+5QSY5e%BYi1 zSbB&?orM`={zODkb6YzAJfum=?AbN30s=9`po~b*C~hQ+Dz~wpsX>C)(#_#4|3UwW z(;At$_!XwB4d3K*is5au;14ni7t1jxAn=>V$4Ao=75tSVnX595vfk?p++d> zjF#c55>@>uA~^iQkrIu?bD40Ae;d)s0+E);NGwM#_n_dy5CCQ4B6XE6w)Ci9JPlz2*)(t{Xh>yefPa5MXT)ML zjF^Y{%#|oCjoWQzS!v8Ye|1gUbt_*^)*9_EWAnc}h>YRu36?BEc5YLzO7!6ScK>!f z8R(EBZ^Z$`uI$~j^W&ozzs>f6^)|HgsggXD{<09Ip8*x9Q#IZr=eR=FP_bYr#ew>g z+}1XS7{d4Pjh`v%4Nj!x-Lo<7DZrKLbjjr{UB$u{nVPQ)N*<*Pzk4EJdvVYoZ2SwP zqN{YE0i40IVNR@jO0z_;H+<`7JghY8DC#9PL*Cp09}$+>mqr1>YMr}3ZSy2uOqLD( zEb3IUmGTkqmk+gy-j1-_Uj=u9P)<85#cD3zlieZhe$kt?3pyld*i%%O*RHS6B1W`c z8Zwj*P?x0d*525w!bP;47)x4RY{3V#EtMLD6Igkl*oF=S2Xf{Z=N{pD8K$V76ul%m z84EaV0d<59|G&F%d3R^_a=oL>?FH8?5xJ}oFf)9Aa{ZX3%Wi2f^+eoqRev3!a@KNb znfI4)Asa<&VkICZilAv8rAJo4VI`T^EjW}Abc~LNoKPYW?#_cA*AznB zg#}an5}hugXBIK9hH?}sFWX!?nlB`)u^wa9)Y6EzFqpIv)Q=g37B4UCo-Wac!!a+h z*x3qTr7vhW>aTPL{f|11Z#$k2t;Z~AV(adlPmRXLG^iOoM+l|TbPfF_bKFb?DybH_ zW*$07pf)zSJsF-uMZc9S)Z8@P7+Kne0l?Yda@F?D(@%`_TD;Vz8u`)XWjVqbx3Mn9Ns#aRMZ|1xOz+=@) znrhg2xMpWsaG}+*{dZCd+P7W=X%f>Hfp;*;tfZ%o$!xu$IEI+JaU^WfjL>;FcM>Y}U_H z$`jW!=mQQh1aBp2%cZ9%Q(-wUT>4{QRGLa6nZ3T#RCEAxE8B$_Ap2mtO4xOSj^8i*9xQWZd5%X9$M)X2k6% zHH9IyIt{oI9DqRy@&(p%QBs`;n67=t@lsSEwI5{45!yoa(x2_pM*n=rrqD<8XbetGh ztb*ZHAy`NSfbZ7ADJGN3txQ_4J5l6eUzniG`#ROAD+Wr?zD>m_4?ZBAHWG zhII@r0bA#XBK9wbpTkrIydy?9t2W8XwS__AZ!BjL9khZ)NeOS(&;E=+x_|8EmnBB% z?g4o;xq8ylDHd%R_ri6RL~B)i*)O3kuh+zXMNj+>~0 zq6kt9AyUpVVI$)?8>Iq+=CU?u?u-P_3{8b>INS&xs5)09&n@5|K7@GUKgO@Q^OOID zFg8>Xs-#WmbCPhv)zE8_;BD2MH?jQg+hR~HviqLA5})SMy4g@<^p(+Q`^K|m_Tq{b z?xsZBQ(`ryW+33kUhw}O2%4=)^Sa577^>M|v%nd( z{l>Dfn`{Z6ErESd7GN&(ilMSf3GxIM*hB!IZT1$QqCCG`L4lU&Cez#Ycr`-LxBdFb z9UTy%9BeyV>f&<7afQRPzZ__2)4%ZZY_`IwqqUq8q(wy9Q(3Mmn4q&}=dJHdAGf?d z5rOQ+MaOKyKzp|m3sVq=i@L}tmG-SdJPp=i7PR^dwNCn z34k@Xqduv`l4pneN!8e|4l1SLHz##?;j4MDsJ4tWM+m>x#eoVP3+pabIu`b>Do{Wp zaakBVtggSu;XsQhSKjU~18R8g0s~ob7_nK9-2!43CZ=c@o*F+MHPy1$4j7i9-Q>6o zzYb-b;ldWV#wF(I6}_=cjSaD2ijouth1f(=d3{Kf6@XD7Z-s^`HPx}qf)_@eF3s-(%|F3=UBBkGpuDtq-mI_iB8Y$x>P_e z&#+gsp)j**4M zf?lnxS#c&=GT0CcF^E&xS)nJrYeG7$KW>^VJS0wMb6f42_;o!?l~fGTHC{Or)=Rxn zX0}Qtsr!XX-K2#76)sDm2~%&SMMK9hc%fL#@-8pHzB-{}VM@eG zQ^`P3nk{Z~ywoE_9SQa!0vB*vjvK+C?c zO|kDuPcBKhStV4_=<`a&+BvNEmIrAn*04|*%)O_serA?8x(0y;3+~tRN!v0@75b>tI z-`zO#AzC5ew=47{VGT#sBL-D8DzuhZBhnDRu|yR+5LK?rK4fip3Ku{?3o6uYXss^ga^0r*JW4iB*cBs~Cqxx4gI(0Oag(KcD1p@|e}|%aFyI zpJMtwPBleqn51d>PvS*du30{RfX|2szhD2dfG<>BoH1v!4Nv`7l?9j|-Do9Rh+eBQ zKwTRi;2dUBl}^o}K{K@2gqm*9I`!g@dnk9@tlh|+m@0ar@zk8dj#4KXbj}T;h8I`W z+*p(Mtg64QawWDgh)kJRv|t*DmNe720J+PmvfftFoe#s5iTkv3sZjwILrMS6j%*ht z8z$8(5&q3s)#h(sUmEv*^dfd#PKaST$1F^uX+G@(`2zP66=CeV50o6$(X;}oKxkwt zwW-dJ(hwv#f2cEfF;j>@%K_N`RS^C8&b!qi&Hhf)H&%>q9$|+yR;p9`LFQh{a#tsD;nJ8N3M5} z9ur^q>7MJ+`dQZmyVgfUGhETZRuB6+si__f#xcIlRrmq)oxJJz4jgSQRyFg1=_kXt@jiy?+%@t}@eGAJ}3V=QFcp2*s} zfv>HSRoDQ90NPl|5}o?c)dNO=DZxnziOgNPR@W&^tri3L*jzT4y}s1G`^sS}T$lM| zE5Nl8yO=kB4d-0k7`;U1a~7eTu*-2B?0_Pc(8n{1u+@Ut&yhs@cjcen)GeB{ZkJ0V@-FiPd5Ao;fuP z2zG^2!X*%rJlYvrMCh|6x*Z0Ilr!yWf)dK5W!d}{F!X=hMK-%?^0T}py%ROBJJjP? zSd7__`)iWPmx>*%vQ|i|bPsUe3m?E$^TD1LrDXqbsH#p9mz%P*Ab_r|;(lGb=gFY1 zzJ7hCviaUo9nr-JLf+38MoibG4iKmg*Mqb5J1WKsAp@{;t!%I(*!39NeSel;rmtwF zEMe<`%hk*OywgVJn)HU+I-$11UUY2Zii~RAX$CyiUN-KYpsq_mucKG7*(Zj(VniAo zK`he9>bDU?d1}=M9CRB(1MI|JWx$nzv9Hqqf3&?Vd*e#eu1#``*v*_jwZRP*SYSXv zfPsO5fq;O3K*I(N8VCpo7#J7?1PmB3V1egPy~lY~wbW9a%&cd>TWd`cC$`id)%AH* zl}rV3h>ek&<2fqTdMTPO#%1n2C`{+roO@vZfa7uLUGhXd{<*(d9xw{FPSfXx>x1K& z-grs`7Li#k^XGQb)$IUDL6buNhwjcVS4CQb6$InC4V|lrO8VIOw#4x9;Og-f5|&y^ z;bg+okpjpcmpLfrZF|6O36yLI_44$OYG8O%>42qbmuN*3HVlm6Uc{}`Ko7%Ac1}%T%AvHR zKz}Pn+ZiE#vMLs;=Gc$N<;yZxF(jsKMxnyU+5jdk=AgxqJ|$NobZ#+7EY-9D%}eMEbW!TA=0UOGKwg^`o{b+ke6@4}aN_w?b}UB9>gqI6 z05DjISz-ZMoNi>pL{g+s>t|qBI(c|o1jQaLKW;#kSSb%B%4n92m1F96W2<#EqJQ^+ zn%2_5aOq5gy!_I6PI-SmOKP!m=nW8n%QmaSMu zS)JX3MS3esn&XQCCE{vw`xW*kR-xI5&6$9ZGVO2XJI)AOJb%OH&30%b2U8-1o`t+r zHEZlWj;SX_{f+7;gKPm|epPKTHSsdHt-U#h_D(jvdYG*=TXsmMW@=*dYDE|C&y4)l zOx!j?8YXikNe)h#;cQ7HUS#s&D`m0pA?CNwLQJ!ff48b`T4YDHm|jRbo$(TqrSf7(>$y0wq@AgPVDyoa=wz^j6sZ?RUDW5nOF8sYw^7&77No za(-BVTtHXd_uy^-!Gv`{I2o4<34=a|wwL`wyxZ`6vjNLC5y6c0dTzTOl$AWZ48g7&Y> zy@oQ1>C*46ohGk+fWu$jIJ_m--#FYqQ8A&;7_WO3Q#(&SyiV~}k^EebY(o3@LdrWu za=IAuLdO*Z>XLL=e;R(Km60A|S43?i_wA zW}cb}ubd52heF&y*A&L){rSI(4)G7=iH`B!oL;Ra$*1G1$0qFaGf~|5+#SzWgT-Ti zKJJnVH41=zoUD%CM+384TC;ro+ZTh;V`^wOB`<0D*zEk>uz!aS8dL~}_YDtuys^#Ab zDg8N1vQcsZm4Cx-apH}xeg6=xQtRYQk!2+%g9tl*qNepOq6&+(hhYfsxLU|IT@$#n zLGH>Bccuj(GB^!4mgum22)#&T1F&DK8`=h89y@6}#ueO!LO>|@y{bs)`=GRSfB*_{ zc<#P0T3;&&pAL_C?*<30fH!>i^r>Qvx+aqTr)x9q22_krqx>4`*SBT8Shk&l`^Ov$ z&B=};v~OWlY6W++7%?kT=Lq}^@t2dkSM2Q^7gdus{JG58%d%lk*uskEu?H8k?=)V2BOBm0KssW-ScGzuxYpUH5tMFo1t-LCc|Y7fFp{< z-JJv-8jRH@Tp+8#`JJQ3H+I*YwOFbNm!jx;x@-E>E$=L(gW* z2+)xTO`_qnNr~91(7EDLjsnj2_zL$n-hk!Qpsu9nepJ=BaZ!hz2K%#?(6XktxzoF0 z2{DaoLIoxr(9{o6bys^@!-Sa)&qJgz4lXcAGKN8Y(2sxdnMxF?u8s7TaJYtZIh7vT z@NeCcOw9_Gv+-eEpRnz6V*ida@O3UXIiWo8RRvh`!;dJj$YyxTA<$?Dt_4 zZ4mVJgR}{Ldwa1yOl3GEEG!|Ex&-BFK!W@g1E90+m%5NDcddgNCQvqI$X7l>Yzax& z(2&zh?r8OyPo6^<>e4%uxNgp^^|=b5Z`TSpqpw@C>3;Fhkk^9TS8}~L!(MsPc7z=* zhit4&av{gT2vm8U z%Ev#el`0bz!(p=JL^tOn1>g)&X^|g$Q;48AqzA}zr@IYMxZkU5hwx7vCZ@40-sxCM zp69OrHd+C6ouoHEXGam}IZzJKi7VM~WPM+o@0LSx8frW@H1}{*9+4a>D|MVCNq7cT zM`EUW>{(7XD|FCx-yk|W2q@g^iF>%$F~wsH*CvPIGL94LK3UC0J0mgPTjeI_zBl;U zC5&F~T)Wx#N)xAU0u=Ru{w!|bI&WC<7-OVhF6itZ0_k)w%PD$`-*syfBsyB*rVzAJ zGyn`ha#!@J=5O!E7uq3IrSW%cFaJ7t3y!U=;I@hAnHPNm<*unKs-%_nGop7%b<|s% z?`NaM$J;Ke#>vjP0vy4MlOK&^ER0V2S885$OA+LT4U|)CX&qdQZqVs!zo0$U2SQ}6 zp$sAvo}~n_F6tFRLLM^u$Z{~)&2lhgW~me3Lf=XiHI-IJkA0?Q%bDmQ0<@AS_Fl2E zH~9QO+0D=S&mym^O%!~nltU>wCU&t9=42j83y4iVuD)nLM7(puwCoa(0@oa-Hcq)k z7?`w?7qkdR5I2Q;huFNjDEe&6A!U?6yKIk?vnPs*GwLHNXaN`v&45UDrP%zn$el-= zd0Cm%q*t4&ebR&DQUj4YREWE336xO+Lm%ak(K}QVE;cV09SW8G$t;(seOWh+Szl7j z9E>uw$D5lPn#KRYuHue+y{e_4^_yS6ei`sL>}wbQjC52-w6V~A->M!M2E@2(M)N4V zYcU>CdW4)D4PMHr ze$^CDx-1;X*I1xcbZ2YtK9Nd3@KHR~RSF-=2GD(sdzen0TqY9^^r zq^v_<_uye7m;JlHSuNr6N7E@dk^=no$FJEon9(^LowqtKV|m-VDyIuntcB>x8a3gP z?bj)p?$VsU%Zv(77*n!@hZh-+MOA!r=^QCclNO#Z6W)${}MtfMjjZk&vTM>Z1H#qivH0)_Ljgon0?M>A7Ujg2@s$VIWY zX%D5o))xalE~0p7fUIlm8b9k~CrxX{%f&^k--|EUo5Wqkq}{8@;Ds?p_aD1BW_XO* zmJ$=PBrU@l`H827U5)CILtFvVY_(BxmBN&}ROD85s9925(P1qdVS=eFGVvQn(C!d(@z+7`(xP7o zfXdw`&yzlOf3WyU*~S5Zk+Ckm0Y%J5XXc}Xu3hsa@oBnC)+iwrX)@nKB zPYmh?lYk%IXP?!cBaMO;PvD4#- zBgUQ6jd`cX_RuMrOteAXjH}NS$j)(e%6Pnh-DNp?bLH8dBderw+t%*f5MQlU*B)MC zyoER&(`_WceI3r7eRvvA?Rn;=Yp>v-0n4=YzT3Gq~LznVG|Kre~=~JC$Qt zB)(l*4Os`}eC?h#wFVX7ayejEtu}cZRSD)XUgR2EhS+Tt9Q8IU%x(X)ZS?YqIHz>b zaX1tEDidV{Ikz!Pv^Bv0Y@xi}*td6U58Zv?>car}-622l^8NAADm#NpVYr-a9MbG6 za$(~XS=kKD7s7FLRu>zG2%QhKI}Zs|q!egHGL@Pwx_rGmbIVSrc$2}stZ%qfWsFy_bA&#Bgva!?VFj_nCemJ!#?hbU z-TZPl-O`M6Jdf5Cs?4;WoNI_nObNkn-fsP>1ov&ljAk*JkiMvWwyIKSP#E@V{Re%wt2t>$A|v-Kr-_*9CC;g| zs+>O9ks=~9M~fCnWtLUPkZxhVzL;LOn}-%@svSmt*`mc{w}e{hNC6J4Pz}pYrA8_zSbdBqFG_RE9!*F_KCu z#_<-{FT}R(Gl34ZEjrl{s|!i=;N!qKD~-EO4Ds@Uu(2+gdmuIhBsEkVSHY#z&{o zC_uP@I+O<%-eHmBg+A)g#GGTxY(bxF)Z_j|x899^bK+0|$IzYe78cb{iA_$D=}M{y zVY$!SIe*-og`$cJO(iJ>LCp9PI%2}$Gf;Dw>X7b=sphM{d z&0F+;nT95S;gCoGT1M2ihd?i={$Tt=V>R}18$*Adi7S%*S=`vXNMZ7e# zk!cH5Nz0zDU&`wO>GoAVsj!0d8tFC()Cb^D>p)e6WnYXHXyl=>GWOfuqty1gdl#ij zUpBqgb60Wx3AK?@GsLVn-{{fR^W zvCP1pYQBdbg%5R?Lw^^D7)@ns5CA`zqP}?#9DnuUhN1+IKl?aDj^?cB13lh^rc*&m z4`DamQzx|Ci1m?rm4^Z*HfFtg@RSRkI#0^?r=<=A#Us*!S8bx2dKMmy!-1M?YT7Pk zR|HR4ZP#h&`tZP{Rk(Q6_;==F%=c%ccWlAD3}G8CuP498F`fU{n1%% znMVY3i*un6u?!zuh;^*i?g+w_4zUnSodMK0^AVpNFP&@XXM)tZDKyYA4A zp}oXu94uHa$1032jo{7Y>mH@`6Cxk2+<;nzFRy<0086u66?_s4wgPJ>h1Emi$0RXN!V#UfXpt{hjuRtQ#u@B=dhz=a>(&!iWnxSeOTl0~OhR|cX==cZg&npJ23{w84N;H`96!_u$3@*j0 zW?{m|iyXVOIhj$A&!`NKHr|BYg+hMPH->N>x)_FcL=*^M+OD4Bn|cH@A*YYspiPE^ zGvA9PnFn_arF2!F=jJmRVF?>wtVvl_f#{_ybSg?ZafXWvrimyxg|&;B?WlE7zNlB( ztLS>Or)W%Aq9_8FK#5YO+_c?+*3G`n5%!JA< zXWk=E zA<_S{iDw7~WF$+q92-w_hUWH1jC^;v3`SKCble2Z7ef0-lX~gD*mtxK9_VxezGrDF zw`VfG^QxTpFCQ~nu~lk)2*^LFge0!^Y9k8x2{ooGbS@NC(NTC2Q$u>6 zFjqS0d>K1;C^f@zZFvTuA=pbKzqu`N2C&lr+}Pi(dcRsj#0SCC4{M2s!ertNO$PP8 z63p%L3L5O|bLU*Vnku)dcokSYx}C%gztRXq6N%Lwq=+UdqE^?!pUx|)RBdxkw)6T_ z|Jgz*)%;X=MYkJVjmMgNq!W?h!zk)x*Fgf)X?gGM+APA4aB=d^LQdjam0>G&mO4c` zg3ZnYhRG!WVi>ZCo2Gjyi@|6XWBy9hMtgYwd8cl2AS7YOfWSVUy?0*->W?GaR*KMk^vfZHb zDwEUm5QDrwsUj7T1>#QY=XSm8?_Cw;$+|}EZIGctMl>B1yw!_e*UeytyPyV?Pzp>#7O28jN?ar#7^&@=m-xtBTv>2_9 zIy&}0k#s8pUgBzCw{#-(O?KBJCnT~tz>5W4)661!tj1UT5s*m4XTxv?;Od?rcf-JW zb$=GgyH(&4Dwch0e`DFB(unryiR<^a%Wtc6nDJSbDBu#n~bOZNli6|2?d#R#t0h!8ss3cL!nUMDO0j{5#u z8CvxkGsXoe>z&1j$N30ERlKB0I;QxF&^1;J7wJ^Bt-x;dS$tWnr^<`vcyn=|9~2#B z`e({8?qJzHLR`yFlKdFD6A}p5lWOP|LLLDbv%cozI;%Tdg+;%X{xn`Pa<|%ItE{tk zmneDLXp3y%(6&m%+^(|_&GO7rSB|p!AJ({jj#)xzr2GS)_4De@g;;0(%j6cGl~<3? z+}^6Ump(-0t>4Hs1!~iUe~>mQsjwmDS@c#s_g{zZ818Ep^xG8Mk;N%o`akc#XdbE3 zCbr#gIrgJah!e7nUFQowGY&2?0K`e+9bMhmiM zON8c~_u)0%hF93AeWJ>qz%P@odhaEM>lg2$qxx&Qk06wVw6aVv+)BEw+I0h3FFMQ1 z+PP^S4veEpOcD?W3}u-dl@$Ev+w-Gk``#V?blDQ%zJ9qMhtWYpGNOFFeZ_H^e^|m} z6Rq*}Ig#&2`qr=@0h#%3goy$m--pBZTRgvAd2>>W9jqg`6{?j|g0i zc>S6Or_26nc!M@i69;9x+bNvG+h==+?iI)%ZGm!Mp9hPUz??^Ar)-M8&MuDSvrP!Y8 zTSn~+qYYC`!x8CZ3GnxD3b$|!Z&UM1?hv-Mnw3)GB!0GJ)aY4STdh>dM-}1u)nOS>y-SzLb|+IIJeR<(9*#6=O>Ui{-=3am{RS6+rK~SpfuXiUGQShnZrM{n=P1$p5e{ zdMH(X^=2F?Xi!jJ67jYemxN|m1&g+wp?41U_g{xoyx})2v=RDmGr-DI7_FX{?+n9w zkFV6Sp!Ey|WY)cUF$d?nKkLIGGU-G$%K3XVM!+884 z>o@+U`qs5x1yi?18>TVkUx!N{)O3wI5$QVV$%dVjB$P*p;QFju%6K#I8k5s=zFt+4 z!m022yq0RnV2_Dv4}(@iA5y=8>DQra!vx}N$EDEBQ_1tV&+#}~yY5guy-7gdrJUc{ljZiaEM9<31$77&X-C{~kwN}3 zf)p|}k-kKq|8~VrC8u9+SQJ<(7q56G!;$9!5@xUKMgIB8+>>fT1ZqNDBVAk6 z!S(y+E83YvG!*zZhf{NjG-8IH@NUzJ5zonNL?kR**zQ+#7llsV7 z`P5GyW&6k_{$(6da8y+ju**qA$-z306n{mAI1i-aJXv^(#gc;C@AKEV(wa4ZrHh*) z3~i%)KU&XzKF&IOf-;fRix7pFQ$P0IeOWWJ5j9E&uhc$WGGo73eD3TGx^#?kNBVI% zDzh-g`5Fhhuni$hq2)Yz!KQwEc+*!-pjY-(cM$+HlnRx^ zfYu6?yfTN18!J-csUF4s*gU?J!;e96az1ywY{l=bQnIcv~336F2Yt>8HuI8EBqN<;O>@Fd#_V;`IF22v; zA*&y;rEV#@R3-x7V=c?SfZn764yyH|PD2vEUb{N>e>f{6i}E}=$~=Bv+tJhYfAP%x z)BFpzS~11x7UES-%kK2;Kj{Y@%JC9$`LO@B4GfEJE1_Md)woqN?%s4ReMey;n4FzuEk)w=(+ z()}%;!RS4e%mik7T;fEzhyiB5SF+99;hPiZ-AFXjVy_dqYKJfaG?O@oYsp&!HP9^yTm zPYG`rym2>^>QiYryea|aO?V4~-tvtY^7?8!R?Sj%vm{^z_8!TfI_& z^ipCXB}o>D!yI2dm6lM!I9yU}s->wvpLT23jiK#grX16V4B@=RnAxWtU|RXnXngqwkm4I8Wu zU8|ZkwGYqvfIgp0Va;p3xGnRMcUr4FoMNsjs$Pl4+SA8X6r{GY_3K-~379N$q)uO> z)RXSwBLvuN*D>GX@f>bzDLYy;0@JOjVMk0~q=Rg8912|Z=OJ{jif~kYV=GS{VEezS?sd6N)&oWAOiMe(rODF*szk~1!UD~$ z;79P|fn2B|rinw>M?zO=a{o4bg$G#X4U`%Bi9p%B?)49g?$seC;w~?t5~Ql*p!!ED z9BP|5gg#A+z?xSj{9JcW+3wU=H;T^|W`mAv72ezYPL zeK`BXj$A?F6l+FShNPIEehXs;YMCdzQx%iI(NXIwYtcw(Xg16%Khq{fCa$O7#Wr5k z<=Vu{6uT@Fe!bJpdh>)Q^!)8^-AoCzm`fhwdwNj=1qnfU-8SP{i4i^yZbHZ{oujdJ zT*eV>em{ox`ra(th1^-w1p1|;!aBn_AtStitDQB4WptM8Bq@=MYEWrVW}fgUhA$n` zxOXHzTG8pQ7 zgV`T9(<3JhqqW+SEd=hP>0V9Q+ppExHF0cBK^!^PsEUnzo8o&Ii*^xxa}7&isyL*y zEc@_+&6DdeI+n}NjQi%Le#Sc3+URBQUp;vu1KMYu6M z7N5dJ)|^%}4F-zI2&uau*a$qI_*OU_=^A2?G3~u>9lTG;XoNVZ2qJWA~}wLYR&I*Hn-y9JY0{S zSeII-jPabQ8LLq&GrtDqClq#F(%hRw~?GQ zKHd|$_DZ*tiJlxExR^e6UfL`I8x}8d0?pd@nJ|DLsFtY0JQGR3BYsIOKLllTVXM1K zDT~)-R99;u(m$0Z9qWVdb7&pvrrSu((l+<#ASq|q2P&r?CH$bdf?%l2w2gm;x@BJl z6kYuvTOy|xf;|ie3bJh7jbSKj(P%M-Y9Va@wGOrveH!}f%3^Py&NszyTa*6;p%-7n zfb7BzMNm+o-v z=5T%s@fI!~pfM_RX{7_U@Mft%I)94_OOLqSJqY4zyn?Wq_h=!qGHwtTX0~xP^Cc0% zAkC6!Dj$F6_1Ij_7FN=Hr+1s0?yomY0x%YYoTRrsBF)=J%mSW#$7x92oXVMerFmx< zXvtFz=}N~H;FlqtLm%$p9Nu@gu-|2RAHApn-8&(S42LUEkq2E(E7%-(8|Cfan~@@T zS`}SX`y;Ez*}q-8Tdhxi4uC;xt0wu>%nmGMn_-qRr2L5BKmoi#ms&4<9}~x1pbQyFv*w z%DPb;0GJiza^VEMb8cpegterp{s?gnT|24Wi|DD;9P~vHF{2%qKwVjTvzYzw^AK}u zq&Yq!&2aCJT?{k|e9iZV)Jx`>ZE}(BF^>Cbd09?~{NY-R4np(6r9QN$dlOj$9aCh;61MgR*n4Ts$J zyEUL^nu4&);*XV2;>K==WNl`kvq7az0Y?h&cxHC2c-< zAA9-GaFDTDmJd)1URxcrIYlTbrXIIT4t+;g|k*Y$u+xhE_HyoI~!tcuxPWc$` zDPA%s#lgR#9j0f*1ix8c#Ykm06 z+QZ4RnG*46f;k-i!ba1@Ub2d4H)~lvMox7e<^)I{uC+T2IUe4#?ZfvZ;Cb6C2`A}ciC_;9n~C3F`-*LChEaFJX$G!k0Z(kC!3Pp0n&TKYX}>N z@T-j~DzY{lQ)X;pn0p$wvRxd;u{)%8pc*IN9uesn4u4(aB~LT8mEq#*CKi7l{u_V2 z|Gk{8UnmgDa+af|qdxpFfx9-Sx;il}E8;TYbUB3g3i6mE1^Vj{U$Q z1w(LySjz>simIpM9_kI!v2rWZg>8wzndo$NIIj5gM0x(&7*$8krMno-#rEuPkueaO zJgUyBxt1eWsWzOSj4i1@6F%y@S62uKus8(O)J2NPE6Np+$(SN7J?5Pj3$U_0NZCG= zmn~H1YrM8>thgefGCMa+{-V;*vs{50kS0Oy?a~`d9>d}9fwnw90XHz(@Z?`R1JBlm z6O~Dd&gV}gRVly_)w9YA9r4XcFMGYC4F#LL z{7g>T0ZyTT9Ou?dZ|@qga5&>>#)+v;vJ)+$ab*`P0=c8{@p3qMMQx8ry4w$E=XR*& z@0WPT;eJ|7Tq61=pe@eeJXU7#y?FMD`B_AHr;ROwjd3Hb)i>NZ1WB~(xoJ|orWFA3 z4g!osCB382JYGuxp=6e6fs&OaZLBpHaYho{dzl4&%hi>*$kZL@|}_BRnD z>{1pgo)Dx5_scn6_5HP0sG1M}VH^p*4DE;;WiwjUgo(MfwN3^$G=)8@qveFN;Sw}~ z<$>sD=p1GyK=MoV^oX=9&c5Iy-CwpYrVfZ;=ZuUf z%aa4q$w|uTrB`dv?L5bkz+LQSc_?rkNOG-do$3V%$*r8cuJ2uEqJn-Pf18V@6mt#@ zip1B|V>ZiPh~)GMMeR1VBVVCNy&FORnCkfK#qUXsi*FiuEeOKpphE6<6>(P^fz=G% z6>k6dK$+!;bsp?;BteTq2#cBi8t*mf?1;-Fr{^mY0C8oiCSGp4yXyZ@m10dL$;yN|pqTU*(m3TRgJ$-b@}yR~*V{G+4XM zPSf`3&>~j@#Wa$Mugzr>LmLqh6GHyaS9yNhEdR%5MGXjz5fgOC8_>v zt}QldxQ@cGwxi1T%4FIsK#>n@CEFT$VuG%Q`O3gVq?fCN2m>h@4*VEF=x6n{qvd)9 zyWc*kj$M|zzsZ(g3y^WUTjlJ+Qi$_{G9({&_3G*%fx1;64S+RfA+!|X0B?E|1Z(Z< zvSArt+laH8sd4g)d~ku}%x?JxsmS)t)ZR)oW8Y4<2+`|RZ!1|3A+#s8wPw=i(8w%3 zd(pqSs@$_z*KMO9^1Bk~T|tzxw1BelWT2vNws@WilkLz>$%>D%-%y>~B;(7co)BtS z;r@}6U5AE~Th1)nGa10vKTeH7R9?Ig{yW$PTMYsysVVwiPJ&aO!Os42%Q%+Af9WK;2=ozT6Z0r42<#z9PBf*X`7Q>;N zl!__9Y8i(q6I?a`3IT8A@c0({bU`{uvh~LUg@P%lJXUpM@;VZDfphKUuJIi6Y$h_# zxdLH0CG~Od)D*sgnrvvG9P^g?HbST;tdJm-naaP|ifN^G^kZG3%NRJZ#bV&ym}JD` zn&`yTb{-a#eXE?-O6A?0MVABS(OsUvm1tP5M7AzA(W(#I%GN4(@B;EGB@G0zLZLRb1Fb7O0aWpyNZe zP^^OOZEw+~>b8qIemx$D8O=GBHj9uF19@+*8}V<&&;DJ1bINoQ;QYjFRuc~~ybwr? zD?SHXnj71!1>}ng^r6DpV){AO8%!mWhfKN)l(Sobx?w#})IzJNH94x>1^&|rj;5Pn zi&2?D7GP+RAu<#3FR{SsB2N#e6fnl{lfq}RkcF;6ec3EFu*Ed`1d72a-Y*}Y*~cPK zO)!PznHxfogubIH(soyIvv}PbR8&{E%Yqe2*A$=2w9Un)>V+E^lzu*%!C4(5hxf&m zy@Mzl)v6l)(aEeDLSsR@8>T-M;Vgj9JB>(3j*F#}Y-qhO4srJWC5}LJ5=8>vL3C?p znS7#1j8}=}8&0xe_?DBtJ-%nl{i?DgZ@n_}y$CbSd?j$Ll;+%~ ziG+oG4r%D*Ya7I^&3PEQMb8&eERaaAtXd@86q2_S0P8?;zqzqQO)mpx(P*x7ByDuB z1Gs_bP-cal^%X9_#-|`dPuaATSS&178JY+WQC?-}#h-T8?9!8|5wv~11*J4^_CCD_ z8k(NPWvFm+r`(8d6kUji{@d_G0F}N86Yl+08o6BTTe`&7qRB^58VHFZQtavJK0U7v zKE|oJC(26N3hd0pDJsx$R0@xUuqBd`5 zS=3euKo4VZ>7W+}A>LaHi!;}GpkwSjM#|+;08Xrz%HSsb8Qr>xQW;jeUp>Q0v7XgL zMn|GB1l8A98weSIn(qh2t2*$@M-0XcLi}EaU4~qok`}!+rhJ8uQFpE4S3B;tp@W4Z z+Oex^TZp-*X1Fjy*8~?+sxC-ngkr{zmUitzhw`IX8f7N<&;Guh;B=QlfyM>;nvX~$ zFsYa2HJQjbz?Dib*bPrdBynH1a2h=VTNGDuD{+dVy(GF1RKSD##ap@R9-6}K8H-D|&T&#<156D(s4nEE`WYSHJM30C-I>N)d1(6@Poovy` zISpN%$f&AaQd!+D5M{F)%C`a{Sx&{Kn8;6D$0yFfz(CY)MQSS79|rN>U-jrf0Gwyj)80=r!l3!g*GjV-sUWFchOJ#))oE z8$u=*V4+T30Mfm%T_HSaTO;}Drm9dKQ5ckQsnI)_i+f9B1j1DPT=eTFLkHN_-`J#5 zsWmrhL<$8>dp3sEhP$2`b8}v6#%~{jEx)>}$@@vE@RAtc9%#&35TJa=t)6GCm3rO@SviaIOl!Qk%d1svuT+cH{v;A)fiMQu#= ze{^FtG4flw*IU=(Zde(w^IiAKnV;u_POHt0Wa@G7ZXGT|)6d}?Qu>QS7`F{erV0WJ zMrb@%!gjmdT3U4xf{N+Xi4>t*jA{}pSwM&*`IFFfNhOi7Hw5#{EyvYW3?k&{JFpMz z$LkR3(g3M@5(xRxRT}Cp2X4bWi-4Rm=yg?tv?_^tJbf>b{z1)8P2EhcZf@>Uv)lE| zY4xO@h_!+%3_-oUL4A#AcB~;>(%wMnT&Q__^Lefy7fRCDnH>q|W>boQphR(#Om8<# zDRUJnpSSi#o8Y@WhxUwLnw?h>b$7dSpM`8N)#PE~@RH*bt%PK$9q{qKUUvwAwYaka zxsSRqJz*cTPv2<~Y#K^2;N=w4KOsO{f@a8BVZgZxS`J1V*AgQoXLqCjG;n9j^kLnR|)uI!Q z+{_4my_Y;XS$@1sQ~yedpiR;eIeqB`zr-#z^|^&|j_8NoZj}$`@|Kxat*I z>JUcQLJ_csVp^czRQAn)XNIJFoMK)0r%|Hx0INhvh8ejs>VZ$FqndLz$Ia{!nzp!v z7j?2&-bUZrcoj=(aKHn6vd$P)T*fn(9xcWgY{JUo)6IhPGTpX6+jqT?^*x7|TSHZO zwPZ0~i5y^RA3)oqGJUIcES}9X4h0ir*Jt&avB@~D1M|$v`RS&eS!ydNMXd3rW}GzS zEbbdJ&n&}W4N_n_=SHOfa09-|nSpFk7CRY_)Vj@qa;#iAo*u_S$7i8V;KwPZ%2O{V zMy?7>&?^;EZOU}kWl{vGIo5p-Hk>R@6!0fo^@?RFNr#?DrvP4=w&RUt+|!2Ty1rw# z$K-r14W_dEkzfK0U?S$AP9It(p5|4;%&Huh?q(1Cdqr}_o=Q>hScw5t8>5PX3=>gt z_OHz@l>=R3V=(bairj0Z$x?Gq$!3iJPFgCzIV9}4f$EAD=1@?YJ`@ib#?{Wpr=jX2 z%OFxn73F^X&0EVm--MbRdMah}?QB}5zuFEwjcr7=gDaFch~3wzUoJw4DYLwa5h(Z* zT+oy7q`R+StJNNwChMYDp`opCWz*?mKxM)Cp*ZBBQ`vOn(aCsN?Ogz}i#=>v)xt^;X2-K{YiAS+}!E zO3|wBuD^rjXt!@wvD%?qxB?ByVqPjkK0kxDBV3dgg2A+LAWc9hx`_XuZ*x zn5%7K(LzNTCFEoLs|h$j7>e2kdB5%rL()~E!S-s^*qoOr^Qk;4p>yJ(fmxm`80wro z9NO9Xf-U`VmT8J6QKn_s`oWT?Mo=6|j~3@o1E)`6=z_G|l8zA9?n&Dz-19KJiub-* zyAh{g5B&8O_?tUvX;Rj7lrgm&QWn1TUL@|LLMyi{6mz`Zp%`<%y3vQurB4n|zF6)D z6}Q`HZ%T~IL*K?RT@;hW`J= zGtq`_UDUZxAo!_c;D#l7A)_Lv9wbs4(eF&GZO1;TJ!GJ9b%koA89~A}ci0QnS=7)7 z1%<0-By$DT*sQWaY>|d5xk(yA7h^nnbl%GRZp`hn428Lpo`bK7#UEAI2zK+ZPS9Ct zItvsX<(B+huwS@YRjJ(TqO&|2;^^>I4w-NKaW{0xC)oL4dG{ zDte0;#c1w@x$p|oAc=R!EyuQGiLokIVZ|4`Cbgs4GLS;MUb|+O`vxsU++W@5S}zfg zN`N@VOIF!9C{c@Y6J5+2_mXWv6L7oqSs43V7eCl8a#8olEL{u}n7k>iat*@shO;}! z+~&I3awJF6EhbHDbo{xOsUmjm4w3m&C@&n$wA@Ok@^r0ulB#bw;N>ym3X(Qq>fkN=*q}{Hmt;1OBsKm{EXN-kfZOMma9phxZ;kk2D1Bzt zV@&4-yUHsWujylJp%|JSc<0(>N?aUsAwm4miGtv+tv4MjzVCvsBUs+x0il^CjI261 zoBD6p>v($c%<9q53e*$=NuhF&Be;8q(l4Fzyj!R&fSYl-0`m^ZK_^g!l`&$)JD9FP zt4|`wTe7m$%f3~9{4lB}Ju2q?7coC<0P|0i*h3md@kZ!8*rU7_R{}IEl9jDG)Zsvh zZWhlDpSVO0P0S0Fazk3>pr7;5QSk^*lEEctmJ`yoOIcYXELUX*udk47H2~<+9aN#k zXANj{*-@1ULh@UjR4&ITwKO+i+Uzn0P{YD;N~w&qI1Ywu%}`dfgU4G%PpQgyMbk_{ zojYWBBl@kp84I5>ssYXquScUYKU+~3yBU@s!txDC`fd$13TttAdlb8n!TBA+dtPk`VlG8w!0g7u%wC47NKahm0Fkx0;**ujfU#6@d&mT;_*2?g{~BpgKAWM$f0tig?-&58Xt0c^TDi;SbmA$}qy@eB-Qr)$7R5m^*^M~rQrcYCOyrUm7l~5gFX4@);Rl8AaW5pE$UW;sQ zK;G_=LcDo>ntDt@ondqeWWKq*@(z;bKL*txSkWk~}xWkPEuv_gSK=(HGbLB9CdD%k6|9E0$n z%0fZL`j*{+Yg+X~t81eOuE4rb`x3MmOdaFOG&m?Q$`Y{W{>kUp;bH;8+lQ3*eST-dW zlr}0OQxYAk)qYZiJKoWJ`WAJHl9_9T304snLf;nadgU!D;wYr34jkN0{y0^bjT6|8 zlDl>OB95@3^^IzQ(gK^eGCj)D*7C_n8wzl4R%b_aoQ1or&?nWMP%Z0?UnbYq`7UrC zZdEj?2|dj)IUZwOfz?_!dIQIr2^Fm%P$gt2xtQQaC~ptkmAem(yZcyaqF>&aGW3s< zen|`T0sHN()w}`p#SthiDn39j%aeHLax?i*cpJ`Em%P1M8eepm+3pKM%oTf=4vq_Q zVTD446{Tz0L_6B{0clZw*d13rbsNB2IGd$VFQ@quxx~Rfl#P|@7;~d6THdB6?&eh- z>sZCwYg6_#iVfZ#NUWnDtTF&BK4yRq)DK`Txrt|`oU(gX&zYnm!uT7?o#Szn?m*G9 zP0F%1s`S=kA%-F^6eQy+tWfNDqyI05Q8oHQmpdw$ggDk1LTC$JPuMJX1Y9mw+~k?u zvHqD9(UI=K?oHZE!B|Rh#_ny)o|^Je`2~;vc2p;D?G%Mg)ER7fwSCeQPgpHXcvLol zq!jLlDS(4JD=DKP7AKaj;gY8zx&F{&zO0bQU?muMfeRSBw9=PE*1H$vqpYFPD$G}r zW*gfzwrF7{C|NQ3wRj|Zkq@p!(%O3*x=wK6)gN6DzDhk;r?fb!wRX|$%?G<;DbxVVoQY=d zn$o&c;e0n`4r30%a*Ob`WhE@qR2Hs}26*4XofbY`%0hmofo&II`*2fq9JJ;-IyM}6 z)#j!#(ZRNU;+Nju9$<8dH??t64irg>fpT9mTGcL2Mi2^_vsj}?2~9@msw%y^hOioG z#pw%w!8_0UasN;w6ECI{ zz)wl>c%l@8NHi0Kmvl-(lqxc_kpc?2`ifyhu@=lxehbQ2%e6@V3ywc|ok}g|sClq; z+bYb09i_(irsDVJ!6nBlPiKmDUQu<|M}wfi6)$~6nP}s=_O@*fu|ya4Uz!w9CAs4i z-kbMuz!)jyJ*OjmCA>?>>mN0N3r1=#*dQ=~WP}Jze6pQ!N@rXP1s~fTLxZ?rhOsYY zEiR`o0A&<#(#;iLs*<}QNoZdKC^azj(8ifo`6gdanW|C^*7~GyiM~;Y6}ik(5!2kA z)1vljBtv_f>|J3XM^WVo)*|hWjbnWXDXx{ivl)6yC=jp!KwKlwISNqbMA&WB|yZkli*dcALQo!k51+x=jvEtX_llhXxCHkn&}d6?j5Te5RnyWd$yB@!Wr40q_eyW{Xiejr5qDEnCMJx zoGI-Ichv5Iirrx53#qKgr{dqW_v4}@FLz9_%~KdxGm+XjWDqoqB^hy?tNd|^&-Ya$ znO(5t+#OytrnjH5`A)s=CG!J5C9%*GU4hz$F-=4dI7>Qbc>9vDKB2fzx}C(KQXxcQ z7LO=YdW?8D!r(h+ULZ{}BJ8c0Pb$W!t{)&_3NjdOh5u3R$>Iz|q|CN;+rtYeN!1%% zQ2X6A9Fy263KF{ZOEokQZpB4CFLWj)ai|D8q^^mSgtj1)l=TT$V1aJ#3s)KkSz9?jB%E-dYv_wE|1dCWx=(w{Noi^8}D;09}I;0a(ZR)1>!m7OS zzEQOhL?XreeqLcQEayJx!+~58k|KWNowj*^)1@OChKdbl<^tisHw_634r5Sh%;hVI z4R)iNyc8Q)Cd48KIZL|<{cu5k2Mg_(4AkXiCvsc6i_`|d(BwWYCphbSSR<|Q`!QbL zVOOeR-!qY$F~lYvas2=$#vqrDDs^jmR#JJ{wDr5@V#ID5t)Ad@_uxRQqyY)MK_B3p zGpGZ^Q-5-J zI8Vn{BtlmQ5{Bvj)^ouHJ<7y0D%S5J{!;Bnm3^jI^DH>JIgb=@bxTOH3f*Q6T{Jn% zRbxlM1Uwm3m%_99O{i~m>sw!YgvI`>lUrcofLNUdSR$po)-%X@h0;}ZPK(6MG*Jx0 z>7=-lM=#cgT4u5yx)T+6{#AU#^lz>n4LG*%li0ZmmskMEfNrf?kge5Vwe%M1J_({6 zo&;3hZ0wp`YBSS=v>Rd*`ibm02t<>n6o#|U#RRi-x8maI!#if7)x4y=?cHEHx|f|I z?S4y<4~Zf+mL0^HWqH$rc<5f#Zc~9#ZT|$VfvlM#R`{h_*6qZK?y9pvyAE8-#-`wQ zMnOCoLQ(4ovt`A!r5l<#P$Vz!W%NUwflY0GGKPe~vM*4%Tl)!fHO@2|Q-NHB$W}`W50TL9l zCw?k>PuC`X72h0L2fA*GfEmkSCMjVd6_^BP#B4>%=Q z5L(N@K4%dcrvI4$WLqA2{!TZT)t#d>kVNXiuGpXHS3lq4%PXrOtuWBn!2&~NTYwP_ zVH6v-HKJCraYvDyL=zD?`P`SS!?gpm-=sKIy>e!P&#-mVt#o>%L3Zd!Hr4pi3W=!gtdWF~wk4EV| z%Dp|je`|-sNe?8>3AH_YCqs=CO$x5z1mV15P&&}C}v{a1-oaa|s7PQMg; z)$$=vM`ExPS=UB9daiVG@MiAQ;GO+ox`M%bZNXqD+6VSn`z`O2ZL6V27XHd8ZA^>C zEs!b;ZN@Z3ETPG%OQRwDLFNj-Z4_mmtw_B@`Iq2BBWxg+dg+&o*0Oujif)MK-)6`X z&y_a0Osq^$+Z)H;{*LS(B?N|0VATJKzYgXASH?%vKlM`vQ1%M2S$mKQb`n-bCBzzr zQ=Zfm9FpN|soJMlgEL6-_e1}!f8whtEW6!|X2U`UmgPGpK(|HJqwF$ugd&hWml#rx zT{Y28ae1K60q1;E2E>{y9L)~etayq&FdQ^>I^IE+(p|-auu3@!*&#*Z0=W%|K5Jg^ zj&?T8tZoRmoJ$>vB7wkA;P@FvFWRZc=5|1}eY5U@Ok6@KA_K^TbY!_D11r7&rk3O|sXqPk(d8gP-d!L+QLxrrN#aKWM%eeAlQNMwrEY0}5*BI9XskQ$>#Sx0QvJ1-60dca@6VL~-W|!23ffJ5kvVVg@ zG6o@FTtimGs1duEdLb1Qm88~logCuXw439U-eS0Wl|D{4dbW)0aET=GD`{MaqY%Q; zx!!m(2K%&1AKF1MeCfqCbn}i67+E-2YgLgk6ERKFECo9i!a*QkiP2LWW;tixtW;9F zc4OaE=FzoZtgR^3I9uh^EUqM-a5LwkER%8#!)GhG;Q9(!=jj~ghQfm35`vXBU{UN% zX^O4E`}9fEE^?rpIYMarEcYN1$_?FsRI}|<5Y_`~&AASv0iGTM>2u4D<1e5+YQ?zJ z$<#(<%kmi=$twrXQL-9xRN;8bqSI&1Qqe-#rYQCUDN=L81Q8AdTYVLflPGLn>m=X1e+s1rdSqWt)2F9jz?RnVwfbLwpN(qW-flbXz3};QKC#?o)kn zC#txM;|6*qj8LD^9^$MTGB(Y{aBkWde#vU1^-|#{^uz1wkFIG)CCuoxL}W|sNXH9FQ86m#e%c7px-?hn)-cuD(2|5b38;E z98`9DF2B(hL?zC`XeMhS-5U3K_-fe*a2Cn*D>P;I>Lg(#2FfF$wTJE?U>Z)s>!kdg z#Ri(5rLGLD4bTTBq`)azZ`fpP4A^hklgBv5mhI-u%|QiHoPLdvkeQ><4A49%D*#!G z7&gVe&+pKTjM+(M@XB!V(OB!QGlW!A*c#3w{6KDJiMb|o8L#F&I0_2{p z_agMT2;^4!Vm}=Es1{0IA(gK0$8Z=(!g*f5re`Q1#}9v4wk7rv!FRb5#KSAA_43ne zvQK;n1?>tH=ByqklGpGrV*>^?xI~wRT#j?SOYO`}z#| zob~mypSNe$wPK5>q!7-=YT$gHy6Ile6kA#iY6D?28i*Yn;~cD?XgD^j5sd zSh}H$mYs27+R_)QKsDeQ(v0QmQVdMIk1E9cU`l@FLNw(igHgbG>4ajk$IUKJYVG*Z z^z$<*0#RRG^>yKM84k4Dw57+|Ae;h&wG%8UPLxS>-pbl1Cj{#i({adPo$TZqllsW2 zES>T}LXAJ^dsH#CGfg8w(=0>ZgN946C>``~*|Zl*m+LpO zB-owZGT7$*Z#g#t(p)Ax4z5%NZvLkneBq>hf7GG_NTGCzrm8ArBdpEznM}x%&f@az z)OKv5a+;O^h@dO^#elMdoJ`C8&-g;JOKyUS$l0K&nQ8{w1t-EUtmhcI<&P&5)k zNmTB)0ghhXzU&B^19cV@DEp(|{slBw`ZA~T)~>h1@4$ ztzKb`&$=(K57<2}OI#CVcn4?V^Ps-|{%7=NizHEs3^tClOh;T5XXS$q2Fo*zp)imp zJ%9aI(q}DDF*W~*KMrAj6%D-2Mws+vW}nMBu`7+OT7FG(QRjO+<>o*0&jU4RPacgx zy}Qy|m`~4azBqtxa8;lBG&M$OfBlFr<9#~jH@QyK>-YNeAOx}V`m~g*8S{5yP-7?&C3W$4>3)Xb1J))1^w&e9<&bH;2U=gFwnM)f=^|`&}q_ zd6S*H9VUzmzlEL6scj7eMz*+z!0Cazq3oKfRl&o;jSWzap{L&03OfT)wnC&n-q9gx zpWM=xeS@iLR9~HtFzI`A+!_b#nO-j2Yv?lfN*v1C?nW(~=GZ1n9pdd44Se&PnTvbj zJgXnxFTXKkMRB-#elQeb>Fcr#A{ZjIq~Z<{JTm-`Y7Eu`X~uxuuTgBuFyh&YU5Sag zYQ!@ZF)dAsXmao@uTKZ^LgOjl3z4)B31_ErD2z4NLlXnNx5sk~?_=m+=liw%?v%oD z_uId#*T-zQ(TZ<*d4 zS9&U(tTXnQE&ccBQ(f`0#@VsPe|tCPdAbTh-ge(kG*!i#vEOvvH)32W;|93H%yO&H zA(M^$y~IFY$unL|@$W>-Ns%Xtn*ueMc1^E%Z|Ib>ia`78O8OK%IzSry9m7Pc@p-(m z-Kh_k*bm=sKMuL=zg?oWOM;iqL6vJo&hN_X+nxry3JEHVpcZMn{BmnEb`Qrs29V3I z>23$U^t{U>9h~b|FPo~F$Mb=%=PxoloPDW6fQDmKk7gxC&Tt9VqpNZi*U>y&y0U_^ z+HT^X(0@|$g@aQ;c$XUk$$LK;qz0YuBZTB#ZHcH~)QX-`-A#h+t1+X3o^o{J^`rVH z{odnGTD@7rOpl=XnN>{Ktuxh%Bf^h&vq@ua+t~Mb7y#)k&O^7C`?Db>n0QMo(eLrc zxK@3?w;uUBhil)G%6#1R&u#NHp78|zJkZ+r6tA?GsW9W$HY2ls{DR$HU}&GbhZu}? z9$+)na&opup5m=4Za3Qv=U)hjAm(FqhaE!P3J&x&4p`SXMAdnS_h?T57 z^@n&a30$%Ou1JxgZR0r74QB=GjcuA#U5!9{UF>%Rvb$A?u0HP@2S>W>J za;8I=nWr_I$IbDilmJ&h5K-s{G!1Y9ZC@AmKF>pUi4*M)D_}Hmo00ncP1hgZhF5_l z*Hw{6J^_rcH_aNyllpL_!HgO2i6$wkdj9zW&sKWPQ3fBD&)_~pC_7v0%?^#}v~XY& zu8S}zOd9a;H8kg89CCB_rgDYv#H742=r>F9U{P=V-Pq{d>j6DCV++)Y$EQ94j^mvR zFw10u1}8*NdFyJOET>*22{A^6Co^_Tz)~NyoP6oIU}J{!;giE9lX2Z8j;}2(wWv); zWH?ZSwG+{ZL~M9t)CT$SB6F+jbKk9WqbiS5!i%qFT~rObw8qsLL&>yf_ZhpD6(6rA z6*jwRW2SlW-wMXTCN_GqcdeZkp0%Yw{5?5#!W}70==w0wN!1Xq;&J+!pc;60Ht2FcDn5pKJbz< z1yJ46&dtL3Ftnx_2xZ8*@Y3*zZ;Y+1s*$t?Dl=`V zf>N<%D}9>%d3=1II9>ttlfQa4j1vBt?Q~KhXK^ChkC99fhcQuPxf^Dr{NgoQ{SvQs zOsCmeZElumrX&FRop+^iZo4pD5hv@L7Pg-g%Kn71Ppw@R4oOG4BubDes;;I2Xo3QF z+vf;2^gulJbgv}_Qd)A!2}n5wZtPV>;ckm- zlLc(hV9mD@{+4MncWIn(f(w(2KK4kUC!}z zHC*?{ZT(#JJSqktgm&MTjm4`;U(`v#TP~;MVGL~-^Vm@~e$ml$i`(@SZ_PDct7moyu3qwAKkEgdi$(MJ98ltkkp>^!)P>ae-ms!`T`ksEZ^i)-+koq%<0 z_8=wzEP+5E{JU3DX0!BxTuUS23iUbNrUMWxC0^rMOx{btgn^-`xM1#+ouWP2A)M0R z%uwRFpOg-Q<_c0m#U8z=a7YbtCF%ihwj8_YC8pWx^yot>XJsEyV?gfNA1Q0m>bQ$-ZdO~69@t{pUa5;BC+=%TN~XUv z6)>{(ci0k zmQ#_5$tw26*e6KB(<_;%>5ESkXiy3>ImZ>bfN%08?; zspE&5IV)AfQ37ZjurAc5$#d?y=?=&Ny^vtWz+z6lMHh_(N3B)0by9Os?`^NC0%0bB zvuWYm$TFjF3^wM;0%6(CsCDWz`u@1GR64t<;t&O}3)x%zWr?6jj}>_>HB4*jsW zajqs@wrhVi(ona#Rf6UTFTZ4@E~|%YPjRDdcR0DLN=TXbW<@q(pGSMp%CC zQkG|~ZZamKcHXH0z7N}`yM3S`fOHN^6yDpq-b%nuu*OpYr(>q~Ftw?=uDO0jOGqA* z2vy~g?c%{_lrEFeB~oe1=ME%dV)pw8${8}9rvvbC(4=?DkK%ETFBP76!pKQkSyxK0@bD=vk>Fu?sLOCJ= zXbA==xh}%7v9ONhPrT7Efmhl#OO3`v>;BS!r=-Mreb9i7p>I$SK6W#ba*T*2Q-eq8 zL78A1N|UQj*#7#14Q5GX(d$!u1F|)6hsy%)^$MdCJJQ0)Krd{h8X76dswNhRK@9{b zl>kT{Z6*)qRRyiK)Z%0jeBDDBPJnw+oJWakG7edD3fpUi)+MxVj4YjM9fslCS*-uQ zIBPC}95zy02>5KIxbpp_k}QWxnylSkNG|t)fy(F>av7vN_3%g!d0^s@+JRis?Y1(qo(_ zGo#{}iLHWZ zUXx|L$r6pyb#nA&x23FO4z)%~7>eD76r%%h13)?OllnVU~<;YVw+-&NJQFG!=RrPD%Xc}B@nm_nDxl*^W* z7Ix8!$E{C80Vu+Mk(Yk_>GYNWv?{SKhjB3XMf1>q(Vh&ozj1rSCLAlW*sfR5y-8#L zs_dbK*!xx1y7u;HokmsfvbzzodZ_b+?m&KPNMqi#=(SmUVBw>+hv|Ly9xT* zWU9!~G|P#t=&G5)XJ9|22;}C~$Fu%Lg@O$jVclM&h;20S7c=LuJHM_{;KFe`0L&}+ z|FxrITX{su95OaC*^9bhO4@$x_^}TBCJR0udel$)vZ~Uz-t>i$) z@cbeh5j)L)cPv6KA_6KLare#hJ+UWQr3(+JCU6plsB=|9cfPMNf7|*JC7>mS(`RXQckdO~+f|Aq}J-Vi5)T-JF8-42m zn#-=YL?2t)%Fm2ZspG^^3ORK~$qEKRSEAKDxtFX^KjW=7#t$m0oGadQN71r_b>+gY z9R^}`o2J%%jKad=i7(gqB=ym6<{r+2S4&E|`Yll|IP^(tm`!eZcf$3eU1PylI+O5W zLQhUD&6R~1nz4;xSd?1UCu(1&T{sJ|)PHr-N9W{0r4`$&qVZ~gah2;hHM?DHCSi0bfk!QZrUtGhOW-A8O=N0}ih26NUU3%Z% zMn#q$pK)3IC~;|XNZdxqazlvBe(Pi16k0?q9t>!6YH14(hA!xIAjX?Voil3Vg`nVK zfLZGvL@ert0grQkgeP#{>2$Ub_J4p<@UXzMEwFao7q(My7Nu>v>?kW%Rjs5-;=_Qj z7BdN5J2}8{B5MgJx#XTk^dW;9~EL#^(5qc5=Gq z*b1LSCb1JP%?}m_5$CUmc=dPKm&Yf*c4R5jV0m?$kv`3!4^8r8E257pPCkOJ55iwb z{GOJ8oPSLy!*CLx?(#h>*J;zJhdlCiD| zEvPp|Z|qYDDG?3~Zi@4?M+MP?G}3o?9Q$JEA@Ur+QwGv+eRuw9=`IM{;a-!35Ufd` z(+PV08RF0I=AW|%Y<$qVUNL8{EL9`#HH~R*3}e=868^C58K$WPJB-i9K;#WISK~8R zhlGRCRg0*t8RfJ4c;1_l z1{JZUC#SsFXqnU7YtVi}Pix!oGoF@({hv=<_GC_uwEDDM6~(9@?w|L# zRbsv^{6o{eSo+W}w4ZTI(rt2?x?MP}yUtF^wv796QD4-@Q2@!#C4IT78uE2hrBMh6 zvdjJ8MevF=w~gB+M$zqz>1)l}uv8fJsL?QJ;q}VF`8X;N0Qv&oJ)MIPAL7ON&}nz> zPa7#jQ1&{;yysc_ufqX_mGz{(HL*01#f--Q@**}v6>svX!W#!@VOJ^yn!wmWM zaXQD7BA(!Ka9wz^kG}0KkKl3IKBNFrRjQi9+l4-)eL97o0_@eaCMuiwpcVXs=rakup^5Qw z8n3=x)`5Cs!po^YDspsDx-rYf6Nk6mxhwnoIG?^HlR%mf*#s17m4q-ir#(Gvk)=+#{HV zRX8k$?Pbn_2)jj7&&`)rVeF?{sN_!XuixaFB4VhX64+ZjX5$&RiBgJ8?pjn()=ikNYYr+BgX z^7%f|?c-D9(na`(n1Ko%l&1g^rcw$B)tH>11qe1s`$%o$z1*dPpv|6&C5C4n_|+I~ zaTuQrT(zzbrxDJ6ej-N0Kk+2th0)0>ppT#9oBOqude97~F$=H92~%38OCwzCMYr)s z4752LdbO2Ks)Jo=FSQ&T6 z&Ot6#&X?7U_+wc#G7qrUt+<4wME87$^u#p(GPd*7FAm^q`)YY~t6bIIqwvZ4;~-hD zHB?lLf2k5%nxW}nCc6&G((l;^yPD#DosQm3>eTgGtVQYLV8gP5OZP54mMRs zH36NOdc8M1lnJp?Zm>*SFiK?lKL|CktfVy(oTfu}e!x7(4qPACE>@ijn1GME{~<%l zh$_W@oPIVof!eJ!1!=X|Z*aIpMOZ^08_RRihbnr_6sc8(E+--0jro}Nbu9j2vLLs% zIICR;u*5t;VJB)JuIHc~9(#4PxD<(fxG8{lHMcsC2WN(+AA=-R9ieRNWe%X_Rct|Y zK16|V3mPjAOUa%OkItf!b`Lx>_4&)=8N)Rm*oql3h!-~%YzRaO_xk5bE4Q9P|0V~+ zJHd|>j#GM7DkZ>mg?k~;Zs^-}IAk_ijI(c8LaMB2n@5zQp!hz1jqyV(v zDZ)zx3%nWIEg2*ZqE0gv2kEv>$IWak35L9ntPU4K&+>x{JV%jyiKiGJOeR?j08;q$ z@^)N_N|U3vfRBdnDph&*w^|6fC#oC<55Lv5flaT}*4@gM<&usP~Jqb;G8D!zUx`CJ!Qq=vUN zTtakgoP;Mmq53y->QpegMzY za8}Canr+QF*VkHSS3wNfB6+&B5EfN{`u4CnH``n-if3y10vfaEED0@04YJSbuyA?O zVga&Ikw19aTG|ZIv9WVsQ5eV|(mwgfgvtSi%pGM z4+^%SDTW0f&h&0G9``%7hsx75IaNM}waBCeI>y-x7f4!XGyJqe z2IcthYGa4eq3d>P?hLoeO1clLY{7K#Y{a3bJ{s6^>xsLyEVI-YW&B|0BnmT-jb3@{ zm#?q}`lW^NA`5=QUN(W~Yp{0jj1kt!>itW$L=!VYxQ* z%WGDKK1gjw)Gu)dD9p*xh^?DKVLwkN~unE?W)G zCQ=wH(eGS%Jk`nPsXg=~ACpnxbsouv~h?Lup3 zAhQ+)-cBcbc>X)|hW@KdrmJQp^k>ZV(11VVuJ`yb zC5@5MAjUGtSTSFO&CGg-%cTW%WDaCPZV_}PSrYhCYkRWn(Ru8^|F_LI^$BqU;>aQ1 z@Yxm8ek%N38*ES64PX`WJn|G!ff>=m!5ZUu(ZtdcF^b^~HL4$24DseqpSm60Ms4+Q z6*!1{*2u@^^|))bwpJibIo#uEE8N4k&?74#{Xr#1YX|{e4hN{NczV{^0q=fUPhmhW z^2^nXhCF8TEgL>1n6w=k+d7TD@LHwGIHW;&2#>3o`grrh-f9z^UhY&ou}1vWsv5ML z$`RRm9>}7CsCkN~)H%!=Gc+|1y{viW-9ygeZgO5IT^Z&F9^9nNtfc3=mVogr20Vq1 zq%iVUlqkE>BOiAO&f_TsRJG)DqaFx4474&C=)8wJT~<+6wj@h93$$yO;6*{GiCB3R zoQnI&sXJ#rq|m;$WJWrwdGci(Vl#CIq`W}{^jCa(h=C-X<4}Xjr^#gkF%Hzq{&U`k zPKP{ADtH+yW&80zR;fc6ATyr zsxURa+Rfu8mzFvL5#+W@Hx&&KhPWfn(cSLa8SyDeZ}_x&r!qFQV47suUXn>Ogh93& zNzaw~C_E-a0NyNoag*#rz2n~f4Q6wb`N@NNOokq+#qz;VGWQq}&FEZ##FKP1QXnjx$TK`?74{^{|fid+FVt(4{iG~lV5mxODnFkYC@JfzKuC!A+Opg|; zORSBZb}olvkL^z$3T-r5c54G&kxkJUcyq8`2*zNlhNe|n`2$l0i;DJ*%B4HwXQ{FW zlVOsbOWc=8?_%f3}xxzLL5 zqcc98_}xRCnRb0{ktycP)|t^vGWD|b(XxM(_@xAPtF*MS^$14%tj_SZ+4cqKZ;)Uz z>*1m+^p%uhSoqL{VIPEY5)`L_**GD3owikL8%R9=E^y!v8-@ZQ3F=`%9<1n4!Qu3l zXL`2IwN8VIi&W+g!KTPCSQ|O zB~Lx!&BK&5U>Sz*C&7-NU-WOUj)Cf{HZm zFcEC%qw-sm_e?eDrzp(gDfiQRnAJb-^BQrqvbM}=5S9XAFNUwAh~boWu;M5b={@k5 zDp&Apj;1VZ+)A_FYe}$ao22oxsj5BY(8K}IYDPSvJ4;AdB{7UzO1FU1d-{1Yk>Z}O};L-5dwp$#Ha1dXB34By9Lcf6^&%B4MikMl|!TJH{>BuvK2 zqh;Xh*7mBCCm@b$Q-cV7v)&F&p?C6WXk>MI6otbt>!n z<$R4=w140c)(uwMrbBL*Z=|b#yz(D%Yn9jjwoWM&R^2X$G$|Yk-(XbNeii9^T!6kS z>W}mUa8pt40Tc)XKz0vwPg5vhSYm+OYh{ro*ypILLIg0khpJ$u zKl8au+gQyzuP&)CRcZGD*V}yc$RXfAE>5APpUJ(*(wzJ;u%(P^hLuw@ktdW5t>> zg3PURKE;UL&eJ@$kDXXv71#Z9I|3s1O*4Q@)cT)*T;F`VF4UL0zg<;BZ9q=C+DR|1 z+#soFF?(B~!P*d!0Yd2ams>vx!&_;~f3&=l;T%kpQY>&j^h0{Z-Qg*3 z%9?r?YAQ(K|It&HUTRt2s!~vt*l9Nn@IVfLq0_c+tZcSq*hzYJM_B*OQj*MRB}O2O z_Cm;|J)@|G~(|m-FHDJ)TwH>|qFcwgh2~Q+zf~!>~7s{bcEJ%0LkY*+DE_ z85hH#G9LUF-3WGc4Bx$Om$CoIC*kXauP6Kz@_}(*9x{zPUiIf>XaWc}|J=1o3@Ql; zW1JU|!eh6Dwp~=A$7kMj*LO;KLVH@8(>Iny#X!?1_wXtnkaa;ZPuF`DqfO;2L_kQ7 zTxYMPdY~!v`oc!ekQI)Qc7%hDgV5%5+wl|fEVRF?(TpBbls{U$W<8l zIEQ9&GLU3*V;J=m9z0uvIFUAtb0r2#(Km4WZJb*m6Os}68Jz9m3;RF*0@bcHO-b$Z zK3fMeXJT^`=+Q+$;=O!h#6le_KdpB-p&Gf55q^LmL>)sd!|pH{2R4N7=QH|{ev-39 zno?=blEdyyZvpa~{pqbAMhHg?Lf#h{MxFDmiO1m?g_GH1pW2rp%fdP2nQ4!Yd@B z4fs)Q376Q2|G{7AZGwh-#54C))6uVd<@Ta7i`CQ5tcrZ`{`l8!NB(Uwt1K%`qfp8^ zeH0V{+c9bEKcS3Tz3F?u@5Asx5=)wXjLU0`<8867GE3hP!0$V<28b9-`PJ;^)3hwA zj;Gf2=dT^T%jleQ?%O^L=exVoY+>=cz7P8$3?Jm|l;_T5-rVNq@4waO1okT-gUw=M zFZ0zg612PPvVcWHHZoPfUhRN5MGrTrssQEtuN|Qv0g83gytJSVv?SeBLt*`B_h%Jx z;s;Rl6lrD!)bHb07H+1HCNj2idLoCZN}_%qetRC;?qMazWlkv8D*T!{rR#wLIdoF) z$QsAB4%TD1%%ML~+d$7esOmgW?QIAoi%!AZ_wBTv)zVMX?Tgf`O)2|tv^ z8$)VdhlAh3-~cL?&0{d;Fm(fyt|lW*4ujT(W}z4NT#J2dSY8*TcQgK5bE^3g7yC zuu>wFR)hhs)O?ilYx%+HMkv(JBBc% zL?U9jYKCAx>uM*uBwP!Mvk+vU6Vv98fdVWNFK8`E3EEnFH?(SBQeeqH**ipQ{~Aex zN+;{RSF_5kEzR3#{>ZBF#HmeKy%c+~0SgdAI;%klpB!vuH>@>?OFJ%#X8+OWpPd*x z5-;7^Ab^IB==@upkhPg{taIqM&t7;}<>&!V($ZV~XF51)og0EieYFBWI~Qrqxlk)M z-Zd_jK|xjRx^?tzBK@wvtgzxpgM{zo*-Qk2^t>k%u{%vKr~bE+kxOGnLgK_H%wVC0 zKO8ry(x^7Zsuu_U>m9#2sf72Sr?iYr*F1FH+_xJ22BVD;55q(*jA|9UY`Gr zLhW(DdpU*pPOtz*r05@2e0@({-*-m)pWhyG@CQKV_6UlYgOwlM(0%04T;VQ%LN=+c zYzvZ^M6NX=b7%S0#&=-=GT-fAGY4Ep?|-fD-L~#dq<2jW$uqgVyLi%4BPtK#!;(e?2gC5`U03bj*DYU zV|uzEch@I4fNiTH3}*U7#L-#?F(`{F{q`QyycXjzF~U#xZ>JCP+Y2L_jSpw0_09+J ziO7c?F_NkCq)mmq*|Lm7`?shWq^m>4rFV;3sY?RVb@Pnr`z%3C-bV;C+&7!KiIa-{L9O-I#YZM59#tUK`h5-!~-oIx%O@gGI4PN(qE zzlHZ+4|;FU96yN0oE3=4O5Vv&hjVz?hf9&W>yFx1pn*e6xwmbj0*z(B=iBuAEP%nl zlh-S`wm?JvXBTe6AzX*ouRZ`QXySnk8e-qNt#A#8OL(IjxHepQP37N8j6-`doC<2h zi(#edrQn;WSOlp{X2vykq8zhDz`gXZ(!`!omC4}5D_M?WCM%B^%opGymMRTK5xl-vpGf?+4N35QN`E&Vj00Q}F~d>>^i{8Oqsvo^h&84u^0JZ+8#r=VpKUFK_;fbIr|PmA?fy zq;`l)t2GEtq`crSne>M)Bj!rrHX9$gMiL8M>C5yNQt$_3*TSy-^?u~R0 z76Ps;-XVyj6SxoK$#Bkpw=elV1op_Z_T~M2EWWMOIyC??U>f?I?#IKMDENLR>n>}!26UaP)z7rU}i_#O?aJ~@tDH$EY5R@P}98kl3Gh9>ZKer?B-KP0(kPNmUid)V|?)0%*{1HnH{wz zSTI0H^+HUP$K-rTgs$SD9!|z_pId-m6(0{5ZpQYa8m_^Hdw4EQ=HC+CsZVb1Af8YXq(c%!26n{`H9WZ zCcEkgjIxk&C#Kl^&#w#=?3+Bwqw^7Ih4(}0_tu({u17zc_;UB23sX2xB8(43R*ZD= zO=VgS)_I8YEzyIg<^F?JDNEIw>SzAoiZI?Zo8+R%e=@k8%;)8(h|RT64_lufG|j%i z@Gq06oyzppO?0IRQcF%53TY?=>fd9!ZJz8~w<{-s3PQ!0y$CYhCk&Qc@FPW-n24)9FxS*;>;+Pyk36%&) z12J<%st>u9U@t?V`JLwdA$AQY64NW!QQkAL};x&0oW=yU1`R z(Z8KQ(z#%)?P8se7|P8Tn+HAUinW5<>yVI3bQtU z9|Y!mY?r~hm-W?@DHXqYNJfn}HrDj%ep(Bk-9IOegCF*=S8vJ&$SM&z&{2dzeNu%hH}vc<$L5l*(v=P)|B@s!bGaGDv$@rF2|& z)cuTk_X!%Wq2L$@puRAZXVu&?JIk~EJNv7kisG;0)o5xCVn$<3g2f)(#C$i{fr-R; zj5q(Y>D9XO)0iy(OUpSfZ~RlM#q|Mf2z4R8Ccf&lv>WBdsEqFI`~OJ}(Ua$J)bIC% z6tnRt$arRhE8$|G+>Yj6<*2Y42|&%sz+K?_B1uqybmY|&03{RLo{^F?MyU?I9HuJO z>vjP)R~Z@4=44$9^1`#s6u4NTtg#oN8{qPmAW}Z<{We$+!%>ycN>8z^B$JE;W;6~G zOEp7$)EiG76)CYGlwI2SgR=I*aMO};rddXPE<)X+{I!E&#FK!MMtc$^4Q@S;xXVln zh_(&yu>+ZJnInY*_mk5WZ0!9QcBdgEtZIk$Sd1j31LSSFjXyFm|5H8U(;Z%3`i^?L zTM^rS!-LASse!Lo1Av5TA0H`f#22eukaXT&u8*4><40`C<_CFu>qesB*Fh-IU!dUaGxzrp-sA93%4O}c zbgZ^vB>BvAkzGkp8zV85$2_IY zK6A^}cw=jATP*uz^AWT1nt=yByl@ebP`?OFw$W#1^^bl>LRTir2ht6GeQH zIy}xbJ+7x!D`K$nUw|zS27kP-=hb0z>OGpjIb4p6= zn{m6QYhwWx%Vpw%6DL<1OHt#sCVfg=4&H|)7ROHDeh{Ubf3{!w)t?-&MbqpB*y5Kq zS=9foW)l6_go7&JdL3i|>^%}Z5NII<7RgO|!a`!V%Uhb-vp?C+<>fSB5N;mLcWdoF zF*sZ_Ma8tszNBmg?V;W^8^iZBeK6_xdM(kB?JI+#uG~)8l<02xNQALk?kva0)9RtZ zK`?prL=M5|thYS7uywLsdfF(TWW#Oe$Q@3apCOo|;BZM84GO)VR z-WtG>UsiaASs6y+FJ1|wwr+Prd#e1QoniI5YD9-Z50L4t_(|E^V0~&uaRd35Ypeff^2!l7s0Fzi0lWk zNv_$!Iy*l*0~3-sEnY24tl@*Yn*CohzWhIRZ&TZhT;Jx~m6rSy){!n9bf;i?W&;DJ zl9&*#q^qh-ciJ%a2sWJLoyZEw9S*iaTW268;Hz!z_|7{!k~x%A)t+X^UcSBW z11h%}I+l_t65r&G7r?L#VMC2@HC=oJ0136Th1gY8`)JE9FKzc4-!G*&;Jp*NVLG{$ z)P6X_Iw(_D`-dMVg8* zA*_6er?l%Niqr(53J4^o>9_vuo_^M6^#8DzVl$EQ~4!rzZcTgs#` zwRJAp*1Tqz%)`m>OEbPw^XsgeJG1qg)tdJ(Ns*8^k z#n=NgTeC~EJ6dl)!2+P=k@?F_t%Cr~o5$wT%j0GM6`zWTujn z@L7n(KLN(Q@jPi`8;d970GTWeT^bF+rgTLx z+Uo5^3dHv~jNJ%$rDN{Xun*23X@N$Gn9S%n<%*0aTY^3Zzarm+hzo1T_+dm$!^Ebd z15Y#|>$2Qgsh7SJNd`!Mux&FvE|hTQ9Jih$tmB9d{+8g1rWW9`jzo^dLeX$v$dvZQRR*{@S~ z4Y<_#+ot4;K~dVu#PW?5UW}5NBoLr+{zOudM^VS2yX91G{VhF79~-BPTJgB7oWG2J zMU{^VHP>0J2`wIEA$q?8LW)}s9DNP$&_u00Go@Qjuh$WbTjQkf1(%yI=APOX(7Q) z175F@<+AhC;BhqGi#|m>a;iL5nP#@c=Jb*nO&DI)AZrq2o*mt-^V-1Btew+^yjZqZ zo*#&y_CDw5;yvuc_i-|x;e+tTWa(~VbDvry4iZpp^ikHFX~AGySdB0nzSohFl#U>w zrLEsM$ur5C&}fS3B99W+e#Wz-a`R0yR4f(&|9M@<{hXbJF5#64u0CN z(+wajuq#CZBp=tiBj^FstI0;?k2s%7vf9?%&9makkg8cRrCGw3?E0b5V-b@c-)6()ahd*23)T?5rG;s17~$y#D|p_ zAWoS(Q0|3ZslvST!P!ydMwTI%40B)n(Ofpl%H%@OSr6@Dfhp13RMcSnp0_B37w|nb zaAH^+ykWIKY%qH@1TZbWLW(F-k?i9%q^WKRL$Jj2W_oF- z(C?k1Bztt^ICjDZ??)UsTUv~=Sb}aOLW^Yj9Zc;Xuc^+=vmqZgM`SAPSw2D5);O^a zKXgl;xm)m)tXFmhiNLB`_MM{?!Y8M`7s*Sl^x-CYZ>AT-y#r+TE2`OGT=4}U`bZyz zZr(6J3X&3g2A7fDopdHB%KQq9Hi0hL3%xUf8%xbbA3dDnV8E!{@RbC%zOiWWFCP^$jznsQGMRZPxk6F zVnjmoytfGi>OG+vf#b~BRn>mi)8h`aYmC|ah!Kbo$zNaAvs)9adNa`l#sdrCOHcU_ ztj8%V!!cYqfbmFwzu2^;hj$m?M;h?dTyxm+@1pzMFQ%+T1I(Nc@w&v;vLjxr{XhTO z9Z%ukc%Ucn)a<|g;mH5_7S5-5wlY*!Ln~sxz$N^{+a-`S88n{w?st0*wzlUnYCwQe zc=9?Jg;bwW&J=crzUYXJQ=|rZaV{rq7FC4WpZ<;ah3mw39JBW~F+;~?_!yj2uhKJFj-leD={ZZw(It0SwR z7_NeO`r)n0{}Gm3;&N2($53JUi~Erf>YYZYe-@$Y2XuId*6#M&)cXKG;glP{hci|z z-buZbrqNLN*o;eTPN!YzDT12hA`c_X10Uk=%9JX9PRae?xuYseUv$5oJsL!AOiJ_> zkLqXW{QqKWwO6h8E8?~&u{MrreDUvYC>_Kih zw5onq8tHaCtd%m8jinL|M7vi;>LkBX#T0|``Q=6hD3I2wj!Es!AI>mP3-`LOX zc!|xCV#v(+|Bzb~A{|eL4KLw6$Frqvs=+*aByPdwl8DM-{V)AW;LqbGH)F*|a=VW4 zLJrpiutradkM)dM99MTlQ}H9dk*ZGgfRcoafoY+Jv$Cyy-UA82)@cIoM&iqdJB`5@ zqU$N_Hx^$~qm2#P2zYbF1DAd$e}ukuZl;%gyw#F#1WqtTY^J!xeGKy)`u;Y@lT!M% z!AN}DsahIB70$eu@#yooOmQDZ=r@FJQCa!VYRz5xu9G^hP^Cu|XrHdfAoYs0*uA6=LW_q!(K6Gs9XS1Ml_=!vYE!6oICn{dx#sm>oGE`bq0EK@&Gq7(>|KGAhc=e)0IZXye^O-*HC&)>do)1dZwNt=4LJ*TGxj=Dgfc4|dGTJX$31=m`;AC&7%x%UH z&!HPEg!~})Qxg}iG_dDzt3A$hBN{hG`uXJs0cR`YjZLiAftK;W8$WxMmVgq=0w(XUwM;Tqyc*qhcWKQjjd zjljBfAh+%Nc>TTfQhw=Y^v$Pd6KHK8#*^psf>6`;-w%a`&%H_{t3zww+|GmFv;;HL zVZGTGRm@n;*6R3)#oe37&ZeCCIAvCAW8=A!BiWUvQs-$OZ&N%@R8fZ5{CL7b$SKsq z5BI!a3A?I{bE0-1$j^)pka?*9vF&myQiESR*)Bhc>cw#^sezZn*o=Q}If|};zCPja zf?ie!0AuGIn`6HVXWWbi!F(2$g%8o?-g98~gu71DEfJM7c!RQU;!1(~vfxWnc~<`U zl2^yqhxpNlh`yqro0V;st#{P-QxZPw1MLQyd(4bdpdM3L(bG@ke_&C~zf{3e_M1bO z_!ANq@3Q?hy~`LX2`duixX#V-Yy86%Z`g8S^(3;)vw|o>gCDSjR{x=`-7U#AQ0iKJFQUbouWDXU zdlp=fDd-#HO-a)%M00nmvzJ;HwW#psJ7!oz(bo3E0tgsEQcLOSEW5+}a7B|@rov0D z1DvX$yK!2y=go|?I`^mVCrjmu6YC@B#*M)h8I}+6Dc(p-s)u%4`z4QuU3gETtw_mP zr^tl%4?^Uv(u5ZrUWbZUW-xhl8Aapxxy0kh*}lg8f5cN^O+8TID~`QIg=omlxtFG; zJpz299llSCe(mSA!e&hGp}a!X70xs6NMQ+t%TSXP1|j2e#1XCIF@wYppu8>45)9#W zlBdj+82mF_wm9%o@ZZ=xC4<`eVV+_znXz)&_xR>SiDxsSr}Qcn(;Y?2`J2fZp=;Ex z`Ut+@1J#ry5D0BE47==Zh@CWpbx8e!Zi~U#20$eAC58D-mr#n!Vrd=ukfr(G`=mE2 zpeo68Pwr>p9>d={V*jX9P~Yu1X2)VAox7b{!Q7n`SzuMUSR2pdLB(R=oigV$35ICy zXJ8W^Y@KpQmX||+khXa3bVQncIT}m5JGUnR2qGXs9`!9DZ$q-LJ1M_y`1WT~d+vEE zsg26r-}Jydnfw&)E5(kPvz#+zGsR(dt2TiftV}b&v8WF@#(cQWgWef{R<2n|AsgAW zQUOefLFAc`=FOGLq`1kK>H)`pNb$Udcm*Z}bmL{zFYh_vuLLq?cIy{I3wiEuWU0lU z+e^H>U|m|~w_1IcSF1a4{jU1*bF=kY0K?=@PS<itxrb59E4qNnG;u2*dDPD0Nj}C_OEYx{a6kP)!(y)2Ny#r?5ZvLY2oDx5Y?2 zh1;+Xx8aSXB~kAALc#@ognyvBAq*{TN3oO1gQcpH&BW^%R1z{~SxzT&WG=BiPm$El zbgw#mmv3O(2Oh|(ecUHIw2E_UFKmxoeYS(3Kg@I}98pD&o_n;`7Eddy=1b&TESgoA z;&L|R2v}C;b(bMO7r0Uvw?i^xDcMEep@so5zc`5%8zxa2@d2-2k8#A|X8#jqsSGLPl}eUjj|p)qg__1Uay4_FRKRMuJS55opgZxo%UFVXAN@=K>Q2L$692J*CPyukD&8GTIU%RvT;u&U=gCLH zm%?H+|4nC&TB{HX5WCef(r|Um7&@-!aoJxr@Jw`7khOOzBwW_IjP2*pr{uNvy&$P;0aw%I|g8t)6aIVHGwS$gCO3ZLnSH|A-*O{*nv_ z1MK2Q#P6;{;vRxPe5>!80>K7gnDNazU+j*hPU~{5c5TUdId(awPH>4iu|M~Br2c>> zo1SQE!!TT9^rD{%0DwGEQGlpH*M?)v%w#4s-07Y%N@0-W!b?jmD6-;^?~Vq>rIV@~ zIl7WA`?A%`z=CP=wJP}VWk4VGh5O$_GdofZ3K$XTqF*RZm1O6 zR-HD+w28mr@EZ`yz3n$wjkoRy#Nt;48g8oNRnOdQ^|2RFjIize(qebfe3heVc&@gw zfX1EO3{rJr%>wR+2enO#^r867Efosyv7h3(o6fNTQB2!5p?ZogNxduwvfK}U=Xjwd zQzVV|h#T#}+qt|`|1DC4RH?Pf9KED+%Lhy6Yq}uHHG*={?S9+kLajkWZa`*oBD7c@~Dd{oC|? z>b`OQNb5_?EyGE0H;)IIi&|`zJU8Zuq$ckGRN^7(@Wh<=DKv{nqNEtro#j zTShDCHx_C&8c6Rcr8ZvLQ&2C|`-s`sZOd5UyCMOBa)C0DFszq$@os@CLvwB5mVIEH zW}njoqMW6_Oa9OKr4%qfc-d;4B5PC6kys3`=uk9kjKh{DPWKo^g@b4$ZrxZQwl-AO zzHGz4at%NUbtnZ~u7Em;C)b~ubo?=R;iNU?L*zbuOVlEhRHakN1HNFk^Xh`mgu|^b z`I=wQqTmmp0WVII%LP{%P$syoQT@i&OndF7o$6eH6T(2r+#j(B2vQ|Fno>GeE&wRQ z#KR!c_56vIvOwXh(e;^=ZR6omSDqy)J%~AD3ueEabrO+8NmXN!M;b);b7%mnP!gMt zwPp| zwVl;7Ubf_(6-YjJ3q%xcTHHIggC(WcOPEm$tyd}psG30-vdlLzTgZd9BzH#OA5q~! zS!eGj#*lR6hb3HI;y!#IZ@Y0ne4lPRUQzro?(OJ}6ss{s8h#r~y5h{qX{HD3dmYqu z?mSYbBz5PeB(CH`%vAIif1Zm`{}Y%}Hg@1?E4F%G!^csN0Yf`QJPg$31Dc1wg*$ax^`E8id_&D)N7k{3qbG&!X#QK`&0TnyZZJ*eyxG4^!ZD z075`)eO=E{{#Bw&kqkN8A~-V1gV6gSAv;G>gVe@OB;ajE?pFMyMDr49ev7{@f9Ey+ zV| zmCOD)oLhiJ!py_a)9HZJi~d{b`g1W#bi%MUmg&%v$!%fBGISxX;E{fhta# zd;t6cLdF@`!@-g~rQd)&N>{cT9p(~_-@~`^^Upj?nPR~Uh{M*HJ{i*dNx}=4{P~&> zdv|#;R%cof3Spm-sFH`+zc!CagC|utWmP6C7T|_tSeF?@1+AR_r^inx(d|5@bY>J- z+n)l6^>?vqFADGK%)vcrRj39(_z1oF9fD^f%R)K*H5|T&Z~P?f)o=Ypr8I^ksu2{_ z0>0&6U(|Z|7WUu6H-3Im`=scD{#I@KvEU^FXr>j6%M$gA0D0)rt95|%3cmQ#RJt;1IYOo@iN>6}kSyvO0=kc@%G3uIbLi8jHAS1cB2SHlL; zc%EpJIirVLqU)uyP+pt%Kcd>5OgS0Mqv&)s#d&P*>O}#`B^_6|A|)KfHAaFfxH8fV zQ>r&lvpG$)!swcL#_d%w4Dq6PgXBw!^$~mXVV>oKmIg ze4afHheM@}w`m{WTa!fZFaMi)^uL@@I)b-hw?->ZBfmlnWxD2{i9qq`CPRCd+^=F| z_nK&NIE}_AI0${%9WEi>!tr$R!!1Jx4N*KiwG$S5#vC^=6ue2o7Q)T7ABInLPQ!_n z;@pPuc!{SopdHW_VK~3{*c=LDfL_=mX&3F0j4X53ju*uzYn+``kO<;@<2V<+nySm~&h=XG6oxNGKfyk9x#x{ZDh=e2NU8)(0)FW#= z>a~i>w9=Sao+>8d30N-O-HReFM{=E%c(p!eRy-`>TyR)4a_k&yPs86^+7-FB4}pKV zR>C4ZrhO=Q^AtyyyVHWry@iPpJXKt72u2qP>yXW2eX9*KPwY|5auUjrW-HM@gCRSq zvlAQ??ELepl-ILcxqG#Fbbbxw`rCd?{gITUmHsz^KWCpX1DYeltYjt!z|X|iZ! z@&(b3I7?vDnUUhyN1>1-x}!3;S`%gs>c|*RBf24e(60#2LXe^=ML9u4GR}l!uEeXL z#?w)n{6)qf8{VYGr&VUcx!tXE0XuU>X~^knGWUhA0e>Q;Sz4HRm!L1Sz#It2C|sQda- zhUH{!pV%6ndc*3@s&mu7#^&$WY^#hfLkyQOgwt60x{Rt^{BS^4;%9`r*$`zP0jjLPHJs)76rYv4WLL^Wxo2BB z>YzEbO}53KmQ`$|k$4a_ukPg$inNinb3=IBf^i=vuUxgWCZ3F7W6jkZ5ztp|{ym<4 zYr1SPNkc9|-Rz|6RGEE z4gl^ja60%^4fin#*KHlPaK+QmsWW?amcsUkacc%RJ!*$w1%}T?QgC?}+o=Yk$5otp zl~hTzVhc<)mlq7PrBjBKUO8VXwGNzR;|p(QMiAuA@0CFe;``1#GrP=7Tevele5NBa zXOZSm!!%dL_1At={!mj*kR7>si(!J_t>XAb9sQOJ$qOG(@v0;Z70Foj1eYA3X6|s| z0+Cl%3|>~PaD>gR+s36C_P?d><(C*liYJw&NoiZ6!BLq7&H1II^WE?r^N{|Jl$(5- zs)D95Nk-oWO-@4z<8WN6As zDDE8!1Yd{rtIs*!6ATSE&rm%7Pv*edC#h#$M#XxU5!Rbs@=_ssXVfY`=O7gS`l%ST z%S+LJU~uMbF@{C*%Pb%?)0o-3!7_G%bKsu0El0s?<(u1Z?BMhF5QZdCGGRz`C!VIP zg9#MrZddAbRfwhYrEibTP@GJ^clBhN-Csj|4~Oht%0@|EJx&WhJo(|QHwaQC{oi|D zX|ZqtWy9c9tVZ*e1!O{7#Zp`NKghRu&qA-Tn=yW;YhGchhaO`1iUGipp$=EfHPR=8 z6djlRKaClTxo4TRtkGPxjA|Xt47CZPbSS}X*c)!^sH)R)@{261` zJf-hT8CPGHJ^;f=9H*RNMF(8Zunp7V>xb!K1XF2MMhiG0izI@m?jATYW65bJnvsR2 z-O!FAG=mDTG=j!Qc!O`g0y2#M?EcrFRw=?r0wgS;fX+p*|3aIEhg_q6Ik)jwEybu| z1_@VYI(Cy07`T|u7K%2dxic^W9EDZ^_U@-kGbiFz?ZgpOQ`b)!pXagn@)#CbU(`Y) z<;#oA8_R}?G4=`e7U9l2l_7wU^f=cu6h+STu$R62wZtPNESfTTbVs)1L*J8jzk4<#V*LNoU8mzaCMQ# z*)VJPGl#!&9hO5&F)Syri&UiI4osbFHVRvo>e!@P3@{+%FM(Vtm{@&G1~VFR5(NZ; z)94guA%K51#iZcKIwQ6PbZM)jzpBgMbhy%@uv&;{f#~N3!YtN(2valQ z8(BZ(((g)?Hysf}uqw>F*8Yr~e;O8a!esu`Gm85lWv#m=iJB&MR=T)$SfCQ zBC!yM>%~zmr~n#H>tqLo7KWY5(jY=jMv2JMa$WHiqqHWpYQC8bbZhfSKXz=snDSDS z(CASH7i)W(R-A_CLa`+r^#tULOsv^lqZ>2Ghwx9DGp-8jL1&U zvTD|fXVs;swx1t*6}=@d*pY!X_~U6b-M z8GoWeAuA5xEqmT#UhV7yNfL$r)vKHrr*eMn%fOe^$AR~Ggl`&cuKMn(76`5K0MZ{o zLV{yi8Wb;~r=f<2`(2DX&tz+PRvK0}Ri??5`_Bgn5TE=*hqiqCF_JBYg9i4RKQDO< zJp7CUJ;ySOOc_=T@fMUBHHBfIWfSZJ5?(TpWhDUHlb72PhR%Bk_fYad2ST`goWF|a z>q3xY!^!^7P8kWUEWtYuIS{^7k|#nyT<{e||il#>I;1vv4XAIUZ__SDlX;xu#ck zC7sMUqfNU~ca}rEENj=nAMu);%9Ea88=!@K+zfFZ_wVqLtd0>~^ei7qeXVLMlw9>m zsog+TBbBo5Z}Ad4x>GJiJXEs!0x+ae7te*VD$8gA!XJQ_2bp;&9Z=(FN)&rH|py5PyzLLw9rLt5A*YMZM0p{gesVUMyPDUpVFPi{yQ+%w zJWL}X6^`67`(C1(anJ>`L;76G0O`jbN8+8m$nu`7?TJ%lYu<*c9$Jh`>tw91aZ;Zb z)IHMQyEAo2d7J8<4>h0`X`&IpDnaKM<-w7%i@5Yr2Vu|7gZ>V(|xx1Rl0OfJhQPGO9A(4 zA4>gH-FqX&EV@GvR}eF~#Qy4D-mZIyAXs{P<(_+DbEI$+Yz{$Vy6F_Auy56n4ZjwC z0y) z&Hp}t=9tsDolD8O1PA!8^FbI&Gwe#ed^!N25*u?14USq>ln z3t`yH=)9qU*qb_RqeDQJ;j!l1@BUSpYyIvbwylC_tr?frUC*rR||qBh(i>Q)17{O=gqgppMN-1eK3XS>*%` z6aQ<&?1@>Ko@w%_hfE5$B&XTj!~&Gklg63SA^rI(`N(c$lYigalg&CK&LzQ==Fk-6*l z4;^$ExD#5bv?`T^t(F=@T`A>Tc57y+?UL#qK33@rdrLQ2qRJnBpvz$ZI*||k_u*UR zyi+?p&C98U>%Pr()#X~k8%EeE7ftCDQ!m`c5-dA!w!qF?DVI1Vk7QdI(I1H3;$odD zN4uh%)YcfPVD|dk6Q;}%W(MY%#K(6($^;rJxgf#@DGy`=^=HG^u%#=M{;!S^bzqk z-H30k@?bPR<4fejG-z;dW>V>+%2ub|9|xb*F@ZwMxkq6=DY6@m@#KTTt!H10!vs!7 zRU+(1#U`>5m0gt!|9H!0@)J|ZM-%Wv;E6J~R`LjtKaI)FXe=Z}CbW9xC5t1m55_W& zB~*6pr{?wV{P@p!kfqvsR_bMpm47=i{-k-+z(x$^u4^d(IvH^m6J@wmMYNSp@+_c0 z(KJP^T=2Ee^4E|tnO7JUde4y2jZPqU5KJWZj0%dTveD2Je1{MoRU(mlt#N0&8ovPT zu`9?Z)pgLcMjKGaIueB}==U#x5hZ3mkFj|@{sB+D#UN-C_CeBdW(O!~s?sJ&abI_Y z$6iwB%F+>D=ChU18MYGo+K~h!`H}4GU0S5J1x-U}P6%~Nhs34Pv?ocHbJY4B&2cjn zZ|7$;22H$qC)4+CEZuS+BC~A2xdPY0l@FAgw30V?BxE&@yHh-adcv!F=7h}zmdmh8 zE-Dodiue(qP;?gw@=0};UE)1bPBfNm%@yVgc1gT6@iZ>+psj`jc|8YgtPOG39xy_#1t}R`5=}ymq7nxnfnIU~M2q0Vb=)=6~J| zPJTM>%^#qKvHY26g(ZUMvw{z$k+*G{4K68r8Yfty@Gfll{otLBQFF&H=7}47a=*C_ zTwZl@AdEmo)mU)-*b~6Q$wVWrTsZTC$(* z;hI`}`(&RmGi{pwaFChCimdZ3mKS*Brp0X!XFGIA(Ubtw2-LsX#t7$W_4wv;Vk^vo zpzb5I>l6dtxYq!xy!OzhWI)s4dk`O!@{^+&Y3?PeQP{Y}Hr7QvqK%v>Vx%v{BbG9R zz1iX_;OfJN`zI3I4t<&B>%Ma_7r8Xgow*P$Vb{$wu@rW7v6_Nc&#VvXfplP2Aq@B6 zLI}ijQeWlbH=RozvQ9&Ytr~pVK^w2JKjVI=WZg<1V`B{+P=Ev`xQIgb$xq3hjv|c2 zC-TPfCp$m$2z%D?5J_>Fo~>)^^i!VGj?L+7JzlDrYW2H8JX&fAX+~-LB@|0C)TveA z5D$#IbHoq=wKPib&Bypyh+-H>2hKK^w(-kcIzRaG`p$xbktU>m0MY4>oOq5&1mj+AAkz{|??l2tVD? zW{v8G|0RvJq;4kpXyEgN+=|_V-SK<@Zh-QR z6-)8DtX3#Q3rbn+!uN4j3!rJ%tFmNV>F{zr1}EjH2$WH0RZjs*=~`kmYiadoK}yIP z>Y|o=BT2CEQaUtq)4pfBRX9No?@Bqs5o#^U>98LEevH@1e!J> z9b+H9k5ekE|EizyYm-2g(i1?ROh>9X5pPnmiH&AkFR$?meU(W1~u#gmqHei?dMGo-*x z095v10uIRy`j!vm(wYA`F3jl81+78YNG6++MOH*~t?$@4BXdo7GE6xznMzO13vMyn zz5JXI$9xq=g6fW+p@Qr_``wnJ} zn4?@bHBrJp@qH9_2Pm}d7OFm?iSxty{w}Q(f>z+xV0rC{X$ka@VQ?=CPv_BC2sBhM zV@GVGs<^fUC{w0z;eFAL5kpN+x95-HJYoPN9H^e*3M%3rXwB-{`MKZ45LTRNT`*s4 znQR`2(OQ&Y7W~Ya4jKL6NW_wX@FqBrqy$TD1S34JF3fU=JtFp-Id;-ISxWlK5Ux$K zIJOP>H`*rKs#KtWmg=iLVTN|aqEHYyD+zQ&16iVc=j2hhgyHQm0;PxvcO769Iqqv zK&Zm;8}g`NtAypOYwzP>XM&=A;-q)yJk!n&#f@`%0(53UTiFRyHgG<$}?P zKebHD)!gI&)^n9FnPXDflw|nQ5Di^VxdV^n!>4}lLmq$aUQ>PI`NIm_@d3iX?150j zg3C&TX;PibhZDY&$*HFeU#X0j)@9*W-0*htj0f}zJfNLK+Np8W|1O!AbVt!sg`V4t z&mEFg0{ZXEm`jvVSFL|oeqeT7fkC1%1d7e`iYIoYl-(+iPeGpg^~?5?pdqj8U4ATvWp zkHHD<1SraHny@TwvYNL7FK4SE)&6^6Bk{5AZV10CSff;-2huu=HWN`PQVP8qY!cKf z?g{7!aX_#Dl?mz6v{+hiy#iW6k@zKAzG#xI6-UUF0dr_VNv%`(HWId#LSqXnhAP5` z=oy(^`qy}F)*s#Y2pKZc7!7u)zw4~@m~UGott6c}O`Wu0Ekn44y>~sb2O2&i4?%rc z8ml+kTTc37alTEj_N-7JXT23{O`C@O*3S0G%8Q7$%VW!KMHYb#uuWO<4dcOt|2THf3VG*!tQiY zv|YDPez5{8o#G>Nha2vL;&Va`-H`lm?v2Ys_8TWbelY3j`r*naFva0&_BE~~Id2~H zgKaHg3_Ew)(@3L*$WvA0ylfOSDl@nJO-^oD!YTX{Pb8VW0dp*Fkj}ltmz{i^8C$3q z#qg{2s*j8#a=$|I^R+{`grT$0wJx4?U*=)VC<6az|EyWYWWX&^)Lm?~gJ}H0uiW3j zQx%jdqFGG>Al`Y)50Dfo`~zc@(cDtBB7a%^Chk8}y}P`PGUad+5Xx)73bDrxxkO7~ zbzk-LxV=T=zFA-#uB6jQ_{zN7&dDplkcEs{+^soJ{LbZ&&XB+$l}==f3PZHj(d!Rf zM>H%oJVuL-vI$OPY(G`*2o=ec>MSjvQB!(8M`k1}&Qs|dhpnH+K#OZQ2ryEOl)4$=T1$s zsLVdFdvi1XAHr27Mmo(lrSjaEx?tGAii>!9X)kx1b(Bb-hh2Be2_Mr~3r^)ZO46|v z43o0)#0|NM|52wz8MZ9?a3MEuggTzGqbfS4zS>41DTDn))6Z!2#I5Ih4|6Y zh)gENc@?z!ks;2O5Y+=GaR{yiM?N-PMt``s*qB0i}B<4*~0@sp1C+l5&eIh@vXYD0Jr`)~_yW@X~uS`=;&p%Y>W&FTwBy~)rN zitK-z5uA>@cYV(0HV_%fNa2aYLQ(oi#&Xk%uP{|RP4jf|+^5Ovj!vHKBnXlFb98+% zCD(*9rHPk;W((EQ(uI@pC!svn+j{~&YsOf7dM*lYrm5%8lI79YP^9~k7)qhW(#bpIlHg~)9cFT&XW2eM`u;|>OZykG}<;1$AL|m*TYw=;Eym>(s zEv1)6UA$N1jt)dgg|@6 z*>Nx?W*zUWB{5u&wK55i&RRqKfis{lG;hDp6r(wI`9^mOPfaZ$vP9B`tM zIw2-=TLL;KQivsUN@$IUl*AQ`Xq+elUxXvxhspt|96%hrJQh5VS$XKTHsF4|rZ z5G+=P&i#8$a^c5#?c%7q`IHt3SU}DqDg3TKvms7Ap0GCe%Dw7GCBMW1S%7|MZo^Z` z0>L`Q!vN@_us@Z8Uns^8HNuKH;gFr$sNPXvCiF3!EYie7KJK+3e{a-biciW70;?3IbF)YXuLy_gB zq;#8~k*S?L&!+?~FH(@xgDV@Aji>O8m;s*}f5ZKPZDcK3C&(On_s|mbC`BI%++K)h+C`qRS_+>L#IT zQW+q9O(xPn&vw-3iWjWu&T_8KtCTR~7Mx!1J;&fRJ-JUTPtFnfY}-|D%11;htdw!) z0CQPcze`s|f8Eu2m!GMXB2;7$ttzK6@4p2?6}+qCi)H2cu4QZj9_nLOuv7rxk;F~6 zx1QCHY2mrB$dbxU6KD+!zpD_W3Odzbr6Hwoc!&wGibo~7pPTnAj1S#zAqSfQ_QUs7 z|DGK1dc27k&4?e3EC@T=I^XWe@(r-V5@VP8t8J%{Yn)68eawk8pp5d_rG}ci_U;4|114oKWv#OzN}E94vP5?A=p>N1SYQk(N}NTp zvpN{E;z~;w>*HilMmoglgyYDzTiar&r0jQ9#`;lEUDnC5Y{U}!jziAVldt}10{YlD zGmaX7uH!~wx0&4Z6XrXCmNl~5@a=j%x0FN`bff7a zRgcnT#~8kM#ozSAn*ZQeY)wz4&7KS7=lqw2%8t3}Zj1GXEz- zV4>wv5&{7e=meVH$1q&YldAPWY>xO))U^r%7~>5keQ-aI!Vt;k!c%r?pL6E!#5}lh zQF(ZE5$$BgYi=-@9IEV!*PdTGVGLFU<6{BJQ~=ne+K~CiLVcn%I~fAUFdLc<@m@>T zHRPj{^|SZ_ztBnL!a=HBab+lQf1u;NaC!>l4%-4zayFW+5qgzZ6ec=ZlciI!EHf)O z1LI^np}_}hZfebYRtxjf8V%d1h^!DQkaN+xp@{h>^08wr|7+#snDE#p@j#LPNW8Xq z6}VK@7CcH47#~Hdm1jw%LwKOyI1|FX z@nRz_L*|hR^qRYc3CahdIp7F^Lp9;|rX(J)bm{h7N*R{C=wwW|AjYP@PN5l&+B^VW zO!?Jpw4}B>ekZf3FI_W+-b?fBlb#pAtEMBRKzpiydTc_8E2uf6*mGs9q}}e9$)!cs zb!f?BWfXvQ_T&-s>2%s~m0V1@?o|_T>B7|ZOBm1aB9zWdqOrf7)gn4!Wii&0GkXMW z%=R#%tIxZMj`nugS5Ps}(v^$a=xgG;Ynh{Z(_Eu8hV0_dNvw%J;-nN}%UiZ@J3mp- zutl(HOQ=ie?WOEr0vYVq_ zXbeCLRqStLTTE9;WN^d_=Cn-fcs&j!1i6ZW+#6d+__~zy*dNR=0eQjIGE>fc+D1xW z0EJ3Ai{O(cFud>&*H1Dg0T7;BFXXK?GrA8?{?~JpTh*7 z)Tx?o*4F)Js!Y86+!)n8Rl2i36YN4mg)tB)Pf%?vUETavWMfJBG-v-RKBf!_w^;k{H_L*-m-R4?vKGeA9|U zC|~f~*^z`!$N#`@XRR2kQLRtfc7pOBc-y%e%V!>+fYCkmwbz-n$;XBB*_>BzC5`~* z0`-mWobHUW5G})gKtiM6maQlNLmvqWeT;YGEGW9~jQTI%ue(2MAaP)!FG z9`~EtL&_)j_8~J#?$<-%# zDbIqnY<6i&XSpU|58W7=VTireT6+QLRY>k6<&Ag6I+FFar*5Ohwiha~Wk-lx6Vxa; zu7ox%wGgj8h#4O1lw_#nh&;Ok8544AQ1Oh2Bva&&?f9*9BltlaPe2jdDHTtuBdRdT z4%=Oj;z=I&D3QqIqbKf}Uur=ASLRS9K_rL9iW%wlYnOhGnx@e1Bjbq0^)<{OQE+&;2=@=TI2#m)9 zFdCb`ejDw|M+T@?+;-BD-KykbCCp?`&jC(BBgH37f@$n>gm6^NPw3_RGx<;O&)>$m zRYm@*htt{)8=m>%uz@2h94jS470PBEG@?vX3)Xj=Pc<+Cu>|)mPSeUFGf$T-pEFyU z-Y4Wqno>$nkE8aEyXMSSHDnKdC;n%18{1(4=QPt!BL# zmO)*b;m$b>3+7O#5Peih+#(+IFsCG{kBAg2e>E#g04~Pnc(O*Hg&dNvLK`JFcGG@y z!ayx^iv1mf&ISWlqAPoWk3cKzgf431&CU%5vlUl_G$)sUBH)RHeT&Bmf9OoM5BXLV z?fnPID&|s9fi6mfq}+3nfja^XZ)_nS@JW)kUHM*+=UqTZ5r{bg0U5^5jQ zqO!udxxTd1TL|O-QCPtGoh`262_F9HhlS2BDoBOq@bZpVsFv?|5Tbg(yZJ2=?-wC) zX^C+H4l$g&4z50P+jc6jO23pk9EKDR@?6qYbjVJ$bO5BJtb^YHyo7G2bck^-kW>}c zkrLJ!VI`i7k9QUI&O%@`Vv(dUH(xF*q*MZjh+-gS!(~>5O?Xb=LvpM==BL_j+EQ8p z5(lv#!UxT*)GOj-r_U6gxli@_Ube4#{n4Ja^|v;i(+o{02CV1mo_ONQR`Qa|Jny!J zhA60z6`?J0WJ*u2%kM(V43E$7N)ccz9eyJU<(Vf-lt1Jhe_86v#M zJYry2y*Att!^JG*gS;`7*_M>f>p}6;GSj}QGvt1wU>d=VAHrGPMP~YUjh*QP@M8-D zlQiqTiL46|thcTF%e5jCNR{K*)Nx1L!?K)(hK?7+j>)17LHdZu0!5c^##3zCx5VgR zJ06GJmu>xxVt!i=;i5+!?nJ0HGG$uKAI>#eR9!Q-!jOJ z>6=vN!9Go$=gwr|C|ee&-Y{3_m6TWbxond5lzwLs=CNG6yY>^Au7Z9$rUenaez5d$ zU3@9sZ5-#$@Lx~@c%^#%^p?iM8U3WunH3z}KiVO6of^c{K`4oqd{pq>WCA1&c#enZ z5)Z9>4b*jB7t8|v$7UFaNvTDcta9fym0p3d_4wz`;Azqr#(2g+@Y~u-{D0QYl}&OT z+1jL^HajfqPkms(fB_p6C{Q3lf&>Nz1_pux1_A~K2@DbxD9~WQ1`Q54f9iYQ6(L!f z>Aduug>(rB5QUp-#RS z{=?VoAx@F=gQ={O+U&!(2;HDqNYzs;oa0%G>Uh2H+W*2Pr}IaODd8d9|?u2wH{p0%bn3nd_h(y;{jl#Bp`7;6}g z2%ET%LlP~g&Ubh)sl?amQgxE5sF?K%O5N11uqIv|p4@=1YJgdm92ASRa{=H?P6#Jr z7)kXE0&i;rCs7Zw6Wjr54Ee28Dr;4ckbJRDLjN^=V4IbFOaX&E+ZIbpvl=UUR)5g-TBepB=r73wXLXN$UdiR7w6(x^e;`AjAYN(fIq;L4a_n?i4RHdg$m}9INL`7X40Gw5Tje! zuN6>r{mEA`L04(8mXTtCgJEMlp<|T0aoY`rx0pu5q$pI*D6sF)ewe@OI4Y&^ z=3ALhXu%=;9Ig0325$_<6)uatAbKU0)&M^A)=u2MP2tQrW5Yk1a9GIIWJSMA8#krd z<%ibps5zy_H373u1BI0N5Rz(DAH%acHSlOt>=14BJ&0>Q^K2JIq zJ;BX|2UW*$G$g7b+uII1AkHGbE4)Du2tr1g9d@_0yax2$bmjVtZV5->7ldeGrYz4Q zI3{fAvVND(!NLMEg~(LM6lxDYkCQ3iw|-8loG7Q4TNT+bmgUI3-5KXORz#xNp(eky z+7*XtCST?$ajIJxt~Q#@(Bv?PV*owcCudXaZdiiG(3AznMb zHeeMmf`=4LaCQ1i*Z@E=lV%A?NGxnRitvBBEOf5+W4$dorGzvBX0&#B^L(8>NZ(hF zjlR+5%V#AAxLcUYL>j$${?1v(Hib5hUmKeQJWG1=g?Obz)U*+eK76B$Q#`~|Iz}4M zjGVR7N#!(IH_AXEysjk!D>=d`UR-t49CxbqDE6qoeK?gU<&d+5ito$Y@U>7~`2-=g zc#t?y6hMbtNbpN{@_NPA39X|=Er{w~BaiB2=%8yB1RnWPY zBpUESe`l?PP-7Ys;2zz~zTw9P_aIg`ddjmL_v27)HGB=cgtd$gy{O}FBtb|h?c)!) z13QtdeyLE+^5?@an~fKaksub%5-MG(o01UC{_AXmz3h=TgF4}?Nc~{zXQVzm@wfq{Xux3qjXL6iw*)DYO5RpRxbKqs-iTQ1y82BrE zYAfCN5!y^>qg*{3udz(w3pjj13(BzOWzkmF^=z834y3T`qh>%q%ipF}Qq21o-bRrg>O3q9 znKROUV^ucF=quvF9x27ygGD>45t287W^L_s0t7U{U4vUB79ZQm{i<^4KA4mE75Zbf zkMfig)>dd)C%%%qBOx$9GnFA^q0I)fRj{7e!bRn6&_cz+OV0yIw>G!BULWWk zEw-Ud=_rcNKXu&6vNZK$Hk)2X9$N{7w#975vhf&$wHvgH+ks2)H`auL)GbEn!0|5V zrRxGUv2L+*pVd8u@N@XlDo|^+lEwgCeUkfu%{+z2Q zj)-oD@y6Vian3#N*e~+8&i3Td3;#qB=9KkB#N4!pk1>45kNEe(-s%Chca*!C70yy2 z7L&5WEQ)Hb$1%S57JX}{IVVjEa*y58%MYmYsK|s#gHA^fJ%&8zSsHDON2x^hHiq%m z_iP~E2f4f5z+)h|lx`jyqu0Sq#>IGj-Dm4-G{d@-26@kqWz|g=sBrWmxT8~_c#~Gh zgU|d$(Wh}f5I?uonCj)4y@UkdEF_Z^0 z3pVQttI~>f{!LNC&Dt&sR%#{Xg4-6Xb+6tI77-qp*-H(p@*r$U_P|uXn`20?y}>Gq z;~R%%H{AL{St6w3!RkzL!mkIc#$I-Hi?JeQ1tn%2he_M|(5ud*>

atJyKYwc_j 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 Ns(5etDh`5Lr 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�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

e##qC9t_z>c- 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 z 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

{&D*Ban{9K%;p;)9igH#Cl78amyWxm;jb`{ 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 diff --git a/docs/dat-thang.1 b/docs/dat-thang.1 new file mode 100644 index 0000000..adc621e --- /dev/null +++ b/docs/dat-thang.1 @@ -0,0 +1,188 @@ +.TH DAT-THANG 1 "October 2026" "dat-thang v0.0.002" "User Commands" +.SH NAME +dat-thang \- SDL2 local archive and peer discovery client +.SH SYNOPSIS +.B dat-thang +.RI [ --archive DIRECTORY ] +.RI [ --site HOSTNAME ] +.RI [ --offline ] +.br +.B dat-thang --version +.SH DESCRIPTION +dat-thang is the SDL2 graphical client for browsing a local filesystem directory, +explicitly loading or creating Dat archives, and controlling the client's online +discovery state. The top-left switch applies to the whole client. OFF leaves all +loaded archive topics, unannounces when possible, closes the network socket, and +clears peer state. ON connects the client and announces archives in its loaded +archive list. Selecting an archive or browsing a directory does not toggle the +client's network state or create an archive. +.PP +Version v0.0.002 is a prototype in pre-alpha. +.PP +The pane to the right of the Merkle graph navigates the local filesystem. +Click a directory to enter it; navigation alone never creates or shares an +archive. The bottom bar displays the selected filename and directory. Use +F9 or IMPORT DAT to load an existing archive from the selected directory, or +CREATE NEW DAT to deliberately create one. Loaded archives are remembered in +the archive switcher and shared whenever the client is online. Secret signing keys are stored under +.B $HOME/.dat/secret_keys/ +using Dat Desktop's sharded discovery-key paths, with one raw secret file per +feed. See the installed key-management documentation. +.PP +Click the local SITE field while an archive is loaded to assign that archive a +dat-dns hostname. Type the hostname and press Return to save a local alias; this +client resolves it immediately. The confirmation dialog copies the corresponding +dat-dns value to the clipboard. Public Dillo-compatible resolution uses the +hostname's HTTPS /.well-known/dat response, whose first line is dat:// followed +by the 64-digit archive key and whose optional second line is TTL=. +Escape cancels hostname editing. The site field can also display the +hostname supplied with --site. Archive displays the selected archive key in +hexadecimal or compact Base32768 form; click the format control to switch and +COPY to copy its dat:// address. Version shows the active +selected metadata-feed version and latest version; click the arrows to check out +another recorded version. The matching Merkle view shows the feed's block count +and a tree view of its active version. +The peer pane distinguishes discovered peer announcements from authenticated +connected peers with a marker on each buddy-list row. Double-click a peer to +open Cable-thang's encrypted direct-message pane. Copy the desired dat:// link +from the local ARCHIVE field, paste it into chat with Ctrl+V, and press Enter +to send it. Click a received dat:// message to copy it, then click the +right-hand ARCHIVE field and press Ctrl+V followed by Enter to begin importing +into the currently selected local directory. The +incoming archive appears in the lower transfer table while peers are found and +metadata downloads. The current Cesspool message record is Cable-specific and +is not Cabal feed interoperability. +.PP +By default dat-thang listens on UDP 3282 and cable-thang listens on UDP 3000, +the stable ports used by Cesspool LAN discovery. If a preferred port is busy, +the client chooses another known Cesspool port. Set DDAT_PORT to pin the local +port. With no configured rendezvous, discovery is limited to the local network; +across networks, both clients need a reachable Cesspool node. +The YOU panel shows the selected room's local Cable feed address. Like Cabal, +the key is scoped to that room's local feed and remains stable across restarts; +room and optional admin keys are separate. The CLI prints the full address with +.BR "cable whoami " . +The panel also shows connected peers, discovered peers, and the total number of +loaded local directories available for sharing. +The lower table lists loaded archives as well as active transfers. Its progress +column is a bar: local archives are complete, while transfer bars count received +blocks against the peer's advertised feed lengths. Received blocks are checked +against SLEEP proofs and saved in per-feed .remote caches for later sessions. +For each local archive, TITLE and DESCRIPTION come from its root `dat.scm` +S-expression. Missing fields fall back to the directory name and archive key. +.SH OPTIONS +.TP +.B --version, -v +Print \fBdat-thang v0.0.002 (prototype, pre-alpha)\fR and exit. +.TP +.B --archive DIRECTORY +Start in DIRECTORY. If it contains an existing .dat archive, load that archive; +otherwise browse the directory without creating or sharing an archive. Defaults +to the current directory. +.TP +.B --site HOSTNAME +Display HOSTNAME in the site field. +.TP +.B --offline +Start with peer discovery disabled, even when the directory already has an +archive. +.TP +.B --smoke +Render one frame and save build/linux/dat-thang-smoke.bmp on GNU/Linux or +build/windows/dat-thang-smoke.bmp on Windows. Intended for headless +render checks. +.SH CONTROLS +.TP +.B F1 +Show this manual page in the client. Use Up/Down or the mouse wheel to scroll; +press Escape to close it. +.TP +.B F9 +Load an existing Dat archive from the selected directory. This does not create +an archive or start discovery; use ON to share it. +.TP +.B CREATE NEW DAT +Create a Dat archive for the selected local directory and import its current +files. Use ON to start discovery after creation. +.TP +.B IMPORT DAT +With no remote address entered, load an existing archive in the selected local +directory. To import a remote archive, enter a dat:// URL in the +right-hand SITE or ARCHIVE field, select an empty destination directory, then +click IMPORT DAT. Verified metadata and content blocks are cached below that +directory's .dat folder. +.TP +.B Escape +Toggle the client menu. +.TP +.B Mouse +Click OFF to disconnect all peers and go offline. Click ON to bring the client +online and announce loaded archives. With no loaded archives it still connects +to Cesspool without announcing a topic. Archive selection changes the local view. +Click entries in the right-hand pane to navigate +directories or select a filename. Click ARCHIVES above the pane to switch +between known local archives. The directory pane has its own scrollbar and +mouse-wheel scrolling. The bottom archive/transfer table includes known shared +archives and active transfers; its horizontal scrollbar can be dragged, and +the mouse wheel scrolls rows (Shift+wheel scrolls columns). Enter a remote dat:// address in the mirrored +SITE or ARCHIVE field before selecting IMPORT DAT. Drag transfer-table header dividers to resize +columns; use Shift+wheel over the table to scroll horizontally. The bottom +track indicates horizontal position. Right-click opens load, create, and +copy-address actions. COPY includes the selected archive version in its dat:// +link. +.SH NETWORK +The client reads endpoints saved by \fBdat bootstrap add host:port\fR or +\fBdat bootstrap set host:port\fR from +$HOME/.config/dat-gpt/bootstrap and also accepts DDAT_BOOTSTRAP as a comma- or +semicolon-separated list. The Settings menu also edits this endpoint and +restarts discovery immediately. Run \fBcesspool-bootstrap\fR on the rendezvous +host with \fB--advertise HOST:PORT\fR to print the address clients should use. +It does not query an outside service for a public IP. Allow the chosen UDP +port through the host firewall and router. +This is Cesspool rendezvous for these C clients; it is not a BitTorrent DHT +node and cannot be substituted for the historical JavaScript Dat bootstrap +servers. OFF leaves every archive topic and closes the Cesspool socket. ON +starts archive discovery; the indicator turns green only after a Cesspool +response or connected peer. +.SH ENVIRONMENT +.TP +.B DDAT_BOOTSTRAP +Comma- or semicolon-separated Cesspool rendezvous endpoints, for example +127.0.0.1:3282. +Saved endpoints from dat bootstrap add are also loaded automatically. +.TP +.B DDAT_PORT +UDP listening port. If unset, Cesspool selects an available default port. +.TP +.B DAT_THANG_MANPAGE +Optional path to the dat-thang manual source displayed by F1. +.SH MENU +Escape opens the vertical Settings, Return, Exit menu. Settings contains +persistent controls for the default copied-address format, sonar audio, and +connecting on startup. Use Up/Down and Enter, or click an item. Return resumes +the client; Exit closes it. Escape from Settings returns to the menu. +.SH AUDIO +Click the speaker icon to play and send a two-second low square-wave cue. +Clicking the dolphin plays and sends a one-second arpeggiated chiptune cue. +The dolphin cue goes to connected authenticated peers. The speaker cue refreshes +discovery for the active archive and is sent to discovered peers as their secure +sessions become ready. Peers play received cues locally; the speaker response +causes the peer to return one dolphin chirp, which plays at the sender and marks +the peer found. The waveform viewer draws samples captured from the actual SDL +audio output. +.SH BUILD DATE +The header shows the program build date in Discordian format (ddate). The +calendar conversion is compiled directly into dat-thang; no external ddate +program or runtime lookup is needed on Windows or GNU/Linux. +.SH DILLO +To install the Dillo DPI helper for the current Dillo source, build with make, run make install using the +same PREFIX as Dillo, add proto.dat=dat/dat.dpi to ~/.dillo/dpidrc, and restart +Dillo. The helper first reads its local archive cache at +~/.dillo/dat/archives/XX/. On a cache miss, it can use the configured +Cesspool bootstrap to find a peer, authenticate the SLEEP session, and fetch +the archive metadata and content feeds into that cache. Start the bootstrap +before the clients so the first archive announcement is recorded. The Linux +side-by-side runbook uses the default GNU/Linux output path, build/linux, to +keep its objects separate from Windows artifacts in a shared checkout. +.SH SEE ALSO +ddat(1), dat(1) diff --git a/docs/dat.1 b/docs/dat.1 index 76308da..4e471ae 100644 --- a/docs/dat.1 +++ b/docs/dat.1 @@ -25,14 +25,82 @@ .Oc .Sh DESCRIPTION -[WIP] dis dat mf thang +The C dat command creates and maintains local Dat archives. Archive metadata +is append-only, so each metadata entry advances the archive version and older +file contents remain addressable by their earlier version number. +.Pp +The status subcommand reports the archive key, current version, metadata entry +count, and latest live path counts. The files subcommand lists the paths at +the current version, or at a selected historical version. +.Pp +Canonical archive addresses use the historical 64-character hexadecimal key. +Base32768 is available for compact test addresses with Cm dat url --compact; +the parser accepts both forms. Secret key filenames and dat-dns records +continue to use hexadecimal keys. +.Pp +The library provides an archive session, Cesspool discovery, and a Hypercore +feed codec. The `hypercore` build selects the legacy V6 wire session. Discovery +and transport establishment are separate layers. +.Sh COMMANDS +.Bl -tag -width "checkout" +.It Cm create , init +Create an archive, write its title, description, and dat:// address as a +`dat.scm` S-expression, and print the address. New archives do not get a +`dat.json` sidecar. `--title` and `--description` set the presentation fields. +.It Cm share , sync +Import changed local paths into the archive. +.It Cm status +Show the archive key and metadata/version counts. +.It Cm files Op version +List the live files and directories at a version. +.It Cm log +Show appended metadata entries, including history and tombstones. +.It Cm cat Ar version path +Write a historical file's bytes to standard output. +.It Cm checkout Ar version destination +Export a historical snapshot to a directory. +.It Cm url Op version Op path +Print an archive URL, optionally pinned to a version and path. +.It Cm resolve Ar hostname +Resolve a dat-dns hostname through the legacy HTTPS /.well-known/dat response +and print its canonical dat:// key and TTL. This does not use the operating +system DNS TXT resolver. +.It Cm dns set Ar hostname archive-dir|dat://key +Create or replace this machine's local dat-dns name mapping. Names are stored +in Pa ~/.dat/dat-dns and used by resolve and the dat-thang SITE field. For +public Dillo-compatible resolution, publish an HTTPS /.well-known/dat file +whose first line is dat:// followed by the archive's 64-digit key and whose +optional second line is TTL=. +.It Cm dns list +List local dat-dns name mappings. +.It Cm dns remove Ar hostname +Remove a local dat-dns name mapping. +.It Cm verify Op dir +Verify metadata and content feeds with the archive's public keys. +.It Cm bootstrap Op udp-port +Run a Cesspool rendezvous node on all local interfaces. The node prints the +DDAT_BOOTSTRAP setting participating C clients should use. The UDP port must +be reachable from those clients through the host firewall and router. Peer +records expire after ten minutes without an announce or lookup. This is the +Cesspool protocol used by dat-thang and cable-thang, not the historical +JavaScript Dat BitTorrent DHT bootstrap protocol. +.It Cm bootstrap add Ar host:port +Save a Cesspool rendezvous endpoint for dat-thang and cable-thang in the +per-user configuration file. It is used in addition to DDAT_BOOTSTRAP. +.It Cm bootstrap set Ar host:port +Replace the active rendezvous list with one Cesspool endpoint. The desktop +client exposes the same setting under Settings, so this command is only needed +for terminal-only setup. +.El .Sh OPTIONS .It Fl d Debug .Sh FILES .Bl -tag -width -indent .It Pa ~/.dat/secret_keys/ -its there +Contains private feed secrets as raw Ed25519 secret bytes, indexed by +discovery key. See the key format and Dat Desktop compatibility notes in +the installed key-management.md documentation. .Sh AUTHORS "Dat" was first written about by Mathias Buus Madsen & Maxwell Ogden (c) 2015 .Nm diff --git a/docs/ddat-gui.md b/docs/ddat-gui.md new file mode 100644 index 0000000..63bc0c8 --- /dev/null +++ b/docs/ddat-gui.md @@ -0,0 +1,126 @@ +# `dat-thang` v0.0.002 + +This release is a prototype in pre-alpha. + +Build with `make ENABLE_SDL2=1` (requires SDL2 and SDL2_ttf development files); the Dat GUI is `build/linux/dat-thang` and the +separate Cable room chat client is `build/linux/cable-thang`. `make +ENABLE_SDL2=1 install` installs both clients and their manuals. Run +`build/linux/dat-thang --archive DIRECTORY --site HOSTNAME` to begin in a local +directory. The initial directory defaults to `.`. + +The GNU/Linux default build path is `build/linux`; the native Windows target +uses `build/windows`. Set `BUILD=...` only when you intentionally need another +isolated build tree. + +Both interfaces are painted with SDL2 and intentionally resemble terminal +clients: fixed-width labels, high-contrast panes, keyboard shortcuts, and +terminal-style status text. They do not use Notcurses. For the existing +JavaScript Cabal terminal client, run `cabal-cli `; `cable` is +only the C address and identity utility. + +Both clients load the bundled `assets/fonts/MorePerfectDOSVGA.ttf`, verify its +family name is `More Perfect DOS VGA`, and render hard edged glyphs at 14 px +with SDL2_ttf. Installed clients load that same font from +`$(PREFIX)/share/datgpt/fonts/`; there is no alternate font setting. + +The dat-thang speaker graphic is the original 84 by 84 pixel crop from the +provided mockup, stored as `assets/datthang-speaker.bmp`. Green pixels from the +mockup background are transparent in the GUI. Installed clients load it from +`$(PREFIX)/share/datgpt/icons/`. + +The top-left OFF/ON switch controls the client network. OFF leaves every joined +topic, unannounces when possible, closes the Cesspool socket, and clears peer +state. ON connects and announces only archives already loaded in the archive +list; navigating the filesystem never creates or loads an archive. The status +indicator turns green after a Cesspool response or connected peer. F9 loads an +existing archive or imports the entered remote link. `--offline` starts with +network discovery disabled. + +Run `build/linux/cesspool-bootstrap --port 3282 --advertise HOST:3282` on the +rendezvous host. It prints the endpoint you supplied and a Windows firewall +rule; it does not query an outside IP lookup service. Forward UDP 3282 through +the edge router or cloud firewall. In each GUI, open Escape > Settings +> Cesspool Node and enter that reachable `host:port`; saving it restarts +discovery. Terminal users can use `dat bootstrap set host:port` or `cable +bootstrap set host:port`. `CESSPOOL_BOOTSTRAP` and `DDAT_BOOTSTRAP` accept comma- or +semicolon-separated endpoints. `DDAT_PORT` selects each client's local UDP +port. With no explicit `DDAT_PORT`, dat-thang listens on UDP 3282 and +cable-thang on UDP 3000 so same-LAN discovery can reach both clients on their +stable ports. If the preferred port is busy, a client chooses another known +Cesspool port; set `DDAT_PORT` to pin a specific local port. The node keeps short-lived peer announcements and returns endpoints +announcing the same topic. This is Cesspool rendezvous for the C clients, not +the historical JavaScript Dat/Bittorrent DHT. + +The pane to the right of the Merkle graph is a local filesystem browser. Click +a directory to navigate into and select it as the local directory. Click a file +to select its name. The bottom bar reports that filename and selected local +directory. `CREATE NEW DAT` opens or creates that directory's archive and begins +discovery when online. With no remote address entered, `IMPORT DAT` opens and +syncs the archive already present in the selected directory. To share an archive, +copy its address with the `COPY` control beside the local ARCHIVE field. +Double-click a peer in the buddy list to open private chat, paste the address +with Ctrl+V, and press Enter to send it. The recipient clicks the received +`dat://` message to copy its address, selects an empty destination directory, +clicks the right-hand ARCHIVE field, presses Ctrl+V, then Enter. Enter starts the remote import; an incoming +archive row appears in the transfer table as peer discovery and metadata download +begin. The mirrored SITE, ARCHIVE, and VERSION fields show its hostname, key, +path, and selected version. Received metadata and content blocks are +proof-verified and cached under that directory's `.dat` folder. The client +learns the content-feed key from the authenticated, verified metadata header. +The status beside the remote fields reports address, discovery, and feed state. +Escape opens the vertical Settings, Return, Exit menu. Settings persists the +default address format, sonar audio, and online-at-startup preferences; use +Up/Down and Enter or click a row. +The table columns can be resized by dragging their header dividers; +the wheel scrolls horizontally over the transfer table. Its bottom track shows +the current horizontal position. The ARCHIVES control above the filesystem pane +switches among remembered shared directories; the list persists in +`$HOME/.dat/dat-thang-shares`. Right-click a transfer for pause/resume, cancel, +reveal destination, or copy address. Right-click elsewhere for load/share, +create, and copy-address actions. +For each locally shared archive, the table's TITLE and DESCRIPTION come from +the archive root's `dat.scm` S-expression, for example +`((title "dat-thang") (description "dat:// implemented over c & scheme"))`. +The client reads this file as Scheme data; missing fields fall back to the +directory name and archive address. + +The client passes discovered Cesspool peers to the archive session, opens uTP, +authenticates each connection with a signed nonce exchange bound to the peer +key, then passes session frames through the feed handshake and replication +code. The peer pane distinguishes discovered announcements from authenticated +connections. The lower table lists authenticated archive sessions and their +replication state. Upload/download readouts count session frames, and the top +diamonds pulse on payload activity. The center hexagon is red offline, amber +online without a connected peer, and green when connected. Awkward Moments, +Share Guy, and Rank remain planned placeholders. Received blocks are verified +against their SLEEP proofs and stored in per-feed `.remote` caches, then +reloaded when the next session starts. + +The original dolphin-in-a-porthole and radar/speaker art from the mockup are +embedded in the client. The dolphin sits at the upper right and sends a +one-second arpeggiated chiptune sonar cue to authenticated connected peers. The +speaker sits at the lower right of the YOU panel and plays a two-second low +square-wave cue; while online on an archive topic, it refreshes discovery and +sends the cue to discovered peers as their authenticated sessions come up. A +receiving peer plays the speaker cue and returns one dolphin chirp; the sender +plays that echo and marks the peer found. The waveform panel plots samples +captured from SDL's audio output ring, so it shows the actual waveform being +played. Cues travel as a small Dat-thang UI record over the authenticated +Cesspool/uTP connection and never enter archive feeds. + +F1 opens the client manual from the source tree or installed manpage. Up/Down +and the mouse wheel scroll it; Escape closes it. Escape toggles the menu when +the manual is closed. The source can be selected with `DAT_THANG_MANPAGE`. + +The site field shows the explicit hostname supplied at launch or a local +`dat dns set` alias associated with the selected archive. Beaker's legacy +`dat:` handler resolves hostnames to keys before opening an archive, and its +`dat-dns` supports DNS-over-HTTPS TXT and `/.well-known/dat`; the bundled +Dillo plugin disables the first option and uses HTTPS `/.well-known/dat`. +The C resolver follows that Dillo behavior after checking local aliases; it +does not call the operating system DNS TXT resolver. The archive field defaults +to the historical 64-digit hexadecimal address; its format control switches to +compact Base32768, and COPY produces the selected format. +The build date uses the Discordian `ddate` calendar. +The Merkle panel is a shape preview driven by metadata entry count, not hashes +or proofs. diff --git a/docs/dillo.md b/docs/dillo.md new file mode 100644 index 0000000..78c228d --- /dev/null +++ b/docs/dillo.md @@ -0,0 +1,201 @@ +# Dillo integration + +Use the Dillo source maintained at . +The project also lists as an up-to-date +mirror; that mirror was used for the Linux run documented below. +The latest published release is 3.2.0; the current source tree identifies its +development version as 3.3.0. The old `dillo.org` host is not controlled by the +Dillo developers. + +`make` builds `$(BUILD)/dat.dpi` (normally `build/linux/dat.dpi`), a native C Dillo +Plugin Interface (DPI) helper. +It uses Dillo's external DPIP protocol and does not link against Dillo internals +or depend on a particular browser ABI. The helper authenticates each accepted +DPIP connection with the `~/.dillo/dpid_comm_keys` value and handles `open_url` +and `DpiBye`. It follows the historical plugin lifecycle: `dpid` passes its +listening socket on stdin, the helper accepts a request connection, and closes +that connection after the response body. DPIP responses do not use HTTP status +lines or `Content-Length`; closing the request socket marks the end of the +page. Current Dillo +continues to load `.dpi` plugins and associate them with protocols in +`~/.dillo/dpidrc`. + +Build and install the DPI with the same prefix used by Dillo. For the usual +`/usr/local` installation: + +```sh +make +sudo make install +``` + +If Dillo uses a different plugin directory, set `PREFIX` and `DPIDIR` on both +commands. The default plugin path is `/usr/local/lib/dillo/dpi/dat/dat.dpi`. +The per-user `~/.dillo/dpidrc` must contain both the DPI root and this mapping +(Dillo does not merge `dpi_dir` from the system config into the per-user file): + +```ini +dpi_dir=/usr/local/lib/dillo/dpi +proto.dat=dat/dat.dpi +``` + +Then restart Dillo (which restarts `dpid`). The plugin first checks Dillo's +local archive cache. On a cache miss it can join the archive's Cesspool topic, +authenticate a peer session, fetch the metadata and content feeds, and then +serve the requested path. Both Dillo's DPI and the seeding Dat-thang client +must use the same Cesspool bootstrap node. Configure that endpoint for the +environment that launches each program: + +```sh +build/linux/dat bootstrap set 127.0.0.1:3282 +``` + +The command stores the node in `~/.config/dat-gpt/bootstrap`. For a public +test, use the reachable `HOST:PORT` of the Cesspool node instead of the local +loopback address. The Cygwin Dillo and native Windows Dat-thang configurations +are separate when their `HOME` directories differ, so configure each using its +own `dat` executable/environment. The helper does not use Hyperswarm or +Hypercore; its remote feed transfer uses this project's Cesspool and SLEEP +session protocol. + +Use the full key shown by Dat-thang, then enter `dat:///` in Dillo. The +helper caches fetched data at `~/.dillo/dat/archives/XX/` and +serves historical versions, `index.html`, `.html` fallback, explicit `//` +directory listings, parent-directory navigation, stat queries, and the MIME +rules used by the old `dillo-dat` plugin. Directory listings retain its +up/directory/file icons and sort directories first, then names in descending +order. The internal `dpi:/dat/` page lists archives opened by this DPI process, +shows title, description, and version from `dat.scm` (read as an S-expression +without evaluating archive contents), falls back to `CNAME`, and provides the +unswarm/reload and quit controls. Status messages identify name resolution, +archive retrieval, and file serving. If a fetch cannot complete, it returns a +text error page that distinguishes an unconfigured or unreachable node, no +peer announcing the archive key, and a peer transfer timeout. + +Version pins use the metadata-entry count shown by `dat status`: +`dat://+2/` reads the snapshot at version 2, while `dat:///` follows +the latest version. Root navigation looks for `index.html`. `make test` checks +the archive behaviors and starts the helper with a real TCP listening socket, +then verifies response bytes and EOF framing over an accepted connection. + +## Linux bootstrap, Dat-thang, and Dillo smoke run + +Run these steps from the repository root after installing the build +dependencies in `INSTALL`. GNU/Linux objects and executables default to +`build/linux`, separate from the Windows build in `build/windows`. The bundled `site/` +archive already has an address and three recorded versions; this flow leaves it +unchanged. + +Build and install the GUI plus the C Dillo helper: + +```sh +./configure +make ENABLE_SDL2=1 +sudo make ENABLE_SDL2=1 install +``` + +Merge these entries into `~/.dillo/dpidrc`, preserving any other DPI entries, +then restart Dillo so `dpid` reloads the mapping: + +```ini +dpi_dir=/usr/local/lib/dillo/dpi +proto.dat=dat/dat.dpi +``` + +Use three terminals. Terminal 1 runs a local discovery node for this same-host +smoke run. Start it before either client so it receives the initial announce: + +```sh +build/linux/cesspool-bootstrap --bind 127.0.0.1 --port 3282 \ + --state-dir "$HOME/.local/state/dat-gpt/cesspool-bootstrap" \ + --advertise 127.0.0.1:3282 +``` + +Terminal 2 configures the client and starts the SDL client with the bundled +archive explicitly loaded. Leave its window open and online: + +```sh +build/linux/dat bootstrap set 127.0.0.1:3282 +DDAT_BOOTSTRAP=127.0.0.1:3282 DDAT_PORT=3000 \ + build/linux/dat-thang --archive "$PWD/site" +``` + +Terminal 3 starts Dillo with an empty per-run cache, so the page must arrive +from the archive being announced by Dat-thang. The temporary home gives Dillo +its own fresh cache and DPID keys without disturbing the normal profile: +keep this shell open for the two `dilloc` version checks below. + +```sh +dillo_home=$(mktemp -d) +mkdir -p "$dillo_home/.dillo" +cat > "$dillo_home/.dillo/dpidrc" <<'EOF' +dpi_dir=/usr/local/lib/dillo/dpi +proto.dat=dat/dat.dpi +EOF +archive=$(build/linux/dat url / "$PWD/site") +HOME="$dillo_home" DDAT_BOOTSTRAP=127.0.0.1:3282 dillo "$archive/" & +sleep 5 +HOME="$dillo_home" dilloc title +HOME="$dillo_home" dilloc dump | grep -F 'The site is online' +``` + +The latest `site/` snapshot is version 3 and displays “Site is online.� Open +the version-1 pin in the existing Dillo window to verify historical navigation; +it predates `dat.scm` and `index.html` and shows the earlier archive listing: + +```sh +historical=$(build/linux/dat url 1 / "$PWD/site") +HOME="$dillo_home" dilloc open "$historical/" +sleep 2 +HOME="$dillo_home" dilloc url +HOME="$dillo_home" dilloc dump | grep -F 'dat-logo.png' +``` + +Return to the setup page at version 3: + +```sh +latest=$(build/linux/dat url 3 / "$PWD/site") +HOME="$dillo_home" dilloc open "$latest/" +sleep 2 +HOME="$dillo_home" dilloc url +HOME="$dillo_home" dilloc title +HOME="$dillo_home" dilloc dump | grep -F 'The site is online' +``` + +For another archive, replace `site` with its loaded local archive directory, +and choose a version from `build/linux/dat log DIRECTORY`. The same Cesspool node +must be reachable by both the Dat-thang GUI and Dillo helper. The bootstrap +process reports announced peer and topic counts. A successful run shows the +GUI online, a cold Dillo cache populated with `.dat/metadata.remote` and +`.dat/content.remote`, and the Dillo title `Site is online`. With this archive, +Dillo loads both `+1` (historical listing) and `+3` (the current setup page). + +## Windows with Cygwin Dillo + +Build the helper from the repository root in Cygwin Bash. The DPI uses the +normal Cygwin `gcc`, `make`, `libcurl-devel`, and `libsodium-devel` packages. +The helper links the bundled S7 reader so `dat.scm` metadata is parsed using the +project's Scheme representation; it never evaluates archive-supplied forms. + +```sh +make build/linux/dat.dpi +install -d /usr/local/lib/dillo/dpi/dat +install -m 755 build/linux/dat.dpi /usr/local/lib/dillo/dpi/dat/dat.dpi.exe +``` + +Put both entries in `~/.dillo/dpidrc` (for this installation that is +`C:\cygwin64\home\Home\.dillo\dpidrc`): + +```ini +dpi_dir=/usr/local/lib/dillo/dpi +proto.dat=dat/dat.dpi.exe +``` + +Restart Dillo so `dpid` reads the configuration. The `site/` folder in this +repository is the archive source; Dillo's cache is separate at +`~/.dillo/dat/archives/XX/`. A seeded archive is browseable only +after its feed data is present in that cache or a live Cesspool fetch completes. +The setup page source is `site/index.html`. `tests/test_dpi.sh` checks the +historical and latest snapshots, S-expression metadata, listing, stat, and +dashboard output. The Windows end-to-end flow, including `+N` navigation across +multiple archives, has been confirmed by the user. The Linux cold-cache flow +above was also run against current Dillo source and the Cesspool bootstrap. diff --git a/docs/key-management.md b/docs/key-management.md new file mode 100644 index 0000000..e03544f --- /dev/null +++ b/docs/key-management.md @@ -0,0 +1,85 @@ +# Archive addresses, keys, and write access + +## ELI5 + +A Dat address is like a mailbox number that is also a read capability: anyone +who knows it can request the archive. The address is not the pen. The private +metadata signing key is the pen that lets a writer append new archive history. +Dat Desktop's `dat-node` exposes this distinction as `archive.writable`; its +file importer requires a writable archive. A reader can download and serve the +archive without being able to change its history. + +The historical address +`dat://59d8099399510e23a3c693fb5887b7bba1586f4117f7ef1c5872622ada0c1181` +contains the public metadata key. It does not contain or reveal the private +signing key. Losing that secret does not make the archive unreadable; it means +you cannot write as its existing owner. Making a new keypair creates a different +archive address. Dat's original archive model is single-writer: a reader does +not gain write permission by downloading a copy. + +## How Dat Desktop stores the secret + +Dat Desktop's `dat-secret-storage` indexes each Hypercore feed's secret by its +**discovery key**, not by the `dat://` metadata public key. The discovery key +is BLAKE2b of `hypercore`, keyed by that feed's public key. The Unix path is +sharded: + +```text +$HOME/.dat/secret_keys// +``` + +The file is random-access storage for the feed's raw 64-byte Ed25519 secret. +The C client reads and writes that same file path and byte format. Dat Desktop +also uses a separate local owner marker to avoid competing local writers; the +C client does not currently maintain that marker. See the +[archived dat-secret-storage implementation](https://github.com/dat-ecosystem-archive/dat-secret-storage/blob/master/index.js) +and [dat-storage documentation](https://www.skypack.dev/view/dat-storage). + +## What this C implementation does + +The C implementation now uses the same per-feed discovery-key paths and raw +64-byte secret format. Both the metadata feed and content feed have their own +files: + +```text +$HOME/.dat/secret_keys// +$HOME/.dat/secret_keys// +``` + +The public halves remain in `/.dat/metadata.key` and +`/.dat/content.key`. The C client validates each secret against its +matching public key before using it. This discovery-key layout and raw secret +file format are the sole supported secret store. + +When the C client opens an archive, its public metadata remains readable +without local signing keys. Writing requires the matching metadata and content +keypairs in the C secret directory, plus normal operating-system write access +to the local archive directory. A local filesystem being writable does not +grant Dat write authority; the matching signing secrets are still required. +Conversely, having the secrets does not override read-only filesystem +permissions. + +## Finding the historical key + +The historical metadata secret's Dat Desktop path is derived from the address +above. Compute its `hypercore` discovery key, then use: + +```text +$HOME/.dat/secret_keys// +``` + +The matching content-feed secret is stored by its own content discovery key. +The C implementation searches both paths directly; it does not need a +conversion step for Dat Desktop's raw secret files. Preserve the original +`~/.dat/secret_keys` tree while testing access. + +`dat keys export ` prints the two keypairs as hexadecimal; +treat that output as secret. `dat keys import + ` checks that both public halves match +the archive before saving their secret bytes in the shared format. Importing newly generated +keypairs cannot restore write access to an existing address. + +For context, the archived [dat-node documentation](https://awesome.datproject.org/dat-node) +describes `archive.writable` and notes that importing files requires a writable +archive. The historical Dat model is single-writer; see the +[Dat security FAQ](https://dat-ecosystem.github.io/docs/docs/security-faq). diff --git a/docs/libcess.md b/docs/libcess.md new file mode 100644 index 0000000..bd89e43 --- /dev/null +++ b/docs/libcess.md @@ -0,0 +1,62 @@ +# libcess + +`libcess` is the Cesspool base used by both p2p applications: + +```text + ┌───────────� + │ libcess │ + │ UDP, peer │ + │ discovery│ + └─────┬─────┘ + ┌───────┴───────� + ┌────▼────� ┌────▼────� + │ ddat/dat│ │ cable │ + │ SLEEP │ │ Cabal │ + │ archives│ │ rooms │ + └─────────┘ └─────────┘ +``` + +The package owns the UDP socket transport, Cesspool messages, bootstrap and +rendezvous client/server logic, peer/topic state, and its Ed25519 bootstrap +identity. It has no SLEEP, Dat, Cable, or JSON dependency. Dat and Cable link +the same `libcess.a` so a configured `NETWORK_PROTOCOL` selects one discovery +network for both applications. The protocol token is data in the root +`NETWORK_PROTOCOL` file for the full project; a standalone libcess build reads +`libcess/NETWORK_PROTOCOL`. + +## Standalone build + +From the project root: + +```sh +make -C libcess +make -C libcess install PREFIX="$HOME/.local" +``` + +This builds `libcess/build/libcess.a` and `libcess/build/cesspool-bootstrap`. +Only a C99 compiler, GNU make, and libsodium development files are required. +The bootstrap executable links only libcess, libsodium, and the platform UDP +socket library; it does not need libcurl or the Dat application library. + +Apps including `cesspool.h` and `cess_net.h` link `libcess.a` after their own +static archive, then link libsodium. The installed headers are under +`include/libcess/`. + +## Bootstrap node + +`cesspool-bootstrap` is the bootstrap service shipped with the library. It +tracks topic announcements and returns peer endpoints; archive and chat data +still travels between peers. The process must stay running and its chosen UDP +port must be reachable from clients. `--advertise HOST:PORT` prints the +externally reachable address; the tool does not call an outside IP lookup +service. Configure that address in `ddat` or `cable` settings on each client. + +The default identity location used by the integrated application is +`dat-gpt/cesspool-bootstrap-state/identity.key` under the platform's state +directory. The raw 96-byte public-plus-secret Ed25519 keypair matches the +existing bootstrap identity file layout, so extracting the library keeps a +running node's identity stable. + +The old `dat_peer_t`, `datnet_t`, and `datnet_*` spellings remain as thin +source-compatibility aliases in the main application headers. New Cesspool +consumers should use `cess_peer_t`, `cessnet_t`, and `cessnet_*`. diff --git a/docs/network-protocol.md b/docs/network-protocol.md new file mode 100644 index 0000000..7c37d1b --- /dev/null +++ b/docs/network-protocol.md @@ -0,0 +1,41 @@ +# Network protocol identity + +`NETWORK_PROTOCOL` in the repository root is the single editable protocol +token. `cesspool_discovery_key` uses it as the Cesspool topic namespace. +Hypercore V6 has no fixed protocol-name field: `userData` is optional +application data, and the handshake `id` is protocol-local. + +The default file contains `cesspool`. Production uses the Cesspool discovery +and transport path. `hypercore` is reserved for compatibility experiments +against the historical Dat/Dillo implementation; it is not the production +network stack and does not refer to Cabal's newer Hypercore stack. The V6 +session opens channel 0 with a clear 24-byte nonce, then encrypts subsequent +stream bytes with XSalsa20; channel 1 carries the content feed. + +Dat-thang keeps one client network key in `~/.dat/dat-thang.network-key`. +Loading or switching a local or remote archive must not replace that key: the +same peer identity announces every archive the client shares. Cable keeps its +Cabal-compatible room identity separately. Archive feed keys still determine +the archive topic; they are not the client's network identity. +The discovery UDP socket tries 3282 first, then 3000, 3002, 3004, 2001, 2003, +and 2005, matching Dillo's `hyperdiscovery` port list. Callers can request a +specific port. + +The compatibility builds use the packed channel/type header and protobuf payload schema +from `extra/schema.proto`. The `cesspool` token selects V7 channel numbering; +`hypercore` selects V6 channel numbering. This message codec is independent +of the transport and Cesspool's UDP discovery packets. + +The Cesspool V7 handshake includes the selected token in `userData`. The V6 +Hypercore handshake omits `userData`, because the legacy implementation treats +it as application-owned data rather than network identity. + +## Scheme application view + +When S7 is enabled, protocol frames can be viewed as a tagged Scheme record +with named fields: `(dat:wire-message (channel . N) (type . N) (payload . +#u8(...)))`. `dat-wire->sexp` and `dat-sexp->wire` call the existing C frame +codec. The record is an application representation only; the frame header, +payload bytes, channel numbering, encryption, and protocol identity continue +to come from the selected C network implementation. Scheme code is installed +under `share/datgpt/scheme` as part of the default install. diff --git a/docs/primitives.md b/docs/primitives.md new file mode 100644 index 0000000..9b4ea03 --- /dev/null +++ b/docs/primitives.md @@ -0,0 +1,107 @@ +# datgpt primitives + +This is the working map of the old `dat://` implementation. Names follow the +project: SLEEP owns feeds, cesspool owns Hyperswarm-style discovery, and dat +joins the feeds into an archive. + +The standalone [libcess package](libcess.md) owns Cesspool discovery and UDP +transport. Both the Dat (`ddat`) and Cable (`cable`) applications link it. + +## SLEEP storage + +- Feed header, tree nodes, signatures, bitfield pages, keys, and data files. +- Flat-tree addressing, spans, roots, leaf and parent hashing. +- Feed open, length, indexed read, append, flush, and close. +- Public-key-only in-memory read-only feeds accept verified remote blocks and + retain their proofs without pretending the local node has signing keys. A + `.remote` cache reopens sparse blocks only after verifying each stored proof. +- Data/tree bit access, index regeneration, scan, encode, and decode. +- Key generation, public/secret persistence, hashes, signatures, root signing. +- Merkle proof nodes, proof encode/decode, hash folding, and root signature check. + +## Archive + +- Paired metadata and content feeds. +- Create, open, close, public keys, writable keypairs. +- Separate secret-key save and writable reopen. +- Header pointing from metadata to content. +- File, directory, symlink, and deletion history entries. +- Entry enumeration, reverse path lookup, and content block lookup. +- Recursive filesystem import for regular files, directories, and symlinks. +- Files are chunked across content blocks and can be exported recursively. +- Snapshot sync compares content, mode, target, and millisecond mtime before + appending; paths removed locally receive tombstones, while unchanged paths + do not extend the feed. +- `dat status`, `dat files`, `dat verify`, `dat resolve`, historical `cat`, + `checkout`, and versioned URL commands are available from the CLI. + +## Peer protocol + +- The root `NETWORK_PROTOCOL` selects legacy V6 framing (`hypercore`) for the + old Dat target; `cesspool` selects V7 framing for the in-tree network mode. +- Protobuf feed-open messages, handshake id/live/userData, request/range/data + messages, extension payloads, and standard DATA node/signature fields. +- Have/want ranges, block requests, cancellation. +- Legacy WANT messages with omitted length run to the peer's current feed end; + responses are bounded by the local feed length and representable bitfield. +- Data plus Merkle proof, and named extensions. +- Per-channel replication state, remote bitfield, and request queue. +- `dat_session` joins one archive key to its discovery topic, carries discovered + peer addresses, opens the archive feeds, gates the wire handshake on an + externally authenticated transport, exchanges HAVE ranges, requests missing + blocks, and verifies signed proofs before caching received blocks. +- A peer callback exposes newly discovered or rediscovered peers so a transport + adapter can dial them and call `dat_session_peer_secure` after authentication. +- `dat_session_poll` runs an optional transport poll callback alongside the + Cesspool UDP poll, refreshes topic membership, and reconciles discovered peers. +- `dat_session_tick` is the nonblocking form of that poll cycle. +- `dat_session_receive` accepts arbitrary byte-stream chunk boundaries, buffers + partial frames, decrypts the V6 stream at byte offsets, and dispatches multiple + coalesced frames from one read. +- A read-only replica keeps received proofs and can serve them onward. The + caller still supplies the authenticated byte transport and poll loop. + +## cesspool + +- UDP socket transport, bootstrap nodes, listen, poll, and destroy. +- Legacy lookup, announce, unannounce, ping, and holepunch vocabulary. +- Topic join/leave and periodic refresh. +- Peer table, failure/last-seen state, topic table, discovery callbacks. +- Legacy protobuf requests/responses and compact public/local peer parsing. +- XOR-distance peer selection and dht-rpc transaction tracking. + +## uTP + +- Header encode/decode and connection identifiers. +- SYN/connect/accept, state acknowledgements, data, FIN, and reset states. +- Send segmentation and timer boundary. +- Receive reordering, cumulative and selective acknowledgements, congestion + window, retransmission timers, and in-flight packet retirement. DATA packets + are capped at the 1200-byte payload size, and malformed control packets are + rejected before entering the reorder buffer. + +## Name resolution + +- `dat-dns` resolution through HTTPS `/.well-known/dat`, plus the local + `~/.dat/dat-dns` name map and TTL-aware cache. The TXT packet decoder is a + compatibility helper and is not used for runtime name resolution. +- Canonical `dat://` output; the domain remains a name for the resolved + archive key rather than a separate archive identity. + +## Later layer + +- S7-facing records use one tagged, named-field form: + `(dat:wire-message (channel . N) (type . N) (payload . #u8(...)))`. + `dat:record-ref` and `dat:record-set` provide the shared field access layer; + protocol payload bytes remain bytevectors. +- `dat-wire->sexp` and `dat-sexp->wire` adapt the existing C frame codec. This + keeps the C codec authoritative for peer bytes while Scheme is the common + application representation; a Scheme record is never serialized directly + onto the network. +- Initial S7 bindings expose archive create, open, close, entry count, and the + wire-record adapter. The Scheme adapters and JSON reader are part of the default install. +- Hyperdrive's JS `Index` and `Stat` records should use the same named-field + convention as their read/write adapters are added; they must retain their + original protocol field numbers and binary fields. +- The local CLI sits above the archive API; peer transport polling and the + authenticated stream layer remain separate callers of `dat_session`. diff --git a/docs/scheme-interop.md b/docs/scheme-interop.md new file mode 100644 index 0000000..954b301 --- /dev/null +++ b/docs/scheme-interop.md @@ -0,0 +1,80 @@ +# Scheme records at protocol boundaries + +## Representation contract + +The old Dat/Cabal implementations use JavaScript objects and JSON because they +run on Node. This implementation uses S7 Scheme values and tagged +S-expressions as its native application representation. Keep records, +configuration, and the C-to-client boundary in that form; translate to JSON +only at a compatibility edge whose existing peer format requires JSON. Dat +wire bytes and Cabal's canonical Hypercore JSON remain their established +protocol formats. + +S7 is the application view for structured records that would otherwise be +assembled as JavaScript objects or JSON-shaped maps. New archive descriptions +are stored as `dat.scm` association lists. Existing wire codecs, feed files, +and peer protocol bytes retain their established formats; `dat.json` is not +written for newly created archives. + +## Record form + +Records are tagged lists with named association fields: + +```scheme +(dat:wire-message + (channel . 1) + (type . 3) + (payload . #u8(...))) +``` + +`dat:record-ref` reads a named field and accepts an optional fallback. +`dat:record-set` returns a record with one field replaced or added. Names are +symbols; strings stay strings; protocol integers stay exact integers; binary +data stays an S7 byte-vector. Absence is represented by no field, not by +conflating an absent value with `#f` or an empty byte-vector. + +## Wire boundary + +`dat-wire->sexp` and `dat-sexp->wire` adapt the C `dat_wire` frame codec. The +frame record keeps the channel, type, and opaque payload bytes named and +inspectable. The active C codec remains authoritative for varints, framing, +message payloads, channel assignments, protocol identity, and encryption. +S-expressions are not sent to Dat peers and do not alter feed blocks or +signatures. Cable's local UI and room-event boundary uses S7 and +S-expressions inside the native C application; it does not start a Node +adapter. + +Protocol-specific adapters can add named records such as `dat:feed-open` or +`dat:hyperdrive-stat` above this frame boundary. Each adapter should map +explicitly to the existing C type or legacy field numbers, preserve unknown +fields when forwarding is required, and keep byte strings as byte-vectors. +This allows Cesspool and test-only legacy Hypercore views to share Scheme-level record +operations while retaining their separate codecs and wire identities. + +## Build and use + +S7 is bundled and required by the default build. The build produces +`ddat-scheme` and links the runtime into the application library. With no +arguments it starts the REPL; `-e` evaluates an expression, `-l` loads a file, +`-I` adds a module directory, and `--repl` enters the REPL after scripts run. Dat's host +runtime registers the C bindings, assembles the module search path, and loads +`dat-init.scm`, `dat-record.scm`, `dat-wire.scm`, and `dat-json.scm` before +user code. `dat:json-parse` accepts strict JSON and returns tagged Scheme +objects/arrays while preserving JSON strings, booleans, numbers, and null. +`dat:json-stringify` maps those same tagged Scheme values back to JSON at +compatibility boundaries. Scheme/S-expression records remain the application +representation; JSON is emitted only where an existing peer format requires +it. The test-only Cabal harness owns Cabal's canonical JSON Hypercore encoding. + +The runtime searches `DAT_SCHEME_PATH` (a platform-separated list), +`DAT_SCHEME_DIR`, the source-tree `scheme/` directory, and the configured +installed directory. Application modules map feature names such as +`dat:archive` to `dat/archive.scm`, load through `(dat:require 'dat:archive)`, +and finish with `(provide 'dat:archive)`. `dat:with-archive` closes an archive +even when its body exits through a Scheme condition. Archive values are S7 +foreign objects: explicit close is idempotent, and garbage collection closes +an archive that the caller abandons. + +This is the embedding boundary S7 expects: the host owns startup, load paths, +native types, resource lifetime, CLI policy, and application modules. The C +wire/feed implementations still own protocol bytes and on-disk formats. diff --git a/docs/sleep.pdf b/docs/sleep.pdf deleted file mode 100644 index 1c59c9118f16b9321d1aadda4a84aab3aac9a2c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 207531 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%0D@u` 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%0Ax=En9NobykYLdQD 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 V2C)P1@d}BG3Imv!6t$E9{|8PBs(Jtb diff --git a/docs/source-distribution.md b/docs/source-distribution.md new file mode 100644 index 0000000..9d86847 --- /dev/null +++ b/docs/source-distribution.md @@ -0,0 +1,38 @@ +# Source and release layout + +The repository has one source tree for both platforms. Generated files have +separate destinations: + +- GNU/Linux compiler outputs: `build/linux/` +- Windows compiler outputs: `build/windows/` +- Windows portable runtime staging: `dist/windows-x64/` +- Installers and platform packages: `dist/installers/` and `dist/linux-*/` +- Source archive: `dist/source/` + +`build/` is disposable compiler output and is ignored by Git. External Dillo +checkouts live in `test-deps/`; machine keys and test-run output live in +`local-data/`. Both are local-only and ignored. Reusable tests and archive +fixtures live under `tests/`, so a clean operation cannot remove them. Do not +zip the working directory directly. To make a source-only archive that retains +the historical fixtures and interoperability tests while excluding machine +data and generated outputs, run from PowerShell at the repository root: + +```powershell +.\package-source.ps1 +``` + +The script writes `dist\source\dat-gpt-v0.0.002-source.zip` (the version comes +from `VERSION`). It includes project sources, documentation, assets, the +historical code under `extra/`, `third_party/`, the complete `tests/` tree, and +the root historical `.dat` verifier fixture. It excludes installed +`node_modules/`, `build/`, `dist/`, +`local-data/`, `test-deps/`, `.git/`, per-machine Dillo keys, and generated +configure files. It also skips local identity keys, cookie jars, SSH private +keys, and `.env` files by name. It does not compile or bundle executables. + +Keep persistent node identity files in per-user application data outside the +checkout. The source ZIP excludes local runtime state. + +For target packages, use `make package` on GNU/Linux and +`build-windows-installer.ps1` on Windows. The Windows `build-windows.ps1` also +creates a portable ZIP without requiring Inno Setup. diff --git a/docs/windows-build.md b/docs/windows-build.md new file mode 100644 index 0000000..a3070b7 --- /dev/null +++ b/docs/windows-build.md @@ -0,0 +1,96 @@ +# Windows build + +The native Windows build uses MSYS2's UCRT64 toolchain and creates a runnable +folder of PE executables plus their runtime DLLs and data files. It does not +reuse the GNU/Linux `build/` directory or generated `config.mk`. + +Install MSYS2, then install the UCRT64 packages needed by the project: + +```sh +pacman -S --needed \ + mingw-w64-ucrt-x86_64-gcc \ + mingw-w64-ucrt-x86_64-make \ + mingw-w64-ucrt-x86_64-pkgconf \ + mingw-w64-ucrt-x86_64-libsodium \ + mingw-w64-ucrt-x86_64-curl \ + mingw-w64-ucrt-x86_64-SDL2 \ + mingw-w64-ucrt-x86_64-SDL2_ttf +``` + +From the repository root in PowerShell, run: + +```powershell +.\build-windows.ps1 +``` + +Compiler outputs, including tests and intermediate files, stay in +`build\windows`. Distribution output is staged separately: `dist\windows-x64` +contains only the eight user-facing programs, their runtime DLLs, assets, +Scheme files, and manuals. `dist\installers` contains a portable ZIP and, when +Inno Setup is installed, the setup executable. Do not distribute the contents +of `build\windows` directly. The build date's Discordian calendar conversion +is compiled into the GUI; no external `ddate` installation is needed. + +To create just the portable ZIP, run `build-windows.ps1`. To create the install +wizard as well, run `build-windows-installer.ps1`. + +`cable-thang.exe` is a native C/S7 client using Cesspool and Sleep. Node.js +and Cabal JavaScript modules are needed only for optional interoperability +tests. Cable audio and video support are experimental and disabled by default, +so the regular Windows build does not require FFmpeg. To enable both, install +FFmpeg and build with: + +```powershell +pacman -S --needed mingw-w64-ucrt-x86_64-ffmpeg +.\build-windows.ps1 -EnableCableMedia +``` + +The UCRT64 FFmpeg libraries provide Cable's AAC-LC encoder/decoder, OBS MPEG-TS +ingest, and low-resolution JPEG video exchange; their runtime DLLs are copied +beside the Windows executables. In `cable-thang`, `OBS INPUT` (or +Ctrl+Shift+V) listens on local UDP port 5000; OBS can publish MPEG-TS there +using its Custom FFmpeg Output. + +The GNU/Linux workflow is `./configure`, `make`, and `make install`; its default +compiler output is `build/linux`. To create a staged GNU/Linux tarball, use +`make package`. Release artifacts go under `dist/` on both platforms. To create +a source-only archive, run `package-source.ps1`; its contents and exclusions +are documented in `docs\source-distribution.md`. +That configuration checks for libcurl because the Dillo-compatible +`dat-dns` lookup uses HTTPS `/.well-known/dat`; it does not call the operating +system DNS TXT resolver. + +## Install wizard + +Install [Inno Setup 6](https://jrsoftware.org/isinfo.php) after installing the +MSYS2 build tools. From PowerShell at the repository root, run: + +```powershell +.\build-windows-installer.ps1 +``` + +The script rebuilds the Windows applications, bundles their DLLs and runtime +assets, and creates `dist\installers\dat-thang-setup-0.0.002-x64.exe`. +The application identifies itself as `dat-thang v0.0.002 (prototype, +pre-alpha)`; the executable file version is `0.0.2.0`. +The wizard offers a per-user or all-users install, a destination folder, +optional desktop shortcut, optional PATH entry for the terminal commands, and +an optional launch at the end. Uninstall removes a PATH entry only when this +installer added it. The app and installer use the same generic green hexagon +icon. The Start menu includes dat-thang and a command prompt with the installed +tools on PATH. + +`cable-thang.exe` is included for opening a room address from the command +prompt and connects through Cesspool. + +`cesspool-bootstrap.exe` runs a dedicated Cesspool discovery node in the +foreground. It is for an operator deploying a reachable public rendezvous +host, not something chat users need to run. The node needs a stable state +directory and inbound UDP on the selected port; see `docs\cesspool-bootstrap.1` +for process-supervisor and firewall setup. The Windows installer includes the +executable and the operator manpage in its `docs` folder. + +Keep the node identity outside the checkout. In PowerShell, run the development +executable with `--state-dir "$env:LOCALAPPDATA\dat-gpt\cesspool-bootstrap-state"`. +That keeps the private identity key in per-user application data rather than in +the source tree or disposable compiler output. diff --git a/extra/cabal-core-5.0.1.tgz b/extra/cabal-core-5.0.1.tgz new file mode 100644 index 0000000000000000000000000000000000000000..15141e2c22368ae216272c2a33819ac64fbbb950 GIT binary patch literal 18630 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 literal 0 HcmV?d00001 diff --git a/extra/cable.png b/extra/cable.png new file mode 100644 index 0000000000000000000000000000000000000000..c63dbe948b773d59d5fbdf771823a4d9935ba759 GIT binary patch literal 8958 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

-h*dk>3%vgo?& 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 literal 0 HcmV?d00001 diff --git a/extra/dat-dpi/.gitignore b/extra/dat-dpi/.gitignore new file mode 100644 index 0000000..c003733 --- /dev/null +++ b/extra/dat-dpi/.gitignore @@ -0,0 +1 @@ +/build/ diff --git a/extra/dat-dpi/LICENSE b/extra/dat-dpi/LICENSE new file mode 100644 index 0000000..dbbe355 --- /dev/null +++ b/extra/dat-dpi/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/extra/dat-dpi/Makefile b/extra/dat-dpi/Makefile new file mode 100644 index 0000000..1326045 --- /dev/null +++ b/extra/dat-dpi/Makefile @@ -0,0 +1,72 @@ +CC ?= cc +AR ?= ar +RANLIB ?= ranlib +OBJCOPY ?= objcopy +INSTALL ?= install +PREFIX ?= /usr/local +DPIDIR ?= $(PREFIX)/lib/dillo/dpi +DESTDIR ?= +BUILD ?= build +CFLAGS ?= -O2 -std=c99 -Wall -Wextra -Wpedantic +CPPFLAGS ?= +LDFLAGS ?= + +PKG_CONFIG ?= pkg-config +CURL_CFLAGS ?= $(shell $(PKG_CONFIG) --cflags libcurl 2>/dev/null) +CURL_LIBS ?= $(shell $(PKG_CONFIG) --libs libcurl 2>/dev/null || echo -lcurl) +SODIUM_CFLAGS ?= $(shell $(PKG_CONFIG) --cflags libsodium 2>/dev/null) +SODIUM_LIBS ?= $(shell $(PKG_CONFIG) --libs libsodium 2>/dev/null || echo -lsodium) + +CPPFLAGS += -Iinc -DSLEEP_SALT -DDAT_BUILD_DIR=\"$(BUILD)\" -DDAT_NETWORK_PROTOCOL=\"$(shell tr -d '\r\n' < NETWORK_PROTOCOL)\" $(CURL_CFLAGS) $(SODIUM_CFLAGS) +LIBS = $(SODIUM_LIBS) $(CURL_LIBS) -lm -ldl + +LIB_SOURCES = src/base32768.c src/cable.c src/cable_feed.c src/dat.c src/dat_browse.c src/dat_dns.c src/dat_fetch.c src/dat_walk.c src/dat_net.c src/dat_wire.c src/dat_session.c \ + src/cesspool.c src/sleep.c src/sleep_tree.c src/sleep_bitfield.c \ + src/sleep_proto.c src/sleep_crypto.c src/sleep_writer.c \ + src/sleep_feed.c src/sleep_proof.c src/sleep_replication.c src/sleep_utp.c +LIB_OBJECTS = $(patsubst src/%.c,$(BUILD)/%.o,$(LIB_SOURCES)) $(BUILD)/sleep_verify_lib.o +DEPFILES = $(LIB_OBJECTS:.o=.d) $(BUILD)/s7.d + +.PHONY: all clean install install-user +all: $(BUILD)/dat.dpi + +$(BUILD): + mkdir -p $@ + +$(BUILD)/%.o: src/%.c | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) -MMD -MP -c $< -o $@ + +$(BUILD)/sleep_verify_lib.o: src/sleep_verify.c | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) -DSLEEP_VERIFY_NO_MAIN -MMD -MP -c $< -o $@ + +# S7 is used only as a parser for archive .scm metadata. Rename its optional +# command-line entry point so the DPI owns the process main(). +$(BUILD)/s7.o: src/s7.c | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) -w -MMD -MP -c $< -o $@ + +$(BUILD)/dpi_s7.o: $(BUILD)/s7.o + $(OBJCOPY) --redefine-sym main=dat_dpi_s7_standalone_main $< $@ + +$(BUILD)/libdatdpi.a: $(LIB_OBJECTS) + $(AR) rcs $@ $(LIB_OBJECTS) + $(RANLIB) $@ + +$(BUILD)/dat.dpi: src/dat_dpi.c src/dat_dpi_icons.h $(BUILD)/libdatdpi.a $(BUILD)/dpi_s7.o + $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) src/dat_dpi.c $(BUILD)/libdatdpi.a $(BUILD)/dpi_s7.o $(LIBS) -o $@ + +install: $(BUILD)/dat.dpi + $(INSTALL) -d "$(DESTDIR)$(DPIDIR)/dat" + $(INSTALL) -m 755 $(BUILD)/dat.dpi "$(DESTDIR)$(DPIDIR)/dat/dat.dpi" + $(INSTALL) -d "$(DESTDIR)$(PREFIX)/share/doc/dat-dpi" + $(INSTALL) -m 644 README.md "$(DESTDIR)$(PREFIX)/share/doc/dat-dpi/README.md" + +install-user: $(BUILD)/dat.dpi + $(INSTALL) -d "$(HOME)/.dillo/dpi/dat" + $(INSTALL) -m 755 $(BUILD)/dat.dpi "$(HOME)/.dillo/dpi/dat/dat.dpi" + @if ! test -f "$(HOME)/.dillo/dpidrc"; then printf '%s\n' 'dpi_dir=$(DPIDIR)' > "$(HOME)/.dillo/dpidrc"; fi + @if ! grep -q '^proto\.dat=' "$(HOME)/.dillo/dpidrc"; then printf '%s\n' 'proto.dat=dat/dat.dpi' >> "$(HOME)/.dillo/dpidrc"; fi + +clean: + rm -rf "$(BUILD)" + +-include $(DEPFILES) diff --git a/extra/dat-dpi/NETWORK_PROTOCOL b/extra/dat-dpi/NETWORK_PROTOCOL new file mode 100644 index 0000000..dea08fb --- /dev/null +++ b/extra/dat-dpi/NETWORK_PROTOCOL @@ -0,0 +1 @@ +cesspool \ No newline at end of file diff --git a/extra/dat-dpi/README.md b/extra/dat-dpi/README.md new file mode 100644 index 0000000..dd2925f --- /dev/null +++ b/extra/dat-dpi/README.md @@ -0,0 +1,92 @@ +# DAT protocol plugin for Dillo + +This repository builds the standalone Dillo Protocol Interface (DPI) plugin for +`dat://` archives. It is kept separate from the dat-thang application repository +so it can be built, packaged, and proposed to Dillo as an independent plugin. + +The plugin implements the Dillo DPI server protocol and serves archive pages +from the C implementation. Archive contents and metadata are read through SLEEP; +`dat.scm` is parsed as S-expressions with the embedded S7 reader and is never +evaluated as executable archive code. The networking identity is the Cesspool +protocol token in `NETWORK_PROTOCOL`. Dat-DNS is the project's archive-name +mapping and is not public DNS. + +## Build requirements + +- A C99 compiler, GNU Make, and binutils (`objcopy`) +- `pkg-config` +- libsodium and libcurl development headers and libraries +- Dillo 3.0.5 or newer with its DPI daemon (`dpid`) + +On Debian-like systems, the build libraries are commonly provided by +`libsodium-dev` and `libcurl4-openssl-dev`; package names vary by distribution. + +Build the plugin: + +```sh +make +``` + +The result is `build/dat.dpi`. The build output is ignored by Git. + +## Install for one user + +```sh +make install-user DPIDIR=/path/to/dillo/dpi +``` + +This installs `~/.dillo/dpi/dat/dat.dpi`. If `~/.dillo/dpidrc` does not exist, it creates one with `dpi_dir` set to the supplied `DPIDIR`; choose the system DPI directory used by your Dillo installation. It then adds this association to +`~/.dillo/dpidrc` if it is not already present: + +```ini +proto.dat=dat/dat.dpi +``` + +Restart Dillo's plugin daemon (`dpidc stop`, then open Dillo again), and navigate +to a `dat://` archive address. User plugins are searched before the configured +system DPI directory. + +## System install + +```sh +sudo make install +``` + +By default, this installs to `/usr/local/lib/dillo/dpi/dat/dat.dpi`. Add the +following line to `~/.dillo/dpidrc` (or the system `dpidrc` used by your Dillo +installation), then restart `dpid`: + +```ini +proto.dat=dat/dat.dpi +``` + +Override paths as needed, for example: + +```sh +make install PREFIX=/usr DPIDIR=/usr/lib/dillo/dpi DESTDIR=/tmp/package-root +``` + +## Protocol/version behavior + +The plugin accepts legacy hexadecimal and compact Base32768 Dat keys. A `+N` +version suffix selects the requested archive version, matching the versioned +navigation supported by the C archive implementation. The plugin accesses +archives through the configured Cesspool networking layer when content is not +available locally. + +## Dillo plugin integration + +Dillo plugins are independent programs using the DPI protocol. Dillo searches +for `.dpi` executables in user and system DPI directories and associates a URL +scheme through `proto.=` in `dpidrc`. See the Dillo +User Manual's Plugins and Dpidrc sections for the browser-side configuration. + +The Dillo project requests plugin proposals as a repository link and short +description. This repository is prepared for that review; it has no configured +remote and has not been submitted upstream. + +## Source and license + +The C implementation and required headers are copied from the dat-thang source +project. This repository carries the project's GNU AGPL v3 license. The bundled +S7 source retains its upstream notices in `src/s7.c`. diff --git a/extra/dat-dpi/inc/base32768.h b/extra/dat-dpi/inc/base32768.h new file mode 100644 index 0000000..56da68c --- /dev/null +++ b/extra/dat-dpi/inc/base32768.h @@ -0,0 +1,17 @@ +#ifndef BASE32768_H +#define BASE32768_H + +#include +#include + +/* UTF-8 C-string form of qntm/base32768's BMP-safe Base32768 encoding. */ +int base32768_encode(const uint8_t *input, size_t input_length, + char *output, size_t output_capacity); +int base32768_decode(const char *input, uint8_t *output, + size_t output_capacity, size_t *output_length); + +/* Enumerate the alphabet: bits=15 selects the 32,768 main characters; + * bits=7 selects the 128 possible final padding characters. */ +int base32768_codepoint_at(unsigned bits, uint32_t index, uint32_t *codepoint); + +#endif diff --git a/extra/dat-dpi/inc/cable.h b/extra/dat-dpi/inc/cable.h new file mode 100644 index 0000000..e0d60e2 --- /dev/null +++ b/extra/dat-dpi/inc/cable.h @@ -0,0 +1,81 @@ +#ifndef CABLE_H +#define CABLE_H + +#include +#include +#include "keys.h" +#include "sleep_feed.h" + +#define CABLE_KEY_BYTES 32u +#define CABLE_CHAT_TEXT_BYTES 768u +#define CABLE_CHAT_WIRE_BYTES (4u + 8u + 2u + CABLE_CHAT_TEXT_BYTES) +#define CABLE_CHAT_SEAL_BYTES (CABLE_CHAT_WIRE_BYTES + 48u) + +enum cable_address_scheme { + CABLE_ADDRESS_CABAL = 1, + CABLE_ADDRESS_CABLE = 2, + CABLE_ADDRESS_HYPER = 3 +}; + +typedef struct cable_chat_message { + uint64_t timestamp_ms; + char text[CABLE_CHAT_TEXT_BYTES + 1u]; + size_t text_length; +} cable_chat_message_t; + +/* A Cabal user address is that user's persistent feed public key. A room + * address may additionally carry a room moderation/admin key. */ +typedef struct cable_room_address { + uint8_t key[CABLE_KEY_BYTES]; + uint8_t admin_key[CABLE_KEY_BYTES]; + enum cable_address_scheme scheme; + int has_admin; +} cable_room_address_t; + +/* The schemes share a 32-byte key representation; the caller decides whether + * it is a room key or a user feed key. hyper:// is a migration alias only. */ +int cable_address_parse(const char *address, uint8_t key[CABLE_KEY_BYTES], + enum cable_address_scheme *scheme); +int cable_address_format(const uint8_t key[CABLE_KEY_BYTES], + enum cable_address_scheme scheme, char *out, size_t capacity); +int cable_room_address_parse(const char *address, cable_room_address_t *room); +int cable_room_address_format(const cable_room_address_t *room, + enum cable_address_scheme scheme, char *out, size_t capacity); +int cable_user_address_format(const uint8_t user_feed_key[CABLE_KEY_BYTES], + enum cable_address_scheme scheme, char *out, size_t capacity); +/* Cabal stores the local writer in a room's storage, so derive a per-room + * local feed-key path instead of sharing one global identity across rooms. */ +int cable_user_identity_path(const char *home, + const uint8_t room_key[CABLE_KEY_BYTES], + char *out, size_t capacity); +/* Create a Cabal room signing key and persist it in the room directory. */ +int cable_room_create(const char *home, uint8_t room_key[CABLE_KEY_BYTES]); +/* Store/load the user's local feed secret key. The public key is derived and + * checked on load. The file contains only the 64-byte Ed25519 secret key. */ +int cable_user_identity_load_or_create(const char *path, sleep_keypair_t *identity); +/* Cabal stores messages as JSON blocks in each user's Hypercore feed. These + * helpers use the same Hypercore 7.x on-disk tree/signature layout. */ +int cable_user_feed_open(const char *home, + const uint8_t room_key[CABLE_KEY_BYTES], + sleep_keypair_t *identity, sleep_feed_t *feed); +int cable_user_feed_append(const char *home, + const uint8_t room_key[CABLE_KEY_BYTES], + const uint8_t *record, size_t record_length, + uint8_t user_key[CABLE_KEY_BYTES]); +int cable_user_feed_publish(const char *home, + const uint8_t room_key[CABLE_KEY_BYTES], + const char *record_type, const char *nick, + const char *channel, const char *text, + uint8_t user_key[CABLE_KEY_BYTES]); +int cable_chat_message_encode(const cable_chat_message_t *message, + uint8_t *out, size_t capacity, size_t *written); +int cable_chat_message_decode(cable_chat_message_t *message, + const uint8_t *input, size_t length); +int cable_chat_seal(const uint8_t recipient_ed25519[32], + const uint8_t *message, size_t message_length, + uint8_t *out, size_t capacity, size_t *written); +int cable_chat_open(const sleep_keypair_t *identity, + const uint8_t *sealed, size_t sealed_length, + uint8_t *out, size_t capacity, size_t *written); + +#endif diff --git a/extra/dat-dpi/inc/cable_media.h b/extra/dat-dpi/inc/cable_media.h new file mode 100644 index 0000000..d313bf8 --- /dev/null +++ b/extra/dat-dpi/inc/cable_media.h @@ -0,0 +1,56 @@ +#ifndef CABLE_MEDIA_H +#define CABLE_MEDIA_H + +#include +#include + +#define CABLE_MEDIA_SESSION_BYTES 16u +#define CABLE_MEDIA_HEADER_BYTES 42u +#define CABLE_MEDIA_MAX_PACKET_BYTES 4096u +#define CABLE_MEDIA_AAC_RATE 48000u +#define CABLE_MEDIA_AAC_BITRATE 96000u +#define CABLE_MEDIA_AAC_CHANNELS 1u +#define CABLE_MEDIA_AAC_SAMPLES 1024u + +enum cable_media_kind { + CABLE_MEDIA_AUDIO_START = 1, + CABLE_MEDIA_AUDIO_FRAME = 2, + CABLE_MEDIA_AUDIO_STOP = 3, + CABLE_MEDIA_VIDEO_START = 4, + CABLE_MEDIA_VIDEO_FRAME = 5, + CABLE_MEDIA_VIDEO_STOP = 6 +}; + +typedef struct cable_media_packet { + uint8_t kind; + uint8_t session[CABLE_MEDIA_SESSION_BYTES]; + uint64_t sequence; + uint64_t timestamp_us; + const uint8_t *payload; + size_t payload_length; +} cable_media_packet_t; + +int cable_media_packet_write(const cable_media_packet_t *packet, + uint8_t *output, size_t capacity, size_t *written); +int cable_media_packet_read(cable_media_packet_t *packet, + const uint8_t *input, size_t length); + +typedef struct cable_aac_encoder { void *implementation; } cable_aac_encoder_t; +typedef struct cable_aac_decoder { void *implementation; } cable_aac_decoder_t; +typedef int (*cable_aac_frame_fn)(void *context, const uint8_t *adts, + size_t length); +typedef int (*cable_pcm_samples_fn)(void *context, const int16_t *samples, + size_t sample_count); + +int cable_aac_encoder_open(cable_aac_encoder_t *encoder); +int cable_aac_encoder_frame(cable_aac_encoder_t *encoder, + const int16_t samples[CABLE_MEDIA_AAC_SAMPLES], + cable_aac_frame_fn output, void *context); +void cable_aac_encoder_close(cable_aac_encoder_t *encoder); +int cable_aac_decoder_open(cable_aac_decoder_t *decoder); +int cable_aac_decoder_frame(cable_aac_decoder_t *decoder, + const uint8_t *adts, size_t length, + cable_pcm_samples_fn output, void *context); +void cable_aac_decoder_close(cable_aac_decoder_t *decoder); + +#endif diff --git a/extra/dat-dpi/inc/cable_room.h b/extra/dat-dpi/inc/cable_room.h new file mode 100644 index 0000000..be29eda --- /dev/null +++ b/extra/dat-dpi/inc/cable_room.h @@ -0,0 +1,30 @@ +#ifndef CABLE_ROOM_H +#define CABLE_ROOM_H + +#include "cable.h" + +#define CABLE_ROOM_CHANNEL_BYTES 64u +#define CABLE_ROOM_NICK_BYTES 64u + +enum cable_room_event_type { + CABLE_ROOM_PEER_JOIN = 1, + CABLE_ROOM_PEER_LEAVE = 2, + CABLE_ROOM_NICK = 3, + CABLE_ROOM_CHANNEL_JOIN = 4, + CABLE_ROOM_TEXT = 5 +}; + +typedef struct cable_room_event { + uint8_t type; + uint64_t timestamp_ms; + uint8_t sender[CABLE_KEY_BYTES]; + char channel[CABLE_ROOM_CHANNEL_BYTES + 1u]; + char nick[CABLE_ROOM_NICK_BYTES + 1u]; + char text[CABLE_CHAT_TEXT_BYTES + 1u]; + size_t text_length; +} cable_room_event_t; + +/* In-memory UI event only. Cable's network records are Cabal Core Hypercore + * values and never serialize this C structure. */ + +#endif diff --git a/extra/dat-dpi/inc/cable_video.h b/extra/dat-dpi/inc/cable_video.h new file mode 100644 index 0000000..5f58eec --- /dev/null +++ b/extra/dat-dpi/inc/cable_video.h @@ -0,0 +1,22 @@ +#ifndef CABLE_VIDEO_H +#define CABLE_VIDEO_H + +#include +#include + +typedef struct cable_video_source cable_video_source_t; + +/* Read an OBS MPEG-TS feed from a local UDP socket, normally port 5000. + * Pixels are copied as RGB24; a generation change means a new frame arrived. */ +cable_video_source_t *cable_video_source_open(const char *url); +void cable_video_source_close(cable_video_source_t *source); +int cable_video_source_frame(cable_video_source_t *source, uint8_t *rgb, + size_t capacity, unsigned *width, unsigned *height, + uint64_t *generation); +/* Encode/decode one low-resolution JPEG frame for volatile Cable media. */ +int cable_video_encode_jpeg(const uint8_t *rgb, unsigned width, unsigned height, + uint8_t *jpeg, size_t capacity, size_t *written); +int cable_video_decode_jpeg(const uint8_t *jpeg, size_t length, uint8_t *rgb, + size_t capacity, unsigned *width, unsigned *height); + +#endif diff --git a/extra/dat-dpi/inc/cesspool.h b/extra/dat-dpi/inc/cesspool.h new file mode 100644 index 0000000..bc2f0de --- /dev/null +++ b/extra/dat-dpi/inc/cesspool.h @@ -0,0 +1,172 @@ +#ifndef CESSPOOL_H +#define CESSPOOL_H + +#include +#include +#include "keys.h" +#include "dat_net.h" + +// bootstrap node +// dht +// discovery +// lookup (key) ↳ lookup1 +// announce +// ping +// holepunch udp + +#define CESSPOOL_TOPIC_BYTES 32u +#define CESSPOOL_PEER_BYTES 32u + +enum cesspool_message_type { + CESSPOOL_LOOKUP = 0, + CESSPOOL_ANNOUNCE = 1, + CESSPOOL_UNANNOUNCE = 2, + CESSPOOL_REPLY = 3, + CESSPOOL_PING = 4, + CESSPOOL_PONG = 5, + CESSPOOL_HOLEPUNCH = 6 +}; + +typedef struct cesspool_message { + uint8_t type; + uint8_t topic[CESSPOOL_TOPIC_BYTES]; + uint8_t peer_id[CESSPOOL_PEER_BYTES]; + uint16_t port; + uint8_t payload[2048]; + size_t payload_len; +} cesspool_message_t; +typedef struct cesspool_request { + uint32_t type; + uint16_t port; + uint8_t local_address[6]; + size_t local_address_len; +} cesspool_request_t; +typedef struct cesspool_response { + uint8_t peers[1024]; + size_t peers_len; + uint8_t local_peers[1024]; + size_t local_peers_len; + /* Cesspool rendezvous records: IPv4(4), UDP port(2), peer key(32). */ + uint8_t peer_info[2048]; + size_t peer_info_len; +} cesspool_response_t; + +#define CESSPOOL_MAX_BOOTSTRAP 16u +#define CESSPOOL_MAX_PEERS 256u +#define CESSPOOL_MAX_TOPICS 64u +#define CESSPOOL_MAX_ANNOUNCEMENTS 1024u +#define CESSPOOL_MAX_TRANSACTIONS 64u +typedef struct cesspool_peer { + dat_peer_t address; + uint8_t public_key[32]; + uint8_t discovery_topic[CESSPOOL_TOPIC_BYTES]; + uint64_t last_seen; + unsigned failures; + int local; + int discovered; +} cesspool_peer_t; +typedef struct cesspool_topic { + uint8_t key[32]; + uint16_t port; + int announcing; + int looking_up; + uint64_t refresh_at; +} cesspool_topic_t; +typedef struct cesspool_transaction { + uint16_t id; + uint8_t target[32]; + uint8_t type; + uint64_t expires_at; + int active; +} cesspool_transaction_t; +typedef struct cesspool_announcement { + uint8_t topic[CESSPOOL_TOPIC_BYTES]; + uint8_t peer_id[CESSPOOL_PEER_BYTES]; + dat_peer_t address; + uint64_t last_seen; +} cesspool_announcement_t; +struct cesspool; +typedef void (*cesspool_transport_packet_fn)(struct cesspool *pool, + const dat_peer_t *source, + const uint8_t *packet, + size_t packet_len); +typedef struct cesspool { + datnet_t net; + dat_peer_t bootstrap_nodes[CESSPOOL_MAX_BOOTSTRAP]; + size_t bootstrap_count; + uint16_t port; + int ready; + int bootstrap_server; + cesspool_peer_t peers[CESSPOOL_MAX_PEERS]; + size_t peer_count; + cesspool_topic_t topics[CESSPOOL_MAX_TOPICS]; + size_t topic_count; + /* A peer may announce many topics; keep each membership independently. */ + cesspool_announcement_t announcements[CESSPOOL_MAX_ANNOUNCEMENTS]; + size_t announcement_count; + uint64_t bootstrap_last_seen; + uint64_t bootstrap_probe_at; + int bootstrap_reachable; + cesspool_transaction_t transactions[CESSPOOL_MAX_TRANSACTIONS]; + uint16_t next_transaction; + void (*on_peer)(struct cesspool *, const cesspool_peer_t *); + void (*on_holepunch)(struct cesspool *, const dat_peer_t *); + cesspool_transport_packet_fn on_transport_packet; + void *transport_context; +} cesspool_t; + +int cesspool_discovery_key(const uint8_t public_key[32], uint8_t topic[32]); +int cesspool_encode(const cesspool_message_t *message, uint8_t *out, + size_t capacity, size_t *written); +int cesspool_decode(cesspool_message_t *message, const uint8_t *input, + size_t input_len); +int cesspool_request_encode(const cesspool_request_t *request, uint8_t *out, + size_t capacity, size_t *written); +int cesspool_request_decode(cesspool_request_t *request, const uint8_t *input, + size_t input_len); +int cesspool_response_encode(const cesspool_response_t *response, uint8_t *out, + size_t capacity, size_t *written); +int cesspool_response_decode(cesspool_response_t *response, const uint8_t *input, + size_t input_len); +int cesspool_process_response(cesspool_t *pool, + const cesspool_response_t *response, + const uint8_t local_prefix[2]); +int cesspool_init(cesspool_t *pool, const uint8_t peer_id[32]); +int cesspool_add_bootstrap(cesspool_t *pool, const char *host, uint16_t port); +/* Load Cesspool bootstrap endpoints from DDAT_BOOTSTRAP and the per-user + * config/dat-gpt/bootstrap file. */ +int cesspool_configure_bootstraps(cesspool_t *pool); +int cesspool_bootstrap_config_add(const char *endpoint); +int cesspool_bootstrap_config_set(const char *endpoint); +int cesspool_bootstrap_config_get(char *endpoint, size_t capacity); +int cesspool_bootstrap_config_list(char *contents, size_t capacity); +int cesspool_bootstrap(cesspool_t *pool); +int cesspool_listen(cesspool_t *pool, const char *host, uint16_t port); +int cesspool_set_bootstrap_server(cesspool_t *pool, int enabled); +int cesspool_announce(cesspool_t *pool, const uint8_t topic[32], uint16_t port); +int cesspool_lookup(cesspool_t *pool, const uint8_t topic[32]); +int cesspool_unannounce(cesspool_t *pool, const uint8_t topic[32], uint16_t port); +int cesspool_ping(cesspool_t *pool, const dat_peer_t *peer); +int cesspool_holepunch(cesspool_t *pool, const dat_peer_t *peer); +int cesspool_poll(cesspool_t *pool, int timeout_ms); +int cesspool_add_peer(cesspool_t *pool, const cesspool_peer_t *peer); +int cesspool_remove_peer(cesspool_t *pool, const uint8_t public_key[32]); +cesspool_peer_t *cesspool_find_peer(cesspool_t *pool, const uint8_t public_key[32]); +int cesspool_join(cesspool_t *pool, const uint8_t topic[32], uint16_t port, + int announce, int lookup); +int cesspool_leave(cesspool_t *pool, const uint8_t topic[32]); +int cesspool_tick(cesspool_t *pool, uint64_t now_ms); +size_t cesspool_closest(cesspool_t *pool, const uint8_t target[32], + cesspool_peer_t **out, size_t capacity); +size_t cesspool_topic_peers(cesspool_t *pool, + const uint8_t topic[CESSPOOL_TOPIC_BYTES], + cesspool_peer_t **out, size_t capacity); +size_t cesspool_announced_peer_count(const cesspool_t *pool); +size_t cesspool_announced_topic_count(const cesspool_t *pool); +int cesspool_transaction_begin(cesspool_t *pool, uint8_t type, + const uint8_t target[32], uint64_t expires_at, + uint16_t *transaction_id); +int cesspool_transaction_finish(cesspool_t *pool, uint16_t transaction_id); +int cesspool_destroy(cesspool_t *pool); + +#endif /* CESSPOOL_H */ diff --git a/extra/dat-dpi/inc/dat.h b/extra/dat-dpi/inc/dat.h new file mode 100644 index 0000000..42bf30d --- /dev/null +++ b/extra/dat-dpi/inc/dat.h @@ -0,0 +1,114 @@ +#ifndef DAT_H +#define DAT_H + +#include +#include +#include "keys.h" +#include "sleep_proto.h" + +#define DAT_KEY_BYTES 32u +#define DAT_BLOCK_BYTES (64u * 1024u) +#define DAT_URL_BYTES 71u +#define DAT_LINK_BYTES 4096u + +typedef struct dat_archive { + char dat_path[1024]; + uint8_t metadata_key[DAT_KEY_BYTES]; + uint8_t content_key[DAT_KEY_BYTES]; + sleep_keypair_t metadata_keys; + sleep_keypair_t content_keys; +} dat_archive_t; +typedef struct dat_link { + uint8_t key[DAT_KEY_BYTES]; + uint64_t version; + int has_version; + char path[2048]; +} dat_link_t; +typedef struct dat_checkout { + const dat_archive_t *archive; + uint64_t version; +} dat_checkout_t; +typedef struct dat_url { + char host[256]; + char path[2048]; + uint64_t version; + int has_version; + int wants_stat; + int force_directory_listing; +} dat_url_t; +typedef struct dat_path_info { + sleep_metadata_entry_t entry; + int exists; + int is_directory; +} dat_path_info_t; +typedef int (*dat_archive_change_fn)(const sleep_metadata_entry_t *entry, + uint64_t version, void *context); +typedef int (*dat_archive_readdir_fn)(const sleep_metadata_entry_t *entry, + const char *name, void *context); + +/* Creates a new two-feed legacy-style archive skeleton under root/.dat. */ +int dat_archive_create(dat_archive_t *archive, const char *root); +int dat_archive_open(dat_archive_t *archive, const char *root); +int dat_archive_open_writable(dat_archive_t *archive, const char *root, + const char *secret_directory); +int dat_archive_save_secrets(const dat_archive_t *archive, + const char *secret_directory); +void dat_archive_close(dat_archive_t *archive); +size_t dat_archive_entry_count(const dat_archive_t *archive); +uint64_t dat_archive_version(const dat_archive_t *archive); +int dat_archive_append_file(dat_archive_t *archive, const char *path, + const uint8_t *data, size_t length, + uint32_t mode, uint64_t mtime); +int dat_archive_append_directory(dat_archive_t *archive, const char *path, + uint32_t mode, uint64_t mtime); +int dat_archive_append_symlink(dat_archive_t *archive, const char *path, + const char *target, uint64_t mtime); +int dat_archive_delete(dat_archive_t *archive, const char *path, uint64_t mtime); +int dat_archive_read_entry(const dat_archive_t *archive, size_t index, + sleep_metadata_entry_t *entry); +int dat_archive_read_file(const dat_archive_t *archive, + const sleep_metadata_entry_t *entry, + uint8_t **data, size_t *length); +int dat_archive_find(const dat_archive_t *archive, const char *path, + sleep_metadata_entry_t *entry); +int dat_archive_find_at(const dat_archive_t *archive, const char *path, + uint64_t version, sleep_metadata_entry_t *entry); +int dat_checkout_init(dat_checkout_t *checkout, const dat_archive_t *archive, + uint64_t version); +int dat_checkout_find(const dat_checkout_t *checkout, const char *path, + sleep_metadata_entry_t *entry); +int dat_checkout_read(const dat_checkout_t *checkout, + const sleep_metadata_entry_t *entry, + uint8_t **data, size_t *length); +int dat_checkout_export(const dat_checkout_t *checkout, const char *archive_path, + const char *filesystem_path); +int dat_archive_changes(const dat_archive_t *archive, uint64_t from_version, + uint64_t to_version, dat_archive_change_fn callback, void *context); +int dat_archive_stat_at(const dat_archive_t *archive, uint64_t version, + const char *path, dat_path_info_t *info); +int dat_archive_readdir_at(const dat_archive_t *archive, uint64_t version, + const char *path, dat_archive_readdir_fn callback, + void *context); +int dat_archive_resolve_at(const dat_archive_t *archive, uint64_t version, + const char *path, int force_directory_listing, + dat_path_info_t *info, int *serve_directory_listing); +const char *dat_content_type(const char *path); +int dat_url_parse(const char *text, dat_url_t *url); +int dat_archive_import_path(dat_archive_t *archive, const char *filesystem_path, + const char *archive_path); +/* Imports only changed paths and records tombstones for paths removed locally. */ +int dat_archive_sync_path(dat_archive_t *archive, const char *filesystem_path, + const char *archive_path); +int dat_archive_export_path(const dat_archive_t *archive, const char *archive_path, + const char *filesystem_path); +int dat_archive_export_at(const dat_archive_t *archive, uint64_t version, + const char *archive_path, const char *filesystem_path); +int dat_key_to_url(const uint8_t key[DAT_KEY_BYTES], char url[DAT_URL_BYTES]); +int dat_key_to_compact_url(const uint8_t key[DAT_KEY_BYTES], char url[DAT_URL_BYTES]); +int dat_key_to_hex(const uint8_t key[DAT_KEY_BYTES], char hex[65]); +int dat_url_to_key(const char *url, uint8_t key[DAT_KEY_BYTES]); +int dat_link_format(const dat_link_t *link, char *out, size_t capacity); +int dat_link_format_compact(const dat_link_t *link, char *out, size_t capacity); +int dat_link_parse(const char *text, dat_link_t *link); + +#endif /* DAT_H */ diff --git a/extra/dat-dpi/inc/dat_dns.h b/extra/dat-dpi/inc/dat_dns.h new file mode 100644 index 0000000..c58fe06 --- /dev/null +++ b/extra/dat-dpi/inc/dat_dns.h @@ -0,0 +1,27 @@ +#ifndef DAT_DNS_H +#define DAT_DNS_H + +#include +#include "dat.h" + +typedef int (*dat_dns_name_iter_fn)(const char *hostname, + const uint8_t key[DAT_KEY_BYTES], void *context); + +/* Resolve a legacy Dat DNS TXT record such as datkey=<64 hex digits>. */ +int dat_dns_resolve(const char *hostname, uint8_t key[DAT_KEY_BYTES], + uint32_t *ttl_seconds); + +/* Local dat-dns aliases are stored in $HOME/.dat/dat-dns (or USERPROFILE on + * native Windows) and override DNS TXT. */ +int dat_dns_name_set(const char *hostname, const uint8_t key[DAT_KEY_BYTES]); +int dat_dns_name_remove(const char *hostname); +int dat_dns_name_resolve(const char *hostname, uint8_t key[DAT_KEY_BYTES]); +int dat_dns_name_for_key(const uint8_t key[DAT_KEY_BYTES], char *hostname, + size_t hostname_capacity); +int dat_dns_name_foreach(dat_dns_name_iter_fn callback, void *context); + +/* Decode TXT record character strings from one DNS answer packet. */ +int dat_dns_parse_answer(const uint8_t *packet, unsigned packet_len, + uint8_t key[DAT_KEY_BYTES], uint32_t *ttl_seconds); + +#endif diff --git a/extra/dat-dpi/inc/dat_fetch.h b/extra/dat-dpi/inc/dat_fetch.h new file mode 100644 index 0000000..71d7b04 --- /dev/null +++ b/extra/dat-dpi/inc/dat_fetch.h @@ -0,0 +1,12 @@ +#ifndef DAT_FETCH_H +#define DAT_FETCH_H + +#include + +/* Fetch the public metadata and content feeds for a Dat key into root/.dat. + * The fetch uses the configured Cesspool network and persists verified sparse + * feed blocks so a later Dillo request can resume it. */ +int dat_fetch_archive(const char *root, const uint8_t metadata_key[32], + unsigned timeout_ms); + +#endif diff --git a/extra/dat-dpi/inc/dat_net.h b/extra/dat-dpi/inc/dat_net.h new file mode 100644 index 0000000..c9ad966 --- /dev/null +++ b/extra/dat-dpi/inc/dat_net.h @@ -0,0 +1,36 @@ +#ifndef DAT_NET_H +#define DAT_NET_H + +#include +#include +#include "dat_socket.h" + +#define DATNET_PORT 3282 +#define DATNET_MAX_PACKET 1400 +#define DATNET_TOPIC_BYTES 32 +#define DATNET_PEERID_BYTES 32 + +typedef struct { + dat_socket_t fd; + struct sockaddr_storage addr; + socklen_t addrlen; + uint8_t id[DATNET_PEERID_BYTES]; +} dat_peer_t; + +typedef struct datnet { + dat_socket_t sock; + uint8_t topic[DATNET_TOPIC_BYTES]; + uint8_t peerid[DATNET_PEERID_BYTES]; + void *context; + void (*on_message)(struct datnet *net, dat_peer_t *peer, const uint8_t *msg, size_t len); +} datnet_t; + +int datnet_init(datnet_t *net, const uint8_t topic[DATNET_TOPIC_BYTES], + const uint8_t peerid[DATNET_PEERID_BYTES]); +int datnet_bind(datnet_t *net, const char *host, uint16_t port); +int datnet_poll(datnet_t *net, int timeout_ms); +int datnet_send(datnet_t *net, const dat_peer_t *peer, + const uint8_t *message, size_t length); +int datnet_close(datnet_t *net); + +#endif /* DAT_NET_H */ diff --git a/extra/dat-dpi/inc/dat_paths.h b/extra/dat-dpi/inc/dat_paths.h new file mode 100644 index 0000000..6f410cd --- /dev/null +++ b/extra/dat-dpi/inc/dat_paths.h @@ -0,0 +1,48 @@ +#ifndef DAT_PATHS_H +#define DAT_PATHS_H + +#include +#include +#include +#ifdef _WIN32 +#include +#endif + +/* Return the user's home directory across POSIX shells and native Windows + * launches. Windows GUI starts commonly have USERPROFILE but no HOME. */ +static inline const char *dat_home_directory(void) +{ + const char *home = getenv("HOME"); + if (home && *home) return home; +#ifdef _WIN32 + home = getenv("USERPROFILE"); + if (home && *home) return home; + { + const char *drive = getenv("HOMEDRIVE"); + const char *path = getenv("HOMEPATH"); + static char combined[1024]; + int n; + if (!drive || !*drive || !path || !*path) return NULL; + n = snprintf(combined, sizeof(combined), "%s%s", drive, path); + return n >= 0 && (size_t)n < sizeof(combined) ? combined : NULL; + } +#else + return NULL; +#endif +} + +/* POSIX rename replaces an existing file; Windows rename does not. These + * state files are written to a sibling temporary file before replacement. */ +static inline int dat_replace_file(const char *temporary, const char *destination) +{ +#ifdef _WIN32 + if (MoveFileExA(temporary, destination, + MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) return 0; + errno = EIO; + return -1; +#else + return rename(temporary, destination); +#endif +} + +#endif diff --git a/extra/dat-dpi/inc/dat_s7.h b/extra/dat-dpi/inc/dat_s7.h new file mode 100644 index 0000000..1d2b5b8 --- /dev/null +++ b/extra/dat-dpi/inc/dat_s7.h @@ -0,0 +1,16 @@ +#ifndef DAT_S7_H +#define DAT_S7_H +#include "s7.h" +#include "dat.h" +typedef struct dat_scheme_runtime { + s7_scheme *scheme; +} dat_scheme_runtime_t; + +int dat_s7_init(s7_scheme *scheme); +int dat_scheme_runtime_init(dat_scheme_runtime_t *runtime); +void dat_scheme_runtime_close(dat_scheme_runtime_t *runtime); +int dat_scheme_write_archive_info(s7_scheme *scheme, const char *root, + const dat_archive_t *archive, + const char *title, const char *description, + int replace); +#endif diff --git a/extra/dat-dpi/inc/dat_session.h b/extra/dat-dpi/inc/dat_session.h new file mode 100644 index 0000000..4203693 --- /dev/null +++ b/extra/dat-dpi/inc/dat_session.h @@ -0,0 +1,151 @@ +#ifndef DAT_SESSION_H +#define DAT_SESSION_H + +#include +#include +#include "cesspool.h" +#include "dat.h" +#include "dat_wire.h" +#include "sleep_feed.h" +#include "sleep_replication.h" + +#define DAT_SESSION_MAX_PEERS 64u +#define DAT_SESSION_CAP_METADATA_ONLY (UINT64_C(1) << 63) + +enum dat_session_state { + DAT_SESSION_CLOSED = 0, + DAT_SESSION_OPEN = 1, + DAT_SESSION_DISCOVERING = 2, + DAT_SESSION_ACTIVE = 3, + DAT_SESSION_CLOSING = 4, + DAT_SESSION_FAILED = 5 +}; + +enum dat_session_peer_state { + DAT_SESSION_PEER_DISCOVERED = 1, + DAT_SESSION_PEER_SECURE = 2, + DAT_SESSION_PEER_READY = 3, + DAT_SESSION_PEER_CLOSED = 4 +}; + +enum dat_session_feed { + DAT_SESSION_METADATA = 0, + DAT_SESSION_CONTENT = 1, + DAT_SESSION_FEED_COUNT = 2 +}; + +typedef struct dat_session_peer { + uint8_t peer_id[DAT_PEER_ID_BYTES]; + uint8_t tx_nonce[24]; + uint8_t rx_nonce[24]; + uint8_t *rx_buffer; + size_t rx_buffer_len; + size_t rx_buffer_capacity; + uint64_t tx_offset; + uint64_t rx_offset; + uint8_t remote_feed_mask; + uint8_t local_feed_mask; + uint8_t remote_content_discovery[32]; + int remote_content_pending; + dat_peer_t address; + uint64_t capabilities; + uint64_t payload_bytes_sent; + uint64_t payload_bytes_received; + int state; + int transport_authenticated; + int paused; + int tx_stream_encrypted; + int rx_stream_encrypted; + sleep_replication_t replication[DAT_SESSION_FEED_COUNT]; +} dat_session_peer_t; + +struct dat_session; +typedef int (*dat_session_send_fn)(void *context, + const uint8_t peer_id[DAT_PEER_ID_BYTES], + const uint8_t *frame, size_t frame_len); +typedef int (*dat_session_request_fn)(void *context, + const uint8_t peer_id[DAT_PEER_ID_BYTES], + enum dat_session_feed feed, + const dat_request_t *request); +/* Called when discovery yields a peer; establish an authenticated transport, + * then report success with dat_session_peer_secure(). */ +typedef void (*dat_session_peer_fn)(void *context, + const dat_session_peer_t *peer); +typedef int (*dat_session_poll_fn)(void *context, int timeout_ms); + +/* + * Session protocol state is separate from connection establishment. In the + * Cesspool path, call peer_secure only after the authenticated transport has + * bound this peer ID to the topic. In legacy Hypercore V6 mode, call it once + * the discovered TCP/uTP stream is connected; V6 performs its feed-key stream + * encryption inside the protocol itself. + */ +typedef struct dat_session { + int state; + int announcing; + int looking_up; + uint16_t port; + uint8_t archive_key[DAT_KEY_BYTES]; + uint8_t discovery_topic[CESSPOOL_TOPIC_BYTES]; + uint8_t peer_id[DAT_PEER_ID_BYTES]; + uint8_t feed_keys[DAT_SESSION_FEED_COUNT][DAT_KEY_BYTES]; + int content_key_known; + cesspool_t *cesspool; + sleep_feed_t *feeds[DAT_SESSION_FEED_COUNT]; + dat_session_peer_t peers[DAT_SESSION_MAX_PEERS]; + size_t peer_count; + dat_session_send_fn send; + dat_session_request_fn on_request; + dat_session_peer_fn on_peer; + void *peer_context; + dat_session_poll_fn poll_transport; + void *poll_context; + void *context; +} dat_session_t; + +int dat_session_init(dat_session_t *session, cesspool_t *cesspool, + const uint8_t archive_key[DAT_KEY_BYTES], + const uint8_t peer_id[DAT_PEER_ID_BYTES], uint16_t port, + sleep_feed_t *metadata_feed, + const uint8_t metadata_key[DAT_KEY_BYTES], + sleep_feed_t *content_feed, + const uint8_t content_key[DAT_KEY_BYTES], + dat_session_send_fn send, void *context); +int dat_session_start(dat_session_t *session, int announce, int lookup); +int dat_session_set_peer_callback(dat_session_t *session, + dat_session_peer_fn callback, void *context); +int dat_session_set_transport_poll(dat_session_t *session, + dat_session_poll_fn poll, void *context); +int dat_session_peer_discovered(dat_session_t *session, + const cesspool_peer_t *discovered_peer); +int dat_session_peer_secure(dat_session_t *session, + const uint8_t peer_id[DAT_PEER_ID_BYTES], + int transport_authenticated); +/* For a metadata-only archive import, install the content key learned from + * the verified metadata header and announce that feed to connected peers. */ +int dat_session_set_content_key(dat_session_t *session, + const uint8_t content_key[DAT_KEY_BYTES]); +int dat_session_expect_content_key(dat_session_t *session); +int dat_session_peer_down(dat_session_t *session, + const uint8_t peer_id[DAT_PEER_ID_BYTES]); +/* Pause this client's incoming block requests without dropping the peer. */ +int dat_session_peer_pause(dat_session_t *session, + const uint8_t peer_id[DAT_PEER_ID_BYTES], int paused); +/* Accept arbitrary transport chunks. Partial and coalesced wire frames are buffered. */ +int dat_session_receive(dat_session_t *session, + const uint8_t peer_id[DAT_PEER_ID_BYTES], + const uint8_t *frame, size_t frame_len); +int dat_session_request_block(dat_session_t *session, + const uint8_t peer_id[DAT_PEER_ID_BYTES], + enum dat_session_feed feed, uint64_t index, + uint64_t bytes); +int dat_session_send_block(dat_session_t *session, + const uint8_t peer_id[DAT_PEER_ID_BYTES], + enum dat_session_feed feed, uint64_t index); +int dat_session_tick(dat_session_t *session, uint64_t now_ms); +int dat_session_poll(dat_session_t *session, int timeout_ms, uint64_t now_ms); +int dat_session_close(dat_session_t *session); +dat_session_peer_t *dat_session_find_peer(dat_session_t *session, + const uint8_t peer_id[DAT_PEER_ID_BYTES]); + +#endif /* DAT_SESSION_H */ diff --git a/extra/dat-dpi/inc/dat_socket.h b/extra/dat-dpi/inc/dat_socket.h new file mode 100644 index 0000000..fe5abff --- /dev/null +++ b/extra/dat-dpi/inc/dat_socket.h @@ -0,0 +1,18 @@ +#ifndef DAT_SOCKET_H +#define DAT_SOCKET_H + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include +typedef SOCKET dat_socket_t; +#define DAT_INVALID_SOCKET INVALID_SOCKET +#else +#include +typedef int dat_socket_t; +#define DAT_INVALID_SOCKET (-1) +#endif + +#endif diff --git a/extra/dat-dpi/inc/dat_wire.h b/extra/dat-dpi/inc/dat_wire.h new file mode 100644 index 0000000..4224a20 --- /dev/null +++ b/extra/dat-dpi/inc/dat_wire.h @@ -0,0 +1,110 @@ +#ifndef DAT_WIRE_H +#define DAT_WIRE_H +#include +#include + +enum dat_message_type { + DAT_MSG_FEED = 0, + DAT_MSG_HANDSHAKE = 1, + DAT_MSG_INFO = 2, + DAT_MSG_HAVE = 3, + DAT_MSG_UNHAVE = 4, + DAT_MSG_WANT = 5, + DAT_MSG_UNWANT = 6, + DAT_MSG_REQUEST = 7, + DAT_MSG_CANCEL = 8, + DAT_MSG_DATA = 9, + DAT_MSG_EXTENSION = 15 +}; + +typedef struct dat_feed_open { + uint8_t discovery_key[32]; + uint8_t nonce[24]; + int has_nonce; +} dat_feed_open_t; + +typedef struct dat_wire_message { + uint64_t channel; + uint64_t type; + const uint8_t *payload; + size_t payload_len; +} dat_wire_message_t; + +#define DAT_PROTOCOL_BYTES 32u +#define DAT_PEER_ID_BYTES 32u +#ifndef DAT_NETWORK_PROTOCOL +#define DAT_NETWORK_PROTOCOL "cesspool" +#endif +/* + * The exact protocol token lives in the repository-root NETWORK_PROTOCOL + * file. The Cesspool handshake carries it in `userData`; legacy Hypercore V6 + * omits that application-owned field and uses the token to select its + * discovery namespace and wire mode. This checkout defaults to `cesspool`. + */ +typedef struct dat_handshake { + uint8_t protocol[DAT_PROTOCOL_BYTES]; + uint8_t peer_id[DAT_PEER_ID_BYTES]; + uint64_t capabilities; + int has_peer_id; +} dat_handshake_t; +int dat_handshake_init(dat_handshake_t *handshake, + const uint8_t peer_id[DAT_PEER_ID_BYTES], + uint64_t capabilities); +int dat_handshake_protocol_matches(const dat_handshake_t *handshake); +typedef struct dat_range { uint64_t start; uint64_t length; } dat_range_t; +typedef struct dat_info { uint8_t uploading; uint8_t downloading; } dat_info_t; +typedef struct dat_request { uint64_t index; uint64_t bytes; uint8_t want_hash; uint8_t want_nodes; } dat_request_t; +#define DAT_DATA_MAX_NODES 128u +typedef struct dat_data_node { + uint64_t index; + uint64_t length; + uint8_t hash[32]; +} dat_data_node_t; +typedef struct dat_data { + uint64_t index; + const uint8_t *value; + size_t value_len; + const uint8_t *proof; + size_t proof_len; + dat_data_node_t nodes[DAT_DATA_MAX_NODES]; + size_t node_count; + uint8_t signature[64]; + int has_signature; +} dat_data_t; +typedef struct dat_cancel { uint64_t index; } dat_cancel_t; +typedef struct dat_extension { + uint64_t id; + const uint8_t *message; + size_t message_len; +} dat_extension_t; + +/* Hypercore frames are length-prefixed; headers pack channel and type. */ +int dat_wire_encode(const dat_wire_message_t *message, uint8_t *out, + size_t capacity, size_t *written); +int dat_wire_decode(dat_wire_message_t *message, const uint8_t *input, + size_t input_len, size_t *consumed); +int dat_handshake_encode(const dat_handshake_t *handshake, uint8_t *out, + size_t capacity, size_t *written); +int dat_handshake_decode(dat_handshake_t *handshake, const uint8_t *input, + size_t input_len); +int dat_feed_open_encode(const dat_feed_open_t *feed, uint8_t *out, + size_t capacity, size_t *written); +int dat_feed_open_decode(dat_feed_open_t *feed, const uint8_t *input, + size_t input_len); +int dat_range_encode(const dat_range_t *range, uint8_t *out, size_t capacity, size_t *written); +int dat_range_decode(dat_range_t *range, const uint8_t *input, size_t input_len); +/* WANT omits length to mean through the current end of a non-live feed. */ +int dat_want_decode(dat_range_t *range, const uint8_t *input, size_t input_len, + uint64_t feed_length); +int dat_info_encode(const dat_info_t *info, uint8_t *out, size_t capacity, + size_t *written); +int dat_info_decode(dat_info_t *info, const uint8_t *input, size_t input_len); +int dat_request_encode(const dat_request_t *request, uint8_t *out, size_t capacity, size_t *written); +int dat_request_decode(dat_request_t *request, const uint8_t *input, size_t input_len); +int dat_data_encode(const dat_data_t *data, uint8_t *out, size_t capacity, size_t *written); +int dat_data_decode(dat_data_t *data, const uint8_t *input, size_t input_len); +int dat_cancel_encode(const dat_cancel_t *cancel, uint8_t *out, size_t capacity, size_t *written); +int dat_cancel_decode(dat_cancel_t *cancel, const uint8_t *input, size_t input_len); +int dat_extension_encode(const dat_extension_t *extension, uint8_t *out, size_t capacity, size_t *written); +int dat_extension_decode(dat_extension_t *extension, const uint8_t *input, size_t input_len); +#endif diff --git a/extra/dat-dpi/inc/errno_compat.h b/extra/dat-dpi/inc/errno_compat.h new file mode 100644 index 0000000..0403f11 --- /dev/null +++ b/extra/dat-dpi/inc/errno_compat.h @@ -0,0 +1,12 @@ +#ifndef DATGPT_ERRNO_COMPAT_H +#define DATGPT_ERRNO_COMPAT_H + +#include + +/* EKEYREJECTED is a Linux extension. Keep the key-authentication failure + * portable while preserving a useful access-denied error on other hosts. */ +#ifndef EKEYREJECTED +#define EKEYREJECTED EACCES +#endif + +#endif diff --git a/extra/dat-dpi/inc/keys.h b/extra/dat-dpi/inc/keys.h new file mode 100644 index 0000000..fb65da4 --- /dev/null +++ b/extra/dat-dpi/inc/keys.h @@ -0,0 +1,17 @@ +#ifndef KEYS_H +#define KEYS_H + +#include + +#define SLEEP_PUB_KEY_BYTES 32 +#define SLEEP_SECRET_KEY_BYTES 64 +#define SLEEP_SIGNATURE_BYTES 64 +#define SLEEP_BLAKE2B_BYTES 32 + +typedef struct { + uint8_t pubkey[SLEEP_PUB_KEY_BYTES]; + uint8_t seckey[SLEEP_SECRET_KEY_BYTES]; +} sleep_keypair_t; + + +#endif /* KEYS_H */ diff --git a/extra/dat-dpi/inc/s7.h b/extra/dat-dpi/inc/s7.h new file mode 100644 index 0000000..fa7269a --- /dev/null +++ b/extra/dat-dpi/inc/s7.h @@ -0,0 +1,1240 @@ +#ifndef S7_H +#define S7_H + +#define S7_VERSION "10.6" +#define S7_DATE "13-Sep-2023" +#define S7_MAJOR_VERSION 10 +#define S7_MINOR_VERSION 6 + +#include /* for int64_t */ + +typedef int64_t s7_int; +typedef double s7_double; + +#ifndef __cplusplus +#ifndef _MSC_VER + #include +#else +#ifndef true + #define bool unsigned char + #define true 1 + #define false 0 +#endif +#endif +#endif + +#if WITH_GMP + /* in g++ these includes need to be outside the extern "C" business */ + #include + #include + #include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct s7_scheme s7_scheme; +typedef struct s7_cell *s7_pointer; + +s7_scheme *s7_init(void); + /* s7_scheme is our interpreter + * s7_pointer is a Scheme object of any (Scheme) type + * s7_init creates the interpreter. + */ +void s7_free(s7_scheme *sc); + +typedef s7_pointer (*s7_function)(s7_scheme *sc, s7_pointer args); /* that is, obj = func(s7, args) -- args is a list of arguments */ +typedef s7_pointer (*s7_pfunc)(s7_scheme *sc); + +s7_pointer s7_f(s7_scheme *sc); /* #f */ +s7_pointer s7_t(s7_scheme *sc); /* #t */ +s7_pointer s7_nil(s7_scheme *sc); /* () */ +s7_pointer s7_undefined(s7_scheme *sc); /* # */ +s7_pointer s7_unspecified(s7_scheme *sc); /* # */ +bool s7_is_unspecified(s7_scheme *sc, s7_pointer val); /* returns true if val is # */ +s7_pointer s7_eof_object(s7_scheme *sc); /* # */ +bool s7_is_null(s7_scheme *sc, s7_pointer p); /* null? */ + + /* these are the Scheme constants; they do not change in value during a run, + * so they can be safely assigned to C global variables if desired. + */ + +bool s7_is_valid(s7_scheme *sc, s7_pointer arg); /* does 'arg' look like an s7 object? */ +bool s7_is_c_pointer(s7_pointer arg); /* (c-pointer? arg) */ +bool s7_is_c_pointer_of_type(s7_pointer arg, s7_pointer type); +void *s7_c_pointer(s7_pointer p); +void *s7_c_pointer_with_type(s7_scheme *sc, s7_pointer p, s7_pointer expected_type, const char *caller, s7_int argnum); +s7_pointer s7_c_pointer_type(s7_pointer p); +s7_pointer s7_make_c_pointer(s7_scheme *sc, void *ptr); /* these are for passing uninterpreted C pointers through Scheme */ +s7_pointer s7_make_c_pointer_with_type(s7_scheme *sc, void *ptr, s7_pointer type, s7_pointer info); + +s7_pointer s7_eval_c_string(s7_scheme *sc, const char *str); /* (eval-string str) */ +s7_pointer s7_eval_c_string_with_environment(s7_scheme *sc, const char *str, s7_pointer e); +s7_pointer s7_object_to_string(s7_scheme *sc, s7_pointer arg, bool use_write); + /* (object->string obj) */ +char *s7_object_to_c_string(s7_scheme *sc, s7_pointer obj); /* same as object->string but returns a C char* directly */ + /* the returned value should be freed by the caller */ + +s7_pointer s7_load(s7_scheme *sc, const char *file); /* (load file) */ +s7_pointer s7_load_with_environment(s7_scheme *sc, const char *filename, s7_pointer e); +s7_pointer s7_load_c_string(s7_scheme *sc, const char *content, s7_int bytes); +s7_pointer s7_load_c_string_with_environment(s7_scheme *sc, const char *content, s7_int bytes, s7_pointer e); +s7_pointer s7_load_path(s7_scheme *sc); /* *load-path* */ +s7_pointer s7_add_to_load_path(s7_scheme *sc, const char *dir); /* (set! *load-path* (cons dir *load-path*)) */ +s7_pointer s7_autoload(s7_scheme *sc, s7_pointer symbol, s7_pointer file_or_function); /* (autoload symbol file-or-function) */ +void s7_autoload_set_names(s7_scheme *sc, const char **names, s7_int size); + + /* the load path is a list of directories to search if load can't find the file passed as its argument. + * + * s7_load and s7_load_with_environment can load shared object files as well as scheme code. + * The scheme (load "somelib.so" (inlet 'init_func 'somelib_init)) is equivalent to + * s7_load_with_environment(s7, "somelib.so", s7_inlet(s7, s7_list(s7, 2, s7_make_symbol(s7, "init_func"), s7_make_symbol(s7, "somelib_init")))) + * s7_load_with_environment returns NULL if it can't load the file. + */ +void s7_quit(s7_scheme *sc); + /* this tries to break out of the current evaluation, leaving everything else intact */ + +void (*s7_begin_hook(s7_scheme *sc))(s7_scheme *sc, bool *val); +void s7_set_begin_hook(s7_scheme *sc, void (*hook)(s7_scheme *sc, bool *val)); + /* call "hook" at the start of any block; use NULL to cancel. + * s7_begin_hook returns the current begin_hook function or NULL. + */ + +s7_pointer s7_eval(s7_scheme *sc, s7_pointer code, s7_pointer e); /* (eval code e) -- e is the optional environment */ +s7_pointer s7_eval_with_location(s7_scheme *sc, s7_pointer code, s7_pointer e, const char *caller, const char *file, s7_int line); +void s7_provide(s7_scheme *sc, const char *feature); /* add feature (as a symbol) to the *features* list */ +bool s7_is_provided(s7_scheme *sc, const char *feature); /* (provided? feature) */ +void s7_repl(s7_scheme *sc); + +s7_pointer s7_error(s7_scheme *sc, s7_pointer type, s7_pointer info); +s7_pointer s7_wrong_type_arg_error(s7_scheme *sc, const char *caller, s7_int arg_n, s7_pointer arg, const char *descr); +s7_pointer s7_wrong_type_error(s7_scheme *sc, s7_pointer caller, s7_int arg_n, s7_pointer arg, s7_pointer descr); + /* set arg_n to 0 to indicate that caller takes only one argument (so the argument number need not be reported */ +s7_pointer s7_out_of_range_error(s7_scheme *sc, const char *caller, s7_int arg_n, s7_pointer arg, const char *descr); +s7_pointer s7_wrong_number_of_args_error(s7_scheme *sc, const char *caller, s7_pointer args); + + /* these are equivalent to (error ...) in Scheme + * the first argument to s7_error is a symbol that can be caught (via (catch tag ...)) + * the rest of the arguments are passed to the error handler (if in catch) + * or printed out (in the default case). If the first element of the list + * of args ("info") is a string, the default error handler treats it as + * a format control string, and passes it to format with the rest of the + * info list as the format function arguments. + * + * s7_wrong_type_arg_error is equivalent to s7_error with a type of 'wrong-type-arg + * and similarly s7_out_of_range_error with type 'out-of-range. + * + * catch in Scheme is taken from Guile: + * + * (catch tag thunk handler) + * + * evaluates 'thunk'. If an error occurs, and the type matches 'tag' (or if 'tag' is #t), + * the handler is called, passing it the arguments (including the type) passed to the + * error function. If no handler is found, the default error handler is called, + * normally printing the error arguments to current-error-port. + */ + +s7_pointer s7_stacktrace(s7_scheme *sc); +s7_pointer s7_history(s7_scheme *sc); /* the current (circular backwards) history buffer */ +s7_pointer s7_add_to_history(s7_scheme *sc, s7_pointer entry); /* add entry to the history buffer */ +bool s7_history_enabled(s7_scheme *sc); +bool s7_set_history_enabled(s7_scheme *sc, bool enabled); + +s7_pointer s7_gc_on(s7_scheme *sc, bool on); /* (gc on) */ + +s7_int s7_gc_protect(s7_scheme *sc, s7_pointer x); +void s7_gc_unprotect_at(s7_scheme *sc, s7_int loc); +s7_pointer s7_gc_protected_at(s7_scheme *sc, s7_int loc); +s7_pointer s7_gc_protect_via_stack(s7_scheme *sc, s7_pointer x); +s7_pointer s7_gc_unprotect_via_stack(s7_scheme *sc, s7_pointer x); +s7_pointer s7_gc_protect_via_location(s7_scheme *sc, s7_pointer x, s7_int loc); +s7_pointer s7_gc_unprotect_via_location(s7_scheme *sc, s7_int loc); + + /* any s7_pointer object held in C (as a local variable for example) needs to be + * protected from garbage collection if there is any chance the GC may run without + * an existing Scheme-level reference to that object. s7_gc_protect places the + * object in a vector that the GC always checks, returning the object's location + * in that table. s7_gc_unprotect_at unprotects the object (removes it from the + * vector) using the location passed to it. s7_gc_protected_at returns the object + * at the given location. + * + * You can turn the GC on and off via s7_gc_on. + * + * There is a built-in lag between the creation of a new object and its first possible GC + * (the lag time is set indirectly by GC_TEMPS_SIZE in s7.c), so you don't need to worry about + * very short term temps such as the arguments to s7_cons in: + * + * s7_cons(s7, s7_make_real(s7, 3.14), + * s7_cons(s7, s7_make_integer(s7, 123), s7_nil(s7))); + */ + +bool s7_is_eq(s7_pointer a, s7_pointer b); /* (eq? a b) */ +bool s7_is_eqv(s7_scheme *sc, s7_pointer a, s7_pointer b); /* (eqv? a b) */ +bool s7_is_equal(s7_scheme *sc, s7_pointer a, s7_pointer b); /* (equal? a b) */ +bool s7_is_equivalent(s7_scheme *sc, s7_pointer x, s7_pointer y); /* (equivalent? x y) */ + +bool s7_is_boolean(s7_pointer x); /* (boolean? x) */ +bool s7_boolean(s7_scheme *sc, s7_pointer x); /* Scheme boolean -> C bool */ +s7_pointer s7_make_boolean(s7_scheme *sc, bool x); /* C bool -> Scheme boolean */ + + /* for each Scheme type (boolean, integer, string, etc), there are three + * functions: s7_(...), s7_make_(...), and s7_is_(...): + * + * s7_boolean(s7, obj) returns the C bool corresponding to the value of 'obj' (#f -> false) + * s7_make_boolean(s7, false|true) returns the s7 boolean corresponding to the C bool argument (false -> #f) + * s7_is_boolean(s7, obj) returns true if 'obj' has a boolean value (#f or #t). + */ + + +bool s7_is_pair(s7_pointer p); /* (pair? p) */ +s7_pointer s7_cons(s7_scheme *sc, s7_pointer a, s7_pointer b); /* (cons a b) */ + +s7_pointer s7_car(s7_pointer p); /* (car p) */ +s7_pointer s7_cdr(s7_pointer p); /* (cdr p) */ + +s7_pointer s7_set_car(s7_pointer p, s7_pointer q); /* (set-car! p q) */ +s7_pointer s7_set_cdr(s7_pointer p, s7_pointer q); /* (set-cdr! p q) */ + +s7_pointer s7_cadr(s7_pointer p); /* (cadr p) */ +s7_pointer s7_cddr(s7_pointer p); /* (cddr p) */ +s7_pointer s7_cdar(s7_pointer p); /* (cdar p) */ +s7_pointer s7_caar(s7_pointer p); /* (caar p) */ + +s7_pointer s7_caadr(s7_pointer p); /* etc */ +s7_pointer s7_caddr(s7_pointer p); +s7_pointer s7_cadar(s7_pointer p); +s7_pointer s7_caaar(s7_pointer p); +s7_pointer s7_cdadr(s7_pointer p); +s7_pointer s7_cdddr(s7_pointer p); +s7_pointer s7_cddar(s7_pointer p); +s7_pointer s7_cdaar(s7_pointer p); + +s7_pointer s7_caaadr(s7_pointer p); +s7_pointer s7_caaddr(s7_pointer p); +s7_pointer s7_caadar(s7_pointer p); +s7_pointer s7_caaaar(s7_pointer p); +s7_pointer s7_cadadr(s7_pointer p); +s7_pointer s7_cadddr(s7_pointer p); +s7_pointer s7_caddar(s7_pointer p); +s7_pointer s7_cadaar(s7_pointer p); +s7_pointer s7_cdaadr(s7_pointer p); +s7_pointer s7_cdaddr(s7_pointer p); +s7_pointer s7_cdadar(s7_pointer p); +s7_pointer s7_cdaaar(s7_pointer p); +s7_pointer s7_cddadr(s7_pointer p); +s7_pointer s7_cddddr(s7_pointer p); +s7_pointer s7_cdddar(s7_pointer p); +s7_pointer s7_cddaar(s7_pointer p); + +bool s7_is_list(s7_scheme *sc, s7_pointer p); /* (list? p) -> (or (pair? p) (null? p)) */ +bool s7_is_proper_list(s7_scheme *sc, s7_pointer p); /* (proper-list? p) */ +s7_int s7_list_length(s7_scheme *sc, s7_pointer a); /* (length a) */ +s7_pointer s7_make_list(s7_scheme *sc, s7_int len, s7_pointer init); /* (make-list len init) */ +s7_pointer s7_list(s7_scheme *sc, s7_int num_values, ...); /* (list ...) */ +s7_pointer s7_list_nl(s7_scheme *sc, s7_int num_values, ...); /* (list ...) arglist should be NULL terminated (more error checks than s7_list) */ +s7_pointer s7_array_to_list(s7_scheme *sc, s7_int num_values, s7_pointer *array); /* array contents -> list */ +void s7_list_to_array(s7_scheme *sc, s7_pointer list, s7_pointer *array, int32_t len); /* list -> array (intended for old code) */ +s7_pointer s7_reverse(s7_scheme *sc, s7_pointer a); /* (reverse a) */ +s7_pointer s7_append(s7_scheme *sc, s7_pointer a, s7_pointer b); /* (append a b) */ +s7_pointer s7_list_ref(s7_scheme *sc, s7_pointer lst, s7_int num); /* (list-ref lst num) */ +s7_pointer s7_list_set(s7_scheme *sc, s7_pointer lst, s7_int num, s7_pointer val); /* (list-set! lst num val) */ +s7_pointer s7_assoc(s7_scheme *sc, s7_pointer obj, s7_pointer lst); /* (assoc obj lst) */ +s7_pointer s7_assq(s7_scheme *sc, s7_pointer obj, s7_pointer x); /* (assq obj lst) */ +s7_pointer s7_member(s7_scheme *sc, s7_pointer obj, s7_pointer lst); /* (member obj lst) */ +s7_pointer s7_memq(s7_scheme *sc, s7_pointer obj, s7_pointer x); /* (memq obj lst) */ +bool s7_tree_memq(s7_scheme *sc, s7_pointer sym, s7_pointer tree); /* (tree-memq sym tree) */ + + +bool s7_is_string(s7_pointer p); /* (string? p) */ +const char *s7_string(s7_pointer p); /* Scheme string -> C string (do not free the string) */ +s7_pointer s7_make_string(s7_scheme *sc, const char *str); /* C string -> Scheme string (str is copied) */ +s7_pointer s7_make_string_with_length(s7_scheme *sc, const char *str, s7_int len); /* same as s7_make_string, but provides strlen */ +s7_pointer s7_make_string_wrapper(s7_scheme *sc, const char *str); +s7_pointer s7_make_string_wrapper_with_length(s7_scheme *sc, const char *str, s7_int len); +s7_pointer s7_make_permanent_string(s7_scheme *sc, const char *str); /* make a string that will never be GC'd */ +s7_pointer s7_make_semipermanent_string(s7_scheme *sc, const char *str); /* for (s7) string permanent within one s7 instance (freed upon s7_free) */ +s7_int s7_string_length(s7_pointer str); /* (string-length str) */ + + +bool s7_is_character(s7_pointer p); /* (character? p) */ +uint8_t s7_character(s7_pointer p); /* Scheme character -> unsigned C char */ +s7_pointer s7_make_character(s7_scheme *sc, uint8_t c); /* unsigned C char -> Scheme character */ + + +bool s7_is_number(s7_pointer p); /* (number? p) */ +bool s7_is_integer(s7_pointer p); /* (integer? p) */ +s7_int s7_integer(s7_pointer p); /* Scheme integer -> C integer (s7_int) */ +s7_pointer s7_make_integer(s7_scheme *sc, s7_int num); /* C s7_int -> Scheme integer */ + +bool s7_is_real(s7_pointer p); /* (real? p) */ +s7_double s7_real(s7_pointer p); /* Scheme real -> C double */ +s7_pointer s7_make_real(s7_scheme *sc, s7_double num); /* C double -> Scheme real */ +s7_pointer s7_make_mutable_real(s7_scheme *sc, s7_double n); +s7_double s7_number_to_real(s7_scheme *sc, s7_pointer x); /* x can be any kind of number */ +s7_double s7_number_to_real_with_caller(s7_scheme *sc, s7_pointer x, const char *caller); +s7_double s7_number_to_real_with_location(s7_scheme *sc, s7_pointer x, s7_pointer caller); +s7_int s7_number_to_integer(s7_scheme *sc, s7_pointer x); +s7_int s7_number_to_integer_with_caller(s7_scheme *sc, s7_pointer x, const char *caller); + +bool s7_is_rational(s7_pointer arg); /* (rational? arg) -- integer or ratio */ +bool s7_is_ratio(s7_pointer arg); /* true if arg is a ratio, not an integer */ +s7_pointer s7_make_ratio(s7_scheme *sc, s7_int a, s7_int b); /* returns the Scheme object a/b */ +s7_pointer s7_rationalize(s7_scheme *sc, s7_double x, s7_double error); /* (rationalize x error) */ +s7_int s7_numerator(s7_pointer x); /* (numerator x) */ +s7_int s7_denominator(s7_pointer x); /* (denominator x) */ +s7_double s7_random(s7_scheme *sc, s7_pointer state); /* (random x) */ +s7_pointer s7_random_state(s7_scheme *sc, s7_pointer seed); /* (random-state seed) */ +s7_pointer s7_random_state_to_list(s7_scheme *sc, s7_pointer args); /* (random-state->list r) */ +void s7_set_default_random_state(s7_scheme *sc, s7_int seed, s7_int carry); +bool s7_is_random_state(s7_pointer p); /* (random-state? p) */ + +bool s7_is_complex(s7_pointer arg); /* (complex? arg) */ +s7_pointer s7_make_complex(s7_scheme *sc, s7_double a, s7_double b); /* returns the Scheme object a+bi */ +s7_double s7_real_part(s7_pointer z); /* (real-part z) */ +s7_double s7_imag_part(s7_pointer z); /* (imag-part z) */ +char *s7_number_to_string(s7_scheme *sc, s7_pointer obj, s7_int radix); /* (number->string obj radix) */ + +bool s7_is_vector(s7_pointer p); /* (vector? p) */ +s7_int s7_vector_length(s7_pointer vec); /* (vector-length vec) */ +s7_int s7_vector_rank(s7_pointer vect); /* number of dimensions in vect */ +s7_int s7_vector_dimension(s7_pointer vec, s7_int dim); +s7_pointer *s7_vector_elements(s7_pointer vec); /* a pointer to the array of s7_pointers */ +s7_int *s7_int_vector_elements(s7_pointer vec); +uint8_t *s7_byte_vector_elements(s7_pointer vec); +s7_double *s7_float_vector_elements(s7_pointer vec); +bool s7_is_float_vector(s7_pointer p); /* (float-vector? p) */ +bool s7_is_int_vector(s7_pointer p); /* (int-vector? p) */ +bool s7_is_byte_vector(s7_pointer p); /* (byte-vector? p) */ + +s7_pointer s7_vector_ref(s7_scheme *sc, s7_pointer vec, s7_int index); /* (vector-ref vec index) */ +s7_pointer s7_vector_set(s7_scheme *sc, s7_pointer vec, s7_int index, s7_pointer a); /* (vector-set! vec index a) */ +s7_pointer s7_vector_ref_n(s7_scheme *sc, s7_pointer vector, s7_int indices, ...); /* multidimensional vector-ref */ +s7_pointer s7_vector_set_n(s7_scheme *sc, s7_pointer vector, s7_pointer value, s7_int indices, ...); /* multidimensional vector-set! */ +s7_int s7_vector_dimensions(s7_pointer vec, s7_int *dims, s7_int dims_size); /* vector dimensions */ +s7_int s7_vector_offsets(s7_pointer vec, s7_int *offs, s7_int offs_size); + +s7_int s7_int_vector_ref(s7_pointer vec, s7_int index); +s7_int s7_int_vector_set(s7_pointer vec, s7_int index, s7_int value); +uint8_t s7_byte_vector_ref(s7_pointer vec, s7_int index); +uint8_t s7_byte_vector_set(s7_pointer vec, s7_int index, uint8_t value); +s7_double s7_float_vector_ref(s7_pointer vec, s7_int index); +s7_double s7_float_vector_set(s7_pointer vec, s7_int index, s7_double value); + +s7_pointer s7_make_vector(s7_scheme *sc, s7_int len); /* (make-vector len) */ +s7_pointer s7_make_int_vector(s7_scheme *sc, s7_int len, s7_int dims, s7_int *dim_info); +s7_pointer s7_make_byte_vector(s7_scheme *sc, s7_int len, s7_int dims, s7_int *dim_info); +s7_pointer s7_make_float_vector(s7_scheme *sc, s7_int len, s7_int dims, s7_int *dim_info); +s7_pointer s7_make_normal_vector(s7_scheme *sc, s7_int len, s7_int dims, s7_int *dim_info); /* make-vector but possibly multidimensional */ +s7_pointer s7_make_float_vector_wrapper(s7_scheme *sc, s7_int len, s7_double *data, s7_int dims, s7_int *dim_info, bool free_data); +s7_pointer s7_make_and_fill_vector(s7_scheme *sc, s7_int len, s7_pointer fill); /* (make-vector len fill) */ + +void s7_vector_fill(s7_scheme *sc, s7_pointer vec, s7_pointer obj); /* (vector-fill! vec obj) */ +s7_pointer s7_vector_copy(s7_scheme *sc, s7_pointer old_vect); +s7_pointer s7_vector_to_list(s7_scheme *sc, s7_pointer vect); /* (vector->list vec) */ + /* + * (vect i) is the same as (vector-ref vect i) + * (set! (vect i) x) is the same as (vector-set! vect i x) + * (vect i j k) accesses the 3-dimensional vect + * (set! (vect i j k) x) sets that element (vector-ref and vector-set! can also be used) + * (make-vector (list 2 3 4)) returns a 3-dimensional vector with the given dimension sizes + * (make-vector '(2 3) 1.0) returns a 2-dim vector with all elements set to 1.0 + */ + +bool s7_is_hash_table(s7_pointer p); /* (hash-table? p) */ +s7_pointer s7_make_hash_table(s7_scheme *sc, s7_int size); /* (make-hash-table size) */ +s7_pointer s7_hash_table_ref(s7_scheme *sc, s7_pointer table, s7_pointer key); + /* (hash-table-ref table key) */ +s7_pointer s7_hash_table_set(s7_scheme *sc, s7_pointer table, s7_pointer key, s7_pointer value); + /* (hash-table-set! table key value) */ +s7_int s7_hash_code(s7_scheme *sc, s7_pointer obj, s7_pointer eqfunc); /* (hash-code obj [eqfunc]) */ + +s7_pointer s7_hook_functions(s7_scheme *sc, s7_pointer hook); /* (hook-functions hook) */ +s7_pointer s7_hook_set_functions(s7_scheme *sc, s7_pointer hook, s7_pointer functions); /* (set! (hook-functions hook) ...) */ + + +bool s7_is_input_port(s7_scheme *sc, s7_pointer p); /* (input-port? p) */ +bool s7_is_output_port(s7_scheme *sc, s7_pointer p); /* (output-port? p) */ +const char *s7_port_filename(s7_scheme *sc, s7_pointer x); /* (port-filename p) */ +s7_int s7_port_line_number(s7_scheme *sc, s7_pointer p); /* (port-line-number p) */ + +s7_pointer s7_current_input_port(s7_scheme *sc); /* (current-input-port) */ +s7_pointer s7_set_current_input_port(s7_scheme *sc, s7_pointer p); /* (set-current-input-port) */ +s7_pointer s7_current_output_port(s7_scheme *sc); /* (current-output-port) */ +s7_pointer s7_set_current_output_port(s7_scheme *sc, s7_pointer p); /* (set-current-output-port) */ +s7_pointer s7_current_error_port(s7_scheme *sc); /* (current-error-port) */ +s7_pointer s7_set_current_error_port(s7_scheme *sc, s7_pointer port); /* (set-current-error-port port) */ +void s7_close_input_port(s7_scheme *sc, s7_pointer p); /* (close-input-port p) */ +void s7_close_output_port(s7_scheme *sc, s7_pointer p); /* (close-output-port p) */ +s7_pointer s7_open_input_file(s7_scheme *sc, const char *name, const char *mode); + /* (open-input-file name mode) */ +s7_pointer s7_open_output_file(s7_scheme *sc, const char *name, const char *mode); + /* (open-output-file name mode) */ + /* mode here is an optional C style flag, "a" for "alter", etc ("r" is the input default, "w" is the output default) */ +s7_pointer s7_open_input_string(s7_scheme *sc, const char *input_string); + /* (open-input-string str) */ +s7_pointer s7_open_output_string(s7_scheme *sc); /* (open-output-string) */ +const char *s7_get_output_string(s7_scheme *sc, s7_pointer out_port); /* (get-output-string port) -- current contents of output string */ + /* don't free the string */ +s7_pointer s7_output_string(s7_scheme *sc, s7_pointer p); /* same but returns an s7 string */ +bool s7_flush_output_port(s7_scheme *sc, s7_pointer p); /* (flush-output-port port) */ + +typedef enum {S7_READ, S7_READ_CHAR, S7_READ_LINE, S7_PEEK_CHAR, S7_IS_CHAR_READY, S7_NUM_READ_CHOICES} s7_read_t; +s7_pointer s7_open_output_function(s7_scheme *sc, void (*function)(s7_scheme *sc, uint8_t c, s7_pointer port)); +s7_pointer s7_open_input_function(s7_scheme *sc, s7_pointer (*function)(s7_scheme *sc, s7_read_t read_choice, s7_pointer port)); + +s7_pointer s7_read_char(s7_scheme *sc, s7_pointer port); /* (read-char port) */ +s7_pointer s7_peek_char(s7_scheme *sc, s7_pointer port); /* (peek-char port) */ +s7_pointer s7_read(s7_scheme *sc, s7_pointer port); /* (read port) */ +void s7_newline(s7_scheme *sc, s7_pointer port); /* (newline port) */ +s7_pointer s7_write_char(s7_scheme *sc, s7_pointer c, s7_pointer port); /* (write-char c port) */ +s7_pointer s7_write(s7_scheme *sc, s7_pointer obj, s7_pointer port); /* (write obj port) */ +s7_pointer s7_display(s7_scheme *sc, s7_pointer obj, s7_pointer port); /* (display obj port) */ +const char *s7_format(s7_scheme *sc, s7_pointer args); /* (format ... */ + + +bool s7_is_syntax(s7_pointer p); /* (syntax? p) */ +bool s7_is_symbol(s7_pointer p); /* (symbol? p) */ +const char *s7_symbol_name(s7_pointer p); /* (symbol->string p) -- don't free the string */ +s7_pointer s7_make_symbol(s7_scheme *sc, const char *name); /* (string->symbol name) */ +s7_pointer s7_gensym(s7_scheme *sc, const char *prefix); /* (gensym prefix) */ + +bool s7_is_keyword(s7_pointer obj); /* (keyword? obj) */ +s7_pointer s7_make_keyword(s7_scheme *sc, const char *key); /* (string->keyword key) */ +s7_pointer s7_keyword_to_symbol(s7_scheme *sc, s7_pointer key); /* (keyword->symbol key) */ + +s7_pointer s7_rootlet(s7_scheme *sc); /* (rootlet) */ +s7_pointer s7_shadow_rootlet(s7_scheme *sc); +s7_pointer s7_set_shadow_rootlet(s7_scheme *sc, s7_pointer let); +s7_pointer s7_curlet(s7_scheme *sc); /* (curlet) */ +s7_pointer s7_set_curlet(s7_scheme *sc, s7_pointer e); /* returns previous curlet */ +s7_pointer s7_outlet(s7_scheme *sc, s7_pointer e); /* (outlet e) */ +s7_pointer s7_sublet(s7_scheme *sc, s7_pointer env, s7_pointer bindings); /* (sublet e ...) */ +s7_pointer s7_inlet(s7_scheme *sc, s7_pointer bindings); /* (inlet ...) */ +s7_pointer s7_varlet(s7_scheme *sc, s7_pointer env, s7_pointer symbol, s7_pointer value); /* (varlet env symbol value) */ +s7_pointer s7_let_to_list(s7_scheme *sc, s7_pointer env); /* (let->list env) */ +bool s7_is_let(s7_pointer e); /* )let? e) */ +s7_pointer s7_let_ref(s7_scheme *sc, s7_pointer env, s7_pointer sym); /* (let-ref e sym) */ +s7_pointer s7_let_set(s7_scheme *sc, s7_pointer env, s7_pointer sym, s7_pointer val); /* (let-set! e sym val) */ +s7_pointer s7_openlet(s7_scheme *sc, s7_pointer e); /* (openlet e) */ +bool s7_is_openlet(s7_pointer e); /* (openlet? e) */ +s7_pointer s7_method(s7_scheme *sc, s7_pointer obj, s7_pointer method); + +/* *s7* */ +/* these renamed because "s7_let_field" seems the same as "s7_let", but here we're referring to *s7*, not any let */ +s7_pointer s7_let_field_ref(s7_scheme *sc, s7_pointer sym); /* (*s7* sym) */ +s7_pointer s7_let_field_set(s7_scheme *sc, s7_pointer sym, s7_pointer new_value); /* (set! (*s7* sym) new_value) */ +/* new names */ +s7_pointer s7_starlet_ref(s7_scheme *sc, s7_pointer sym); /* (*s7* sym) */ +s7_pointer s7_starlet_set(s7_scheme *sc, s7_pointer sym, s7_pointer new_value); /* (set! (*s7* sym) new_value) */ + +s7_pointer s7_name_to_value(s7_scheme *sc, const char *name); /* name's value in the current environment (after turning name into a symbol) */ +s7_pointer s7_symbol_table_find_name(s7_scheme *sc, const char *name); +s7_pointer s7_symbol_value(s7_scheme *sc, s7_pointer sym); +s7_pointer s7_symbol_set_value(s7_scheme *sc, s7_pointer sym, s7_pointer val); +s7_pointer s7_symbol_local_value(s7_scheme *sc, s7_pointer sym, s7_pointer local_env); +bool s7_for_each_symbol_name(s7_scheme *sc, bool (*symbol_func)(const char *symbol_name, void *data), void *data); +bool s7_for_each_symbol(s7_scheme *sc, bool (*symbol_func)(const char *symbol_name, void *data), void *data); + + /* these access the current environment and symbol table, providing + * a symbol's current binding (s7_name_to_value takes the symbol name as a char*, + * s7_symbol_value takes the symbol itself, s7_symbol_set_value changes the + * current binding, and s7_symbol_local_value uses the environment passed + * as its third argument). + * + * To iterate over the complete symbol table, use s7_for_each_symbol_name, + * and s7_for_each_symbol. Both call 'symbol_func' on each symbol, passing it + * the symbol or symbol name, and the uninterpreted 'data' pointer. + * the current binding. The for-each loop stops if the symbol_func returns true, + * or at the end of the table. + */ + +s7_pointer s7_dynamic_wind(s7_scheme *sc, s7_pointer init, s7_pointer body, s7_pointer finish); + +bool s7_is_immutable(s7_pointer p); +s7_pointer s7_immutable(s7_pointer p); + +void s7_define(s7_scheme *sc, s7_pointer env, s7_pointer symbol, s7_pointer value); +bool s7_is_defined(s7_scheme *sc, const char *name); +s7_pointer s7_define_variable(s7_scheme *sc, const char *name, s7_pointer value); +s7_pointer s7_define_variable_with_documentation(s7_scheme *sc, const char *name, s7_pointer value, const char *help); +s7_pointer s7_define_constant(s7_scheme *sc, const char *name, s7_pointer value); +s7_pointer s7_define_constant_with_documentation(s7_scheme *sc, const char *name, s7_pointer value, const char *help); +s7_pointer s7_define_constant_with_environment(s7_scheme *sc, s7_pointer envir, const char *name, s7_pointer value); + /* These functions add a symbol and its binding to either the top-level environment + * or the 'env' passed as the second argument to s7_define. Except for s7_define, they return + * the name as a symbol. + * + * s7_define_variable(sc, "*features*", s7_nil(sc)); + * + * in s7.c is equivalent to the top level form + * + * (define *features* ()) + * + * s7_define_variable is simply s7_define with string->symbol and the global environment. + * s7_define_constant is s7_define but makes its "definee" immutable. + * s7_define is equivalent to define in Scheme, except that it does not return the value. + */ + +bool s7_is_function(s7_pointer p); +bool s7_is_procedure(s7_pointer x); /* (procedure? x) */ +bool s7_is_macro(s7_scheme *sc, s7_pointer x); /* (macro? x) */ +s7_pointer s7_closure_body(s7_scheme *sc, s7_pointer p); +s7_pointer s7_closure_let(s7_scheme *sc, s7_pointer p); +s7_pointer s7_closure_args(s7_scheme *sc, s7_pointer p); +s7_pointer s7_funclet(s7_scheme *sc, s7_pointer p); /* (funclet x) */ +bool s7_is_aritable(s7_scheme *sc, s7_pointer x, s7_int args); /* (aritable? x args) */ +s7_pointer s7_arity(s7_scheme *sc, s7_pointer x); /* (arity x) */ +const char *s7_help(s7_scheme *sc, s7_pointer obj); /* (help obj) */ +s7_pointer s7_make_continuation(s7_scheme *sc); /* call/cc... (see example below) */ + +const char *s7_documentation(s7_scheme *sc, s7_pointer p); /* (documentation x) if any (don't free the string) */ +const char *s7_set_documentation(s7_scheme *sc, s7_pointer p, const char *new_doc); +s7_pointer s7_setter(s7_scheme *sc, s7_pointer obj); /* (setter obj) */ +s7_pointer s7_set_setter(s7_scheme *sc, s7_pointer p, s7_pointer setter); /* (set! (setter p) setter) */ +s7_pointer s7_signature(s7_scheme *sc, s7_pointer func); /* (signature obj) */ +s7_pointer s7_make_signature(s7_scheme *sc, s7_int len, ...); /* procedure-signature data */ +s7_pointer s7_make_circular_signature(s7_scheme *sc, s7_int cycle_point, s7_int len, ...); + +/* possibly unsafe functions: */ +s7_pointer s7_make_function(s7_scheme *sc, const char *name, s7_function fnc, s7_int required_args, s7_int optional_args, bool rest_arg, const char *doc); + +/* safe functions: */ +s7_pointer s7_make_safe_function(s7_scheme *sc, const char *name, s7_function fnc, s7_int required_args, s7_int optional_args, bool rest_arg, const char *doc); +s7_pointer s7_make_typed_function(s7_scheme *sc, const char *name, s7_function f, + s7_int required_args, s7_int optional_args, bool rest_arg, const char *doc, s7_pointer signature); + +/* arglist or body possibly unsafe: */ +s7_pointer s7_define_function(s7_scheme *sc, const char *name, s7_function fnc, s7_int required_args, s7_int optional_args, bool rest_arg, const char *doc); + +/* arglist and body safe: */ +s7_pointer s7_define_safe_function(s7_scheme *sc, const char *name, s7_function fnc, s7_int required_args, s7_int optional_args, bool rest_arg, const char *doc); +s7_pointer s7_define_typed_function(s7_scheme *sc, const char *name, s7_function fnc, + s7_int required_args, s7_int optional_args, bool rest_arg, + const char *doc, s7_pointer signature); + +/* arglist unsafe or body unsafe: */ +s7_pointer s7_define_unsafe_typed_function(s7_scheme *sc, const char *name, s7_function fnc, + s7_int required_args, s7_int optional_args, bool rest_arg, + const char *doc, s7_pointer signature); + +/* arglist safe, body possibly unsafe: */ +s7_pointer s7_define_semisafe_typed_function(s7_scheme *sc, const char *name, s7_function fnc, + s7_int required_args, s7_int optional_args, bool rest_arg, + const char *doc, s7_pointer signature); + +s7_pointer s7_make_function_star(s7_scheme *sc, const char *name, s7_function fnc, const char *arglist, const char *doc); +s7_pointer s7_make_safe_function_star(s7_scheme *sc, const char *name, s7_function fnc, const char *arglist, const char *doc); +void s7_define_function_star(s7_scheme *sc, const char *name, s7_function fnc, const char *arglist, const char *doc); +void s7_define_safe_function_star(s7_scheme *sc, const char *name, s7_function fnc, const char *arglist, const char *doc); +void s7_define_typed_function_star(s7_scheme *sc, const char *name, s7_function fnc, const char *arglist, const char *doc, s7_pointer signature); +s7_pointer s7_define_macro(s7_scheme *sc, const char *name, s7_function fnc, s7_int required_args, s7_int optional_args, bool rest_arg, const char *doc); + + /* s7_make_function creates a Scheme function object from the s7_function 'fnc'. + * Its name (for s7_describe_object) is 'name', it requires 'required_args' arguments, + * can accept 'optional_args' other arguments, and if 'rest_arg' is true, it accepts + * a "rest" argument (a list of all the trailing arguments). The function's documentation + * is 'doc'. The s7_make_functions return the new function, but the s7_define_function (and macro) + * procedures return the name as a symbol (a desire for backwards compatibility brought about this split). + * + * s7_define_function is the same as s7_make_function, but it also adds 'name' (as a symbol) to the + * global (top-level) environment, with the function as its value (and returns the symbol, not the function). + * For example, the Scheme function 'car' is essentially: + * + * s7_pointer g_car(s7_scheme *sc, s7_pointer args) {return(s7_car(s7_car(args)));} + * + * then bound to the name "car": + * + * s7_define_function(sc, "car", g_car, 1, 0, false, "(car obj)"); + * ^ one required arg, no optional arg, no "rest" arg + * + * s7_is_function returns true if its argument is a function defined in this manner. + * s7_apply_function applies the function (the result of s7_make_function) to the arguments. + * + * s7_define_macro defines a Scheme macro; its arguments are not evaluated (unlike a function), + * but the macro's returned value (assumed to be some sort of Scheme expression) is evaluated. + * s7_define_macro returns the name as a symbol. + * + * Use the "unsafe" definer if the function might call the evaluator itself in some way (s7_apply_function for example), + * or messes with s7's stack. + */ + + /* In s7, (define* (name . args) body) or (define name (lambda* args body)) + * define a function that takes optional (keyword) named arguments. + * The "args" is a list that can contain either names (normal arguments), + * or lists of the form (name default-value), in any order. When called, + * the names are bound to their default values (or #f), then the function's + * current arglist is scanned. Any name that occurs as a keyword (":name") + * precedes that argument's new value. Otherwise, as values occur, they + * are plugged into the environment based on their position in the arglist + * (as normal for a function). So, + * + * (define* (hi a (b 32) (c "hi")) (list a b c)) + * (hi 1) -> '(1 32 "hi") + * (hi :b 2 :a 3) -> '(3 2 "hi") + * (hi 3 2 1) -> '(3 2 1) + * + * :rest causes its argument to be bound to the rest of the arguments at that point. + * + * The C connection to this takes the function name, the C function to call, the argument + * list as written in Scheme, and the documentation string. s7 makes sure the arguments + * are ordered correctly and have the specified defaults before calling the C function. + * s7_define_function_star(sc, "a-func", a_func, "arg1 (arg2 32)", "an example of C define*"); + * Now (a-func :arg1 2) calls the C function a_func(2, 32). See the example program in s7.html. + * + * In s7 Scheme, define* can be used just for its optional arguments feature, but that is + * included in s7_define_function. s7_define_function_star implements keyword arguments + * for C-level functions (as well as optional/rest arguments). + */ + +s7_pointer s7_apply_function(s7_scheme *sc, s7_pointer fnc, s7_pointer args); +s7_pointer s7_apply_function_star(s7_scheme *sc, s7_pointer fnc, s7_pointer args); + +s7_pointer s7_call(s7_scheme *sc, s7_pointer func, s7_pointer args); +s7_pointer s7_call_with_location(s7_scheme *sc, s7_pointer func, s7_pointer args, const char *caller, const char *file, s7_int line); +s7_pointer s7_call_with_catch(s7_scheme *sc, s7_pointer tag, s7_pointer body, s7_pointer error_handler); + + /* s7_call takes a Scheme function and applies it to 'args' (a list of arguments) returning the result. + * s7_pointer kar; + * kar = s7_make_function(sc, "car", g_car, 1, 0, false, "(car obj)"); + * s7_integer(s7_call(sc, kar, s7_cons(sc, s7_cons(sc, s7_make_integer(sc, 123), s7_nil(sc)), s7_nil(sc)))); + * returns 123. + * + * s7_call_with_location passes some information to the error handler. + * s7_call makes sure some sort of catch exists if an error occurs during the call, but + * s7_apply_function does not -- it assumes the catch has been set up already. + * s7_call_with_catch wraps an explicit catch around a function call ("body" above); + * s7_call_with_catch(sc, tag, body, err) is equivalent to (catch tag body err). + */ + +bool s7_is_dilambda(s7_pointer obj); +s7_pointer s7_dilambda(s7_scheme *sc, + const char *name, + s7_pointer (*getter)(s7_scheme *sc, s7_pointer args), + s7_int get_req_args, s7_int get_opt_args, + s7_pointer (*setter)(s7_scheme *sc, s7_pointer args), + s7_int set_req_args, s7_int set_opt_args, + const char *documentation); +s7_pointer s7_typed_dilambda(s7_scheme *sc, + const char *name, + s7_pointer (*getter)(s7_scheme *sc, s7_pointer args), + s7_int get_req_args, s7_int get_opt_args, + s7_pointer (*setter)(s7_scheme *sc, s7_pointer args), + s7_int set_req_args, s7_int set_opt_args, + const char *documentation, + s7_pointer get_sig, s7_pointer set_sig); +s7_pointer s7_dilambda_with_environment(s7_scheme *sc, s7_pointer envir, + const char *name, + s7_pointer (*getter)(s7_scheme *sc, s7_pointer args), + s7_int get_req_args, s7_int get_opt_args, + s7_pointer (*setter)(s7_scheme *sc, s7_pointer args), + s7_int set_req_args, s7_int set_opt_args, + const char *documentation); + +s7_pointer s7_values(s7_scheme *sc, s7_pointer args); /* (values ...) */ +bool s7_is_multiple_value(s7_pointer obj); /* is obj the results of (values ...) */ + +s7_pointer s7_make_iterator(s7_scheme *sc, s7_pointer e); /* (make-iterator e) */ +bool s7_is_iterator(s7_pointer obj); /* (iterator? obj) */ +bool s7_iterator_is_at_end(s7_scheme *sc, s7_pointer obj); /* (iterator-at-end? obj) */ +s7_pointer s7_iterate(s7_scheme *sc, s7_pointer iter); /* (iterate iter) */ + +s7_pointer s7_copy(s7_scheme *sc, s7_pointer args); /* (copy ...) */ +s7_pointer s7_fill(s7_scheme *sc, s7_pointer args); /* (fill! ...) */ +s7_pointer s7_type_of(s7_scheme *sc, s7_pointer arg); /* (type-of arg) */ + + + +/* -------------------------------------------------------------------------------- */ +/* c types/objects */ + +void s7_mark(s7_pointer p); + +bool s7_is_c_object(s7_pointer p); +s7_int s7_c_object_type(s7_pointer obj); +void *s7_c_object_value(s7_pointer obj); +void *s7_c_object_value_checked(s7_pointer obj, s7_int type); +s7_pointer s7_make_c_object(s7_scheme *sc, s7_int type, void *value); +s7_pointer s7_make_c_object_with_let(s7_scheme *sc, s7_int type, void *value, s7_pointer let); +s7_pointer s7_make_c_object_without_gc(s7_scheme *sc, s7_int type, void *value); +s7_pointer s7_c_object_let(s7_pointer obj); +s7_pointer s7_c_object_set_let(s7_scheme *sc, s7_pointer obj, s7_pointer e); +/* the "let" in s7_make_c_object_with_let and s7_c_object_set_let needs to be GC protected by marking it in the c_object's mark function */ + +s7_int s7_make_c_type(s7_scheme *sc, const char *name); /* create a new c_object type */ + +/* old style free/mark/equal */ +void s7_c_type_set_free (s7_scheme *sc, s7_int tag, void (*gc_free)(void *value)); +void s7_c_type_set_mark (s7_scheme *sc, s7_int tag, void (*mark)(void *value)); +void s7_c_type_set_equal (s7_scheme *sc, s7_int tag, bool (*equal)(void *value1, void *value2)); + +/* new style free/mark/equal and equivalent */ +void s7_c_type_set_gc_free (s7_scheme *sc, s7_int tag, s7_pointer (*gc_free) (s7_scheme *sc, s7_pointer obj)); /* free c_object function, new style*/ +void s7_c_type_set_gc_mark (s7_scheme *sc, s7_int tag, s7_pointer (*mark) (s7_scheme *sc, s7_pointer obj)); /* mark function, new style */ +void s7_c_type_set_is_equal (s7_scheme *sc, s7_int tag, s7_pointer (*is_equal) (s7_scheme *sc, s7_pointer args)); +void s7_c_type_set_is_equivalent(s7_scheme *sc, s7_int tag, s7_pointer (*is_equivalent)(s7_scheme *sc, s7_pointer args)); + +void s7_c_type_set_ref (s7_scheme *sc, s7_int tag, s7_pointer (*ref) (s7_scheme *sc, s7_pointer args)); +void s7_c_type_set_set (s7_scheme *sc, s7_int tag, s7_pointer (*set) (s7_scheme *sc, s7_pointer args)); +void s7_c_type_set_length (s7_scheme *sc, s7_int tag, s7_pointer (*length) (s7_scheme *sc, s7_pointer args)); +void s7_c_type_set_copy (s7_scheme *sc, s7_int tag, s7_pointer (*copy) (s7_scheme *sc, s7_pointer args)); +void s7_c_type_set_fill (s7_scheme *sc, s7_int tag, s7_pointer (*fill) (s7_scheme *sc, s7_pointer args)); +void s7_c_type_set_reverse (s7_scheme *sc, s7_int tag, s7_pointer (*reverse) (s7_scheme *sc, s7_pointer args)); +void s7_c_type_set_to_list (s7_scheme *sc, s7_int tag, s7_pointer (*to_list) (s7_scheme *sc, s7_pointer args)); +void s7_c_type_set_to_string (s7_scheme *sc, s7_int tag, s7_pointer (*to_string) (s7_scheme *sc, s7_pointer args)); +void s7_c_type_set_getter (s7_scheme *sc, s7_int tag, s7_pointer getter); +void s7_c_type_set_setter (s7_scheme *sc, s7_int tag, s7_pointer setter); +/* For the copy function, either the first or second argument can be a c-object of the given type. */ + + /* These functions create a new Scheme object type. There is a simple example in s7.html. + * + * s7_make_c_type creates a new C-based type for Scheme. It returns an s7_int "tag" used to indentify this type elsewhere. + * The functions associated with this type are set via s7_c_type_set*: + * + * free: the function called when an object of this type is about to be garbage collected + * mark: called during the GC mark pass -- you should call s7_mark + * on any embedded s7_pointer associated with the object (including its "let") to protect if from the GC. + * gc_mark and gc_free are new forms of mark and free, taking the c_object s7_pointer rather than its void* value + * equal: compare two objects of this type; (equal? obj1 obj2) -- this is the old form + * is_equal: compare objects as in equal? -- this is the new form of equal? + * is_equivalent: compare objects as in equivalent? + * ref: a function that is called whenever an object of this type + * occurs in the function position (at the car of a list; the rest of the list + * is passed to the ref function as the arguments: (obj ...)) + * set: a function that is called whenever an object of this type occurs as + * the target of a generalized set! (set! (obj ...) val) + * length: the function called when the object is asked what its length is. + * copy: the function called when a copy of the object is needed. + * fill: the function called to fill the object with some value. + * reverse: similarly... + * to_string: object->string for an object of this type + * getter/setter: these help the optimizer handle applicable c-objects (see s7test.scm for an example) + * + * s7_is_c_object returns true if 'p' is a c_object + * s7_c_object_type returns the c_object's type (the s7_int passed to s7_make_c_object) + * s7_c_object_value returns the value bound to that c_object (the void *value of s7_make_c_object) + * s7_make_c_object creates a new Scheme entity of the given type with the given (uninterpreted) value + * s7_mark marks any Scheme c_object as in-use (use this in the mark function to mark + * any embedded s7_pointer variables). + */ + +/* -------------------------------------------------------------------------------- */ +/* the new clm optimizer! this time for sure! + * d=double, i=integer, v=c_object, p=s7_pointer + * first return type, then arg types, d_vd -> returns double takes c_object and double (i.e. a standard clm generator) + * + * It is possible to tell s7 to call a foreign function directly, without any scheme-related + * overhead. The call needs to take the form of one of the s7_*_t functions in s7.h. For example, + * one way to call + is to pass it two s7_double arguments and get an s7_double back. This is the + * s7_d_dd_t function (the first letter gives the return type, the rest give successive argument types). + * We tell s7 about it via s7_set_d_dd_function. Whenever s7's optimizer encounters + with two arguments + * that it (the optimizer) knows are s7_doubles, in a context where an s7_double result is expected, + * s7 calls the s7_d_dd_t function directly without consing a list of arguments, and without + * wrapping up the result as a scheme cell. + */ + +s7_pfunc s7_optimize(s7_scheme *sc, s7_pointer expr); + +typedef s7_double (*s7_float_function)(s7_scheme *sc); +s7_float_function s7_float_optimize(s7_scheme *sc, s7_pointer expr); + +typedef s7_double (*s7_d_t)(void); +void s7_set_d_function(s7_scheme *sc, s7_pointer f, s7_d_t df); +s7_d_t s7_d_function(s7_pointer f); + +typedef s7_double (*s7_d_d_t)(s7_double x); +void s7_set_d_d_function(s7_scheme *sc, s7_pointer f, s7_d_d_t df); +s7_d_d_t s7_d_d_function(s7_pointer f); + +typedef s7_double (*s7_d_dd_t)(s7_double x1, s7_double x2); +void s7_set_d_dd_function(s7_scheme *sc, s7_pointer f, s7_d_dd_t df); +s7_d_dd_t s7_d_dd_function(s7_pointer f); + +typedef s7_double (*s7_d_ddd_t)(s7_double x1, s7_double x2, s7_double x3); +void s7_set_d_ddd_function(s7_scheme *sc, s7_pointer f, s7_d_ddd_t df); +s7_d_ddd_t s7_d_ddd_function(s7_pointer f); + +typedef s7_double (*s7_d_dddd_t)(s7_double x1, s7_double x2, s7_double x3, s7_double x4); +void s7_set_d_dddd_function(s7_scheme *sc, s7_pointer f, s7_d_dddd_t df); +s7_d_dddd_t s7_d_dddd_function(s7_pointer f); + +typedef s7_double (*s7_d_v_t)(void *v); +void s7_set_d_v_function(s7_scheme *sc, s7_pointer f, s7_d_v_t df); +s7_d_v_t s7_d_v_function(s7_pointer f); + +typedef s7_double (*s7_d_vd_t)(void *v, s7_double d); +void s7_set_d_vd_function(s7_scheme *sc, s7_pointer f, s7_d_vd_t df); +s7_d_vd_t s7_d_vd_function(s7_pointer f); + +typedef s7_double (*s7_d_vdd_t)(void *v, s7_double x1, s7_double x2); +void s7_set_d_vdd_function(s7_scheme *sc, s7_pointer f, s7_d_vdd_t df); +s7_d_vdd_t s7_d_vdd_function(s7_pointer f); + +typedef s7_double (*s7_d_vid_t)(void *v, s7_int i, s7_double d); +void s7_set_d_vid_function(s7_scheme *sc, s7_pointer f, s7_d_vid_t df); +s7_d_vid_t s7_d_vid_function(s7_pointer f); + +typedef s7_double (*s7_d_p_t)(s7_pointer p); +void s7_set_d_p_function(s7_scheme *sc, s7_pointer f, s7_d_p_t df); +s7_d_p_t s7_d_p_function(s7_pointer f); + +typedef s7_double (*s7_d_pd_t)(s7_pointer v, s7_double x); +void s7_set_d_pd_function(s7_scheme *sc, s7_pointer f, s7_d_pd_t df); +s7_d_pd_t s7_d_pd_function(s7_pointer f); + +typedef s7_double (*s7_d_7pi_t)(s7_scheme *sc, s7_pointer v, s7_int i); +void s7_set_d_7pi_function(s7_scheme *sc, s7_pointer f, s7_d_7pi_t df); +s7_d_7pi_t s7_d_7pi_function(s7_pointer f); + +typedef s7_double (*s7_d_7pid_t)(s7_scheme *sc, s7_pointer v, s7_int i, s7_double d); +void s7_set_d_7pid_function(s7_scheme *sc, s7_pointer f, s7_d_7pid_t df); +s7_d_7pid_t s7_d_7pid_function(s7_pointer f); + +typedef s7_double (*s7_d_id_t)(s7_int i, s7_double d); +void s7_set_d_id_function(s7_scheme *sc, s7_pointer f, s7_d_id_t df); +s7_d_id_t s7_d_id_function(s7_pointer f); + +typedef s7_double (*s7_d_ip_t)(s7_int i, s7_pointer p); +void s7_set_d_ip_function(s7_scheme *sc, s7_pointer f, s7_d_ip_t df); +s7_d_ip_t s7_d_ip_function(s7_pointer f); + +typedef s7_int (*s7_i_i_t)(s7_int x); +void s7_set_i_i_function(s7_scheme *sc, s7_pointer f, s7_i_i_t df); +s7_i_i_t s7_i_i_function(s7_pointer f); + +typedef s7_int (*s7_i_7d_t)(s7_scheme *sc, s7_double x); +void s7_set_i_7d_function(s7_scheme *sc, s7_pointer f, s7_i_7d_t df); +s7_i_7d_t s7_i_7d_function(s7_pointer f); + +typedef s7_int (*s7_i_ii_t)(s7_int i1, s7_int i2); +void s7_set_i_ii_function(s7_scheme *sc, s7_pointer f, s7_i_ii_t df); +s7_i_ii_t s7_i_ii_function(s7_pointer f); + +typedef s7_int (*s7_i_7p_t)(s7_scheme *sc, s7_pointer p); +void s7_set_i_7p_function(s7_scheme *sc, s7_pointer f, s7_i_7p_t df); +s7_i_7p_t s7_i_7p_function(s7_pointer f); + +typedef bool (*s7_b_p_t)(s7_pointer p); +void s7_set_b_p_function(s7_scheme *sc, s7_pointer f, s7_b_p_t df); +s7_b_p_t s7_b_p_function(s7_pointer f); + +typedef s7_pointer (*s7_p_d_t)(s7_scheme *sc, s7_double x); +void s7_set_p_d_function(s7_scheme *sc, s7_pointer f, s7_p_d_t df); +s7_p_d_t s7_p_d_function(s7_pointer f); + +typedef s7_pointer (*s7_p_p_t)(s7_scheme *sc, s7_pointer p); +void s7_set_p_p_function(s7_scheme *sc, s7_pointer f, s7_p_p_t df); +s7_p_p_t s7_p_p_function(s7_pointer f); + +typedef s7_pointer (*s7_p_pp_t)(s7_scheme *sc, s7_pointer p1, s7_pointer p2); +void s7_set_p_pp_function(s7_scheme *sc, s7_pointer f, s7_p_pp_t df); +s7_p_pp_t s7_p_pp_function(s7_pointer f); + +typedef s7_pointer (*s7_p_ppp_t)(s7_scheme *sc, s7_pointer p1, s7_pointer p2, s7_pointer p3); +void s7_set_p_ppp_function(s7_scheme *sc, s7_pointer f, s7_p_ppp_t df); +s7_p_ppp_t s7_p_ppp_function(s7_pointer f); + +/* Here is an example of using these functions; more extensive examples are in clm2xen.c in sndlib, and in s7.c. + * (This example comes from a HackerNews discussion): + * plus.c: + * -------- + * #include "s7.h" + * + * s7_pointer g_plusone(s7_scheme *sc, s7_pointer args) {return(s7_make_integer(sc, s7_integer(s7_car(args)) + 1));} + * s7_int plusone(s7_int x) {return(x + 1);} + * + * void plusone_init(s7_scheme *sc) + * { + * s7_define_safe_function(sc, "plusone", g_plusone, 1, 0, false, ""); + * s7_set_i_i_function(sc, s7_name_to_value(sc, "plusone"), plusone); + * } + * -------- + * gcc -c plus.c -fPIC -O2 -lm + * gcc plus.o -shared -o plus.so -ldl -lm -Wl,-export-dynamic + * repl + * <1> (load "plus.so" (inlet 'init_func 'plusone_init)) + * -------- + */ + +/* -------------------------------------------------------------------------------- */ + +/* maybe remove these? */ +s7_pointer s7_slot(s7_scheme *sc, s7_pointer symbol); +s7_pointer s7_slot_value(s7_pointer slot); +s7_pointer s7_slot_set_value(s7_scheme *sc, s7_pointer slot, s7_pointer value); +s7_pointer s7_make_slot(s7_scheme *sc, s7_pointer env, s7_pointer symbol, s7_pointer value); +void s7_slot_set_real_value(s7_scheme *sc, s7_pointer slot, s7_double value); + +/* -------------------------------------------------------------------------------- */ + +#if (!DISABLE_DEPRECATED) +typedef s7_int s7_Int; +typedef s7_double s7_Double; + +#define s7_is_object s7_is_c_object +#define s7_object_type s7_c_object_type +#define s7_object_value s7_c_object_value +#define s7_make_object s7_make_c_object +#define s7_mark_object s7_mark +#define s7_UNSPECIFIED(Sc) s7_unspecified(Sc) +#endif + + +bool s7_is_bignum(s7_pointer obj); +#if WITH_GMP + mpfr_t *s7_big_real(s7_pointer x); + mpz_t *s7_big_integer(s7_pointer x); + mpq_t *s7_big_ratio(s7_pointer x); + mpc_t *s7_big_complex(s7_pointer x); + + bool s7_is_big_real(s7_pointer x); + bool s7_is_big_integer(s7_pointer x); + bool s7_is_big_ratio(s7_pointer x); + bool s7_is_big_complex(s7_pointer x); + + s7_pointer s7_make_big_real(s7_scheme *sc, mpfr_t *val); + s7_pointer s7_make_big_integer(s7_scheme *sc, mpz_t *val); + s7_pointer s7_make_big_ratio(s7_scheme *sc, mpq_t *val); + s7_pointer s7_make_big_complex(s7_scheme *sc, mpc_t *val); +#endif + + +/* -------------------------------------------------------------------------------- + * + * s7 changes + * + * 17-Mar-23: moved s7_is_bignum declaration outside WITH_GMP. + * -------- + * 9-Nov: nan, nan-payload, +nan.. + * 19-Oct: s7_let_field* synonyms: s7_starlet*. + * 16-Sep: s7_number_to_real_with_location. s7_wrong_type_error. s7_make_string_wrapper_with_length. s7_make_semipermanent_string. + * 21-Apr: s7_is_multiple_value. + * 11-Apr: removed s7_apply_*. + * 22-Mar: s7_eval_with_location. + * 16-Mar: s7_list_to_array for the s7_apply_* changes. + * 8-Mar-22: moved s7_apply_* to xen.h if DISABLE_DEPRECATED. + * -------- + * 24-Nov: moved s7_p_p_t and friends into s7.h. + * 23-Sep: s7_make_byte_vector, s7_is_byte_vector, s7_byte_vector_ref|set|elements. + * 25-Aug: s7_output_string (like s7_get_output_string, but returns an s7 string). + * 19-Jul: s7_is_random_state, s7_make_normal_vector. s7_array_to_list. + * 12-Apr: s7_optimize now returns an s7_pfunc, not an s7_function. + * 7-Apr: removed the "args" parameter from s7_float_function. added s7_make_c_object_without_gc. + * 31-Mar: vector-rank, vector-dimension. + * 17-Mar: removed deprecated nan.0 and inf.0 due to compiler stupidity. + * 25-Jan: s7_define_semisafe_typed_function. + * 6-Jan-21: s7_hash_code. + * -------- + * 14-Oct: s7_load_c_string and s7_load_c_string_with_environment. + * 10-Sep: s7_free. + * 5-Aug: s7_make_list. + * 31-July: s7_define_constant_with_environment and s7_dilambda_with_environment. + * 29-July: open-input|output-function. add S7_NUM_READ_CHOICES to s7_read_t enum and remove (unused) S7_READ_BYTE. + * 20-July: s7_c_pointer_with_type. notcurses_s7.c and nrepl.scm. *autoload-hook*. + * 8-July: s7_int|float_vector_ref|set. subvector parameter order changed. + * 17-June: removed deprecated *s7* accessors. + * 20-May: libarb_s7.c. + * 12-May: s7_is_big*. + * 6-May: added s7_scheme* initial arguments to s7_set_* opt_func calls (s7_set_d_d_function for example). + * 23-Apr: added s7_scheme* initial argument to s7_is_eqv. + * 9-Mar: move openlets to (*s7* 'openlets), s7-version to (*s7* 'version), deprecate nan.0 and inf.0. + * 17-Feb: s7_let_field_ref|set for *s7* access. *function* to replace __func__. + * deprecate __func__, s7_print_length, s7_float_format_precision, s7_set_gc_stats. + * 31-Jan: macro(*) and bacro(*) -- unnamed macros analogous to lambda(*). + * 20-Jan: debug.scm and (*s7* 'debug), trace-in, dynamic-unwind. + * remove coverlets (openlets is now a dilambda). + * 10-Jan: s7_c_type_set_gc_free and s7_c_type_set_gc_mark. + * 2-Jan-20: s7_c_type_set_is_equal and s7_c_type_set_is_equivalent. + * -------- + * 2-Nov: s7_repl. + * 30-Oct: change S7_DATE format, and start updating it to reflect s7.c. + * 30-Jul: define-expansion*. + * 12-Jul: s7_call_with_catch, s7_load now returns NULL if file not found (rather than raise an error). + * 8-July: most-positive-fixnum and most-negative-fixnum moved to *s7*. + * 23-May: added s7_scheme argument to s7_c_object_set_let. + * 19-May: s7_gc_stats renamed s7_set_gc_stats. + * 7-May: s7_gc_unprotect_via_stack and s7_gc_(un)protect_via_location. + * 22-Mar: s7_float_format_precision. port-position. port-file. + * 4-Jan-19: morally-equal? -> equivalent? + * -------- + * 29-Dec: s7_c_type_set_getter|setter (implicit c-object access). + * 23-Dec: remove hash-table, rename hash-table* to hash-table. add weak-hash-table. + * 3-Dec: deprecate s7_gc_unprotect (use s7_gc_unprotect_at). + * 21-Nov: added s7_history_enabled and s7_set_history_enabled. + * 3-Nov: removed the "value" argument from s7_for_each_symbol. + * 22-Sep: s7_list_nl. + * 12-Sep: byte-vectors can be multidimensional; homogenous vectors of any built-in type. typed hash-tables. + * 29-Jul: symbol-setter deprecated (use setter). s7_symbol_documentation (and setter) folded into s7_documentation. + * 12-Jul: changed s7_vector_dimensions|offsets. + * Added s7_scheme* arg to make_permanent_string and several of the optimizer functions. + * 3-Jul: changed make-shared-vector to subvector. + * 20-May: s7_keyword_to_symbol. + * 6-May: s7_mark_c_object -> s7_mark. + * 26-Apr: s7_c_type_set_to_list|string, s7_c_type_set_apply -> s7_c_type_set_ref, removed s7_c_type_set_set|apply_direct + * c_type length|set|ref are now s7_functions (args, not obj, etc). + * 23-Mar: s7_peek_char and s7_read_char now return s7_pointer, s7_write_char takes s7_pointer, not int32_t c + * s7_gc_protect and friends now return/take s7_int location, not uint32_t. + * removed s7_new_type_x. + * 19-Mar: int32_t -> s7_int in various functions. + * 17-Mar: deprecate s7_ulong and s7_ulong_long functions. + * 26-Jan-18: s7_set_setter. + * -------- + * 11-Dec: s7_gc_protect_via_stack + * 3-Oct: renamed procedure-signature -> signature, procedure-documentation -> documentation, and procedure-setter -> setter. + * 18-Sep: s7_immutable, s7_is_immutable. define-constant follows lexical scope now. + * s7_symbol_access -> s7_symbol_setter, symbol-access -> symbol-setter. + * 3-Aug: object->c_object name changes. + * 28-Jul: s7_make_c_pointer_with_type and s7_c_pointer_type. + * 24-Jul: int64_t rather than long long int, and various related changes. + * 18-Jul: s7_make_object_with_let. + * 8-July: s7_define_typed_function_star, s7_make_function_star. s7_apply_function_star. + * 27-June: s7_make_string_wrapper. + * 22-May: lambda* keyword arg handling changed slightly. + * 9-May: s7_history, s7_add_to_history. + * 20-Apr: s7_tree_memq (for Snd), s7_type_of, many changes for new clm optimizer. + * 10-Apr: added s7_scheme first argument to s7_iterator_is_at_end. + * 28-Mar: removed the "rf", "pf" and "if" clm optimization functions. + * s7_optimize, s7_float_optimize, s7_procedure_signature. + * 22-Feb: removed the "gf" clm optimization functions. + * 11-Feb: #e, #i, #d removed. #i(...) is an int-vector constant, #r(...) a float-vector. + * 2-Jan-17: {apply_values} -> apply-values, {list} -> list-values, and {append} -> append. + * -------- + * 23-Sep: make-keyword -> string->keyword. + * 9-Aug: s7_varlet. + * 29-Jul: s7_define_unsafe_typed_function. + * 30-May: symbol takes any number of args. make-vector no longer takes an optional fourth argument. + * 24-May: let-ref/set! check rootlet now if let is not an open let; setter for with-let. + * 20-Feb: removed last vestiges of quasiquoted vector support. + * 3-Feb: *cload-directory*. + * 14-Jan: profile.scm. Moved multiple-value-set! to stuff.scm. Setter for port-line-number. + * 7-Jan: s7_load_with_environment. + * s7_eval_c_string takes only one statement now (use begin to handle multiple statements) + * 4-Jan-16: remove s7_eval_form, change s7_eval to take its place. + * -------- + * 11-Dec: owlet error-history field if WITH_HISTORY=1 + * 6-Nov: removed :key and :optional. + * 16-Oct: s7_make_random_state -> s7_random_state. + * 16-Aug: remove s7_define_integer_function, s7_function_set_removes_temp, + * add s7_define_typed_function, s7_make_signature. + * 5-Aug: added s7_scheme* arg to s7_openlet and s7_outlet. + * 3-Jul: s7_Double -> s7_double, s7_Int -> s7_int. Removed function_chooser_data. + * 27-Jun: s7_rf_t, s7_rp_t etc. + * 19-Jun: removed the ex_parser stuff, set_step_safe, s7_ex_fallback. + * 5-May: s7_make_iterator and friends. + * 16-Apr: added s7_fill, changed arg interpretation of s7_copy, s7_dynamic_wind. + * 30-Mar: s7_eval_c_string_with_environment (repl experiment). + * 19-Mar: repl.scm. + * 28-Feb: s7_vector_print_length -> s7_print_length, set case also. + * 25-Feb: s7_closure_* funcs to replace clumsy (deprecated) s7_procedure_source. + * 29-Jan: changed args to s7_new_type_x (added s7_scheme arg, fill! takes s7_function). + * 14-Jan-15: make-iterator, iterator? + * -------- + * 26-Dec: s7_arity replaces s7_procedure_arity. s7_define_integer_function. deprecate s7_procedure_name. + * 5-Nov: s7_shadow_rootlet and s7_set_shadow_rootlet. + * 30-Aug: s7_make_safe_function (for cload.scm). + * 25-July: define and friends now return the value, not the symbol. + * procedure_with_setter -> dilambda. + * environment -> let. All the replaced names are deprecated. + * 30-June: s7_method. + * 16-June: remove unoptimize and s7_unoptimize. + * 14-May: s7_define_safe_function_star. Removed s7_catch_all. + * 22-Apr: remove s7_apply_n_10, s7_is_valid_pointer, s7_keyword_eq_p. + * 5-Mar-14: s7_heap_size, s7_gc_freed (subsequently removed). + * -------- + * 8-Nov: s7_symbol_documentation, s7_define_constant_with_documentation. + * 17-Oct: bignum-precision (procedure-with-setter) is now an integer variable named *bignum-precision*. + * 28-Aug: s7_int|float_vector_elements (homogeneous vectors), libc.scm. + * 16-Aug: ~W directive in format, make-shared-vector. + * 23-Jul: s7_autoload_set_names, libm.scm, libdl.scm, libgdbm.scm, r7rs.scm, s7libtest.scm. + * 21-Jul: s7_is_valid (replaces deprecated s7_is_valid_pointer). + * 24-Jun: some bool-related changes for Windows Visual C++, including change to s7_begin_hook. + * 3-June: s7_autoload. + * 28-May: export s7_is_provided. Added s7_scheme* arg to s7_procedure_environment. + * 21-May: equality predicate optional arg in make-hash-table. + * 14-May: glistener.c, glistener.h, s7_symbol_table_find_name (for glistener). + * 2-May: r7rs changes: flush-output-port, vector-append, read|write-string, boolean=?, symbol=?. + * start/end args for string-fill!, vector-fill!, string->list, vector->list, and copy. + * exit, emergency-exit. + * 7-Apr: removed s7_scheme* arg from s7_slot_value, added s7_is_local_variable. + * 25-Mar: char-position, string-position, environment-ref, environment-set! added to the scheme side. + * 9-Jan-13: s7_cos, s7_sin, other optimization changes. + * -------- + * 24-Dec: s7_set_object_array_info and other such changes. + * 20-Nov: removed s7_set_error_exiter and s7_error_and_exit which I think have never been used. + * 22-Oct: changed args to s7_function_class and s7_function_set_class. + * 22-Aug: symbol->dynamic-value. + * 10-Aug: exported s7_outer_environment. + * 6-Aug: removed WITH_OPTIMIZATION. + * 25-July: environment (in scheme). s7_vector_ref_n and s7_vector_set_n. s7_copy. + * added s7_scheme arg to s7_number_to_real|integer. + * 16-July: s7_function_returns_temp (an experiment). + * 2-July: s7_object_set_* functions. + * 11-June: throw. + * 4-June. s7_object_environment. + * 31-May: added s7_scheme argument to all the optimizer chooser functions. + * 24-May: open-environment? + * 17-May: arity, aritable? + * removed trace and untrace. + * 14-May: s7_list. s7_procedure_set_setter. Removed s7_procedure_getter. + * procedure-setter is settable: removed most of procedure-with-setter. + * make-type replaced by open-environment. + * 11-May: s7 2.0: hook implementation changed completely. + * s7_environment_ref|set. + * 4-May: *error-info* replaced by error-environment, and stacktrace has changed. + * 22-Apr: #_ = startup (built-in) value of name + * 17-Apr: with-baffle. + * 14-Apr: WITH_SYSTEM_EXTRAS (default 0) has additional OS and IO functions: + * directory? file-exists? delete-file getenv directory->list system + * 26-Mar: "@" as exponent, WITH_AT_SIGN_AS_EXPONENT switch (default is 1). + * 18-Mar: removed *trace-hook*. + * 6-Feb: random-state?, hash-table-iterator?, and morally-equal? + * 18-Jan: s7_environment_to_list and environment->list return just the local environment's bindings. + * outer-environment returns the environment enclosing its argument (an environment). + * environments are now applicable objects. + * added the object system example to s7.html. + * 12-Jan: added reverse argument to s7_new_type_x. This is needed because an object might implement + * the apply and set methods, but they might refer to different things. + * 6-Jan-12: added (scheme side) logbit?. + * -------- + * 21-Dec: s7_eval, s7_make_slot, s7_slot_set_value. + * changed s7_symbol_slot to s7_slot, and s7_symbol_slot_value to s7_slot_value. + * 26-Oct: s7_procedure_name. + * 6-Oct: changed s7_make_closure args: split the code argument in two (args and body). + * s7_make_closure(... code ...) is now s7_make_closure(... car(code), cdr(code) ...) + * s7_is_environment. + * 19-Aug: s7_function_chooser_data. + * 11-Aug: s7_symbol_accessor functions. s7_cxxxxr. + * 9-Aug: s7_function_chooser, s7_function_choice, s7_function_choice_set_direct. + * 20-Jul: s7_function_class, s7_function_set_class, and s7_function_set_chooser. + * 14-Jul: removed thread and profiling support. + * 5-June: s7_define_safe_function and s7_unoptimize exported; added unoptimize function in scheme. + * 30-May: environment->list and s7_environment_to_list since environments are no longer alists internally. + * 26-May: added s7_scheme argument to s7_procedure_setter and getter (old names had "with_setter_"). + * 28-Apr: s7_help. + * 5-Apr: pair-line-number. + * 14-Mar: s7_make_random_state, optional state argument to s7_random, random-state->list, s7_random_state_to_list. + * 10-Feb: s7_vector_print_length, s7_set_vector_print_length. + * 7-Feb: s7_begin_hook, s7_set_begin_hook. + * 25-Jan: s7_is_thread, s7_thread, s7_make_thread, s7_thread_s7, s7_thread_data. + * s7_is_lock, s7_make_lock, s7_lock. + * changed s7_thread_variable_value to s7_thread_variable. + * 23-Jan: removed (scheme-level) quit. + * 17-Jan-11: make-hash-table-iterator. + * map and for-each accept any applicable object as the first argument. + * format's ~{...~} directive can handle any applicable object. + * -------- + * 17-Dec: removed unquote-splicing; replaced by (unquote (apply values ...)). + * 12-Dec: environment? + * 7-Dec: member and assoc have an optional third arg, the comparison function. + * 1-Dec: *gc-stats* in Scheme, s7_gc_stats in C. + * gmp and gtk-repl examples in s7.html. + * 21-Nov: Load C module example in s7.html. + * 12-Nov: *trace-hook*, *load-hook*, *error-hook*, and *unbound-variable-hook* are now s7 hooks. + * 9-Nov: hooks: C side: s7_is_hook, s7_make_hook, s7_hook_apply, s7_hook_functions, s7_hook_arity, s7_hook_documentation. + * s7 side: hook?, make-hook, hook, hook-apply, hook-functions, hook-arity, hook-documentation. + * 8-Nov: Closure defined in C example in s7.html. + * 23-Oct: s7_call_with_location for better error reporting. + * 19-Oct: *stdin*, *stdout*, *stderr* for default IO ports (rather than nil which is ambiguous). + * 14-Oct: removed special variable support. + * 30-Sep: setters for current-input-port, current-output-port, and current-error-port. + * 30-Aug: :allow-other-keys in define*. + * 10-Aug: added boolean argument use_write to s7_object_to_string (true=write, false=display). + * 30-July: special macro for access to dynamic binding. + * s7_symbol_special_value for C-side access to dynamic bindings. + * s7_is_macro. + * port-closed? returns #t if its argument (a port) is closed. + * 22-July: s7_make_character takes uint32_t, rather than int. + * added symbol function for funny symbol names. + * 12-July: initial-environment. + * 7-July: removed force and delay: use slib. + * 3-July: new backquote implementation. + * 28-June: syntactic keywords (e.g. lambda) are applicable. + * 7-June: changed key arg in s7_hash_table_ref|set to be s7_pointer, not const char*. + * hash-tables can now handle any s7 object as the key. + * map and for-each now pass a hash-table entry to the function, rather than an internal alist. + * reverse of a hash-table reverses the keys and values (i.e. old value becomes new key, etc). + * 2-June: removed procedure-with-setter-setter-arity and folded that info into procedure-arity (use cdddr). + * 22-May: multidimensional vectors are no longer optional. + * 9-May: s7_read_char and s7_peek_char have to return an int, not a char (=-1, but 255 is a legit char). + * s7_write_char and s7_open_output_function have similar changes. + * 3-May: *#readers* to customize #... reading. Also nan? and infinite?. + * multidimensional vector constants using #nD(...): (#2D((1 2 3) (4 5 6)) 0 0) -> 1. + * 13-Apr: removed hash-table|vector|string-for-each -- these are handled by for-each. + * also removed vector-map -- map is generic, but always returns a list. + * 12-Apr: removed immutable constant checks -- see s7.html. + * 7-Apr: *unbound-variable-hook*. + * augment-environment and s7_augment_environment. + * 29-Mar: symbol-access, s7_symbol_access, s7_symbol_set_access. + * C example of notification in s7.html. + * 25-Mar: make-type. s7_is_equal now includes an s7_scheme pointer as its first argument. + * 24-Mar: s7_is_defined. + * 19-Mar: removed encapsulation mechanism and s7_define_set_function. + * 18-Mar: added macro?. + * 27-Feb: removed r4rs-style macro syntax. + * 17-Feb: s7_number_to_integer. + * 20-Jan-10: removed the stack function. + * -------- + * 16-Dec: hash-table-for-each. + * 1-Dec: mpc versions before 0.8.0 are no longer supported. + * 24-Nov: define-macro* and defmacro*. + * force and delay included only if WITH_FORCE set, promise? removed. + * 17-Nov: s7_is_boolean no longer takes the s7_scheme argument. + * 7-Nov: s7_vector_dimensions, s7_vector_offsets, example of use. + * 3-Nov: s7_vector_rank. + * 30-Oct: *trace-hook*. + * 12-Oct: s7_port_filename. + * 5-Oct: s7_c_pointer and friends. + * 14-Sep: s7_values, s7_make_continuation, and a better interrupt example. + * vector-for-each, vector-map, string-for-each. + * 7-Sep: s7_open_input_function. with-environment. receive. + * 3-Sep: s7.html, s7-slib-init.scm. + * s7_stacktrace in s7.h. + * 27-Aug: vector and hash-table sizes are now s7_ints, rather than ints. + * 20-Aug: s7_remove_from_heap. + * 17-Aug: *error-info*. + * 7-Aug: s7_define_function_with_setter. + * s7_quit and example of signal handling. + * 6-Aug: encapsulation. s7_define_set_function. s7_new_type_x. + * generic function: copy, and length is generic. + * 1-Aug: lower-case versions of s7_T and friends. + * s7_define_macro. macroexpand. + * strings are set-applicable (like vectors). + * 31-Jul: *error-hook*. + * 30-Jul: changed backtrace handling: removed backtrace stuff, added stacktrace. + * removed gc-verbose and load-verbose replaced by *load-hook*. + * 23-Jul: __func__. + * 20-Jul: trace and untrace. + * 14-Jul: replaced s7_make_closure_star with s7_define_function_star. + * 29-Jun: s7_format declaration. + * 12-May: s7_is_constant. + * 20-Apr: changed rationalize to be both r5rs-acceptable and fast. + * 6-Apr: added s7_make_permanent_string. + * 14-Mar: removed s7_local_gc_protect and s7_local_gc_unprotect. + * 4-Mar: multidimensional and applicable vectors. + * 1-Mar: s7_random added to s7.h. + * 29-Jan: s7_is_bignum and friends. + * 26-Jan: added s7_scheme arg to s7_vector_fill. + * 16-Jan: s7_is_ulong_long and friends for C pointers in 64-bit situations. + * 9-Jan-09 multiprecision arithmetic (gmp, mpfr, mpc) on the WITH_GMP switch + * -------- + * 29-Dec: "+" specialization example, s7_apply_function. + * 3-Dec: s7_open_output_function. + * 30-Nov: s7_wrong_number_of_args_error. + * 24-Nov: changed s7_make_counted_string to s7_make_string_with_length. + * also added built-in format and define* + * 10-Nov: s7_define_constant, + * built-in (scheme-side) pi, most-positive-fixnum, most-negative-fixnum + * 7-Nov: removed s7_is_immutable and friends, s7_reverse_in_place. + * removed the s7_pointer arg to s7_gc_on. + * added s7_UNSPECIFIED + * 25-Oct: added name arg to s7_make_procedure_with_setter, + * and s7_scheme arg to new_type print func. + * 1-Oct-08 version 1.0 + */ + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/extra/dat-dpi/inc/sleep.h b/extra/dat-dpi/inc/sleep.h new file mode 100644 index 0000000..dcc4ec6 --- /dev/null +++ b/extra/dat-dpi/inc/sleep.h @@ -0,0 +1,127 @@ +// à¥� सूकà¥�षà¥�म शरीर +#ifndef SLEEP_H +#define SLEEP_H +// TODO implement SLEEP +/*metadata.key +metadata.signatures +metadata.bitfield +metadata.tree +metadata.data +content.key +content.signatures +content.bitfield +content.tree*/ +#include "keys.h" +#include +#include +// test +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int sleep_init(const char *root); + +#define SLEEP_HEADER_SIZE 32u + +#define SLEEP_MAGIC_BITFIELD 0x05025700u +#define SLEEP_MAGIC_SIGNATURE 0x05025701u +#define SLEEP_MAGIC_TREE 0x05025702u + +// TODO MAGIC_BITFIELD, MAGIC_SIG, MAGIC_TREE + +#define SLEEP_ALGO_BLAKE2B "BLAKE2b" +#define SLEEP_ALGO_ED25519 "Ed25519" + +#define SLEEP_TREE_ENTRY_SIZE 40u +#define SLEEP_SIG_ENTRY_SIZE 64u +#define SLEEP_BITFIELD_ENTRY_SIZE 3584u + +enum sleep_hash_type { + SLEEP_HASH_LEAF = 0, + SLEEP_HASH_PARENT = 1, + SLEEP_HASH_ROOT = 2 +}; + +// Wire +#if defined(__GNUC__) || defined(__clang__) || defined(__MUSL__) + #define SLEEP_PACKED __attribute__((packed)) +#else + #define SLEEP_PACKED + #pragma pack(push,1) // pragma deez +#endif + + +typedef struct SLEEP_PACKED sleep_header { + uint32_t magic_be; + uint8_t version; + uint16_t entry_size_be; + uint8_t algo_len; + char algo[24]; +} sleep_header_t; +// TODO assert must be 32 bytes + +// 32 byte hash + 8 byte BE len +typedef struct SLEEP_PACKED sleep_tree_entry { + uint8_t hash[32]; + uint64_t length_be; +} sleep_tree_entry_t; +// TODO assert + +// 64 bytes +typedef struct SLEEP_PACKED sleep_sig_entry { + uint8_t sig[64]; +} sleep_sig_entry_t; +// TODO assert + +// 3584-byte historical record. The final 256 bytes are opaque for now. +typedef struct SLEEP_PACKED sleep_bitfield_entry { + uint8_t data[1024]; + uint8_t tree[2048]; + uint8_t index[256]; + uint8_t reserved[256]; +} sleep_bitfield_entry_t; +// TODO DOTO ODOT assert it + +#if !defined(__GNUC__) && !defined(__clang__) + #pragma pack(pop) +#endif + +// TODO BE INT into and out of buffers ** + +// crypto helpers +#ifdef SLEEP_SALT +int sleep_gen_ed25519(uint8_t pubkey[32], uint8_t seckey[64]); +// sign +int sleep_sign_ed25519(const uint8_t *msg, size_t msglen, + const uint8_t seckey[64], uint8_t sig_out[64]); +// verify +int sleep_verify_ed25519(const uint8_t *msg, size_t msglen, + const uint8_t pubkey[32], const uint8_t sig[64]); +// discovery +int sleep_compute_discovery_key(const uint8_t pubkey[32], uint8_t out32[32]); +#endif /* SLEEP_SALT */ + +// function decs +uint64_t sleep_tree_index(uint64_t depth, uint64_t offset); +uint64_t sleep_tree_depth(uint64_t index); +uint64_t sleep_tree_offset(uint64_t index, uint64_t depth); +uint64_t sleep_tree_parent(uint64_t index); +int sleep_tree_left_child(uint64_t index, uint64_t *out); +int sleep_tree_right_child(uint64_t index, uint64_t *out); +int sleep_tree_sibling(uint64_t index, uint64_t *out); +uint64_t sleep_tree_left_span(uint64_t index); +uint64_t sleep_tree_right_span(uint64_t index); +void sleep_tree_spans(uint64_t index, uint64_t *left, uint64_t *right); +size_t sleep_tree_count(uint64_t index); +size_t sleep_tree_count_leaves(uint64_t index); +size_t sleep_tree_full_roots(uint64_t index, uint64_t *out, size_t capacity); +int sleep_tree_hash_block(const uint8_t *data, size_t len, uint8_t out_hash[32]); +int sleep_tree_hash_parent(const uint8_t left[32], const uint8_t right[32], + uint64_t length, uint8_t out_hash[32]); + +#ifdef __cplusplus +} +#endif +#endif /* SLEEP_H */ diff --git a/extra/dat-dpi/inc/sleep_bitfield.h b/extra/dat-dpi/inc/sleep_bitfield.h new file mode 100644 index 0000000..26d8c69 --- /dev/null +++ b/extra/dat-dpi/inc/sleep_bitfield.h @@ -0,0 +1,27 @@ +#ifndef SLEEP_BITFIELD_H +#define SLEEP_BITFIELD_H +#include +#include + +#define SLEEP_BITFIELD_DATA_BYTES 1024u +#define SLEEP_BITFIELD_TREE_BYTES 2048u +#define SLEEP_BITFIELD_INDEX_BYTES 256u +#define SLEEP_BITFIELD_RESERVED_BYTES 256u + +typedef struct sleep_bitfield { + uint8_t data[SLEEP_BITFIELD_DATA_BYTES]; + uint8_t tree[SLEEP_BITFIELD_TREE_BYTES]; + uint8_t index[SLEEP_BITFIELD_INDEX_BYTES]; + uint8_t reserved[SLEEP_BITFIELD_RESERVED_BYTES]; +} sleep_bitfield_t; + +void sleep_bitfield_clear(sleep_bitfield_t *field); +int sleep_bitfield_get_data(const sleep_bitfield_t *field, size_t bit); +int sleep_bitfield_get_tree(const sleep_bitfield_t *field, size_t bit); +int sleep_bitfield_set_data(sleep_bitfield_t *field, size_t bit, int present); +int sleep_bitfield_set_tree(sleep_bitfield_t *field, size_t bit, int present); +void sleep_bitfield_reindex(sleep_bitfield_t *field); +size_t sleep_bitfield_next_data(const sleep_bitfield_t *field, size_t start, int present); +int sleep_bitfield_encode(const sleep_bitfield_t *field, uint8_t *out, size_t capacity); +int sleep_bitfield_decode(sleep_bitfield_t *field, const uint8_t *input, size_t length); +#endif diff --git a/extra/dat-dpi/inc/sleep_crypto.h b/extra/dat-dpi/inc/sleep_crypto.h new file mode 100644 index 0000000..34aa17c --- /dev/null +++ b/extra/dat-dpi/inc/sleep_crypto.h @@ -0,0 +1,29 @@ +#ifndef SLEEP_CRYPTO_H +#define SLEEP_CRYPTO_H + +#include +#include +#include "keys.h" +#define SLEEP_ROOT_RECORD_BYTES 48u + +int sleep_crypto_init(void); +int sleep_stream_xsalsa20_xor(uint8_t *out, const uint8_t *in, size_t length, + const uint8_t nonce[24], const uint8_t key[32], + uint64_t stream_offset); +int sleep_gen_keypair(sleep_keypair_t *kp); +int sleep_save_pubkey(const char *path, const uint8_t pubkey[SLEEP_PUB_KEY_BYTES]); +int sleep_load_pubkey(const char *path, uint8_t pubkey[SLEEP_PUB_KEY_BYTES]); +int sleep_save_keypair(const char *path, const sleep_keypair_t *keypair); +int sleep_load_keypair(const char *path, sleep_keypair_t *keypair); + +int sleep_blake2b(uint8_t out[SLEEP_BLAKE2B_BYTES], const uint8_t *in, size_t inlen); + +int sleep_sign(uint8_t sig[SLEEP_SIGNATURE_BYTES], const uint8_t *msg, size_t msglen, const sleep_keypair_t *kp); + +int sleep_verify(const uint8_t sig[SLEEP_SIGNATURE_BYTES], const uint8_t *msg, size_t msglen, const uint8_t pubkey[SLEEP_PUB_KEY_BYTES]); + +int sleep_sign_roots(uint8_t sig[SLEEP_SIGNATURE_BYTES], const uint8_t *roots, size_t nroots, const uint64_t *indices, const uint64_t *lengths, const sleep_keypair_t *kp); + +int sleep_verify_roots(const uint8_t sig[SLEEP_SIGNATURE_BYTES], const uint8_t *roots, size_t nroots, const uint64_t *indices, const uint64_t *lengths, const uint8_t pub[SLEEP_PUB_KEY_BYTES]); + +#endif /* SLEEP_CRYPTO_H */ diff --git a/extra/dat-dpi/inc/sleep_feed.h b/extra/dat-dpi/inc/sleep_feed.h new file mode 100644 index 0000000..0d36c11 --- /dev/null +++ b/extra/dat-dpi/inc/sleep_feed.h @@ -0,0 +1,43 @@ +#ifndef SLEEP_FEED_H +#define SLEEP_FEED_H +#include +#include +#include "keys.h" + +typedef struct sleep_feed { + char directory[1024]; + char name[64]; + uint8_t **blocks; + size_t *lengths; + uint8_t **proofs; + size_t *proof_lengths; + size_t length; + size_t capacity; + sleep_keypair_t keypair; + int writable; + int dirty; + int remote_only; + int persist_remote; +} sleep_feed_t; + +int sleep_feed_open(sleep_feed_t *feed, const char *directory, const char *name, + const sleep_keypair_t *keypair); +/* Opens an in-memory, read-only feed replica using only its public key. */ +int sleep_feed_open_remote(sleep_feed_t *feed, const char *directory, + const char *name, const uint8_t public_key[32]); +/* Merge verified cached remote blocks and persist subsequent received blocks. */ +int sleep_feed_load_remote(sleep_feed_t *feed, const uint8_t public_key[32]); +size_t sleep_feed_length(const sleep_feed_t *feed); +int sleep_feed_read(const sleep_feed_t *feed, size_t index, uint8_t **data, size_t *length); +int sleep_feed_append(sleep_feed_t *feed, const uint8_t *data, size_t length); +/* Keeps already verified remote blocks and proofs in this session's memory. */ +int sleep_feed_append_remote(sleep_feed_t *feed, size_t index, + const uint8_t *data, size_t length, + const uint8_t *proof, size_t proof_length); +int sleep_feed_truncate(sleep_feed_t *feed, size_t length); +int sleep_feed_has(const sleep_feed_t *feed, size_t index); +int sleep_feed_byte_offset(const sleep_feed_t *feed, size_t index, + uint64_t *offset); +int sleep_feed_flush(sleep_feed_t *feed); +void sleep_feed_close(sleep_feed_t *feed); +#endif diff --git a/extra/dat-dpi/inc/sleep_proof.h b/extra/dat-dpi/inc/sleep_proof.h new file mode 100644 index 0000000..b2c9921 --- /dev/null +++ b/extra/dat-dpi/inc/sleep_proof.h @@ -0,0 +1,43 @@ +#ifndef SLEEP_PROOF_H +#define SLEEP_PROOF_H +#include +#include +#include "sleep_feed.h" + +#define SLEEP_PROOF_HASH_BYTES 32u +#define SLEEP_PROOF_MAX_NODES 64u +typedef struct sleep_proof_node { + uint64_t index; + uint64_t length; + uint8_t hash[SLEEP_PROOF_HASH_BYTES]; +} sleep_proof_node_t; +typedef struct sleep_proof { + uint64_t block; + uint64_t block_length; + uint8_t block_hash[SLEEP_PROOF_HASH_BYTES]; + sleep_proof_node_t nodes[SLEEP_PROOF_MAX_NODES]; + size_t node_count; + sleep_proof_node_t roots[SLEEP_PROOF_MAX_NODES]; + size_t root_count; + uint8_t signature[64]; + int has_signature; +} sleep_proof_t; + +void sleep_proof_init(sleep_proof_t *proof, uint64_t block); +int sleep_proof_add_node(sleep_proof_t *proof, uint64_t index, + uint64_t length, const uint8_t hash[32]); +int sleep_proof_add_root(sleep_proof_t *proof, uint64_t index, + uint64_t length, const uint8_t hash[32]); +int sleep_proof_create(const sleep_feed_t *feed, uint64_t block, + sleep_proof_t *proof); +int sleep_proof_from_nodes(uint64_t block, const uint8_t *value, + size_t value_len, const sleep_proof_node_t *nodes, + size_t node_count, const uint8_t signature[64], + uint64_t feed_length, sleep_proof_t *proof); +int sleep_proof_encode(const sleep_proof_t *proof, uint8_t *out, + size_t capacity, size_t *written); +int sleep_proof_decode(sleep_proof_t *proof, const uint8_t *input, + size_t input_len); +int sleep_proof_verify(const sleep_proof_t *proof, const uint8_t *block, + size_t block_len, const uint8_t public_key[32]); +#endif diff --git a/extra/dat-dpi/inc/sleep_proto.h b/extra/dat-dpi/inc/sleep_proto.h new file mode 100644 index 0000000..9a11a35 --- /dev/null +++ b/extra/dat-dpi/inc/sleep_proto.h @@ -0,0 +1,26 @@ +#ifndef SLEEP_PROTO_H +#define SLEEP_PROTO_H +#include +#include +#define SLEEP_PROTO_KEY_BYTES 32u +typedef struct sleep_metadata_header { char type[32]; uint8_t content[32]; size_t content_len; } sleep_metadata_header_t; +enum sleep_entry_type { SLEEP_ENTRY_FILE=1, SLEEP_ENTRY_DIRECTORY=2, SLEEP_ENTRY_SYMLINK=3, SLEEP_ENTRY_DELETE=4 }; +typedef struct sleep_metadata_entry { + char path[1024]; + uint32_t type; + uint32_t mode; + uint64_t size; + uint64_t mtime; + uint64_t block_offset; + uint64_t block_count; + char linkname[1024]; +} sleep_metadata_entry_t; +size_t sleep_varint_size(uint64_t value); +int sleep_varint_encode(uint64_t value, uint8_t *out, size_t capacity, size_t *written); +int sleep_varint_decode(const uint8_t *input, size_t input_len, uint64_t *value, size_t *consumed); +int sleep_metadata_header_encode(const sleep_metadata_header_t *header, uint8_t *out, size_t capacity, size_t *written); +int sleep_metadata_header_decode(sleep_metadata_header_t *header, const uint8_t *input, size_t input_len); +int sleep_write_metadata_header(const char *path, const uint8_t content_pubkey[32]); +int sleep_metadata_entry_encode(const sleep_metadata_entry_t *entry, uint8_t *out, size_t capacity, size_t *written); +int sleep_metadata_entry_decode(sleep_metadata_entry_t *entry, const uint8_t *input, size_t input_len); +#endif diff --git a/extra/dat-dpi/inc/sleep_replication.h b/extra/dat-dpi/inc/sleep_replication.h new file mode 100644 index 0000000..35d8b49 --- /dev/null +++ b/extra/dat-dpi/inc/sleep_replication.h @@ -0,0 +1,44 @@ +#ifndef SLEEP_REPLICATION_H +#define SLEEP_REPLICATION_H +#include +#include +#include "sleep_feed.h" +#include "sleep_bitfield.h" +#include "sleep_proof.h" + +#define SLEEP_REPLICATION_QUEUE 128u +typedef struct sleep_replication_request { uint64_t index; uint64_t bytes; int hash; int nodes; } sleep_replication_request_t; +typedef struct sleep_replication { + sleep_feed_t *feed; + uint64_t channel; + uint64_t remote_length; + sleep_bitfield_t remote_bitfield; + sleep_replication_request_t requests[SLEEP_REPLICATION_QUEUE]; + size_t request_head, request_count; + int live, uploading, downloading; +} sleep_replication_t; + +int sleep_replication_init(sleep_replication_t *replication, + sleep_feed_t *feed, uint64_t channel); +int sleep_replication_have(sleep_replication_t *replication, + uint64_t start, uint64_t length); +int sleep_replication_unhave(sleep_replication_t *replication, + uint64_t start, uint64_t length); +int sleep_replication_want(sleep_replication_t *replication, + uint64_t start, uint64_t length); +int sleep_replication_request(sleep_replication_t *replication, + const sleep_replication_request_t *request); +int sleep_replication_next(sleep_replication_t *replication, + sleep_replication_request_t *request); +int sleep_replication_complete(sleep_replication_t *replication, + uint64_t index); +int sleep_replication_is_requested(const sleep_replication_t *replication, + uint64_t index); +int sleep_replication_unwant(sleep_replication_t *replication, + uint64_t start, uint64_t length); +void sleep_replication_clear_requests(sleep_replication_t *replication); +int sleep_replication_receive(sleep_replication_t *replication, + const uint8_t *block, size_t block_len, + const sleep_proof_t *proof, + const uint8_t feed_public_key[32]); +#endif diff --git a/extra/dat-dpi/inc/sleep_utp.h b/extra/dat-dpi/inc/sleep_utp.h new file mode 100644 index 0000000..860665d --- /dev/null +++ b/extra/dat-dpi/inc/sleep_utp.h @@ -0,0 +1,39 @@ +#ifndef SLEEP_UTP_H +#define SLEEP_UTP_H +#include +#include +#include "dat_socket.h" + +#define SLEEP_UTP_MTU 1200u +#define SLEEP_UTP_WINDOW 64u +enum sleep_utp_state { SLEEP_UTP_CLOSED, SLEEP_UTP_SYN_SENT, SLEEP_UTP_SYN_RECV, SLEEP_UTP_CONNECTED, SLEEP_UTP_FIN_SENT, SLEEP_UTP_RESET }; +enum sleep_utp_packet_type { SLEEP_UTP_DATA, SLEEP_UTP_FIN, SLEEP_UTP_STATE, SLEEP_UTP_RESET_PACKET, SLEEP_UTP_SYN }; +typedef struct sleep_utp_packet { uint8_t type, version; uint16_t connection_id; uint32_t timestamp, timestamp_diff, window_size; uint16_t sequence, acknowledgement; uint32_t selective_ack; int has_selective_ack; const uint8_t *payload; size_t payload_len; } sleep_utp_packet_t; +typedef struct sleep_utp_slot { uint16_t sequence; uint8_t data[SLEEP_UTP_MTU]; size_t length; uint64_t sent_at; unsigned retries; int used; } sleep_utp_slot_t; +typedef struct sleep_utp { + dat_socket_t socket; + struct sockaddr_storage remote; + socklen_t remote_len; + uint16_t send_id, receive_id, sequence, acknowledgement; + uint32_t receive_window, send_window; + enum sleep_utp_state state; + uint64_t retransmit_at; + unsigned syn_retries; + uint32_t congestion_window, slow_start_threshold; + uint32_t round_trip_time, round_trip_variance; + size_t bytes_in_flight; + sleep_utp_slot_t unacked[SLEEP_UTP_WINDOW]; + sleep_utp_slot_t reorder[SLEEP_UTP_WINDOW]; + void *context; + void (*on_data)(struct sleep_utp *, const uint8_t *, size_t); +} sleep_utp_t; +int sleep_utp_init(sleep_utp_t *utp, dat_socket_t udp_socket); +int sleep_utp_connect(sleep_utp_t *utp, const struct sockaddr *remote, socklen_t remote_len); +int sleep_utp_accept(sleep_utp_t *utp, const sleep_utp_packet_t *syn, const struct sockaddr *remote, socklen_t remote_len); +int sleep_utp_packet_encode(const sleep_utp_packet_t *packet, uint8_t *out, size_t capacity, size_t *written); +int sleep_utp_packet_decode(sleep_utp_packet_t *packet, const uint8_t *input, size_t input_len); +int sleep_utp_input(sleep_utp_t *utp, const uint8_t *packet, size_t packet_len); +int sleep_utp_send(sleep_utp_t *utp, const uint8_t *data, size_t length); +int sleep_utp_tick(sleep_utp_t *utp, uint64_t now_ms); +int sleep_utp_close(sleep_utp_t *utp); +#endif diff --git a/extra/dat-dpi/inc/sleep_verify.h b/extra/dat-dpi/inc/sleep_verify.h new file mode 100644 index 0000000..7be1419 --- /dev/null +++ b/extra/dat-dpi/inc/sleep_verify.h @@ -0,0 +1,6 @@ +#ifndef SLEEP_VERIFY_H +#define SLEEP_VERIFY_H + +int sleep_verify_dat(const char *dat_dir); + +#endif \ No newline at end of file diff --git a/extra/dat-dpi/inc/sleep_writer.h b/extra/dat-dpi/inc/sleep_writer.h new file mode 100644 index 0000000..474bfab --- /dev/null +++ b/extra/dat-dpi/inc/sleep_writer.h @@ -0,0 +1,18 @@ +#ifndef SLEEP_WRITER_H +#define SLEEP_WRITER_H +#include +#include +#include "sleep.h" +#ifdef __cplusplus +extern "C" { +#endif +int sleep_write_feed(const char *feed_dir, + const char *feed_name, + const uint8_t *const *blocks, + const size_t *lengths, + size_t nblocks, + const sleep_keypair_t *keypair); +#ifdef __cplusplus +} +#endif +#endif diff --git a/extra/dat-dpi/inc/win_compat.h b/extra/dat-dpi/inc/win_compat.h new file mode 100644 index 0000000..8ec6c94 --- /dev/null +++ b/extra/dat-dpi/inc/win_compat.h @@ -0,0 +1,80 @@ +#ifndef DATGPT_WIN_COMPAT_H +#define DATGPT_WIN_COMPAT_H + +/* Small POSIX spellings used by the shared C sources on native Windows. */ +#ifdef _WIN32 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef mkdir +#define mkdir(path, mode) _mkdir(path) +#endif +#ifndef fsync +#define fsync(fd) _commit(fd) +#endif +#ifndef getpid +#define getpid _getpid +#endif +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#endif +#ifndef O_NOFOLLOW +#define O_NOFOLLOW 0 +#endif +#ifndef EKEYREJECTED +#define EKEYREJECTED EACCES +#endif +#ifndef S_ISLNK +#define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK) +#endif +#ifndef S_IFLNK +#define S_IFLNK 0120000 +#endif +#ifndef fchmod +#define fchmod(fd, mode) ((void)(fd), (void)(mode), 0) +#endif +#ifndef strcasecmp +#define strcasecmp _stricmp +#endif +#ifndef strncasecmp +#define strncasecmp _strnicmp +#endif +static inline char *dat_win_getenv_compat(const char *name) +{ + char *value = getenv(name); + if (!value && name && !strcmp(name, "HOME")) value = getenv("USERPROFILE"); + return value; +} +#define getenv dat_win_getenv_compat +static inline int dat_win_lstat(const char *path, struct stat *info) +{ + DWORD attributes; + if (stat(path, info)) return -1; + attributes = GetFileAttributesA(path); + if (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_REPARSE_POINT)) + info->st_mode = (info->st_mode & ~S_IFMT) | S_IFLNK; + return 0; +} +#ifndef lstat +#define lstat dat_win_lstat +#endif +static inline int dat_win_readlink(const char *path, char *target, size_t capacity) +{ (void)path; (void)target; (void)capacity; errno = ENOTSUP; return -1; } +static inline int dat_win_symlink(const char *target, const char *path) +{ (void)target; (void)path; errno = ENOTSUP; return -1; } +#ifndef readlink +#define readlink dat_win_readlink +#endif +#ifndef symlink +#define symlink dat_win_symlink +#endif +#endif + +#endif diff --git a/extra/dat-dpi/inc/win_dirent.h b/extra/dat-dpi/inc/win_dirent.h new file mode 100644 index 0000000..de0eb3c --- /dev/null +++ b/extra/dat-dpi/inc/win_dirent.h @@ -0,0 +1,53 @@ +#ifndef DATGPT_WIN_DIRENT_H +#define DATGPT_WIN_DIRENT_H +#ifdef _WIN32 +#include +#include +#include +#include +#include +#define NAME_MAX MAX_PATH +typedef struct dirent { char d_name[MAX_PATH]; } struct_dirent_t; +typedef struct dat_win_dir { + HANDLE handle; + WIN32_FIND_DATAA data; + int first; + struct dirent entry; +} DIR; +static DIR *dat_opendir(const char *path) +{ + DIR *dir = (DIR *)calloc(1, sizeof(*dir)); + char pattern[MAX_PATH * 2]; + int n; + if (!dir) return NULL; + n = snprintf(pattern, sizeof(pattern), "%s\\*", path); + if (n < 0 || (size_t)n >= sizeof(pattern)) { free(dir); errno = ENAMETOOLONG; return NULL; } + dir->handle = FindFirstFileA(pattern, &dir->data); + if (dir->handle == INVALID_HANDLE_VALUE) { free(dir); errno = ENOENT; return NULL; } + dir->first = 1; + return dir; +} +static struct dirent *dat_readdir(DIR *dir) +{ + if (!dir) return NULL; + if (dir->first) dir->first = 0; + else if (!FindNextFileA(dir->handle, &dir->data)) return NULL; + strncpy(dir->entry.d_name, dir->data.cFileName, sizeof(dir->entry.d_name) - 1); + dir->entry.d_name[sizeof(dir->entry.d_name) - 1] = 0; + return &dir->entry; +} +static int dat_closedir(DIR *dir) +{ + int rc; + if (!dir) { errno = EINVAL; return -1; } + rc = FindClose(dir->handle) ? 0 : -1; + free(dir); + return rc; +} +#define opendir dat_opendir +#define readdir dat_readdir +#define closedir dat_closedir +#else +#include +#endif +#endif diff --git a/extra/dat-dpi/inc/win_fnmatch.h b/extra/dat-dpi/inc/win_fnmatch.h new file mode 100644 index 0000000..a65dd15 --- /dev/null +++ b/extra/dat-dpi/inc/win_fnmatch.h @@ -0,0 +1,24 @@ +#ifndef DATGPT_WIN_FNMATCH_H +#define DATGPT_WIN_FNMATCH_H +#ifdef _WIN32 +static int dat_win_fnmatch(const char *pattern, const char *text) +{ + while (*pattern) { + if (*pattern == '*') { + while (*pattern == '*') pattern++; + if (!*pattern) return 0; + do { if (dat_win_fnmatch(pattern, text) == 0) return 0; } while (*text++); + return 1; + } + if (!*text) return 1; + if (*pattern == '?') { pattern++; text++; continue; } + if (*pattern != *text) return 1; + pattern++; text++; + } + return *text ? 1 : 0; +} +#define fnmatch(pattern, text, flags) ((void)(flags), dat_win_fnmatch((pattern), (text))) +#else +#include +#endif +#endif diff --git a/extra/dat-dpi/src/base32768.c b/extra/dat-dpi/src/base32768.c new file mode 100644 index 0000000..12aa907 --- /dev/null +++ b/extra/dat-dpi/src/base32768.c @@ -0,0 +1,231 @@ +/* + * Base32768 alphabet and bit-packing ported from qntm/base32768. + * Upstream implementation: https://github.com/qntm/base32768 + * Copyright (c) 2021 qntm. Used under the MIT license; see + * third_party/base32768.LICENSE. + */ +#include "base32768.h" +#include +#include + +typedef struct base_range { + uint32_t first, last, offset; +} base_range_t; + +#define BASE32768_MAX_RANGES 128u +static base_range_t repertoire[2][BASE32768_MAX_RANGES]; +static size_t repertoire_count[2]; +static uint32_t repertoire_size[2]; +static int repertoire_ready; +static int repertoire_error; + +/* The pairs encode inclusive ranges. Keep in sync with upstream's pairStrings. */ +static const char *const pair_strings[2] = { + "ҠҿԀԟڀڿݠޟ߀ߟကဟႠႿᄀᅟᆀᆟᇠሿበቿዠዿጠጿᎠá�Ÿá� á™Ÿáš á›Ÿáž€ážŸá  á¡Ÿá£€á£Ÿá¦€á¦Ÿá§ á§¿á¨ á¨¿á¯€á¯Ÿá°€á°Ÿá´€á´Ÿâ‡ â‡¿â‹€â‹Ÿâ�€â�Ÿâ�€â�Ÿâ”€â�Ÿâž€âž¿â €â¥¿â¦ â¦¿â¨ â©Ÿâª€âª¿â« â­Ÿâ°€â°Ÿâ²€â³Ÿâ´€â´Ÿâµ€âµŸâº â»Ÿã‡€ã‡Ÿã�€ä¶Ÿä·€é¾¿ê€€ê‘¿ê’ ê’¿ê”€ê—¿ê™€ê™Ÿêš ê›Ÿêœ€ê�Ÿêž€êžŸê¡€ê¡Ÿ", + "ƀƟɀʟ" +}; + +static int utf8_next(const unsigned char *s, size_t length, size_t *offset, + uint32_t *codepoint) +{ + unsigned char a, b, c, d; + if (*offset >= length) return -1; + a = s[(*offset)++]; + if (a < 0x80) { *codepoint = a; return 0; } + if (a >= 0xc2 && a <= 0xdf) { + if (*offset >= length) return -1; + b = s[(*offset)++]; + if ((b & 0xc0) != 0x80) return -1; + *codepoint = ((uint32_t)(a & 0x1f) << 6) | (b & 0x3f); + return 0; + } + if (a >= 0xe0 && a <= 0xef) { + if (length - *offset < 2) return -1; + b = s[(*offset)++]; c = s[(*offset)++]; + if ((b & 0xc0) != 0x80 || (c & 0xc0) != 0x80 || + (a == 0xe0 && b < 0xa0) || (a == 0xed && b >= 0xa0)) return -1; + *codepoint = ((uint32_t)(a & 0x0f) << 12) | + ((uint32_t)(b & 0x3f) << 6) | (c & 0x3f); + return 0; + } + if (a >= 0xf0 && a <= 0xf4) { + if (length - *offset < 3) return -1; + b = s[(*offset)++]; c = s[(*offset)++]; d = s[(*offset)++]; + if ((b & 0xc0) != 0x80 || (c & 0xc0) != 0x80 || (d & 0xc0) != 0x80 || + (a == 0xf0 && b < 0x90) || (a == 0xf4 && b >= 0x90)) return -1; + *codepoint = ((uint32_t)(a & 7) << 18) | ((uint32_t)(b & 0x3f) << 12) | + ((uint32_t)(c & 0x3f) << 6) | (d & 0x3f); + return 0; + } + return -1; +} + +static size_t utf8_put(uint32_t cp, char *out) +{ + if (cp < 0x80) { out[0] = (char)cp; return 1; } + if (cp < 0x800) { + out[0] = (char)(0xc0 | (cp >> 6)); + out[1] = (char)(0x80 | (cp & 0x3f)); return 2; + } + if (cp < 0x10000) { + out[0] = (char)(0xe0 | (cp >> 12)); + out[1] = (char)(0x80 | ((cp >> 6) & 0x3f)); + out[2] = (char)(0x80 | (cp & 0x3f)); return 3; + } + out[0] = (char)(0xf0 | (cp >> 18)); + out[1] = (char)(0x80 | ((cp >> 12) & 0x3f)); + out[2] = (char)(0x80 | ((cp >> 6) & 0x3f)); + out[3] = (char)(0x80 | (cp & 0x3f)); return 4; +} + +static int initialize_repertoire(void) +{ + size_t row; + if (repertoire_ready) return repertoire_error ? -1 : 0; + repertoire_ready = 1; + for (row = 0; row < 2; row++) { + const char *text = pair_strings[row]; + size_t length = strlen(text), offset = 0; + uint32_t points[BASE32768_MAX_RANGES * 2u]; + size_t point_count = 0, i; + while (offset < length) { + if (point_count >= sizeof(points) / sizeof(points[0]) || + utf8_next((const unsigned char *)text, length, &offset, + &points[point_count])) { + repertoire_error = 1; return -1; + } + point_count++; + } + if (point_count % 2 || point_count / 2 > BASE32768_MAX_RANGES) { + repertoire_error = 1; return -1; + } + for (i = 0; i < point_count; i += 2) { + base_range_t *range = &repertoire[row][repertoire_count[row]++]; + if (points[i] > points[i + 1]) { repertoire_error = 1; return -1; } + range->first = points[i]; range->last = points[i + 1]; + range->offset = repertoire_size[row]; + repertoire_size[row] += points[i + 1] - points[i] + 1; + } + } + if (repertoire_size[0] != 32768u || repertoire_size[1] != 128u) { + repertoire_error = 1; return -1; + } + return 0; +} + +static int index_to_codepoint(unsigned bits, uint32_t index, uint32_t *out) +{ + size_t row = bits == 15 ? 0 : 1, i; + if (initialize_repertoire()) return -1; + for (i = 0; i < repertoire_count[row]; i++) { + const base_range_t *range = &repertoire[row][i]; + uint32_t count = range->last - range->first + 1; + if (index >= range->offset && index - range->offset < count) { + *out = range->first + index - range->offset; + return 0; + } + } + return -1; +} + +int base32768_codepoint_at(unsigned bits, uint32_t index, uint32_t *codepoint) +{ + if (!codepoint || (bits == 15 && index >= 32768u) || + (bits == 7 && index >= 128u) || (bits != 15 && bits != 7)) return -1; + return index_to_codepoint(bits, index, codepoint); +} + +static int codepoint_to_index(uint32_t cp, unsigned *bits, uint32_t *index) +{ + size_t row, i; + if (initialize_repertoire()) return -1; + for (row = 0; row < 2; row++) for (i = 0; i < repertoire_count[row]; i++) { + const base_range_t *range = &repertoire[row][i]; + if (cp >= range->first && cp <= range->last) { + *bits = row == 0 ? 15u : 7u; + *index = range->offset + cp - range->first; + return 0; + } + } + return -1; +} + +int base32768_encode(const uint8_t *input, size_t input_length, + char *output, size_t output_capacity) +{ + size_t i, used = 0; + uint32_t value = 0; + unsigned value_bits = 0; + if ((!input && input_length) || !output || !output_capacity) { errno = EINVAL; return -1; } + if (initialize_repertoire()) { errno = EPROTO; return -1; } + for (i = 0; i < input_length; i++) { + int bit; + for (bit = 7; bit >= 0; bit--) { + uint32_t cp; + char encoded[4]; + size_t n; + value = (value << 1) | ((input[i] >> bit) & 1u); + if (++value_bits == 15) { + if (index_to_codepoint(15, value, &cp)) { errno = EPROTO; return -1; } + n = utf8_put(cp, encoded); + if (used + n >= output_capacity) { errno = ENOSPC; return -1; } + memcpy(output + used, encoded, n); used += n; + value = 0; value_bits = 0; + } + } + } + if (value_bits) { + unsigned target_bits = value_bits >= 8 ? 15u : 7u; + uint32_t cp; + char encoded[4]; + size_t n; + while (value_bits < target_bits) { + value = (value << 1) | 1u; + value_bits++; + } + if (index_to_codepoint(target_bits, value, &cp)) { errno = EPROTO; return -1; } + n = utf8_put(cp, encoded); + if (used + n >= output_capacity) { errno = ENOSPC; return -1; } + memcpy(output + used, encoded, n); used += n; + } + output[used] = '\0'; + return 0; +} + +int base32768_decode(const char *input, uint8_t *output, + size_t output_capacity, size_t *output_length) +{ + size_t length, offset = 0, used = 0, i = 0, total_characters = 0, scan = 0; + unsigned pending_bits = 0; + uint32_t pending = 0; + if (!input || (!output && output_capacity) || !output_length) { errno = EINVAL; return -1; } + *output_length = 0; + length = strlen(input); + if (initialize_repertoire()) { errno = EPROTO; return -1; } + while (scan < length) { + uint32_t ignored; + if (utf8_next((const unsigned char *)input, length, &scan, &ignored)) { + errno = EILSEQ; return -1; + } + total_characters++; + } + while (offset < length) { + uint32_t cp, value; + unsigned bits, bit; + if (utf8_next((const unsigned char *)input, length, &offset, &cp) || + codepoint_to_index(cp, &bits, &value)) { errno = EILSEQ; return -1; } + if (bits == 7 && i + 1 != total_characters) { errno = EINVAL; return -1; } + for (bit = bits; bit > 0; bit--) { + pending = (pending << 1) | ((value >> (bit - 1)) & 1u); + if (++pending_bits == 8) { + if (used >= output_capacity) { errno = ENOSPC; return -1; } + output[used++] = (uint8_t)pending; + pending = 0; pending_bits = 0; + } + } + i++; + } + if (pending != ((1u << pending_bits) - 1u)) { errno = EINVAL; return -1; } + *output_length = used; + return 0; +} diff --git a/extra/dat-dpi/src/cable.c b/extra/dat-dpi/src/cable.c new file mode 100644 index 0000000..8fa36ef --- /dev/null +++ b/extra/dat-dpi/src/cable.c @@ -0,0 +1,200 @@ +#define _POSIX_C_SOURCE 200809L +#include "cable.h" +#include "sleep_crypto.h" +#include +#include +#include +#include +#include +#include +#include +#include + +static int hex_value(char c) +{ + if(c>='0'&&c<='9')return c-'0'; + if(c>='a'&&c<='f')return c-'a'+10; + if(c>='A'&&c<='F')return c-'A'+10; + return -1; +} + +int cable_address_parse(const char *address,uint8_t key[32],enum cable_address_scheme *scheme) +{ + const char *hex=address; enum cable_address_scheme parsed=0; size_t i; + if(!address||!key){errno=EINVAL;return -1;} + if(!strncmp(hex,"cabal://",8)){parsed=CABLE_ADDRESS_CABAL;hex+=8;} + else if(!strncmp(hex,"cable://",8)){parsed=CABLE_ADDRESS_CABLE;hex+=8;} + else if(!strncmp(hex,"hyper://",8)){parsed=CABLE_ADDRESS_HYPER;hex+=8;} + if(strlen(hex)!=64){errno=EINVAL;return -1;} + for(i=0;i<32;i++){int hi=hex_value(hex[i*2]),lo=hex_value(hex[i*2+1]);if(hi<0||lo<0){errno=EINVAL;return -1;}key[i]=(uint8_t)((hi<<4)|lo);} + if(scheme)*scheme=parsed?parsed:CABLE_ADDRESS_CABAL; + return 0; +} + +int cable_address_format(const uint8_t key[32],enum cable_address_scheme scheme,char *out,size_t cap) +{ + static const char digits[]="0123456789abcdef";const char *prefix;size_t i; + if(!key||!out){errno=EINVAL;return -1;} + switch(scheme){case CABLE_ADDRESS_CABAL:prefix="cabal://";break;case CABLE_ADDRESS_CABLE:prefix="cable://";break;case CABLE_ADDRESS_HYPER:prefix="hyper://";break;default:errno=EINVAL;return -1;} + if(cap<73){errno=ENOSPC;return -1;} + memcpy(out,prefix,8);for(i=0;i<32;i++){out[8+i*2]=digits[key[i]>>4];out[9+i*2]=digits[key[i]&15];}out[72]='\0';return 0; +} + +int cable_room_address_parse(const char *address,cable_room_address_t *room) +{ + const char *query; char key_text[73]; size_t key_len; + if(!address||!room){errno=EINVAL;return -1;} + memset(room,0,sizeof(*room)); + query=strchr(address,'?'); + key_len=query?(size_t)(query-address):strlen(address); + if(key_len>=sizeof(key_text)){errno=EINVAL;return -1;} + memcpy(key_text,address,key_len);key_text[key_len]='\0'; + if(cable_address_parse(key_text,room->key,&room->scheme))return -1; + if(query){ + static const char prefix[]="?admin="; + if(strncmp(query,prefix,sizeof(prefix)-1)){errno=EINVAL;return -1;} + if(cable_address_parse(query+sizeof(prefix)-1,room->admin_key,NULL))return -1; + room->has_admin=1; + } + return 0; +} + +int cable_room_address_format(const cable_room_address_t *room,enum cable_address_scheme scheme,char *out,size_t cap) +{ + char key[73],admin[73]; + if(!room||!out){errno=EINVAL;return -1;} + if(cable_address_format(room->key,scheme,key,sizeof(key)))return -1; + if(!room->has_admin){if(capadmin_key,CABLE_ADDRESS_CABAL,admin,sizeof(admin)))return -1; + if(cap<8+64+7+64+1){errno=ENOSPC;return -1;} + memcpy(out,key,72);memcpy(out+72,"?admin=",7);memcpy(out+79,admin+8,64);out[143]='\0';return 0; +} + +int cable_user_address_format(const uint8_t key[32],enum cable_address_scheme scheme,char *out,size_t cap) +{ + return cable_address_format(key,scheme,out,cap); +} + +int cable_user_identity_path(const char *home,const uint8_t room_key[32],char *out,size_t cap) +{ + static const char digits[]="0123456789abcdef"; + char base[1024],rooms[1056],roomdir[1120];size_t i;int n; + if(!home||!home[0]||!room_key||!out){errno=EINVAL;return -1;} + n=snprintf(base,sizeof(base),"%s/.cable",home); + if(n<0||(size_t)n>=sizeof(base)){errno=ENAMETOOLONG;return -1;} + if(mkdir(base,0700)<0&&errno!=EEXIST)return -1; + n=snprintf(rooms,sizeof(rooms),"%s/rooms",base); + if(n<0||(size_t)n>=sizeof(rooms)){errno=ENAMETOOLONG;return -1;} + if(mkdir(rooms,0700)<0&&errno!=EEXIST)return -1; + if(strlen(rooms)+1+64>=sizeof(roomdir)){errno=ENAMETOOLONG;return -1;} + strcpy(roomdir,rooms);strcat(roomdir,"/"); + for(i=0;i<32;i++){roomdir[strlen(rooms)+1+i*2]=digits[room_key[i]>>4];roomdir[strlen(rooms)+2+i*2]=digits[room_key[i]&15];} + roomdir[strlen(rooms)+65]='\0'; + if(mkdir(roomdir,0700)<0&&errno!=EEXIST)return -1; + n=snprintf(out,cap,"%s/user-feed.key",roomdir); + if(n<0||(size_t)n>=cap){errno=ENAMETOOLONG;return -1;} + return 0; +} + +int cable_room_create(const char *home,uint8_t room_key[32]) +{ + sleep_keypair_t room; char feed_path[1200], secret_path[1300]; + size_t offset=0; int fd=-1, saved=0, rc=-1; ssize_t wrote; + if(!home||!room_key){errno=EINVAL;return -1;} + secret_path[0]='\0'; + memset(&room,0,sizeof(room)); + if(sleep_crypto_init()||sleep_gen_keypair(&room))goto done; + if(cable_user_identity_path(home,room.pubkey,feed_path,sizeof(feed_path)))goto done; + { + static const char suffix[]="/user-feed.key"; + size_t n=strlen(feed_path); + if(n= (int)sizeof(secret_path)) { errno=ENAMETOOLONG; goto done; } + fd=open(secret_path,O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOFOLLOW,0600); + if(fd<0)goto done; + while(offset=0)close(fd); + if(rc&&secret_path[0])unlink(secret_path); + sodium_memzero(&room,sizeof(room)); + if(rc)errno=saved; + return rc; +} + +int cable_user_identity_load_or_create(const char *path,sleep_keypair_t *identity) +{ + int fd; struct stat st; char parent[1024]; char *slash; ssize_t n; uint8_t check[32]; + if(!path||!identity||strlen(path)>=sizeof(parent)){errno=EINVAL;return -1;} + if(sleep_crypto_init())return -1; + strcpy(parent,path);slash=strrchr(parent,'/'); + if(slash){*slash='\0';if(parent[0]&&mkdir(parent,0700)<0&&errno!=EEXIST)return -1;} + fd=open(path,O_RDONLY|O_CLOEXEC|O_NOFOLLOW); + if(fd>=0){ + if(fstat(fd,&st)||!S_ISREG(st.st_mode)||st.st_size!=crypto_sign_SECRETKEYBYTES){close(fd);errno=EINVAL;return -1;} + if((st.st_mode&077)!=0&&fchmod(fd,0600)){int saved=errno;close(fd);errno=saved;return -1;} + n=read(fd,identity->seckey,crypto_sign_SECRETKEYBYTES);close(fd); + if(n!=crypto_sign_SECRETKEYBYTES||crypto_sign_ed25519_sk_to_pk(identity->pubkey,identity->seckey)!=0){errno=EIO;return -1;} + if(crypto_sign_ed25519_sk_to_pk(check,identity->seckey)!=0||sodium_memcmp(check,identity->pubkey,32)){errno=EINVAL;return -1;} + return 0; + } + if(errno!=ENOENT)return -1; + if(sleep_gen_keypair(identity))return -1; + fd=open(path,O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOFOLLOW,0600); + if(fd<0){if(errno==EEXIST)return cable_user_identity_load_or_create(path,identity);return -1;} + n=write(fd,identity->seckey,crypto_sign_SECRETKEYBYTES); + if(n!=crypto_sign_SECRETKEYBYTES){int saved=errno?errno:EIO;close(fd);unlink(path);errno=saved;return -1;} + if(fsync(fd)){int saved=errno;close(fd);unlink(path);errno=saved;return -1;} + if(close(fd)){int saved=errno;unlink(path);errno=saved;return -1;} + return 0; +} + +static void put64be(uint8_t *p,uint64_t v){int i;for(i=0;i<8;i++)p[i]=(uint8_t)(v>>(56-8*i));} +static uint64_t get64be(const uint8_t *p){uint64_t v=0;int i;for(i=0;i<8;i++)v=(v<<8)|p[i];return v;} + +int cable_chat_message_encode(const cable_chat_message_t *m,uint8_t *out,size_t cap,size_t *written) +{ + size_t total; + if(!m||!out||!written||m->text_length>CABLE_CHAT_TEXT_BYTES||strlen(m->text)!=m->text_length){errno=EINVAL;return -1;} + total=14+m->text_length;if(captimestamp_ms);out[12]=(uint8_t)(m->text_length>>8);out[13]=(uint8_t)m->text_length; + memcpy(out+14,m->text,m->text_length);*written=total;return 0; +} + +int cable_chat_message_decode(cable_chat_message_t *m,const uint8_t *in,size_t n) +{ + size_t text_len; + if(!m||!in||n<14||memcmp(in,"CBL1",4)){errno=EPROTO;return -1;} + text_len=((size_t)in[12]<<8)|in[13];if(text_len>CABLE_CHAT_TEXT_BYTES||n!=14+text_len||memchr(in+14,0,text_len)){errno=EPROTO;return -1;} + memset(m,0,sizeof(*m));m->timestamp_ms=get64be(in+4);memcpy(m->text,in+14,text_len);m->text_length=text_len;return 0; +} + +int cable_chat_seal(const uint8_t recipient[32],const uint8_t *message,size_t n,uint8_t *out,size_t cap,size_t *written) +{ + uint8_t curve[crypto_box_PUBLICKEYBYTES]; + if(!recipient||(!message&&n)||!out||!written){errno=EINVAL;return -1;} + if(cappubkey)!=0||crypto_sign_ed25519_sk_to_curve25519(curve_sk,identity->seckey)!=0){errno=EINVAL;return -1;} + if(crypto_box_seal_open(out,sealed,(unsigned long long)n,curve_pk,curve_sk)!=0){errno=EACCES;return -1;} + sodium_memzero(curve_sk,sizeof(curve_sk));*written=plain;return 0; +} diff --git a/extra/dat-dpi/src/cable_feed.c b/extra/dat-dpi/src/cable_feed.c new file mode 100644 index 0000000..d5c2541 --- /dev/null +++ b/extra/dat-dpi/src/cable_feed.c @@ -0,0 +1,205 @@ +#define _POSIX_C_SOURCE 200809L +#include "cable.h" +#include "cable_room.h" +#include "sleep_crypto.h" +#include +#include +#include +#include +#include +#include +#include + +static int ensure_link(const char *directory, const char *link_name, + const char *target) +{ + char path[1400], observed[1024]; + ssize_t length; + struct stat info; + int n = snprintf(path, sizeof(path), "%s/%s", directory, link_name); + if (n < 0 || (size_t)n >= sizeof(path)) { errno = ENAMETOOLONG; return -1; } + if (symlink(target, path) == 0) return 0; + if (errno != EEXIST) return -1; + if (lstat(path, &info) || !S_ISLNK(info.st_mode)) { errno = EEXIST; return -1; } + length = readlink(path, observed, sizeof(observed) - 1); + if (length < 0) return -1; + observed[length] = '\0'; + if (strcmp(observed, target)) { errno = EEXIST; return -1; } + return 0; +} + +static int ensure_localname(const char *directory) +{ + char path[1400], contents[16]; + FILE *file; + size_t length; + int n = snprintf(path, sizeof(path), "%s/localname", directory); + if (n < 0 || (size_t)n >= sizeof(path)) { errno = ENAMETOOLONG; return -1; } + file = fopen(path, "rb"); + if (file) { + length = fread(contents, 1, sizeof(contents), file); + if (ferror(file) || fclose(file)) return -1; + if (length != 5 || memcmp(contents, "local", 5)) { errno = EEXIST; return -1; } + return 0; + } + if (errno != ENOENT) return -1; + file = fopen(path, "wbx"); + if (!file) return -1; + if (fwrite("local", 1, 5, file) != 5 || fflush(file)) { + int saved = errno ? errno : EIO; + fclose(file); errno = saved; return -1; + } + if (fclose(file)) return -1; + return 0; +} + +int cable_user_feed_open(const char *home, const uint8_t room_key[32], + sleep_keypair_t *identity, sleep_feed_t *feed) +{ + char identity_path[1200], room_dir[1200], feed_dir[1240]; + char *slash; + int n; + if (!home || !room_key || !identity || !feed) { errno = EINVAL; return -1; } + if (cable_user_identity_path(home, room_key, identity_path, + sizeof(identity_path)) || + cable_user_identity_load_or_create(identity_path, identity)) return -1; + slash = strrchr(identity_path, '/'); + if (!slash) { errno = EINVAL; goto fail_identity; } + *slash = '\0'; + n = snprintf(room_dir, sizeof(room_dir), "%s/0", identity_path); + if (n < 0 || (size_t)n >= sizeof(room_dir)) { errno = ENAMETOOLONG; goto fail_identity; } + if (mkdir(room_dir, 0700) < 0 && errno != EEXIST) goto fail_identity; + n = snprintf(feed_dir, sizeof(feed_dir), "%s", room_dir); + if (n < 0 || (size_t)n >= sizeof(feed_dir)) { errno = ENAMETOOLONG; goto fail_identity; } + /* Kappa Core 3 / multifeed 3 stores the local writer in room/0 and + * addresses its storage by the bare Hypercore names. Keep the SLEEP + * filename convention as symlinked aliases over the same Hypercore files. */ + if (ensure_link(feed_dir, "feed.tree", "tree") || + ensure_link(feed_dir, "feed.signatures", "signatures") || + ensure_link(feed_dir, "feed.bitfield", "bitfield") || + ensure_link(feed_dir, "feed.data", "data") || + ensure_link(feed_dir, "feed.key", "key") || + ensure_link(feed_dir, "secret_key", "../user-feed.key") || + ensure_localname(feed_dir)) goto fail_identity; + if (sleep_feed_open(feed, feed_dir, "feed", identity)) goto fail_identity; + return 0; +fail_identity: + sodium_memzero(identity, sizeof(*identity)); + return -1; +} + +int cable_user_feed_append(const char *home, const uint8_t room_key[32], + const uint8_t *record, size_t record_length, + uint8_t user_key[32]) +{ + sleep_keypair_t identity; + sleep_feed_t feed; + int rc = -1; + memset(&identity, 0, sizeof(identity)); + memset(&feed, 0, sizeof(feed)); + if (!record || !record_length || !user_key) { errno = EINVAL; return -1; } + if (cable_user_feed_open(home, room_key, &identity, &feed)) return -1; + if (sleep_feed_append(&feed, record, record_length) || sleep_feed_flush(&feed)) goto done; + memcpy(user_key, identity.pubkey, sizeof(identity.pubkey)); + rc = 0; +done: + sleep_feed_close(&feed); + sodium_memzero(&identity, sizeof(identity)); + return rc; +} + +static int append_json_text(char *out, size_t capacity, size_t *offset, + const char *text) +{ + const unsigned char *p = (const unsigned char *)text; + if (*offset >= capacity) { errno = ENOSPC; return -1; } + out[(*offset)++] = '"'; + for (; *p; p++) { + const char *escaped = NULL; + char control[7]; + switch (*p) { + case '"': escaped = "\\\""; break; + case '\\': escaped = "\\\\"; break; + case '\b': escaped = "\\b"; break; + case '\f': escaped = "\\f"; break; + case '\n': escaped = "\\n"; break; + case '\r': escaped = "\\r"; break; + case '\t': escaped = "\\t"; break; + default: break; + } + if (*p < 0x20 && !escaped) { + snprintf(control, sizeof(control), "\\u%04x", *p); + escaped = control; + } + if (escaped) { + size_t n = strlen(escaped); + if (n > capacity - *offset) { errno = ENOSPC; return -1; } + memcpy(out + *offset, escaped, n); *offset += n; + } else { + if (*offset >= capacity) { errno = ENOSPC; return -1; } + out[(*offset)++] = (char)*p; + } + } + if (*offset >= capacity) { errno = ENOSPC; return -1; } + out[(*offset)++] = '"'; + return 0; +} + +int cable_user_feed_publish(const char *home, const uint8_t room_key[32], + const char *record_type, const char *nick, + const char *channel, const char *text, + uint8_t user_key[32]) +{ + char *record; + size_t capacity, offset = 0; + struct timespec now; + unsigned long long timestamp; + int n, rc = -1; + if (!home || !room_key || !record_type || !user_key || + (strcmp(record_type, "about") && strcmp(record_type, "chat/text")) || + (!strcmp(record_type, "about") && !nick) || + (!strcmp(record_type, "chat/text") && (!channel || !text))) { + errno = EINVAL; return -1; + } + if (strlen(record_type) > 16 || (nick && strlen(nick) > CABLE_CHAT_TEXT_BYTES) || + (channel && strlen(channel) > CABLE_ROOM_CHANNEL_BYTES) || + (text && strlen(text) > CABLE_CHAT_TEXT_BYTES)) { errno = EMSGSIZE; return -1; } + capacity = 512 + strlen(record_type) * 6 + (nick ? strlen(nick) * 6 : 0) + + (channel ? strlen(channel) * 6 : 0) + (text ? strlen(text) * 6 : 0); + record = malloc(capacity); + if (!record) return -1; + if (clock_gettime(CLOCK_REALTIME, &now)) goto done; + timestamp = (unsigned long long)now.tv_sec * 1000u + + (unsigned long long)now.tv_nsec / 1000000u; + n = snprintf(record, capacity, "{\"type\":"); + if (n < 0 || (size_t)n >= capacity) { errno = ENOSPC; goto done; } + offset = (size_t)n; + if (append_json_text(record, capacity, &offset, record_type)) goto done; + n = snprintf(record + offset, capacity - offset, ",\"content\":{"); + if (n < 0 || (size_t)n >= capacity - offset) { errno = ENOSPC; goto done; } + offset += (size_t)n; + if (!strcmp(record_type, "about")) { + n = snprintf(record + offset, capacity - offset, "\"name\":"); + if (n < 0 || (size_t)n >= capacity - offset) { errno = ENOSPC; goto done; } + offset += (size_t)n; + if (append_json_text(record, capacity, &offset, nick)) goto done; + } else { + n = snprintf(record + offset, capacity - offset, "\"channel\":"); + if (n < 0 || (size_t)n >= capacity - offset) { errno = ENOSPC; goto done; } + offset += (size_t)n; + if (append_json_text(record, capacity, &offset, channel)) goto done; + n = snprintf(record + offset, capacity - offset, ",\"text\":"); + if (n < 0 || (size_t)n >= capacity - offset) { errno = ENOSPC; goto done; } + offset += (size_t)n; + if (append_json_text(record, capacity, &offset, text)) goto done; + } + n = snprintf(record + offset, capacity - offset, + "},\"timestamp\":%llu}", timestamp); + if (n < 0 || (size_t)n >= capacity - offset) { errno = ENOSPC; goto done; } + offset += (size_t)n; + rc = cable_user_feed_append(home, room_key, (const uint8_t *)record, + offset, user_key); +done: + free(record); + return rc; +} diff --git a/extra/dat-dpi/src/cesspool.c b/extra/dat-dpi/src/cesspool.c new file mode 100644 index 0000000..9217025 --- /dev/null +++ b/extra/dat-dpi/src/cesspool.c @@ -0,0 +1,748 @@ +#define _POSIX_C_SOURCE 200112L +// hyperswarm dht +#include "cesspool.h" +#include "dat_wire.h" +#include "sleep_proto.h" +#include +#include +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#endif +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif +#ifdef SLEEP_SALT +#include +#endif + +#define CESSPOOL_PACKET_BYTES 72u +#define CESSPOOL_MAX_RENDEZVOUS_PEERS 34u +#define CESSPOOL_PEER_TTL_MS 600000u +#define CESSPOOL_BOOTSTRAP_PROBE_MS 5000u +#define CESSPOOL_BOOTSTRAP_TIMEOUT_MS 15000u +static uint64_t monotonic_ms(void) +{ + struct timespec now; + if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) return 0; + return (uint64_t)now.tv_sec * 1000u + (uint64_t)now.tv_nsec / 1000000u; +} +static int pb_var(uint8_t tag,uint64_t value,uint8_t*out,size_t cap,size_t*off){size_t n;if(*off>=cap){errno=ENOSPC;return-1;}out[(*off)++]=tag;if(sleep_varint_encode(value,out+*off,cap-*off,&n))return-1;*off+=n;return 0;} +static int pb_bytes(uint8_t tag,const uint8_t*data,size_t len,uint8_t*out,size_t cap,size_t*off){size_t n;if(*off>=cap){errno=ENOSPC;return-1;}out[(*off)++]=tag;if(sleep_varint_encode(len,out+*off,cap-*off,&n))return-1;*off+=n;if(len>cap-*off){errno=ENOSPC;return-1;}memcpy(out+*off,data,len);*off+=len;return 0;} + +int cesspool_request_encode(const cesspool_request_t*r,uint8_t*out,size_t cap,size_t*written){size_t off=0;if(!r||!out){errno=EINVAL;return-1;}if(pb_var(0x08,r->type,out,cap,&off))return-1;if(r->port&&pb_var(0x10,r->port,out,cap,&off))return-1;if(r->local_address_len&&pb_bytes(0x1a,r->local_address,r->local_address_len,out,cap,&off))return-1;if(written)*written=off;return 0;} +int cesspool_request_decode(cesspool_request_t*r,const uint8_t*in,size_t len){size_t off=0,n;uint64_t v;if(!r||!in){errno=EINVAL;return-1;}memset(r,0,sizeof(*r));while(offtype=(uint32_t)v;else r->port=(uint16_t)v;}else if(tag==0x1a){if(sleep_varint_decode(in+off,len-off,&v,&n))return-1;off+=n;if(v>sizeof(r->local_address)||v>len-off){errno=EPROTO;return-1;}memcpy(r->local_address,in+off,(size_t)v);r->local_address_len=(size_t)v;off+=(size_t)v;}else{errno=EPROTO;return-1;}}return 0;} +int cesspool_response_encode(const cesspool_response_t *r, uint8_t *out, + size_t cap, size_t *written) +{ + size_t off = 0; + if (!r || !out) { errno = EINVAL; return -1; } + if (r->peers_len && pb_bytes(0x0a, r->peers, r->peers_len, out, cap, &off)) return -1; + if (r->local_peers_len && pb_bytes(0x12, r->local_peers, r->local_peers_len, out, cap, &off)) return -1; + if (r->peer_info_len && pb_bytes(0x1a, r->peer_info, r->peer_info_len, out, cap, &off)) return -1; + if (written) *written = off; + return 0; +} +int cesspool_response_decode(cesspool_response_t *r, const uint8_t *in, size_t len) +{ + size_t off = 0, n; + uint64_t v; + if (!r || !in) { errno = EINVAL; return -1; } + memset(r, 0, sizeof(*r)); + while (off < len) { + uint8_t tag = in[off++], *dst; + size_t *dstlen, capacity; + if (sleep_varint_decode(in + off, len - off, &v, &n)) return -1; + off += n; + if (v > len - off) { errno = EPROTO; return -1; } + if (tag == 0x0a) { dst = r->peers; dstlen = &r->peers_len; capacity = sizeof(r->peers); } + else if (tag == 0x12) { dst = r->local_peers; dstlen = &r->local_peers_len; capacity = sizeof(r->local_peers); } + else if (tag == 0x1a) { dst = r->peer_info; dstlen = &r->peer_info_len; capacity = sizeof(r->peer_info); } + else { off += (size_t)v; continue; } + if (v > capacity) { errno = EOVERFLOW; return -1; } + memcpy(dst, in + off, (size_t)v); + *dstlen = (size_t)v; + off += (size_t)v; + } + return 0; +} +int cesspool_process_response(cesspool_t*p,const cesspool_response_t*r,const uint8_t prefix[2]){size_t off;cesspool_peer_t peer;struct sockaddr_in*address;if(!p||!r){errno=EINVAL;return-1;}if(r->peers_len%6||r->local_peers_len%4){errno=EPROTO;return-1;}for(off=0;offpeers_len;off+=6){memset(&peer,0,sizeof(peer));address=(struct sockaddr_in*)&peer.address.addr;address->sin_family=AF_INET;memcpy(&address->sin_addr,r->peers+off,4);memcpy(&address->sin_port,r->peers+off+4,2);peer.address.addrlen=sizeof(*address);memcpy(peer.public_key,r->peers+off,6);cesspool_add_peer(p,&peer);}if(prefix)for(off=0;offlocal_peers_len;off+=4){memset(&peer,0,sizeof(peer));address=(struct sockaddr_in*)&peer.address.addr;address->sin_family=AF_INET;((uint8_t*)&address->sin_addr)[0]=prefix[0];((uint8_t*)&address->sin_addr)[1]=prefix[1];((uint8_t*)&address->sin_addr)[2]=r->local_peers[off];((uint8_t*)&address->sin_addr)[3]=r->local_peers[off+1];memcpy(&address->sin_port,r->local_peers+off+2,2);peer.address.addrlen=sizeof(*address);peer.local=1;memcpy(peer.public_key,&address->sin_addr,4);memcpy(peer.public_key+4,&address->sin_port,2);cesspool_add_peer(p,&peer);}return 0;} + +int cesspool_discovery_key(const uint8_t key[32], uint8_t topic[32]) +{ + if (!key || !topic) { errno = EINVAL; return -1; } +#ifdef SLEEP_SALT + /* The root NETWORK_PROTOCOL token selects the discovery namespace. + * With "hypercore", this matches legacy Dat's discoveryKey derivation. */ + return crypto_generichash(topic, 32, + (const unsigned char *)DAT_NETWORK_PROTOCOL, + strlen(DAT_NETWORK_PROTOCOL), key, 32); +#else + memcpy(topic, key, 32); + return 0; +#endif +} + +int cesspool_encode(const cesspool_message_t *m, uint8_t *out, + size_t capacity, size_t *written) +{ + size_t total = CESSPOOL_PACKET_BYTES; + if (!m || !out) { errno = EINVAL; return -1; } + if (m->type > CESSPOOL_HOLEPUNCH) { errno = EINVAL; return -1; } + if (m->type == CESSPOOL_REPLY) { + if (capacity < CESSPOOL_PACKET_BYTES + 2 || + m->payload_len > UINT16_MAX || + m->payload_len > capacity - CESSPOOL_PACKET_BYTES - 2) { + errno = ENOSPC; return -1; + } + total += 2 + m->payload_len; + } + if (capacity < total) { errno = ENOSPC; return -1; } + memcpy(out, "CSP0", 4); + out[4] = m->type; + out[5] = m->type == CESSPOOL_REPLY ? 1 : 0; + out[6] = (uint8_t)(m->port >> 8); + out[7] = (uint8_t)m->port; + memcpy(out + 8, m->topic, 32); + memcpy(out + 40, m->peer_id, 32); + if (m->type == CESSPOOL_REPLY) { + out[72] = (uint8_t)(m->payload_len >> 8); + out[73] = (uint8_t)m->payload_len; + memcpy(out + 74, m->payload, m->payload_len); + } + if (written) *written = total; + return 0; +} + +int cesspool_decode(cesspool_message_t *m, const uint8_t *in, size_t len) +{ + if (!m || !in) { errno = EINVAL; return -1; } + if (len < CESSPOOL_PACKET_BYTES || memcmp(in, "CSP0", 4) || + in[4] > CESSPOOL_HOLEPUNCH) { + errno = EPROTO; + return -1; + } + memset(m, 0, sizeof(*m)); + m->type = in[4]; + m->port = ((uint16_t)in[6] << 8) | in[7]; + memcpy(m->topic, in + 8, 32); + memcpy(m->peer_id, in + 40, 32); + if (m->type == CESSPOOL_REPLY) { + if (in[5] != 1 || len < CESSPOOL_PACKET_BYTES + 2) { errno = EPROTO; return -1; } + m->payload_len = ((size_t)in[72] << 8) | in[73]; + if (m->payload_len > sizeof(m->payload) || len != CESSPOOL_PACKET_BYTES + 2 + m->payload_len) { errno = EPROTO; return -1; } + memcpy(m->payload, in + 74, m->payload_len); + } else if (in[5] != 0 || len != CESSPOOL_PACKET_BYTES) { errno = EPROTO; return -1; } + return 0; +} + +static int send_message(cesspool_t *pool,const dat_peer_t *peer,uint8_t type,const uint8_t topic[32],uint16_t port) +{ + cesspool_message_t message;uint8_t packet[DATNET_MAX_PACKET];size_t len; + memset(&message,0,sizeof(message));message.type=type;message.port=port;if(topic)memcpy(message.topic,topic,32);memcpy(message.peer_id,pool->net.peerid,32); + if(cesspool_encode(&message,packet,sizeof(packet),&len))return-1; + return datnet_send(&pool->net,peer,packet,len); +} +static int send_reply(cesspool_t *pool, const dat_peer_t *peer, + const uint8_t topic[32], const cesspool_response_t *response) +{ + cesspool_message_t message; + uint8_t packet[DATNET_MAX_PACKET]; + size_t payload_len, packet_len; + memset(&message, 0, sizeof(message)); + message.type = CESSPOOL_REPLY; + memcpy(message.topic, topic, CESSPOOL_TOPIC_BYTES); + memcpy(message.peer_id, pool->net.peerid, CESSPOOL_PEER_BYTES); + if (cesspool_response_encode(response, message.payload, + sizeof(message.payload), &payload_len)) return -1; + message.payload_len = payload_len; + if (cesspool_encode(&message, packet, sizeof(packet), &packet_len)) return -1; + return datnet_send(&pool->net, peer, packet, packet_len); +} +static int send_bootstrap(cesspool_t *pool,uint8_t type,const uint8_t topic[32],uint16_t port) +{ + static const uint16_t lan_ports[] = {DATNET_PORT,3000,3002,3004,2001,2003,2005}; + size_t i;int sent=0,lan_sent=0; + if (!pool) { errno = EINVAL; return -1; } + for(i=0;ibootstrap_count;i++) + if(send_message(pool,&pool->bootstrap_nodes[i],type,topic,port)==0)sent++; + /* Always keep zero-configuration LAN discovery active, even when a + * rendezvous node is configured. Routers do not forward these broadcasts; + * this complements Internet discovery and lets same-LAN peers connect + * without requiring them to share bootstrap configuration. */ + for (i = 0; i < sizeof(lan_ports) / sizeof(lan_ports[0]); i++) { + dat_peer_t target; + struct sockaddr_in *address; + memset(&target, 0, sizeof(target)); + address = (struct sockaddr_in *)&target.addr; + address->sin_family = AF_INET; + address->sin_addr.s_addr = htonl(INADDR_BROADCAST); + address->sin_port = htons(lan_ports[i]); + target.addrlen = sizeof(*address); + if (send_message(pool, &target, type, topic, port) == 0) lan_sent++; + } + if (pool->bootstrap_count) return (sent || lan_sent) ? 0 : -1; + return 0; /* LAN broadcasts are best-effort, never a join failure. */ +} +static int has_topic(const cesspool_t *pool,const uint8_t topic[32],int announcing) +{ + size_t i; + for(i=0;itopic_count;i++)if(!memcmp(pool->topics[i].key,topic,32)&&(!announcing||pool->topics[i].announcing))return 1; + return 0; +} +static int same_peer_address(const dat_peer_t *a, const dat_peer_t *b) +{ + const struct sockaddr_in *aa, *bb; + if (!a || !b || a->addrlen < (socklen_t)sizeof(struct sockaddr_in) || + b->addrlen < (socklen_t)sizeof(struct sockaddr_in)) return 0; + aa = (const struct sockaddr_in *)&a->addr; + bb = (const struct sockaddr_in *)&b->addr; + return aa->sin_family == AF_INET && bb->sin_family == AF_INET && + aa->sin_addr.s_addr == bb->sin_addr.s_addr && aa->sin_port == bb->sin_port; +} + +static int announcement_add(cesspool_t *pool, const uint8_t topic[32], + const uint8_t peer_id[32], const dat_peer_t *address, + uint64_t now) +{ + size_t i; + for (i = 0; i < pool->announcement_count; i++) { + cesspool_announcement_t *entry = &pool->announcements[i]; + if (!memcmp(entry->topic, topic, 32) && !memcmp(entry->peer_id, peer_id, 32)) { + entry->address = *address; + entry->last_seen = now; + return 0; + } + } + if (pool->announcement_count >= CESSPOOL_MAX_ANNOUNCEMENTS) { + errno = ENOSPC; + return -1; + } + { + cesspool_announcement_t *entry = &pool->announcements[pool->announcement_count++]; + memcpy(entry->topic, topic, 32); + memcpy(entry->peer_id, peer_id, 32); + entry->address = *address; + entry->last_seen = now; + } + return 0; +} + +static void announcement_remove(cesspool_t *pool, const uint8_t topic[32], + const uint8_t peer_id[32]) +{ + size_t i = 0; + while (i < pool->announcement_count) { + cesspool_announcement_t *entry = &pool->announcements[i]; + if (!memcmp(entry->topic, topic, 32) && !memcmp(entry->peer_id, peer_id, 32)) { + pool->announcements[i] = pool->announcements[--pool->announcement_count]; + continue; + } + i++; + } +} + +static int peer_has_announcement(const cesspool_t *pool, const uint8_t peer_id[32]) +{ + size_t i; + for (i = 0; i < pool->announcement_count; i++) + if (!memcmp(pool->announcements[i].peer_id, peer_id, 32)) return 1; + return 0; +} + +static int send_rendezvous_reply(cesspool_t *pool, dat_peer_t *requester, + const uint8_t topic[32]) +{ + cesspool_response_t response; + size_t i; + memset(&response, 0, sizeof(response)); + for (i = 0; i < pool->announcement_count; i++) { + const cesspool_announcement_t *peer = &pool->announcements[i]; + const struct sockaddr_in *address; + uint8_t *record; + if (memcmp(peer->topic, topic, 32) || same_peer_address(&peer->address, requester) || + peer->address.addrlen < (socklen_t)sizeof(struct sockaddr_in)) continue; + address = (const struct sockaddr_in *)&peer->address.addr; + if (address->sin_family != AF_INET || + response.peer_info_len / 38 >= CESSPOOL_MAX_RENDEZVOUS_PEERS || + response.peer_info_len + 38 > sizeof(response.peer_info)) continue; + record = response.peer_info + response.peer_info_len; + memcpy(record, &address->sin_addr, 4); + memcpy(record + 4, &address->sin_port, 2); + memcpy(record + 6, peer->peer_id, 32); + response.peer_info_len += 38; + } + return send_reply(pool, requester, topic, &response); +} + +static int receive_rendezvous_reply(cesspool_t *pool, + const cesspool_message_t *message) +{ + cesspool_response_t response; + size_t i; + if (cesspool_response_decode(&response, message->payload, message->payload_len)) return -1; + if (response.peer_info_len % 38) { errno = EPROTO; return -1; } + for (i = 0; i < response.peer_info_len; i += 38) { + const uint8_t *record = response.peer_info + i; + cesspool_peer_t peer; + struct sockaddr_in *address; + memset(&peer, 0, sizeof(peer)); + address = (struct sockaddr_in *)&peer.address.addr; + address->sin_family = AF_INET; + memcpy(&address->sin_addr, record, 4); + memcpy(&address->sin_port, record + 4, 2); + peer.address.addrlen = sizeof(*address); + memcpy(peer.public_key, record + 6, 32); + memcpy(peer.discovery_topic, message->topic, 32); + peer.discovered = 1; + peer.last_seen = monotonic_ms(); + if (announcement_add(pool, message->topic, peer.public_key, + &peer.address, peer.last_seen)) continue; + if (cesspool_add_peer(pool, &peer)) return -1; + } + return 0; +} + +static void cesspool_on_message(datnet_t *net,dat_peer_t *source,const uint8_t *packet,size_t length) +{ + cesspool_t *pool=(cesspool_t *)net->context; + cesspool_message_t message; + cesspool_peer_t peer; + if(!pool)return; + if(cesspool_decode(&message,packet,length)){ + if(pool->on_transport_packet)pool->on_transport_packet(pool,source,packet,length); + return; + } + if(message.type>CESSPOOL_HOLEPUNCH||!memcmp(message.peer_id,net->peerid,32))return; + if (message.type == CESSPOOL_REPLY) { + size_t i; + int from_bootstrap = 0; + for (i = 0; i < pool->bootstrap_count; i++) + if (same_peer_address(&pool->bootstrap_nodes[i], source)) { + from_bootstrap = 1; + break; + } + if (!from_bootstrap) return; + /* A malformed datagram is not proof that the configured rendezvous + * endpoint is speaking Cesspool, so validate before refreshing online. */ + if (receive_rendezvous_reply(pool, &message)) return; + pool->bootstrap_last_seen = monotonic_ms(); + pool->bootstrap_reachable = 1; + return; + } + if (message.type == CESSPOOL_PONG) { + size_t i; + for (i = 0; i < pool->bootstrap_count; i++) { + if (same_peer_address(&pool->bootstrap_nodes[i], source)) { + pool->bootstrap_last_seen = monotonic_ms(); + pool->bootstrap_reachable = 1; + break; + } + } + return; + } + memset(&peer,0,sizeof(peer));peer.address=*source;memcpy(peer.public_key,message.peer_id,32); + peer.last_seen = monotonic_ms(); + if (message.type == CESSPOOL_ANNOUNCE) { + peer.discovered = 1; + memcpy(peer.discovery_topic, message.topic, sizeof(peer.discovery_topic)); + if (announcement_add(pool, message.topic, message.peer_id, source, + peer.last_seen)) return; + } + cesspool_add_peer(pool,&peer); + if (message.type == CESSPOOL_UNANNOUNCE) { + cesspool_peer_t *known = cesspool_find_peer(pool, message.peer_id); + announcement_remove(pool, message.topic, message.peer_id); + if (known) known->discovered = peer_has_announcement(pool, message.peer_id); + } else if(message.type==CESSPOOL_PING)send_message(pool,source,CESSPOOL_PONG,NULL,pool->port); + else if(message.type==CESSPOOL_LOOKUP&&pool->bootstrap_server) + (void)send_rendezvous_reply(pool, source, message.topic); + else if(message.type==CESSPOOL_LOOKUP&&has_topic(pool,message.topic,1)) + send_message(pool,source,CESSPOOL_ANNOUNCE,message.topic,pool->port); + else if(message.type==CESSPOOL_HOLEPUNCH&&pool->on_holepunch)pool->on_holepunch(pool,source); +} +int cesspool_init(cesspool_t *pool,const uint8_t peer_id[32]) +{ + uint8_t empty[32]={0};if(!pool||!peer_id){errno=EINVAL;return-1;}memset(pool,0,sizeof(*pool));if(datnet_init(&pool->net,empty,peer_id))return-1;pool->net.context=pool;pool->net.on_message=cesspool_on_message;return 0; +} +int cesspool_add_bootstrap(cesspool_t *pool,const char *host,uint16_t port) +{ + struct addrinfo hints, *resolved = NULL; + struct sockaddr_in *address; + dat_peer_t *peer; + if(!pool||!host||!port||pool->bootstrap_count>=CESSPOOL_MAX_BOOTSTRAP){errno=EINVAL;return-1;} + memset(&hints,0,sizeof(hints));hints.ai_family=AF_INET;hints.ai_socktype=SOCK_DGRAM; + if(getaddrinfo(host,NULL,&hints,&resolved)!=0||!resolved){errno=EHOSTUNREACH;return-1;} + peer=&pool->bootstrap_nodes[pool->bootstrap_count];memset(peer,0,sizeof(*peer)); + memcpy(&peer->addr,resolved->ai_addr,resolved->ai_addrlen);peer->addrlen=(socklen_t)resolved->ai_addrlen; + address=(struct sockaddr_in*)&peer->addr;address->sin_port=htons(port); + freeaddrinfo(resolved); + { + size_t i; + for (i = 0; i < pool->bootstrap_count; i++) + if (same_peer_address(&pool->bootstrap_nodes[i], peer)) return 0; + } + if (pool->bootstrap_count >= CESSPOOL_MAX_BOOTSTRAP) { errno = ENOSPC; return -1; } + pool->bootstrap_count++;return 0; +} +static int add_endpoint_list(cesspool_t *pool, const char *setting) +{ + char list[2048], *item, *next; + int added = 0; + if (!setting || !*setting) return 0; + if (strlen(setting) >= sizeof(list)) { errno = EOVERFLOW; return -1; } + strcpy(list, setting); + for (item = list; item && *item; item = next) { + char *separator, *end; + unsigned long port; + next = strpbrk(item, ",;\r\n"); + if (next) *next++ = '\0'; + separator = strrchr(item, ':'); + if (!separator || separator == item || !separator[1]) continue; + *separator++ = '\0'; + port = strtoul(separator, &end, 10); + if (*end || port == 0 || port > 65535) continue; + if (cesspool_add_bootstrap(pool, item, (uint16_t)port) != 0) return -1; + added++; + } + return added; +} + +static int bootstrap_config_path(char *path, size_t capacity, int make_dirs) +{ + const char *home = getenv("HOME"); + const char *config = getenv("XDG_CONFIG_HOME"); + char directory[1024], config_directory[1024]; + if (!home || !*home) home = getenv("USERPROFILE"); +#ifdef _WIN32 + if ((!home || !*home) && getenv("HOMEDRIVE") && getenv("HOMEPATH")) { + static char combined[1024]; + if (snprintf(combined, sizeof(combined), "%s%s", getenv("HOMEDRIVE"), getenv("HOMEPATH")) + < (int)sizeof(combined)) home = combined; + } +#endif + if (!home || !*home) home = "."; + if (!config || !*config) { + if (snprintf(config_directory, sizeof(config_directory), "%s/.config", home) >= (int)sizeof(config_directory)) { + errno = ENAMETOOLONG; return -1; + } + config = config_directory; + } + if (snprintf(directory, sizeof(directory), "%s/dat-gpt", config) >= (int)sizeof(directory) || + snprintf(path, capacity, "%s/bootstrap", directory) >= (int)capacity) { + errno = ENAMETOOLONG; return -1; + } + if (make_dirs) { + char parent[1024]; + if (snprintf(parent, sizeof(parent), "%s", config) >= (int)sizeof(parent)) { errno = ENAMETOOLONG; return -1; } + if (mkdir(parent, 0700) && errno != EEXIST) return -1; + if (mkdir(directory, 0700) && errno != EEXIST) return -1; + } + return 0; +} + +int cesspool_configure_bootstraps(cesspool_t *pool) +{ + char path[1200], line[512]; + FILE *file; + int count = 0, loaded; + if (!pool) { errno = EINVAL; return -1; } + loaded = add_endpoint_list(pool, getenv("DDAT_BOOTSTRAP")); + if (loaded < 0) return -1; + count += loaded; + loaded = add_endpoint_list(pool, getenv("CABLE_BOOTSTRAP")); + if (loaded < 0) return -1; + count += loaded; + if (bootstrap_config_path(path, sizeof(path), 0)) return count; + file = fopen(path, "r"); + if (!file) return errno == ENOENT ? count : -1; + while (fgets(line, sizeof(line), file)) { + line[strcspn(line, "\r\n#")] = '\0'; + loaded = add_endpoint_list(pool, line); + if (loaded < 0) { fclose(file); return -1; } + count += loaded; + } + if (ferror(file)) { fclose(file); return -1; } + fclose(file); + return count; +} + +int cesspool_bootstrap_config_add(const char *endpoint) +{ + char path[1200], copy[512], *separator, *end; + unsigned long port; + FILE *file; + if (!endpoint || strlen(endpoint) >= sizeof(copy)) { errno = EINVAL; return -1; } + strcpy(copy, endpoint); + separator = strrchr(copy, ':'); + if (!separator || separator == copy || !separator[1]) { errno = EINVAL; return -1; } + *separator++ = '\0'; port = strtoul(separator, &end, 10); + if (*end || port == 0 || port > 65535) { errno = EINVAL; return -1; } + if (bootstrap_config_path(path, sizeof(path), 1)) return -1; + file = fopen(path, "a"); + if (!file) return -1; + { + int failed = fprintf(file, "%s:%lu\n", copy, port) < 0; + if (fclose(file)) failed = 1; + return failed ? -1 : 0; + } +} + +int cesspool_bootstrap_config_set(const char *endpoint) +{ + char path[1200], temporary[1240], copy[512], *separator, *end; + unsigned long port; + FILE *file; + int n, failed; + if (!endpoint || strlen(endpoint) >= sizeof(copy)) { errno = EINVAL; return -1; } + strcpy(copy, endpoint); + separator = strrchr(copy, ':'); + if (!separator || separator == copy || !separator[1]) { errno = EINVAL; return -1; } + *separator++ = '\0'; + port = strtoul(separator, &end, 10); + if (*end || port == 0 || port > 65535) { errno = EINVAL; return -1; } + if (bootstrap_config_path(path, sizeof(path), 1)) return -1; + n = snprintf(temporary, sizeof(temporary), "%s.tmp", path); + if (n < 0 || (size_t)n >= sizeof(temporary)) { errno = ENAMETOOLONG; return -1; } + file = fopen(temporary, "w"); + if (!file) return -1; + failed = fprintf(file, "%s:%lu\n", copy, port) < 0; + if (fclose(file)) failed = 1; + if (failed || chmod(temporary, S_IRUSR | S_IWUSR)) { + int saved = errno; + (void)remove(temporary); + errno = saved; + return -1; + } +#ifdef _WIN32 + if (!MoveFileExA(temporary, path, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) { + (void)remove(temporary); + errno = EIO; + return -1; + } +#else + if (rename(temporary, path)) { + int saved = errno; + (void)remove(temporary); + errno = saved; + return -1; + } +#endif + return 0; +} + +int cesspool_bootstrap_config_get(char *endpoint, size_t capacity) +{ + char path[1200], line[512]; + FILE *file; + size_t length; + if (!endpoint || !capacity) { errno = EINVAL; return -1; } + endpoint[0] = '\0'; + if (bootstrap_config_path(path, sizeof(path), 0)) return -1; + file = fopen(path, "r"); + if (!file) return -1; + while (fgets(line, sizeof(line), file)) { + line[strcspn(line, "\r\n#")] = '\0'; + if (!line[0]) continue; + length = strlen(line); + if (length >= capacity) { fclose(file); errno = ENOSPC; return -1; } + memcpy(endpoint, line, length + 1); + fclose(file); + return 0; + } + fclose(file); + errno = ENOENT; + return -1; +} + +int cesspool_bootstrap_config_list(char *contents, size_t capacity) +{ + char path[1200]; + FILE *file; + size_t length; + if (!contents || !capacity) { errno = EINVAL; return -1; } + contents[0] = '\0'; + if (bootstrap_config_path(path, sizeof(path), 0)) return -1; + file = fopen(path, "r"); + if (!file) return -1; + length = fread(contents, 1, capacity - 1, file); + if (ferror(file)) { int saved = errno; fclose(file); errno = saved; return -1; } + if (!feof(file)) { fclose(file); errno = ENOSPC; return -1; } + contents[length] = '\0'; + return fclose(file) == 0 ? 0 : -1; +} +int cesspool_bootstrap(cesspool_t *pool){if(!pool||!pool->bootstrap_count){errno=EINVAL;return-1;}return send_bootstrap(pool,CESSPOOL_PING,NULL,pool->port);} +int cesspool_listen(cesspool_t *pool,const char *host,uint16_t port){ + static const uint16_t defaults[]={DATNET_PORT,3000,3002,3004,2001,2003,2005}; + struct sockaddr_in bound;socklen_t length=sizeof(bound);size_t i,count; + if(!pool){errno=EINVAL;return-1;} + count=port?1:sizeof(defaults)/sizeof(defaults[0]); + for(i=0;inet,host,candidate)==0)break; + if(port||errno!=EADDRINUSE)return-1; + } + if(i==count){errno=EADDRINUSE;return-1;} + if(getsockname(pool->net.sock,(struct sockaddr*)&bound,&length)){datnet_close(&pool->net);return-1;} + pool->port=ntohs(bound.sin_port);pool->ready=1;return 0; +} +int cesspool_set_bootstrap_server(cesspool_t *pool, int enabled) +{ + if (!pool || !pool->ready) { errno = EINVAL; return -1; } + pool->bootstrap_server = !!enabled; + return 0; +} +int cesspool_announce(cesspool_t *pool,const uint8_t topic[32],uint16_t port){if(!pool||!topic){errno=EINVAL;return-1;}return send_bootstrap(pool,CESSPOOL_ANNOUNCE,topic,port?port:pool->port);} +int cesspool_lookup(cesspool_t *pool,const uint8_t topic[32]){if(!pool||!topic){errno=EINVAL;return-1;}return send_bootstrap(pool,CESSPOOL_LOOKUP,topic,0);} +int cesspool_unannounce(cesspool_t *pool,const uint8_t topic[32],uint16_t port){if(!pool||!topic){errno=EINVAL;return-1;}return send_bootstrap(pool,CESSPOOL_UNANNOUNCE,topic,port);} +int cesspool_ping(cesspool_t *pool,const dat_peer_t *peer){if(!pool||!peer){errno=EINVAL;return-1;}return send_message(pool,peer,CESSPOOL_PING,NULL,pool->port);} +int cesspool_holepunch(cesspool_t *pool,const dat_peer_t *peer){if(!pool||!peer){errno=EINVAL;return-1;}return send_message(pool,peer,CESSPOOL_HOLEPUNCH,NULL,pool->port);} +int cesspool_poll(cesspool_t *pool,int timeout_ms){if(!pool){errno=EINVAL;return-1;}return datnet_poll(&pool->net,timeout_ms);} +cesspool_peer_t *cesspool_find_peer(cesspool_t*p,const uint8_t key[32]){size_t i;if(!p||!key)return NULL;for(i=0;ipeer_count;i++)if(!memcmp(p->peers[i].public_key,key,32))return&p->peers[i];return NULL;} +int cesspool_add_peer(cesspool_t*p,const cesspool_peer_t*peer){cesspool_peer_t*old;int newly_discovered;if(!p||!peer){errno=EINVAL;return-1;}old=cesspool_find_peer(p,peer->public_key);if(old){newly_discovered=peer->discovered&&(!old->discovered||memcmp(peer->discovery_topic,old->discovery_topic,32));{cesspool_peer_t merged=*peer;if(!peer->discovered&&old->discovered){merged.discovered=1;memcpy(merged.discovery_topic,old->discovery_topic,32);}*old=merged;}if(newly_discovered&&p->on_peer)p->on_peer(p,old);return 0;}if(p->peer_count>=CESSPOOL_MAX_PEERS){errno=ENOSPC;return-1;}p->peers[p->peer_count++]=*peer;if(peer->discovered&&p->on_peer)p->on_peer(p,peer);return 0;} +int cesspool_remove_peer(cesspool_t*p,const uint8_t key[32]){size_t i;if(!p||!key){errno=EINVAL;return-1;}for(i=0;ipeer_count;i++)if(!memcmp(p->peers[i].public_key,key,32)){p->peers[i]=p->peers[--p->peer_count];return 0;}errno=ENOENT;return-1;} +int cesspool_join(cesspool_t *p, const uint8_t topic[32], uint16_t port, + int announce, int lookup) +{ + cesspool_topic_t entry; + int announced_now = 0; + if (!p || !topic || (!announce && !lookup)) { errno = EINVAL; return -1; } + if (p->topic_count >= CESSPOOL_MAX_TOPICS) { errno = ENOSPC; return -1; } + memset(&entry, 0, sizeof(entry)); + memcpy(entry.key, topic, CESSPOOL_TOPIC_BYTES); + entry.port = port ? port : p->port; + entry.announcing = !!announce; + entry.looking_up = !!lookup; + /* No configured rendezvous falls back to best-effort LAN broadcast. */ + if (announce) { + if (cesspool_announce(p, topic, entry.port)) return -1; + announced_now = 1; + } + if (lookup && cesspool_lookup(p, topic)) { + if (announced_now) (void)cesspool_unannounce(p, topic, entry.port); + return -1; + } + p->topics[p->topic_count++] = entry; + return 0; +} +int cesspool_leave(cesspool_t*p,const uint8_t topic[32]){size_t i;if(!p||!topic){errno=EINVAL;return-1;}for(i=0;itopic_count;i++)if(!memcmp(p->topics[i].key,topic,32)){if(p->topics[i].announcing)cesspool_unannounce(p,topic,p->topics[i].port);p->topics[i]=p->topics[--p->topic_count];return 0;}errno=ENOENT;return-1;} +int cesspool_tick(cesspool_t *p, uint64_t now) +{ + size_t i = 0; + uint64_t wall_now; + if (!p) { errno = EINVAL; return -1; } + for (i = 0; i < p->topic_count; i++) if (now >= p->topics[i].refresh_at) { + if (p->topics[i].announcing) (void)cesspool_announce(p, p->topics[i].key, p->topics[i].port); + if (p->topics[i].looking_up) (void)cesspool_lookup(p, p->topics[i].key); + p->topics[i].refresh_at = now + 240000u; + } + if (!p->bootstrap_server && p->bootstrap_count && now >= p->bootstrap_probe_at) { + int probed = 0; + for (i = 0; i < p->topic_count; i++) if (p->topics[i].looking_up) { + (void)cesspool_lookup(p, p->topics[i].key); + probed = 1; + } + if (!probed) (void)cesspool_bootstrap(p); + p->bootstrap_probe_at = now + CESSPOOL_BOOTSTRAP_PROBE_MS; + } + for (i = 0; i < CESSPOOL_MAX_TRANSACTIONS; i++) + if (p->transactions[i].active && now >= p->transactions[i].expires_at) + p->transactions[i].active = 0; + i = 0; + wall_now = monotonic_ms(); + if (p->bootstrap_count && p->bootstrap_last_seen && wall_now >= p->bootstrap_last_seen) + p->bootstrap_reachable = wall_now - p->bootstrap_last_seen < CESSPOOL_BOOTSTRAP_TIMEOUT_MS; + else if (p->bootstrap_count) p->bootstrap_reachable = 0; + while (i < p->announcement_count) { + cesspool_announcement_t *entry = &p->announcements[i]; + if (!entry->last_seen || (wall_now >= entry->last_seen && + wall_now - entry->last_seen >= CESSPOOL_PEER_TTL_MS)) { + p->announcements[i] = p->announcements[--p->announcement_count]; + continue; + } + i++; + } + /* Client peer rows are a view of live topic announcements. Without + * reconciling this bit after pruning, a peer remains "discovered" forever + * even though its rendezvous record has expired. */ + for (i = 0; i < p->peer_count; i++) { + cesspool_peer_t *peer = &p->peers[i]; + size_t announcement; + peer->discovered = 0; + for (announcement = 0; announcement < p->announcement_count; announcement++) { + const cesspool_announcement_t *entry = &p->announcements[announcement]; + if (memcmp(peer->public_key, entry->peer_id, CESSPOOL_PEER_BYTES)) continue; + peer->discovered = 1; + memcpy(peer->discovery_topic, entry->topic, CESSPOOL_TOPIC_BYTES); + if (entry->last_seen > peer->last_seen) peer->last_seen = entry->last_seen; + break; + } + } + if (p->bootstrap_server) { + i = 0; + while (i < p->peer_count) { + cesspool_peer_t *peer = &p->peers[i]; + if (!peer->last_seen || (wall_now >= peer->last_seen && + wall_now - peer->last_seen >= CESSPOOL_PEER_TTL_MS)) { + p->peers[i] = p->peers[--p->peer_count]; + continue; + } + i++; + } + } + return 0; +} +static int distance_less(const uint8_t*a,const uint8_t*b,const uint8_t target[32]){size_t i;for(i=0;i<32;i++){uint8_t da=a[i]^target[i],db=b[i]^target[i];if(da!=db)return dapeer_count;i++){if(npeers[i];else if(cap&&distance_less(p->peers[i].public_key,out[n-1]->public_key,target))out[n-1]=&p->peers[i];for(j=n;j>1&&distance_less(out[j-1]->public_key,out[j-2]->public_key,target);j--){cesspool_peer_t*t=out[j-1];out[j-1]=out[j-2];out[j-2]=t;}}return n;} +size_t cesspool_topic_peers(cesspool_t *p, const uint8_t topic[32], + cesspool_peer_t **out, size_t cap) +{ + size_t i, n = 0; + if (!p || !topic || (!out && cap)) return 0; + for (i = 0; i < p->announcement_count && n < cap; i++) { + cesspool_announcement_t *entry = &p->announcements[i]; + cesspool_peer_t *peer; + size_t j; + if (memcmp(entry->topic, topic, 32)) continue; + for (j = 0; j < n; j++) if (!memcmp(out[j]->public_key, entry->peer_id, 32)) break; + if (j < n) continue; + peer = cesspool_find_peer(p, entry->peer_id); + if (peer) out[n++] = peer; + } + return n; +} + +size_t cesspool_announced_peer_count(const cesspool_t *p) +{ + size_t i, j, count = 0; + if (!p) return 0; + for (i = 0; i < p->announcement_count; i++) { + for (j = 0; j < i; j++) + if (!memcmp(p->announcements[i].peer_id, p->announcements[j].peer_id, 32)) break; + if (j == i) count++; + } + return count; +} + +size_t cesspool_announced_topic_count(const cesspool_t *p) +{ + size_t i, j, count = 0; + if (!p) return 0; + for (i = 0; i < p->announcement_count; i++) { + for (j = 0; j < i; j++) + if (!memcmp(p->announcements[i].topic, p->announcements[j].topic, 32)) break; + if (j == i) count++; + } + return count; +} +int cesspool_transaction_begin(cesspool_t*p,uint8_t type,const uint8_t target[32],uint64_t expires,uint16_t*out){size_t i,j,attempt;uint16_t candidate;if(!p||!target||!out){errno=EINVAL;return-1;}for(i=0;itransactions[i].active){for(attempt=0;attemptnext_transaction;if(!candidate)candidate=++p->next_transaction;for(j=0;jtransactions[j].active&&p->transactions[j].id==candidate)break;if(j==CESSPOOL_MAX_TRANSACTIONS){cesspool_transaction_t*t=&p->transactions[i];memset(t,0,sizeof(*t));t->active=1;t->type=type;t->id=candidate;t->expires_at=expires;memcpy(t->target,target,32);*out=t->id;return 0;}}errno=ENOSPC;return-1;}errno=ENOSPC;return-1;} +int cesspool_transaction_finish(cesspool_t*p,uint16_t id){size_t i;if(!p){errno=EINVAL;return-1;}for(i=0;itransactions[i].active&&p->transactions[i].id==id){p->transactions[i].active=0;return 0;}errno=ENOENT;return-1;} +int cesspool_destroy(cesspool_t *pool){if(!pool){errno=EINVAL;return-1;}pool->ready=0;return datnet_close(&pool->net);} diff --git a/extra/dat-dpi/src/dat.c b/extra/dat-dpi/src/dat.c new file mode 100644 index 0000000..255eedd --- /dev/null +++ b/extra/dat-dpi/src/dat.c @@ -0,0 +1,235 @@ +#include "dat.h" +#include "base32768.h" +#include "sleep.h" +#include "sleep_crypto.h" +#include "sleep_proto.h" +#include "sleep_writer.h" +#include "sleep_feed.h" +#include "errno_compat.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +int dat_key_to_hex(const uint8_t key[DAT_KEY_BYTES], char hex[65]) +{ + static const char digits[] = "0123456789abcdef"; + size_t i; + if (!key || !hex) { errno = EINVAL; return -1; } + for (i = 0; i < DAT_KEY_BYTES; i++) { + hex[i * 2] = digits[key[i] >> 4]; + hex[i * 2 + 1] = digits[key[i] & 15]; + } + hex[64] = '\0'; + return 0; +} + +int dat_key_to_url(const uint8_t key[DAT_KEY_BYTES], char url[DAT_URL_BYTES]) +{ + if (!key || !url) { errno = EINVAL; return -1; } + memcpy(url, "dat://", 6); + if (dat_key_to_hex(key, url + 6)) return -1; + return 0; +} +int dat_key_to_compact_url(const uint8_t key[DAT_KEY_BYTES], char url[DAT_URL_BYTES]) +{ + if (!key || !url) { errno = EINVAL; return -1; } + memcpy(url, "dat://", 6); + if (base32768_encode(key, DAT_KEY_BYTES, url + 6, DAT_URL_BYTES - 6)) return -1; + return 0; +} +static int hex_value(char c){if(c>='0'&&c<='9')return c-'0';if(c>='a'&&c<='f')return c-'a'+10;if(c>='A'&&c<='F')return c-'A'+10;return-1;} +int dat_url_to_key(const char *url, uint8_t key[DAT_KEY_BYTES]) +{ + size_t i, length, decoded_length = 0; + int hi, lo; + if (!url || !key) { errno = EINVAL; return -1; } + if (!strncmp(url, "dat://", 6)) url += 6; + length = strlen(url); + if (length == 64) { + for (i = 0; i < DAT_KEY_BYTES; i++) { + hi = hex_value(url[i * 2]); lo = hex_value(url[i * 2 + 1]); + if (hi < 0 || lo < 0) { errno = EINVAL; return -1; } + key[i] = (uint8_t)((hi << 4) | lo); + } + return 0; + } + if (base32768_decode(url, key, DAT_KEY_BYTES, &decoded_length) || + decoded_length != DAT_KEY_BYTES) { errno = EINVAL; return -1; } + return 0; +} +static int dat_link_format_with(const dat_link_t*l,char*out,size_t cap,int compact){char base[DAT_URL_BYTES];int n;if(!l||!out){errno=EINVAL;return-1;}if((compact?dat_key_to_compact_url(l->key,base):dat_key_to_url(l->key,base)))return-1;if(l->has_version)n=snprintf(out,cap,"%s+%llu%s%s",base,(unsigned long long)l->version,*l->path&&l->path[0]!='/'?"/":"",l->path);else n=snprintf(out,cap,"%s%s%s",base,*l->path&&l->path[0]!='/'?"/":"",l->path);if(n<0||(size_t)n>=cap){errno=ENOSPC;return-1;}return 0;} +int dat_link_format(const dat_link_t*l,char*out,size_t cap){return dat_link_format_with(l,out,cap,0);} +int dat_link_format_compact(const dat_link_t*l,char*out,size_t cap){return dat_link_format_with(l,out,cap,1);} +int dat_link_parse(const char *text, dat_link_t *link) +{ + const char *p, *suffix, *plus; + char encoded[DAT_URL_BYTES], version[32]; + size_t key_length, n; + if (!text || !link) { errno = EINVAL; return -1; } + memset(link, 0, sizeof(*link)); + p = !strncmp(text, "dat://", 6) ? text + 6 : text; + suffix = strpbrk(p, "/+?#"); + key_length = suffix ? (size_t)(suffix - p) : strlen(p); + if (!key_length || key_length >= sizeof(encoded)) { errno = EINVAL; return -1; } + memcpy(encoded, p, key_length); encoded[key_length] = '\0'; + if (dat_url_to_key(encoded, link->key)) return -1; + suffix = p + key_length; + plus = *suffix == '+' ? suffix : NULL; + if (plus) { + const char *end = plus + 1; + while (*end >= '0' && *end <= '9') end++; + n = (size_t)(end - (plus + 1)); + if (!n || n >= sizeof(version)) { errno = EINVAL; return -1; } + memcpy(version, plus + 1, n); version[n] = '\0'; + link->version = (uint64_t)strtoull(version, NULL, 10); + link->has_version = 1; suffix = end; + } + if (*suffix && *suffix != '/') { errno = EINVAL; return -1; } + if (*suffix == '/') suffix++; + if (strlen(suffix) >= sizeof(link->path)) { errno = ENAMETOOLONG; return -1; } + strcpy(link->path, suffix); + return 0; +} + +int dat_archive_create(dat_archive_t *archive, const char *root) +{ + sleep_keypair_t metadata_keys, content_keys; + sleep_metadata_header_t header; + uint8_t metadata_block[128], empty = 0; + const uint8_t *metadata_blocks[1], *content_blocks[1]; + size_t metadata_lengths[1], content_lengths[1] = { 0 }; + size_t metadata_len; + int n; + + if (!archive || !root || !*root) { errno = EINVAL; return -1; } + memset(archive, 0, sizeof(*archive)); + n = snprintf(archive->dat_path, sizeof(archive->dat_path), "%s/.dat", root); + if (n < 0 || (size_t)n >= sizeof(archive->dat_path)) { errno = ENAMETOOLONG; return -1; } + if (sleep_init(root) || sleep_crypto_init() || sleep_gen_keypair(&metadata_keys) || sleep_gen_keypair(&content_keys)) return -1; + + memset(&header, 0, sizeof(header)); + strcpy(header.type, "hyperdrive"); + memcpy(header.content, content_keys.pubkey, 32); header.content_len = 32; + if (sleep_metadata_header_encode(&header, metadata_block, sizeof(metadata_block), &metadata_len)) return -1; + + metadata_blocks[0] = metadata_block; metadata_lengths[0] = metadata_len; + content_blocks[0] = ∅ + if (sleep_write_feed(archive->dat_path, "metadata", metadata_blocks, metadata_lengths, 1, &metadata_keys)) return -1; + if (sleep_write_feed(archive->dat_path, "content", content_blocks, content_lengths, 1, &content_keys)) return -1; + memcpy(archive->metadata_key, metadata_keys.pubkey, 32); + memcpy(archive->content_key, content_keys.pubkey, 32); + archive->metadata_keys = metadata_keys; + archive->content_keys = content_keys; + return 0; +} + +int dat_archive_open(dat_archive_t*a,const char*root){char path[1200];int n;if(!a||!root){errno=EINVAL;return-1;}memset(a,0,sizeof(*a));n=snprintf(a->dat_path,sizeof(a->dat_path),"%s/.dat",root);if(n<0||(size_t)n>=sizeof(a->dat_path)){errno=ENAMETOOLONG;return-1;}snprintf(path,sizeof(path),"%s/metadata.key",a->dat_path);if(sleep_load_pubkey(path,a->metadata_key))return-1;snprintf(path,sizeof(path),"%s/content.key",a->dat_path);return sleep_load_pubkey(path,a->content_key);} +/* Dat Desktop's dat-secret-storage indexes each Hypercore secret by the + * discovery key and stores the raw 64-byte Ed25519 secret at that path. */ +static int feed_secret_path(char *out,size_t cap,const char *root,const uint8_t pub[32]) +{ + uint8_t disc[32]; char hex[65], shard[1200]; int n; + if(sleep_compute_discovery_key(pub,disc)||dat_key_to_hex(disc,hex))return -1; + n=snprintf(shard,sizeof(shard),"%s/%c%c",root,hex[0],hex[1]); + if(n<0||(size_t)n>=sizeof(shard)){errno=ENAMETOOLONG;return -1;} + n=snprintf(out,cap,"%s/%s",shard,hex+2); + if(n<0||(size_t)n>=cap){errno=ENAMETOOLONG;return -1;} return 0; +} + +static int load_feed_secret(const char *root,const uint8_t pub[32],sleep_keypair_t *kp) +{ + char path[1400]; FILE *f; uint8_t raw[sizeof(*kp)]; size_t n; int saved; + if(feed_secret_path(path,sizeof(path),root,pub))return -1; + f=fopen(path,"rb"); + if(f){n=fread(raw,1,sizeof(raw),f);if(ferror(f)){saved=errno;fclose(f);errno=saved;return -1;}fclose(f); + if(n==SLEEP_SECRET_KEY_BYTES){memcpy(kp->seckey,raw,n);if(crypto_sign_ed25519_sk_to_pk(kp->pubkey,kp->seckey)!=0||memcmp(kp->pubkey,pub,32)){errno=EKEYREJECTED;return -1;}return 0;} + errno=EINVAL;return -1; + } + return -1; +} + +static int save_feed_secret(const char *root,const sleep_keypair_t *kp) +{ + char path[1400], parent[1400], *slash; FILE *f; int fd, saved; + if(feed_secret_path(path,sizeof(path),root,kp->pubkey))return -1; + strcpy(parent,path); slash=strrchr(parent,'/'); if(!slash){errno=EINVAL;return -1;} *slash='\0'; + if(mkdir(parent,0700)&&errno!=EEXIST)return -1; + f=fopen(path,"rb"); if(f){uint8_t raw[sizeof(*kp)];size_t n=fread(raw,1,sizeof(raw),f);fclose(f); + if(n==SLEEP_SECRET_KEY_BYTES&&!memcmp(raw,kp->seckey,SLEEP_SECRET_KEY_BYTES))return 0; + errno=EEXIST;return -1; + } + fd=open(path,O_WRONLY|O_CREAT|O_EXCL,0600);if(fd<0){if(errno==EEXIST)return save_feed_secret(root,kp);return -1;} + if(write(fd,kp->seckey,SLEEP_SECRET_KEY_BYTES)!=(ssize_t)SLEEP_SECRET_KEY_BYTES){saved=errno;close(fd);unlink(path);errno=saved?saved:EIO;return -1;} + if(close(fd)){saved=errno;unlink(path);errno=saved;return -1;}return 0; +} + +int dat_archive_open_writable(dat_archive_t*a,const char*root,const char*secrets) +{ + if(dat_archive_open(a,root))return -1; + if(!secrets){errno=EINVAL;return -1;} + if(load_feed_secret(secrets,a->metadata_key,&a->metadata_keys)|| + load_feed_secret(secrets,a->content_key,&a->content_keys))return -1; + return 0; +} + +int dat_archive_save_secrets(const dat_archive_t*a,const char*secrets) +{ + if(!a||!secrets){errno=EINVAL;return -1;} + if(mkdir(secrets,0700)&&errno!=EEXIST)return -1; + if(save_feed_secret(secrets,&a->metadata_keys))return -1; + return save_feed_secret(secrets,&a->content_keys); +} +void dat_archive_close(dat_archive_t*a){if(a)memset(a,0,sizeof(*a));} +size_t dat_archive_entry_count(const dat_archive_t*a){sleep_feed_t f;size_t n=0;if(a&&sleep_feed_open(&f,a->dat_path,"metadata",NULL)==0){n=sleep_feed_length(&f);sleep_feed_close(&f);}return n?n-1:0;} +uint64_t dat_archive_version(const dat_archive_t*a){return (uint64_t)dat_archive_entry_count(a);} + +int dat_archive_append_file(dat_archive_t *a,const char *path,const uint8_t *data,size_t length,uint32_t mode,uint64_t mtime) +{ + sleep_feed_t content,metadata;sleep_metadata_entry_t entry;uint8_t encoded[2300];size_t encoded_len,block,off=0,count=0,part; + if(!a||!path||(!data&&length)||strlen(path)>=sizeof(entry.path)){errno=EINVAL;return-1;} + if(sleep_feed_open(&content,a->dat_path,"content",&a->content_keys))return-1; + block=sleep_feed_length(&content);do{part=length-off>DAT_BLOCK_BYTES?DAT_BLOCK_BYTES:length-off;if(sleep_feed_append(&content,data?data+off:NULL,part)){sleep_feed_close(&content);return-1;}off+=part;count++;}while(offdat_path,"metadata",&a->metadata_keys))return-1; + if(sleep_feed_append(&metadata,encoded,encoded_len)||sleep_feed_flush(&metadata)){sleep_feed_close(&metadata);return-1;}sleep_feed_close(&metadata);return 0; +} +int dat_archive_read_entry(const dat_archive_t *a,size_t index,sleep_metadata_entry_t *entry) +{ + sleep_feed_t f;uint8_t *data;size_t len;int rc;if(!a||!entry){errno=EINVAL;return-1;}if(sleep_feed_open(&f,a->dat_path,"metadata",NULL))return-1; + /* Metadata block zero is the archive header. */ rc=sleep_feed_read(&f,index+1,&data,&len);sleep_feed_close(&f);if(rc)return-1;rc=sleep_metadata_entry_decode(entry,data,len);free(data);return rc; +} +int dat_archive_read_file(const dat_archive_t *a,const sleep_metadata_entry_t *entry,uint8_t **data,size_t *length) +{ + sleep_feed_t f;uint8_t*out,*block;size_t total=0,n,i;if(!a||!entry||!data||!length||entry->type!=SLEEP_ENTRY_FILE){errno=EINVAL;return-1;}if(entry->size>SIZE_MAX){errno=EOVERFLOW;return-1;}out=malloc(entry->size?(size_t)entry->size:1);if(!out)return-1;if(sleep_feed_open(&f,a->dat_path,"content",NULL)){free(out);return-1;}for(i=0;iblock_count;i++){if(sleep_feed_read(&f,(size_t)entry->block_offset+i,&block,&n)||n>(size_t)entry->size-total){sleep_feed_close(&f);free(out);return-1;}if(n)memcpy(out+total,block,n);total+=n;free(block);}sleep_feed_close(&f);if(total!=(size_t)entry->size){free(out);errno=EPROTO;return-1;}*data=out;*length=total;return 0; +} + +static int append_metadata_only(dat_archive_t *a,const sleep_metadata_entry_t *entry) +{ + sleep_feed_t metadata;uint8_t encoded[2300];size_t len;int rc; + if(sleep_metadata_entry_encode(entry,encoded,sizeof(encoded),&len))return-1; + if(sleep_feed_open(&metadata,a->dat_path,"metadata",&a->metadata_keys))return-1; + rc=sleep_feed_append(&metadata,encoded,len);if(!rc)rc=sleep_feed_flush(&metadata);sleep_feed_close(&metadata);return rc; +} +int dat_archive_append_directory(dat_archive_t *a,const char *path,uint32_t mode,uint64_t mtime) +{ + sleep_metadata_entry_t e;if(!a||!path||strlen(path)>=sizeof(e.path)){errno=EINVAL;return-1;}memset(&e,0,sizeof(e));strcpy(e.path,path);e.type=SLEEP_ENTRY_DIRECTORY;e.mode=mode;e.mtime=mtime;return append_metadata_only(a,&e); +} +int dat_archive_append_symlink(dat_archive_t *a,const char *path,const char *target,uint64_t mtime) +{ + sleep_metadata_entry_t e;if(!a||!path||!target||strlen(path)>=sizeof(e.path)||strlen(target)>=sizeof(e.linkname)){errno=EINVAL;return-1;}memset(&e,0,sizeof(e));strcpy(e.path,path);strcpy(e.linkname,target);e.type=SLEEP_ENTRY_SYMLINK;e.mtime=mtime;return append_metadata_only(a,&e); +} +int dat_archive_delete(dat_archive_t*a,const char*path,uint64_t mtime){sleep_metadata_entry_t e;if(!a||!path||strlen(path)>=sizeof(e.path)){errno=EINVAL;return-1;}memset(&e,0,sizeof(e));strcpy(e.path,path);e.type=SLEEP_ENTRY_DELETE;e.mtime=mtime;return append_metadata_only(a,&e);} +int dat_archive_find(const dat_archive_t*a,const char*path,sleep_metadata_entry_t*entry){size_t n,i;if(!a||!path||!entry){errno=EINVAL;return-1;}n=dat_archive_entry_count(a);for(i=n;i>0;i--){if(dat_archive_read_entry(a,i-1,entry)==0&&!strcmp(entry->path,path)){if(entry->type==SLEEP_ENTRY_DELETE){errno=ENOENT;return-1;}return 0;}}errno=ENOENT;return-1;} +int dat_archive_find_at(const dat_archive_t*a,const char*path,uint64_t version,sleep_metadata_entry_t*entry){size_t n,i;if(!a||!path||!entry){errno=EINVAL;return-1;}n=dat_archive_entry_count(a);if(version>n){errno=ERANGE;return-1;}for(i=(size_t)version;i>0;i--){if(dat_archive_read_entry(a,i-1,entry)==0&&!strcmp(entry->path,path)){if(entry->type==SLEEP_ENTRY_DELETE){errno=ENOENT;return-1;}return 0;}}errno=ENOENT;return-1;} +int dat_checkout_init(dat_checkout_t*c,const dat_archive_t*a,uint64_t version){if(!c||!a||version>dat_archive_version(a)){errno=ERANGE;return-1;}c->archive=a;c->version=version;return 0;} +int dat_checkout_find(const dat_checkout_t*c,const char*path,sleep_metadata_entry_t*entry){if(!c){errno=EINVAL;return-1;}return dat_archive_find_at(c->archive,path,c->version,entry);} +int dat_checkout_read(const dat_checkout_t*c,const sleep_metadata_entry_t*entry,uint8_t**data,size_t*length){if(!c){errno=EINVAL;return-1;}return dat_archive_read_file(c->archive,entry,data,length);} +int dat_checkout_export(const dat_checkout_t*c,const char*archive_path,const char*filesystem_path){if(!c){errno=EINVAL;return-1;}return dat_archive_export_at(c->archive,c->version,archive_path,filesystem_path);} +int dat_archive_changes(const dat_archive_t*a,uint64_t from,uint64_t to,dat_archive_change_fn callback,void*context){sleep_metadata_entry_t entry;uint64_t version,last;if(!a||!callback){errno=EINVAL;return-1;}last=dat_archive_version(a);if(from>to||to>last){errno=ERANGE;return-1;}for(version=from+1;version<=to;version++){if(dat_archive_read_entry(a,(size_t)(version-1),&entry)||callback(&entry,version,context))return-1;}return 0;} diff --git a/extra/dat-dpi/src/dat_browse.c b/extra/dat-dpi/src/dat_browse.c new file mode 100644 index 0000000..9053807 --- /dev/null +++ b/extra/dat-dpi/src/dat_browse.c @@ -0,0 +1,314 @@ +#define _POSIX_C_SOURCE 200809L +#include "dat.h" + +#include +#include +#include +#include +#include +#include + +static int normalize_url_path(const char *src, char *dst, size_t capacity) +{ + char decoded[2048]; + size_t i = 0, decoded_len = 0, out = 1, segments[1024], count = 0; + int trailing_slash; + if (!src || !dst || capacity < 2 || strlen(src) >= sizeof(decoded)) { + errno = ENAMETOOLONG; + return -1; + } + while (src[i]) { + unsigned char c = (unsigned char)src[i++]; + if (c == '%') { + int hi, lo; + if (!src[i] || !src[i + 1]) { errno = EINVAL; return -1; } + hi = isdigit((unsigned char)src[i]) ? src[i] - '0' : + (tolower((unsigned char)src[i]) >= 'a' && tolower((unsigned char)src[i]) <= 'f' ? tolower((unsigned char)src[i]) - 'a' + 10 : -1); + lo = isdigit((unsigned char)src[i + 1]) ? src[i + 1] - '0' : + (tolower((unsigned char)src[i + 1]) >= 'a' && tolower((unsigned char)src[i + 1]) <= 'f' ? tolower((unsigned char)src[i + 1]) - 'a' + 10 : -1); + if (hi < 0 || lo < 0) { errno = EINVAL; return -1; } + c = (unsigned char)((hi << 4) | lo); i += 2; + } + if (!c || c == '\\') { errno = EINVAL; return -1; } + decoded[decoded_len++] = (char)c; + } + decoded[decoded_len] = '\0'; + trailing_slash = decoded_len == 0 || decoded[decoded_len - 1] == '/'; + dst[0] = '/'; + i = 0; + while (i < decoded_len) { + size_t start, n; + while (i < decoded_len && decoded[i] == '/') i++; + start = i; + while (i < decoded_len && decoded[i] != '/') i++; + n = i - start; + if (!n) break; + if (n == 1 && decoded[start] == '.') { trailing_slash = 1; continue; } + if (n == 2 && decoded[start] == '.' && decoded[start + 1] == '.') { + trailing_slash = 1; + if (count) out = segments[--count]; + continue; + } + trailing_slash = 0; + if (count >= sizeof(segments) / sizeof(segments[0]) || + out + n + 2 > capacity) { errno = ENAMETOOLONG; return -1; } + segments[count++] = out; + if (out > 1) dst[out++] = '/'; + memcpy(dst + out, decoded + start, n); + out += n; + } + if (trailing_slash && out > 1) dst[out++] = '/'; + if (out >= capacity) { errno = ENAMETOOLONG; return -1; } + dst[out] = '\0'; + return 0; +} + +static int parse_decimal(const char *s, uint64_t *value) +{ + char *end; + unsigned long long v; + if (!s || !*s || !value) return -1; + errno = 0; v = strtoull(s, &end, 10); + if (errno || *end) return -1; + *value = (uint64_t)v; + return 0; +} + +int dat_url_parse(const char *text, dat_url_t *url) +{ + const char *authority, *path, *query, *end; + size_t host_len, path_len; + char query_copy[512], raw_path[2048], *item, *save; + char *plus; + if (!text || !url || strncmp(text, "dat://", 6)) { errno = EINVAL; return -1; } + memset(url, 0, sizeof(*url)); + authority = text + 6; + path = strpbrk(authority, "/?"); + end = path ? path : authority + strlen(authority); + host_len = (size_t)(end - authority); + if (!host_len || host_len >= sizeof(url->host)) { errno = EINVAL; return -1; } + memcpy(url->host, authority, host_len); url->host[host_len] = 0; + plus = strchr(url->host, '+'); + if (plus) { + if (parse_decimal(plus + 1, &url->version)) { errno = EINVAL; return -1; } + *plus = 0; url->has_version = 1; + if (!url->host[0]) { errno = EINVAL; return -1; } + } + query = (*end == '?') ? end : strchr(end, '?'); + if (*end == '/') { + path_len = query ? (size_t)(query - end) : strlen(end); + if (path_len >= sizeof(raw_path)) { errno = ENAMETOOLONG; return -1; } + memcpy(raw_path, end, path_len); raw_path[path_len] = 0; + } else { strcpy(raw_path, "/"); path_len = 1; } + if (path_len >= 2 && raw_path[path_len - 1] == '/' && raw_path[path_len - 2] == '/') + url->force_directory_listing = 1; + if (normalize_url_path(raw_path, url->path, sizeof(url->path))) return -1; + if (query) { + if (strlen(query + 1) >= sizeof(query_copy)) { errno = EOVERFLOW; return -1; } + strcpy(query_copy, query + 1); + for (item = strtok_r(query_copy, "&", &save); item; item = strtok_r(NULL, "&", &save)) { + char *equals = strchr(item, '='); + if (equals) *equals++ = 0; + if (!strcmp(item, "version")) { + if (parse_decimal(equals, &url->version)) { errno = EINVAL; return -1; } + if (url->has_version && url->version != (uint64_t)strtoull(equals, NULL, 10)) { errno = EINVAL; return -1; } + url->has_version = 1; + } else if (!strcmp(item, "stat")) { + url->wants_stat = !equals || strcmp(equals, "0") != 0; + } + } + } + return 0; +} + +static int canonical_entry_path(const char *input, char *out, size_t capacity) +{ + const char *p = input; + size_t n; + while (*p == '/') p++; + n = strlen(p); + while (n && p[n - 1] == '/') n--; + if (n >= capacity) { errno = ENAMETOOLONG; return -1; } + memcpy(out, p, n); out[n] = 0; + return 0; +} + +static int find_normalized(const dat_archive_t *archive, uint64_t version, + const char *path, sleep_metadata_entry_t *entry) +{ + size_t i; + for (i = (size_t)version; i > 0; i--) { + sleep_metadata_entry_t current; + const char *candidate; + if (dat_archive_read_entry(archive, i - 1, ¤t)) return -1; + candidate = current.path; while (*candidate == '/') candidate++; + if (strcmp(candidate, path)) continue; + if (current.type == SLEEP_ENTRY_DELETE) { errno = ENOENT; return -1; } + *entry = current; + return 0; + } + errno = ENOENT; + return -1; +} + +int dat_archive_stat_at(const dat_archive_t *archive, uint64_t version, + const char *path, dat_path_info_t *info) +{ + char wanted[2048]; + size_t i; + if (!archive || !path || !info || canonical_entry_path(path, wanted, sizeof(wanted))) { errno = EINVAL; return -1; } + if (version > dat_archive_version(archive)) { errno = ERANGE; return -1; } + memset(info, 0, sizeof(*info)); + if (find_normalized(archive, version, wanted, &info->entry) == 0) { + info->exists = 1; + info->is_directory = info->entry.type == SLEEP_ENTRY_DIRECTORY; + return 0; + } + if (errno != ENOENT) return -1; + for (i = 0; i < (size_t)version; i++) { + sleep_metadata_entry_t e, latest; + const char *candidate; + size_t prefix = strlen(wanted); + if (dat_archive_read_entry(archive, i, &e)) return -1; + candidate = e.path; while (*candidate == '/') candidate++; + if (prefix && (strncmp(candidate, wanted, prefix) || candidate[prefix] != '/')) continue; + if (!prefix && !*candidate) continue; + if (find_normalized(archive, version, candidate, &latest)) { + if (errno == ENOENT) continue; + return -1; + } + if (latest.type == SLEEP_ENTRY_DELETE) continue; + memset(info, 0, sizeof(*info)); + strcpy(info->entry.path, wanted); + info->entry.type = SLEEP_ENTRY_DIRECTORY; + info->exists = 1; info->is_directory = 1; + return 0; + } + errno = ENOENT; + return -1; +} + +int dat_archive_readdir_at(const dat_archive_t *archive, uint64_t version, + const char *path, dat_archive_readdir_fn callback, + void *context) +{ + char wanted[2048]; + size_t i, prefix, seen_count = 0; + char **seen; + dat_path_info_t info; + if (!archive || !path || !callback || canonical_entry_path(path, wanted, sizeof(wanted))) { errno = EINVAL; return -1; } + if (dat_archive_stat_at(archive, version, wanted, &info) || !info.is_directory) { if (!errno) errno = ENOTDIR; return -1; } + prefix = strlen(wanted); + seen = calloc(version ? (size_t)version : 1, sizeof(*seen)); + if (!seen) return -1; + for (i = 0; i < (size_t)version; i++) { + sleep_metadata_entry_t e, latest; + const char *candidate, *relative, *slash; + char name[1024], child[2048]; + size_t child_len; + if (dat_archive_read_entry(archive, i, &e)) { free(seen); return -1; } + candidate = e.path; while (*candidate == '/') candidate++; + if (prefix) { + if (strncmp(candidate, wanted, prefix) || candidate[prefix] != '/') continue; + relative = candidate + prefix + 1; + } else relative = candidate; + if (!*relative) continue; + slash = strchr(relative, '/'); + child_len = slash ? (size_t)(slash - relative) : strlen(relative); + if (!child_len || child_len >= sizeof(name)) continue; + memcpy(name, relative, child_len); name[child_len] = 0; + { + size_t s; + for (s = 0; s < seen_count; s++) if (!strcmp(seen[s], name)) break; + if (s < seen_count) continue; + } + if (prefix) snprintf(child, sizeof(child), "%s/%s", wanted, name); + else snprintf(child, sizeof(child), "%s", name); + if (find_normalized(archive, version, child, &latest)) { + if (errno == ENOENT) { + /* A descendant may imply a directory even without an explicit record. */ + if (!slash || dat_archive_stat_at(archive, version, child, &info)) continue; + latest = info.entry; + } else { free(seen); return -1; } + } + if (latest.type == SLEEP_ENTRY_DELETE) continue; + seen[seen_count] = strdup(name); + if (!seen[seen_count]) { while (seen_count) free(seen[--seen_count]); free(seen); return -1; } + seen_count++; + if (callback(&latest, name, context)) { while (seen_count) free(seen[--seen_count]); free(seen); return -1; } + } + while (seen_count) free(seen[--seen_count]); + free(seen); + return 0; +} + +static int lookup_index(const dat_archive_t *archive, uint64_t version, + const char *path, dat_path_info_t *info) +{ + char index_path[2048]; + int n; + if (*path) n = snprintf(index_path, sizeof(index_path), "%s/index.html", path); + else n = snprintf(index_path, sizeof(index_path), "index.html"); + if (n < 0 || (size_t)n >= sizeof(index_path)) { errno = ENAMETOOLONG; return -1; } + if (dat_archive_stat_at(archive, version, index_path, info)) return -1; + return info->entry.type == SLEEP_ENTRY_FILE ? 0 : -1; +} + +int dat_archive_resolve_at(const dat_archive_t *archive, uint64_t version, + const char *path, int force_listing, + dat_path_info_t *info, int *serve_listing) +{ + char normalized[2048], html_path[2048]; + const char *p; + size_t n; + int trailing_slash, is_directory; + if (!archive || !path || !info || !serve_listing || canonical_entry_path(path, normalized, sizeof(normalized))) { errno = EINVAL; return -1; } + p = path + strlen(path); + trailing_slash = p > path && p[-1] == '/'; + *serve_listing = 0; + if (dat_archive_stat_at(archive, version, normalized, info)) { + /* Hyperdrive resolves /name to /name.html when the direct path is absent. */ + if (errno != ENOENT || force_listing) return -1; + n = strlen(normalized); + if (n + sizeof(".html") > sizeof(html_path)) { errno = ENAMETOOLONG; return -1; } + snprintf(html_path, sizeof(html_path), "%s.html", normalized); + if (!dat_archive_stat_at(archive, version, html_path, info) && + info->entry.type == SLEEP_ENTRY_FILE) return 0; + errno = ENOENT; + return -1; + } + is_directory = info->is_directory; + if (force_listing) { + if (!is_directory) { errno = ENOTDIR; return -1; } + *serve_listing = 1; return 0; + } + if (trailing_slash || is_directory) { + if (!lookup_index(archive, version, normalized, info)) return 0; + if (!is_directory) { errno = ENOENT; return -1; } + *serve_listing = 1; return 0; + } + if (info->entry.type == SLEEP_ENTRY_FILE) return 0; + n = strlen(normalized); + if (n + sizeof(".html") > sizeof(html_path)) { errno = ENAMETOOLONG; return -1; } + snprintf(html_path, sizeof(html_path), "%s.html", normalized); + if (!dat_archive_stat_at(archive, version, html_path, info) && info->entry.type == SLEEP_ENTRY_FILE) return 0; + errno = ENOENT; + return -1; +} + +const char *dat_content_type(const char *path) +{ + const char *dot = strrchr(path ? path : "", '.'); + if (!dot) return "text/plain"; + dot++; + if (!strcasecmp(dot, "html") || !strcasecmp(dot, "htm")) return "text/html"; + if (!strcasecmp(dot, "css")) return "text/css"; + if (!strcasecmp(dot, "js")) return "application/javascript"; + if (!strcasecmp(dot, "json")) return "application/json"; + if (!strcasecmp(dot, "svg")) return "image/svg+xml"; + if (!strcasecmp(dot, "png")) return "image/png"; + if (!strcasecmp(dot, "gif")) return "image/gif"; + if (!strcasecmp(dot, "jpg") || !strcasecmp(dot, "jpeg")) return "image/jpeg"; + if (!strcasecmp(dot, "webp")) return "image/webp"; + return "text/plain"; +} diff --git a/extra/dat-dpi/src/dat_dns.c b/extra/dat-dpi/src/dat_dns.c new file mode 100644 index 0000000..22a38f8 --- /dev/null +++ b/extra/dat-dpi/src/dat_dns.c @@ -0,0 +1,421 @@ +#define _DEFAULT_SOURCE +#include "dat_dns.h" +#include "dat_paths.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DAT_DNS_PACKET_MAX 65536 +#define DAT_DNS_TXT_MAX 4096 +#define DAT_DNS_CACHE_SIZE 32 + +typedef struct dat_dns_cache_entry { + char hostname[256]; + uint8_t key[DAT_KEY_BYTES]; + uint64_t expires_at; + int active; +} dat_dns_cache_entry_t; + +static dat_dns_cache_entry_t dns_cache[DAT_DNS_CACHE_SIZE]; +static size_t dns_cache_next; +static int curl_initialized; +static int hex_value(unsigned char c); + +typedef struct dat_dns_body { + char text[4096]; + size_t length; + int overflow; +} dat_dns_body_t; + +static size_t receive_dat_dns_body(char *data, size_t size, size_t count, void *context) +{ + dat_dns_body_t *body = (dat_dns_body_t *)context; + size_t bytes = size * count; + if (bytes > sizeof(body->text) - body->length - 1) { body->overflow = 1; return 0; } + memcpy(body->text + body->length, data, bytes); + body->length += bytes; + body->text[body->length] = '\0'; + return bytes; +} + +static int parse_wellknown_record(const char *body, size_t length, + uint8_t key[DAT_KEY_BYTES], uint32_t *ttl_out) +{ + const char *line_end, *ttl_line; + size_t key_length, i; + uint32_t ttl = 3600; + if (!body || !key) { errno = EINVAL; return -1; } + line_end = memchr(body, '\n', length); + if (!line_end) line_end = body + length; + if ((size_t)(line_end - body) < 70 || strncasecmp(body, "dat://", 6)) { errno = EPROTO; return -1; } + key_length = (size_t)(line_end - body) - 6; + if (key_length < 64) { errno = EPROTO; return -1; } + for (i = 0; i < DAT_KEY_BYTES; i++) { + int hi = hex_value((unsigned char)body[6 + i * 2]); + int lo = hex_value((unsigned char)body[7 + i * 2]); + if (hi < 0 || lo < 0) { errno = EPROTO; return -1; } + key[i] = (uint8_t)((hi << 4) | lo); + } + if (key_length > 64 && body[70] != '+' && body[70] != '/' && body[70] != '?') { errno = EPROTO; return -1; } + ttl_line = line_end < body + length ? line_end + 1 : NULL; + if (ttl_line && !strncasecmp(ttl_line, "ttl=", 4)) { + char *end; + unsigned long parsed = strtoul(ttl_line + 4, &end, 10); + if (end != ttl_line + 4 && (*end == '\n' || *end == '\r' || *end == '\0')) + ttl = parsed > 604800ul ? 604800u : (uint32_t)parsed; + } + if (ttl_out) *ttl_out = ttl; + return 0; +} + +static int hex_value(unsigned char c) +{ + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return -1; +} + +static int normalize_hostname(const char *input, char output[256]) +{ + size_t n, label_start = 0, i; + if (!input || !*input) { errno = EINVAL; return -1; } + n = strlen(input); + if (n && input[n - 1] == '.') n--; + if (!n || n > 253) { errno = EINVAL; return -1; } + for (i = 0; i < n; i++) { + unsigned char c = (unsigned char)input[i]; + if (c == '.') { + size_t label_length = i - label_start; + if (!label_length || label_length > 63 || input[label_start] == '-' || input[i - 1] == '-') { + errno = EINVAL; return -1; + } + output[i] = '.'; + label_start = i + 1; + } else if (isalnum(c) || c == '-') output[i] = (char)tolower(c); + else { errno = EINVAL; return -1; } + } + if (label_start == n || n - label_start > 63 || input[label_start] == '-' || input[n - 1] == '-') { + errno = EINVAL; return -1; + } + output[n] = '\0'; + return 0; +} + +static int names_file(char *path, size_t capacity, int create) +{ + const char *home = dat_home_directory(); + char directory[1024]; + int n; + if (!home || !*home) { errno = ENOENT; return -1; } + n = snprintf(directory, sizeof(directory), "%s/.dat", home); + if (n < 0 || (size_t)n >= sizeof(directory)) { errno = ENAMETOOLONG; return -1; } + if (create && mkdir(directory, 0700) && errno != EEXIST) return -1; + n = snprintf(path, capacity, "%s/dat-dns", directory); + if (n < 0 || (size_t)n >= capacity) { errno = ENAMETOOLONG; return -1; } + return 0; +} + +static int parse_name_line(char *line, char hostname[256], uint8_t key[DAT_KEY_BYTES]) +{ + char name[256], hex[65]; + size_t i; + if (sscanf(line, "%255s %64s", name, hex) != 2 || strlen(hex) != 64 || + normalize_hostname(name, hostname)) return -1; + for (i = 0; i < DAT_KEY_BYTES; i++) { + int hi = hex_value((unsigned char)hex[i * 2]); + int lo = hex_value((unsigned char)hex[i * 2 + 1]); + if (hi < 0 || lo < 0) return -1; + key[i] = (uint8_t)((hi << 4) | lo); + } + return 0; +} + +int dat_dns_name_foreach(dat_dns_name_iter_fn callback, void *context) +{ + char path[1200], line[400], hostname[256]; + uint8_t key[DAT_KEY_BYTES]; + FILE *file; + if (!callback || names_file(path, sizeof(path), 0)) { if (!callback) errno = EINVAL; return -1; } + file = fopen(path, "r"); + if (!file) return errno == ENOENT ? 0 : -1; + while (fgets(line, sizeof(line), file)) { + if (!parse_name_line(line, hostname, key) && callback(hostname, key, context)) { + fclose(file); return 1; + } + } + if (ferror(file)) { fclose(file); return -1; } + fclose(file); + return 0; +} + +typedef struct name_lookup_context { + const uint8_t *key; + char *hostname; + size_t capacity; + int found; +} name_lookup_context_t; + +static int find_name_for_key(const char *name, const uint8_t key[DAT_KEY_BYTES], void *context) +{ + name_lookup_context_t *lookup = (name_lookup_context_t *)context; + if (!memcmp(lookup->key, key, DAT_KEY_BYTES)) { + if (strlen(name) >= lookup->capacity) { errno = ENOSPC; return 1; } + strcpy(lookup->hostname, name); + lookup->found = 1; + } + return 0; +} + +int dat_dns_name_for_key(const uint8_t key[DAT_KEY_BYTES], char *hostname, + size_t hostname_capacity) +{ + name_lookup_context_t lookup; + if (!key || !hostname || !hostname_capacity) { errno = EINVAL; return -1; } + lookup.key = key; lookup.hostname = hostname; lookup.capacity = hostname_capacity; lookup.found = 0; + if (dat_dns_name_foreach(find_name_for_key, &lookup) < 0) return -1; + if (!lookup.found) { errno = ENOENT; return -1; } + return 0; +} + +typedef struct name_resolve_context { + const char *hostname; + uint8_t *key; + int found; +} name_resolve_context_t; + +static int find_key_for_name(const char *name, const uint8_t key[DAT_KEY_BYTES], void *context) +{ + name_resolve_context_t *lookup = (name_resolve_context_t *)context; + if (!strcasecmp(lookup->hostname, name)) { + memcpy(lookup->key, key, DAT_KEY_BYTES); + lookup->found = 1; + return 1; + } + return 0; +} + +int dat_dns_name_resolve(const char *hostname, uint8_t key[DAT_KEY_BYTES]) +{ + char normalized[256]; + name_resolve_context_t lookup; + if (!key || normalize_hostname(hostname, normalized)) { if (!key) errno = EINVAL; return -1; } + lookup.hostname = normalized; lookup.key = key; lookup.found = 0; + if (dat_dns_name_foreach(find_key_for_name, &lookup) < 0) return -1; + if (!lookup.found) { errno = ENOENT; return -1; } + return 0; +} + +static int update_name(const char *hostname, const uint8_t *key, int remove_alias) +{ + char normalized[256], path[1200], temporary[1250], line[400], old_name[256]; + uint8_t old_key[DAT_KEY_BYTES]; + FILE *input = NULL, *output = NULL; + char hex[65]; + int found = 0, n, result = -1; + if (normalize_hostname(hostname, normalized) || (!remove_alias && !key)) return -1; + if (names_file(path, sizeof(path), 1)) return -1; + n = snprintf(temporary, sizeof(temporary), "%s.tmp", path); + if (n < 0 || (size_t)n >= sizeof(temporary)) { errno = ENAMETOOLONG; return -1; } + input = fopen(path, "r"); + if (!input && errno != ENOENT) return -1; + output = fopen(temporary, "w"); + if (!output) { if (input) fclose(input); return -1; } + while (input && fgets(line, sizeof(line), input)) { + if (!parse_name_line(line, old_name, old_key) && !strcasecmp(old_name, normalized)) { + found = 1; + if (!remove_alias) { + if (dat_key_to_hex(key, hex) || fprintf(output, "%s %s\n", normalized, hex) < 0) + goto done; + } + } else if (fputs(line, output) == EOF) goto done; + } + if (input && ferror(input)) goto done; + if (!remove_alias && !found) { + if (dat_key_to_hex(key, hex) || fprintf(output, "%s %s\n", normalized, hex) < 0) goto done; + } + if (fclose(output)) { output = NULL; goto done; } + output = NULL; + if (input && fclose(input)) { input = NULL; goto done; } + input = NULL; + if (remove_alias && !found) { errno = ENOENT; goto done; } + if (dat_replace_file(temporary, path)) goto done; + result = 0; +done: + if (input) fclose(input); + if (output) fclose(output); + if (result) unlink(temporary); + return result; +} + +int dat_dns_name_set(const char *hostname, const uint8_t key[DAT_KEY_BYTES]) +{ + return update_name(hostname, key, 0); +} + +int dat_dns_name_remove(const char *hostname) +{ + return update_name(hostname, NULL, 1); +} + +static int parse_datkey(const char *txt, size_t length, uint8_t key[32]) +{ + size_t i; + const char *value = NULL; + for (i = 0; i + 7 <= length; i++) { + if ((txt[i] == 'd' || txt[i] == 'D') && + (txt[i + 1] == 'a' || txt[i + 1] == 'A') && + (txt[i + 2] == 't' || txt[i + 2] == 'T') && + (txt[i + 3] == 'k' || txt[i + 3] == 'K') && + (txt[i + 4] == 'e' || txt[i + 4] == 'E') && + (txt[i + 5] == 'y' || txt[i + 5] == 'Y') && txt[i + 6] == '=') { + value = txt + i + 7; + break; + } + } + if (!value || (size_t)(txt + length - value) < 64) return 0; + for (i = 0; i < 32; i++) { + int hi = hex_value((unsigned char)value[i * 2]); + int lo = hex_value((unsigned char)value[i * 2 + 1]); + if (hi < 0 || lo < 0) return 0; + key[i] = (uint8_t)((hi << 4) | lo); + } + if ((size_t)(txt + length - value) > 64 && value[64] != '&' && value[64] != ';' && + value[64] != ' ' && value[64] != '"' && value[64] != '\'') + return 0; + return 1; +} + +int dat_dns_parse_answer(const uint8_t *packet, unsigned packet_len, + uint8_t key[DAT_KEY_BYTES], uint32_t *ttl_seconds) +{ + unsigned offset = 12, questions, answers, record_index; + if (!packet || packet_len < 12 || !key) { errno = EINVAL; return -1; } + questions = ((unsigned)packet[4] << 8) | packet[5]; + answers = ((unsigned)packet[6] << 8) | packet[7]; + for (record_index = 0; record_index < questions; record_index++) { + unsigned hops = 0, cursor = offset; + int jumped = 0; + for (;;) { + unsigned label; + if (cursor >= packet_len || ++hops > packet_len) { errno = EPROTO; return -1; } + label = packet[cursor++]; + if ((label & 0xc0u) == 0xc0u) { + if (cursor >= packet_len) { errno = EPROTO; return -1; } + if (!jumped) offset = cursor + 1; + cursor = ((label & 0x3fu) << 8) | packet[cursor]; jumped = 1; continue; + } + if (label & 0xc0u) { errno = EPROTO; return -1; } + if (!label) { if (!jumped) offset = cursor; break; } + if (label > 63 || label > packet_len - cursor) { errno = EPROTO; return -1; } + cursor += label; + } + if (packet_len - offset < 4) { errno = EPROTO; return -1; } + offset += 4; + } + for (record_index = 0; record_index < answers; record_index++) { + unsigned hops = 0, cursor = offset, type, data_length, end; + int jumped = 0; + uint32_t ttl; + for (;;) { + unsigned label; + if (cursor >= packet_len || ++hops > packet_len) { errno = EPROTO; return -1; } + label = packet[cursor++]; + if ((label & 0xc0u) == 0xc0u) { + if (cursor >= packet_len) { errno = EPROTO; return -1; } + if (!jumped) offset = cursor + 1; + cursor = ((label & 0x3fu) << 8) | packet[cursor]; jumped = 1; continue; + } + if (label & 0xc0u) { errno = EPROTO; return -1; } + if (!label) { if (!jumped) offset = cursor; break; } + if (label > 63 || label > packet_len - cursor) { errno = EPROTO; return -1; } + cursor += label; + } + if (packet_len - offset < 10) { errno = EPROTO; return -1; } + type = ((unsigned)packet[offset] << 8) | packet[offset + 1]; + ttl = ((uint32_t)packet[offset + 4] << 24) | ((uint32_t)packet[offset + 5] << 16) | + ((uint32_t)packet[offset + 6] << 8) | packet[offset + 7]; + data_length = ((unsigned)packet[offset + 8] << 8) | packet[offset + 9]; + offset += 10; + if (data_length > packet_len - offset) { errno = EPROTO; return -1; } + end = offset + data_length; + if (type == 16) { + char text[DAT_DNS_TXT_MAX]; + size_t used = 0; + while (offset < end) { + unsigned part = packet[offset++]; + if (part > end - offset || used + part >= sizeof(text)) { errno = EPROTO; return -1; } + memcpy(text + used, packet + offset, part); used += part; offset += part; + } + if (parse_datkey(text, used, key)) { if (ttl_seconds) *ttl_seconds = ttl; return 0; } + } else offset = end; + } + errno = ENOENT; + return -1; +} + +int dat_dns_resolve(const char *hostname, uint8_t key[DAT_KEY_BYTES], + uint32_t *ttl_seconds) +{ + size_t i; + uint64_t now; + uint32_t ttl = 0; + char normalized[256], request_url[320]; + if (!hostname || !*hostname || !key) { errno = EINVAL; return -1; } + if (normalize_hostname(hostname, normalized)) return -1; + if (dat_dns_name_resolve(normalized, key) == 0) { + if (ttl_seconds) *ttl_seconds = 0; + return 0; + } + now = (uint64_t)time(NULL); + for (i = 0; i < DAT_DNS_CACHE_SIZE; i++) { + if (dns_cache[i].active && dns_cache[i].expires_at <= now) + dns_cache[i].active = 0; + if (dns_cache[i].active && !strcasecmp(normalized, dns_cache[i].hostname)) { + memcpy(key, dns_cache[i].key, DAT_KEY_BYTES); + if (ttl_seconds) *ttl_seconds = (uint32_t)(dns_cache[i].expires_at - now); + return 0; + } + } + { + CURL *curl; + CURLcode result; + long status = 0; + dat_dns_body_t body; + int url_length = snprintf(request_url, sizeof(request_url), "https://%s/.well-known/dat", normalized); + if (url_length < 0 || (size_t)url_length >= sizeof(request_url)) { errno = ENAMETOOLONG; return -1; } + if (!curl_initialized) { if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) { errno = EIO; return -1; } curl_initialized = 1; } + memset(&body, 0, sizeof(body)); + curl = curl_easy_init(); + if (!curl) { errno = ENOMEM; return -1; } + curl_easy_setopt(curl, CURLOPT_URL, request_url); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, 2000L); + curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 2000L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, receive_dat_dns_body); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &body); + result = curl_easy_perform(curl); + if (result == CURLE_OK) curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); + curl_easy_cleanup(curl); + if (result != CURLE_OK || status != 200 || body.overflow) { errno = EHOSTUNREACH; return -1; } + if (parse_wellknown_record(body.text, body.length, key, &ttl)) return -1; + } + if (ttl_seconds) *ttl_seconds = ttl; + if (ttl) { + dat_dns_cache_entry_t *entry = &dns_cache[dns_cache_next++ % DAT_DNS_CACHE_SIZE]; + strcpy(entry->hostname, hostname); + memcpy(entry->key, key, DAT_KEY_BYTES); + entry->expires_at = now + ttl; + entry->active = 1; + } + return 0; +} diff --git a/extra/dat-dpi/src/dat_dpi.c b/extra/dat-dpi/src/dat_dpi.c new file mode 100644 index 0000000..770d6b4 --- /dev/null +++ b/extra/dat-dpi/src/dat_dpi.c @@ -0,0 +1,758 @@ +#define _POSIX_C_SOURCE 200809L +#include "dat.h" +#include "dat_dns.h" +#include "dat_fetch.h" +#include "dat_paths.h" +#include "dat_dpi_icons.h" +#include "s7.h" +#include "sleep_feed.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TAG_CAP 8192 +#define DPI_ARCHIVES_MAX 128 + +typedef struct { + char key[65]; + char root[1200]; + char title[256]; + char description[512]; + uint64_t version; + int used; +} dpi_archive_t; + +static dpi_archive_t open_archives[DPI_ARCHIVES_MAX]; +static s7_scheme *metadata_scheme; +static int quit_requested; + +typedef struct { + char *data; + size_t length; + size_t capacity; +} body_buffer_t; + +static int body_appendf(body_buffer_t *body, const char *format, ...) +{ + va_list args, copy; + int needed; + size_t capacity; + char *data; + va_start(args, format); + va_copy(copy, args); + needed = vsnprintf(NULL, 0, format, copy); + va_end(copy); + if (needed < 0) { va_end(args); return -1; } + if ((size_t)needed > SIZE_MAX - body->length - 1) { va_end(args); errno = EOVERFLOW; return -1; } + if (body->length + (size_t)needed + 1 > body->capacity) { + capacity = body->capacity ? body->capacity : 512; + while (capacity < body->length + (size_t)needed + 1) { + if (capacity > SIZE_MAX / 2) { capacity = body->length + (size_t)needed + 1; break; } + capacity *= 2; + } + data = realloc(body->data, capacity); + if (!data) { va_end(args); return -1; } + body->data = data; + body->capacity = capacity; + } + vsnprintf(body->data + body->length, body->capacity - body->length, format, args); + va_end(args); + body->length += (size_t)needed; + return 0; +} + +static void send_page(const char *url, const char *mime, const void *body, size_t length) +{ + const char *p; + printf("\nContent-Type: %s\r\n\r\n", mime); + if (length) fwrite(body, 1, length, stdout); + fflush(stdout); +} + +static int send_file(const char *url, const char *mime, const dat_archive_t *archive, + const sleep_metadata_entry_t *entry) +{ + sleep_feed_t feed; + uint64_t block; + size_t sent = 0; + const char *p; + if (entry->size > SIZE_MAX) { errno = EOVERFLOW; return -1; } + if (sleep_feed_open(&feed, archive->dat_path, "content", NULL)) return -1; + for (block = 0; block < entry->block_count; block++) { + if (!sleep_feed_has(&feed, (size_t)entry->block_offset + (size_t)block)) { + sleep_feed_close(&feed); errno = ENOENT; return -1; + } + } + fputs("\nContent-Type: %s\r\n\r\n", mime); + fflush(stdout); + for (block = 0; block < entry->block_count; block++) { + uint8_t *data = NULL; + size_t length = 0; + if (sleep_feed_read(&feed, (size_t)entry->block_offset + (size_t)block, + &data, &length) || length > (size_t)entry->size - sent) { + free(data); sleep_feed_close(&feed); errno = EIO; return -1; + } + if (length && fwrite(data, 1, length, stdout) != length) { + free(data); sleep_feed_close(&feed); errno = EIO; return -1; + } + sent += length; + free(data); + } + sleep_feed_close(&feed); + fflush(stdout); + if (sent != (size_t)entry->size) { errno = EPROTO; return -1; } + return ferror(stdout) ? -1 : 0; +} + +static void send_status(const char *message) +{ + const char *p; + fputs("", stdout); + fflush(stdout); +} + +static void send_reload(const char *url) +{ + const char *p; + fputs("", stdout); + fflush(stdout); +} + +static int html_append(body_buffer_t *body, const char *text) +{ + const unsigned char *p = (const unsigned char *)(text ? text : ""); + for (; *p; p++) { + const char *escape = NULL; + switch (*p) { + case '&': escape = "&"; break; + case '<': escape = "<"; break; + case '>': escape = ">"; break; + case '\"': escape = """; break; + case '\'': escape = "'"; break; + default: break; + } + if (escape) { if (body_appendf(body, "%s", escape)) return -1; } + else if (body_appendf(body, "%c", *p)) return -1; + } + return 0; +} + +static int url_append_path(body_buffer_t *body, const char *path) +{ + static const char hex[] = "0123456789ABCDEF"; + const unsigned char *p = (const unsigned char *)path; + for (; *p; p++) { + if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || + (*p >= '0' && *p <= '9') || strchr("/-_.~", *p)) { + if (body_appendf(body, "%c", *p)) return -1; + } else if (body_appendf(body, "%%%c%c", hex[*p >> 4], hex[*p & 15])) return -1; + } + return 0; +} + +/* Dillo DPIP tags end in space, quote, greater-than. */ +static int read_tag(FILE *in, char *tag, size_t cap) +{ + int c, quote = 0; + size_t n = 0; + while ((c = fgetc(in)) != EOF) { + if (!n && isspace((unsigned char)c)) continue; + if (n + 1 >= cap) { errno = EOVERFLOW; return -1; } + tag[n++] = (char)c; + if (c == '\'') { + if (quote) { + c = fgetc(in); + if (c == '\'') { + if (n + 1 >= cap) { errno = EOVERFLOW; return -1; } + tag[n++] = (char)c; + continue; + } + if (c != EOF) ungetc(c, in); + } + quote = !quote; + } + if (n >= 2 && tag[n - 2] == '\'' && tag[n - 1] == '>') { + tag[n] = 0; + return 1; + } + } + return n ? -1 : 0; +} + +static char *attribute(const char *tag, const char *name) +{ + char needle[64]; + const char *p, *start, *end; + char *out; + size_t n, used = 0; + if (snprintf(needle, sizeof(needle), " %s='", name) >= (int)sizeof(needle)) return NULL; + p = strstr(tag, needle); + if (p) start = p + strlen(needle); + else if (tag[0] == '<' && !strncmp(tag + 1, needle + 1, strlen(needle) - 1)) + start = tag + strlen(needle); + else return NULL; + end = start; + while (*end) { + if (*end == '\'' && end[1] != '\'') break; + if (*end == '\'' && end[1] == '\'') end += 2; + else end++; + } + if (!*end) return NULL; + n = (size_t)(end - start); + out = malloc(n + 1); + if (!out) return NULL; + while (start < end) { + out[used++] = *start; + if (*start == '\'' && start + 1 < end && start[1] == '\'') start++; + start++; + } + out[used] = 0; + return out; +} + +static int authenticate(const char *tag) +{ + char path[1024], line[512], secret[128]; + FILE *f; + char *msg; + int ok = 0; + const char *home = dat_home_directory(); + if (!home || snprintf(path, sizeof(path), "%s/.dillo/dpid_comm_keys", home) >= (int)sizeof(path)) return 0; + f = fopen(path, "r"); + if (!f) return 0; + if (fgets(line, sizeof(line), f) && sscanf(line, "%*u %127s", secret) == 1) { + msg = attribute(tag, "msg"); + if (msg && strlen(msg) == strlen(secret)) ok = memcmp(msg, secret, strlen(secret)) == 0; + free(msg); + } + fclose(f); + return ok; +} + +static s7_pointer manifest_field(s7_pointer record, const char *name) +{ + s7_pointer fields = record; + while (s7_is_pair(fields)) { + s7_pointer field = s7_car(fields), key, values; + if (s7_is_pair(field)) { + key = s7_car(field); + values = s7_cdr(field); + if (s7_is_symbol(key) && !strcmp(s7_symbol_name(key), name) && + s7_is_pair(values)) return s7_car(values); + } + fields = s7_cdr(fields); + } + return NULL; +} + +static void read_manifest(const dat_archive_t *archive, uint64_t version, + dpi_archive_t *record) +{ + dat_path_info_t info; + int listing; + uint8_t *data = NULL; + size_t length = 0; + if (!metadata_scheme) metadata_scheme = s7_init(); + if (metadata_scheme && + dat_archive_resolve_at(archive, version, "/dat.scm", 0, &info, &listing) == 0 && + !listing && info.entry.type == SLEEP_ENTRY_FILE && + dat_archive_read_file(archive, &info.entry, &data, &length) == 0 && + length < 1024u * 1024u && !memchr(data, 0, length)) { + char *source = malloc(length + 1); + if (source) { + s7_pointer port, parsed, title, description; + memcpy(source, data, length); source[length] = '\0'; + port = s7_open_input_string(metadata_scheme, source); + parsed = port ? s7_read(metadata_scheme, port) : NULL; + if (port) s7_close_input_port(metadata_scheme, port); + title = parsed ? manifest_field(parsed, "title") : NULL; + description = parsed ? manifest_field(parsed, "description") : NULL; + if (title && s7_is_string(title)) snprintf(record->title, sizeof(record->title), "%s", s7_string(title)); + if (description && s7_is_string(description)) snprintf(record->description, sizeof(record->description), "%s", s7_string(description)); + free(source); + } + } + free(data); + if (!record->title[0] || !record->description[0]) { + uint8_t *cname = NULL; + size_t cname_length = 0; + if (dat_archive_resolve_at(archive, version, "/CNAME", 0, &info, &listing) == 0 && + !listing && info.entry.type == SLEEP_ENTRY_FILE && + dat_archive_read_file(archive, &info.entry, &cname, &cname_length) == 0 && + cname_length && cname_length < sizeof(record->title)) { + while (cname_length && (cname[cname_length - 1] == '\n' || cname[cname_length - 1] == '\r')) cname_length--; + if (!record->title[0]) { memcpy(record->title, cname, cname_length); record->title[cname_length] = '\0'; } + } + free(cname); + } +} + +static dpi_archive_t *remember_archive(const dat_archive_t *archive, const char *keyhex, + const char *root, uint64_t version) +{ + size_t i; + dpi_archive_t *record = NULL; + for (i = 0; i < DPI_ARCHIVES_MAX; i++) { + if (open_archives[i].used && !strcmp(open_archives[i].key, keyhex)) { + record = &open_archives[i]; break; + } + if (!open_archives[i].used && !record) record = &open_archives[i]; + } + if (!record) return NULL; + if (!record->used) { + memset(record, 0, sizeof(*record)); + snprintf(record->key, sizeof(record->key), "%s", keyhex); + snprintf(record->root, sizeof(record->root), "%s", root); + snprintf(record->title, sizeof(record->title), "%s", keyhex); + record->used = 1; + } + record->version = version; + read_manifest(archive, version, record); + return record; +} + +static int parse_query_value(const char *url, const char *name, char *out, size_t capacity) +{ + const char *query = strchr(url, '?'); + size_t name_length = strlen(name); + if (!query) return 0; + query++; + while (*query) { + const char *end = strchr(query, '&'); + size_t part_length = end ? (size_t)(end - query) : strlen(query); + const char *equals = memchr(query, '=', part_length); + if (equals && (size_t)(equals - query) == name_length && !memcmp(query, name, name_length)) { + size_t value_length = part_length - name_length - 1; + if (value_length >= capacity) { errno = ENAMETOOLONG; return -1; } + memcpy(out, equals + 1, value_length); out[value_length] = '\0'; + return 1; + } + if (!end) break; + query = end + 1; + } + return 0; +} + +static int error_page(const char *url, int status, const char *message) +{ + const char *p; + (void)status; + printf("\nContent-Type: text/plain\r\n\r\n%s\n", message); + fflush(stdout); + return ferror(stdout) ? -1 : 0; +} + +typedef struct { + sleep_metadata_entry_t entry; + char name[1024]; + int type; +} listing_context_t; + +typedef struct { + listing_context_t *entries; + size_t count; + size_t capacity; +} listing_accumulator_t; + +static int list_entry(const sleep_metadata_entry_t *entry, const char *name, void *context) +{ + listing_accumulator_t *list = context; + listing_context_t *next; + if (list->count == list->capacity) { + size_t capacity = list->capacity ? list->capacity * 2 : 16; + next = realloc(list->entries, capacity * sizeof(*next)); + if (!next) return -1; + list->entries = next; list->capacity = capacity; + } + next = &list->entries[list->count++]; + memset(next, 0, sizeof(*next)); + next->entry = *entry; + next->type = entry->type == SLEEP_ENTRY_DIRECTORY ? 3 : 2; + if (strlen(name) >= sizeof(next->name)) { errno = ENAMETOOLONG; return -1; } + strcpy(next->name, name); + return 0; +} + +static int compare_listing(const void *left, const void *right) +{ + const listing_context_t *a = left, *b = right; + if (a->type != b->type) return b->type - a->type; + return -strcoll(a->name, b->name); +} + +static int append_listing_row(body_buffer_t *body, const dat_url_t *url, + const listing_context_t *entry, const char *link_path) +{ + const char *icon = entry->type == 1 ? dat_dpi_icon_updir : + entry->type == 3 ? dat_dpi_icon_directory : dat_dpi_icon_file; + char date[64] = ""; + if (entry->entry.mtime) { + time_t seconds = (time_t)(entry->entry.mtime / 1000u); + struct tm *tm = gmtime(&seconds); + if (tm) strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", tm); + } + if (body_appendf(body, "\"%s\"type == 1 ? "up" : entry->type == 2 ? "directory" : "file", icon)) return -1; + if (html_append(body, url->host)) return -1; + if (url->has_version && body_appendf(body, "+%llu", (unsigned long long)url->version)) return -1; + if (url_append_path(body, link_path) || body_appendf(body, "\">") || + html_append(body, entry->name)) return -1; + if (entry->type == 3 && body_appendf(body, "/")) return -1; + if (body_appendf(body, "")) return -1; + if (*date && html_append(body, date)) return -1; + if (body_appendf(body, "")) return -1; + if (entry->type == 2 && body_appendf(body, "%llu", (unsigned long long)entry->entry.size)) return -1; + return body_appendf(body, "\n"); +} + +static const char *dpi_content_type(const char *pathname) +{ + const char *dot = strrchr(pathname, '.'); + if (!dot) return "text/plain"; + dot++; + if (!strcmp(dot, "htm") || !strcmp(dot, "html")) return "text/html"; + if (!strcmp(dot, "png")) return "image/png"; + if (!strcmp(dot, "gif")) return "image/gif"; + if (!strcmp(dot, "jpg")) return "image/jpeg"; + return "text/plain"; +} + +static int serve_not_found(const char *url) +{ + static const char page[] = "

Not found

"; + send_page(url, "text/html", page, sizeof(page) - 1); + return -1; +} + +static int serve_timeout(const char *url) +{ + static const char page[] = "

Timed out

"; + send_page(url, "text/html", page, sizeof(page) - 1); + return -1; +} + +static int serve_directory(const char *url_text, const dat_url_t *url, + const dat_archive_t *archive, uint64_t version) +{ + listing_accumulator_t entries = {0}; + listing_context_t parent; + body_buffer_t body = {0}; + char parent_path[2048]; + size_t i, n; + int failed = 0; + if (dat_archive_readdir_at(archive, version, url->path, list_entry, &entries)) { + free(entries.entries); + return serve_not_found(url_text); + } + qsort(entries.entries, entries.count, sizeof(*entries.entries), compare_listing); + if (body_appendf(&body, "")) failed = 1; + if (!failed && html_append(&body, url->path)) failed = 1; + if (!failed && body_appendf(&body, "

")) failed = 1; + if (!failed && html_append(&body, url->path)) failed = 1; + if (!failed && body_appendf(&body, "

")) failed = 1; + if (strcmp(url->path, "/")) { + memset(&parent, 0, sizeof(parent)); + parent.type = 1; + strcpy(parent.name, ".."); + strcpy(parent_path, url->path); + n = strlen(parent_path); + while (n && parent_path[n - 1] == '/') parent_path[--n] = '\0'; + while (n && parent_path[n - 1] != '/') parent_path[--n] = '\0'; + if (!n) strcpy(parent_path, "/"); + else parent_path[n] = '\0'; + if (!failed && append_listing_row(&body, url, &parent, parent_path)) failed = 1; + } + for (i = 0; !failed && i < entries.count; i++) { + body_buffer_t href = {0}; + const listing_context_t *entry = &entries.entries[i]; + if (body_appendf(&href, "%s", url->path) || + (href.length && href.data[href.length - 1] != '/' && body_appendf(&href, "/")) || + body_appendf(&href, "%s%s", entry->name, entry->type == 3 ? "/" : "") || + append_listing_row(&body, url, entry, href.data)) failed = 1; + free(href.data); + } + if (!failed && body_appendf(&body, "
")) failed = 1; + if (failed) { + free(body.data); free(entries.entries); + return error_page(url_text, 500, "Unable to build directory listing"); + } + send_page(url_text, "text/html", body.data, body.length); + free(body.data); free(entries.entries); + return 0; +} + +static int json_append_string(body_buffer_t *body, const char *text) +{ + const unsigned char *p = (const unsigned char *)(text ? text : ""); + if (body_appendf(body, "\"")) return -1; + for (; *p; p++) { + if (*p == '\"' || *p == '\\') { if (body_appendf(body, "\\%c", *p)) return -1; } + else if (*p < 0x20) { if (body_appendf(body, "\\u%04x", *p)) return -1; } + else if (body_appendf(body, "%c", *p)) return -1; + } + return body_appendf(body, "\""); +} + +static int serve(const char *url_text) +{ + dat_url_t url; + uint8_t key[DAT_KEY_BYTES]; + const char *home = dat_home_directory(); + char root[1200], keyhex[65], fetch_error[512]; + dat_archive_t archive; + dat_path_info_t info; + int listing = 0; + uint64_t version; + if (dat_url_parse(url_text, &url)) { + error_page(url_text, 400, "Invalid dat:// URL"); return -1; + } + send_status("resolving name"); + if (dat_url_to_key(url.host, key) && dat_dns_resolve(url.host, key, NULL)) { + error_page(url_text, 404, "Dat hostname has no usable datkey TXT record"); return -1; + } + if (!home) { error_page(url_text, 500, "User home directory is unavailable"); return -1; } + if (dat_key_to_hex(key, keyhex)) { error_page(url_text, 500, "Unable to format archive key"); return -1; } + if (snprintf(root, sizeof(root), "%s/.dillo/dat/archives/%.2s/%s", + home, keyhex, keyhex + 2) >= (int)sizeof(root)) { + error_page(url_text, 400, "Dillo archive cache path is too long"); return -1; + } + if (dat_archive_open(&archive, root)) { + send_status("getting archive"); + if (dat_fetch_archive(root, key, 10000u)) { + int saved = errno; + if (saved == ETIMEDOUT) return serve_timeout(url_text); + const char *reason = saved == ENETUNREACH ? "No Cesspool bootstrap is configured" : + saved == EHOSTUNREACH ? "Cesspool bootstrap did not answer" : + saved == ENOENT ? "No peer is announcing this archive key" : + strerror(saved); + snprintf(fetch_error, sizeof(fetch_error), "Cesspool archive fetch failed: %s", reason); + return error_page(url_text, 404, fetch_error); + } + if (dat_archive_open(&archive, root)) { + snprintf(fetch_error, sizeof(fetch_error), "Fetched archive cannot be opened: %s", strerror(errno)); + return error_page(url_text, 404, fetch_error); + } + } + version = url.has_version ? url.version : dat_archive_version(&archive); + if (version > dat_archive_version(&archive)) { + dat_archive_close(&archive); return serve_not_found(url_text); + } + if (dat_archive_resolve_at(&archive, version, url.path, + url.force_directory_listing, &info, &listing)) { + int saved = errno; + dat_archive_close(&archive); + if (saved == ETIMEDOUT || saved == EAGAIN) return serve_timeout(url_text); + return serve_not_found(url_text); + } + (void)remember_archive(&archive, keyhex, root, version); + send_status("serving file"); + if (url.wants_stat) { + body_buffer_t stat = {0}; + if (body_appendf(&stat, "{\n \"path\": ") || json_append_string(&stat, url.path) || + body_appendf(&stat, ",\n \"directory\": %s,\n \"size\": %llu,\n \"mtime\": %llu\n}\n", + info.is_directory ? "true" : "false", + (unsigned long long)info.entry.size, (unsigned long long)info.entry.mtime)) { + free(stat.data); dat_archive_close(&archive); return error_page(url_text, 500, "Stat response is too large"); + } + send_page(url_text, "text/plain", stat.data, stat.length); + free(stat.data); + } else if (listing) { + serve_directory(url_text, &url, &archive, version); + } else { + if (info.entry.type != SLEEP_ENTRY_FILE) { + dat_archive_close(&archive); return serve_not_found(url_text); + } + if (send_file(url_text, dpi_content_type(info.entry.path), &archive, &info.entry)) { + dat_archive_close(&archive); return error_page(url_text, 500, "File content unavailable"); + } + } + dat_archive_close(&archive); + fflush(stdout); + return ferror(stdout) ? -1 : 0; +} + +static int serve_dashboard(const char *url) +{ + char action[32], key[80], reload[2048]; + int has_action, has_key, has_reload; + body_buffer_t body = {0}; + size_t i, count = 0; + if (parse_query_value(url, "unswarm", action, sizeof(action)) > 0) { + has_key = parse_query_value(url, "key", key, sizeof(key)); + has_reload = parse_query_value(url, "reload", reload, sizeof(reload)); + if (has_key > 0) { + for (i = 0; i < DPI_ARCHIVES_MAX; i++) + if (open_archives[i].used && !strcmp(open_archives[i].key, key)) + memset(&open_archives[i], 0, sizeof(open_archives[i])); + send_status("unswarming archive"); + send_reload(has_reload > 0 ? reload : "dpi:/dat/"); + return 0; + } + } + has_action = parse_query_value(url, "quit", action, sizeof(action)); + if (has_action > 0) { + static const char quitting[] = "Quitting"; + send_page(url, "text/plain", quitting, sizeof(quitting) - 1); + quit_requested = 1; + return 0; + } + if (body_appendf(&body, "Dat")) goto fail; + if (body_appendf(&body, "

Archives

")) goto fail; + for (i = 0; i < DPI_ARCHIVES_MAX; i++) if (open_archives[i].used) count++; + if (!count && body_appendf(&body, "

None

")) goto fail; + for (i = 0; i < DPI_ARCHIVES_MAX; i++) { + dpi_archive_t *archive = &open_archives[i]; + if (!archive->used) continue; + if (body_appendf(&body, "

", archive->key) || + html_append(&body, archive->title[0] ? archive->title : archive->key) || + body_appendf(&body, "")) goto fail; + if (archive->description[0] && + (body_appendf(&body, " - ") || html_append(&body, archive->description))) goto fail; + if (body_appendf(&body, "
version %llu

", + archive->key, (unsigned long long)archive->version, + (unsigned long long)archive->version) || + body_appendf(&body, "

Peers

None")) goto fail; + if (body_appendf(&body, "
", archive->key)) goto fail; + } + if (body_appendf(&body, "")) goto fail; + send_page(url, "text/html", body.data, body.length); + free(body.data); + return 0; +fail: + free(body.data); + return error_page(url, 500, "Unable to build Dat dashboard"); +} + +static int serve_internal(const char *url) +{ + const char *path = strstr(url, "://"); + const char *query; + char decoded[2048]; + size_t n; + if (!strncmp(url, "dpi:/", 5)) path = url + 4; + else if (path) path = strchr(path + 3, '/'); + if (!path) path = "/"; + query = strchr(path, '?'); + n = query ? (size_t)(query - path) : strlen(path); + if (n >= sizeof(decoded)) return error_page(url, 400, "Internal DPI path is too long"); + memcpy(decoded, path, n); decoded[n] = '\0'; + if (!strcmp(decoded, "/dat/")) return serve_dashboard(url); + return serve_not_found(url); +} + +static int serve_stdio_requests(void) +{ + char tag[TAG_CAP]; + int result = read_tag(stdin, tag, sizeof(tag)); + if (result <= 0 || !authenticate(tag)) return 1; + while (!quit_requested) { + char *cmd, *url; + result = read_tag(stdin, tag, sizeof(tag)); + if (result <= 0) break; + cmd = attribute(tag, "cmd"); + url = attribute(tag, "url"); + if (cmd && !strcmp(cmd, "DpiBye")) { free(cmd); free(url); break; } + if (cmd && !strcmp(cmd, "open_url") && url) { + if (!strncmp(url, "dat:", 4)) serve(url); + else if (!strncmp(url, "dpi:/", 5)) serve_internal(url); + else error_page(url, 404, "Not found"); + } + free(cmd); + free(url); + } + return 0; +} + +static int serve_socket_requests(void) +{ + int listener = dup(STDIN_FILENO), stopped = 0; + if (listener < 0) return -1; + while (!stopped) { + int client = accept(listener, NULL, NULL); + int saved_in, saved_out, stop_after_request = 0; + char tag[TAG_CAP]; + if (client < 0) { + if (errno == EINTR) continue; + close(listener); + return -1; + } + saved_in = dup(STDIN_FILENO); + saved_out = dup(STDOUT_FILENO); + if (saved_in < 0 || saved_out < 0 || dup2(client, STDIN_FILENO) < 0 || + dup2(client, STDOUT_FILENO) < 0) { + if (saved_in >= 0) close(saved_in); + if (saved_out >= 0) close(saved_out); + close(client); + close(listener); + return -1; + } + clearerr(stdin); + clearerr(stdout); + if (read_tag(stdin, tag, sizeof(tag)) > 0 && authenticate(tag)) { + while (!stop_after_request) { + char *cmd, *url; + int result = read_tag(stdin, tag, sizeof(tag)); + if (result <= 0) break; + cmd = attribute(tag, "cmd"); + url = attribute(tag, "url"); + if (cmd && !strcmp(cmd, "DpiBye")) { + stopped = 1; + stop_after_request = 1; + } else if (cmd && !strcmp(cmd, "open_url") && url) { + if (!strncmp(url, "dat:", 4)) serve(url); + else if (!strncmp(url, "dpi:/", 5)) serve_internal(url); + else error_page(url, 404, "Not found"); + stop_after_request = 1; + } + free(cmd); + free(url); + } + } + fflush(stdout); + /* The historical dillo-dat plugin ends the accepted socket per page. */ + dup2(saved_in, STDIN_FILENO); + dup2(saved_out, STDOUT_FILENO); + close(saved_in); + close(saved_out); + close(client); + clearerr(stdin); + clearerr(stdout); + } + close(listener); + return 0; +} + +int main(int argc, char **argv) +{ + int result; + (void)setlocale(LC_COLLATE, ""); + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + metadata_scheme = s7_init(); + if (argc == 2 && !strcmp(argv[1], "--stdio-test")) + result = serve_stdio_requests(); + else + result = serve_socket_requests(); + if (metadata_scheme) s7_free(metadata_scheme); + return result < 0 ? 1 : result; +} diff --git a/extra/dat-dpi/src/dat_dpi_icons.h b/extra/dat-dpi/src/dat_dpi_icons.h new file mode 100644 index 0000000..988d42d --- /dev/null +++ b/extra/dat-dpi/src/dat_dpi_icons.h @@ -0,0 +1,33 @@ +#ifndef DAT_DPI_ICONS_H +#define DAT_DPI_ICONS_H +/* PNG data URIs copied from the bundled historical dillo-dat DPI. */ +static const char dat_dpi_icon_updir[] = + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAKxJ" + "REFUeNpi/P//PwMlgImBQjAMDGBBF2BkZISz09LSwCE8a9YsuCBGoIMEkDEMJCUl/b90+QoYg9i41LNgc1ZycvL/hMQkhgcPH4H5" + "iUnJIJf9nzt3LiNBL2RkZPwPj4hk4BMUYuDh44MEFDMLQ0xsHAMrKyvIJYyEwuDLiuXLeP7+/Qv3EihcmJmZGZiYmL5gqEcPFKBi" + "AyDFjCPQ/wLVX8BrwGhSJh0ABBgAsetR5KBfw9EAAAAASUVORK5CYII=" + ; +static const char dat_dpi_icon_directory[] = + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAN1wAADdcBQiibeAAAABl0" + "RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAXdEVYdEF1dGhvcgBMYXBvIENhbGFtYW5kcmVp35EaKgAAACl0RVh0" + "RGVzY3JpcHRpb24AQmFzZWQgb2YgSmFrdWIgU3RlaW5lciBkZXNpZ26ghAVzAAABbElEQVQ4jaWQO0tDQRCFz2x2A8YHQoogaKFW" + "2qSysbATsdAIWgrWlhIFBRvLoFhZW/gb0vgPRBAStEgExZA2VR7X3Nw7MxY3BhUjCU6zMOz5zrcL/HPo/HDzREFnZMj1tgoI1FPm" + "/ePL/M2fgNxRxltaXh8xxkCEoSIQYQQdH6XHO6/T8ZePL/PFfgBLCifCqJQfesswDNBoNhAEnQQRFXLZjV+qAefiRQsAba/e27MI" + "Wl4Ta1t7SE3N9lVXEVxfnaYtyJjS0z04DCMlF8fK6jaSyRQatUpfwFhypvsEUrOze4CxiUmoAlBF4LfwXq/1DUcG3UJhRmJ0HI1a" + "9c/AzxGOAAYApEsbCiBfAMrDA5T5nwb8zYCHN/j8RABQFYAINGgYgEhUamPGKLOQiyciCFH3NABRdFsFqhoVqUJV4bebiBmjNmZd" + "8eW5kJ6bXxhUAADw9lpWY12BLrKZRWNjt0EYTA8DsM5Vw7a/9gEhN65EVGzVRQAAAABJRU5ErkJggg=" + ; +static const char dat_dpi_icon_file[] = + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAN1wAADdcBQiibeAAAABl0" + "RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAASdEVYdFRpdGxlAFBhcGVyIFNoZWV0c7mvkfkAAAAXdEVYdEF1dGhv" + "cgBMYXBvIENhbGFtYW5kcmVp35EaKgAAACd0RVh0RGVzY3JpcHRpb24Ad2l0aCBhIEhVR0UgaGVscCBmcm9tIEpha3VihlQHswAA" + "AhNJREFUOI11kstqU1EUhr91ctI2A2uTNsRaOxDEkeILiIgTL6CCAx+iUnTSgQPBRxAFSxWhA8XiBQst7aQjUV+kMWlzOaeJVZvs" + "y3JwctK0wQWLvQabb/3/v7eoKuubqzdFZMk5PwuKqqIKoAB/Qba8d8/v3b2/xfFSVVbXPpWbUUO990Pd7Xa0Uv2paxurf1Y+vnuc" + "wA87AOh0OjP5iQL7v/dptWOacZ1ao0plZ5vdepV2q8Wt67dzxanik7fvlxcGBQQAxlgAqpUK5e0KO5Ua9d2IuNlmL/pFuVwhCAKu" + "XrmWGx0Ze/pm+dXlFBAmAANAYSqPcy5p73DO4pwjE8OHzyuMZXNcvHAp9/3H1wXgWx9gjQGURi3CWjuU01S+xMkTBbxYgiCQg4OD" + "Gy9ePsvMzz1yfQUKTBTGcc7iVVHv8T5V4hhhFJExzp09z8bmesarzwIpINkaN1s454YUpCWBkC706gcysEkG+clxnPNo7y/0PsMh" + "QHoAa1CvwyFCQBAoipBcFY4eyWCtxTt/FCBAHO3h7P8tZMIMpeI0xlh8z+pABkLpVBG0J1UGVKQKVBARrDH9rAaeERq1iG63298Y" + "hiFnZmf63rWXiTEGd9wCwOmZaUTkaA8ooJfpEEBEqnEcTRcKk//1n1a73QIkMtZ0EluqzD98cCfMhoum2y2pgpI84fEZlGx2pG6M" + "mVtafP0F4B+wR1eZMTEGTgAAAABJRU5ErkJggg==" + ; +#endif diff --git a/extra/dat-dpi/src/dat_fetch.c b/extra/dat-dpi/src/dat_fetch.c new file mode 100644 index 0000000..d62c69e --- /dev/null +++ b/extra/dat-dpi/src/dat_fetch.c @@ -0,0 +1,617 @@ +#define _POSIX_C_SOURCE 200809L +#include "dat_fetch.h" +#include "dat.h" +#include "dat_paths.h" +#include "cesspool.h" +#include "dat_session.h" +#include "sleep_crypto.h" +#include "sleep_utp.h" + +#include +#include +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#include +#include +#else +#include +#include +#include +#include +#define mkdir(path, mode) _mkdir(path) +#define chmod(path, mode) 0 +#endif + +#define FETCH_HELLO_BYTES 132u +#define FETCH_ACK_BYTES 100u +#define FETCH_RECORD_LIMIT (4u * 1024u * 1024u) + +typedef struct fetch_client fetch_client_t; +typedef struct fetch_transport { + sleep_utp_t utp; + fetch_client_t *owner; + uint8_t peer_id[32]; + uint8_t local_nonce[32], remote_nonce[32]; + uint8_t auth[FETCH_HELLO_BYTES + FETCH_ACK_BYTES]; + size_t auth_length; + uint8_t *tx, *rx; + size_t tx_length, tx_offset, tx_capacity; + size_t rx_length, rx_capacity; + int used, authenticated, hello_sent, hello_received; + int ack_sent, ack_received, waiting_incoming; +} fetch_transport_t; + +struct fetch_client { + cesspool_t pool; + sleep_keypair_t identity; + sleep_feed_t metadata, content; + dat_session_t session; + uint8_t metadata_key[32], content_key[32], topic[32]; + fetch_transport_t transports[DAT_SESSION_MAX_PEERS]; + int pool_ready, session_ready, content_known, failed; + int failure_errno; +}; + +static uint64_t fetch_now_ms(void) +{ + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts)) return 0; + return (uint64_t)ts.tv_sec * 1000u + (uint64_t)ts.tv_nsec / 1000000u; +} + +static int ensure_dir(const char *path) +{ + if (mkdir(path, 0700) == 0 || errno == EEXIST) return 0; + return -1; +} + +static int ensure_dir_tree(const char *path) +{ + char copy[1200]; + size_t i, length; + if (!path || !*path) { errno = EINVAL; return -1; } + length = strlen(path); + if (length >= sizeof(copy)) { errno = ENAMETOOLONG; return -1; } + memcpy(copy, path, length + 1); + for (i = 0; i < length; i++) { + if (copy[i] != '/' && copy[i] != '\\') continue; + if (i == 0 || (i == 2 && copy[1] == ':')) continue; + { + char saved = copy[i]; + copy[i] = '\0'; + if (*copy && ensure_dir(copy)) return -1; + copy[i] = saved; + } + } + return ensure_dir(copy); +} + +/* Cached public keys identify the archive. Never replace a mismatched key + * when a later request reuses this cache directory. */ +static int persist_pubkey(const char *path, const uint8_t key[32]) +{ + uint8_t existing[32]; + if (sleep_load_pubkey(path, existing) == 0) { + if (memcmp(existing, key, sizeof(existing))) { errno = EACCES; return -1; } + return 0; + } + if (errno != ENOENT) return -1; + return sleep_save_pubkey(path, key); +} + +static int load_identity(sleep_keypair_t *identity) +{ + const char *home = dat_home_directory(); + char directory[1100], path[1200]; + int n; + if (!home || !*home) { errno = ENOENT; return -1; } + n = snprintf(directory, sizeof(directory), "%s/.dat", home); + if (n < 0 || (size_t)n >= sizeof(directory) || ensure_dir(directory)) return -1; + n = snprintf(path, sizeof(path), "%s/dillo-dat.network-key", directory); + if (n < 0 || (size_t)n >= sizeof(path)) { errno = ENAMETOOLONG; return -1; } + if (sleep_load_keypair(path, identity) == 0) return 0; + if (errno != ENOENT || sleep_gen_keypair(identity) || + sleep_save_keypair(path, identity) || chmod(path, 0600)) return -1; + return 0; +} + +static fetch_transport_t *transport_by_id(fetch_client_t *client, + const uint8_t peer_id[32]) +{ + size_t i; + for (i = 0; i < DAT_SESSION_MAX_PEERS; i++) + if (client->transports[i].used && + !memcmp(client->transports[i].peer_id, peer_id, 32)) + return &client->transports[i]; + return NULL; +} + +static fetch_transport_t *transport_create(fetch_client_t *client, + const uint8_t peer_id[32]) +{ + size_t i; + fetch_transport_t *transport = transport_by_id(client, peer_id); + if (transport) return transport; + for (i = 0; i < DAT_SESSION_MAX_PEERS; i++) { + transport = &client->transports[i]; + if (transport->used) continue; + memset(transport, 0, sizeof(*transport)); + if (sleep_utp_init(&transport->utp, client->pool.net.sock)) return NULL; + transport->used = 1; + transport->owner = client; + memcpy(transport->peer_id, peer_id, 32); + transport->utp.context = transport; + return transport; + } + errno = ENOSPC; + return NULL; +} + +static int flush_transport(fetch_transport_t *transport) +{ + while (transport->tx_offset < transport->tx_length && + transport->utp.state == SLEEP_UTP_CONNECTED) { + size_t amount = transport->tx_length - transport->tx_offset; + if (amount > SLEEP_UTP_MTU) amount = SLEEP_UTP_MTU; + if (sleep_utp_send(&transport->utp, transport->tx + transport->tx_offset, amount)) { + if (errno == EAGAIN || errno == ENOBUFS) return 0; + return -1; + } + transport->tx_offset += amount; + } + if (transport->tx_offset == transport->tx_length) + transport->tx_offset = transport->tx_length = 0; + return 0; +} + +static int queue_transport(fetch_transport_t *transport, const uint8_t *bytes, size_t length) +{ + size_t queued, needed, capacity; + uint8_t *next; + if (transport->tx_offset && transport->tx_length + length > transport->tx_capacity) { + memmove(transport->tx, transport->tx + transport->tx_offset, + transport->tx_length - transport->tx_offset); + transport->tx_length -= transport->tx_offset; + transport->tx_offset = 0; + } + queued = transport->tx_length - transport->tx_offset; + if (length > FETCH_RECORD_LIMIT - queued) { errno = ENOBUFS; return -1; } + needed = transport->tx_length + length; + if (needed > transport->tx_capacity) { + capacity = transport->tx_capacity ? transport->tx_capacity : 4096; + while (capacity < needed) { + if (capacity >= FETCH_RECORD_LIMIT / 2) { capacity = FETCH_RECORD_LIMIT; break; } + capacity *= 2; + } + next = realloc(transport->tx, capacity); + if (!next) return -1; + transport->tx = next; + transport->tx_capacity = capacity; + } + memcpy(transport->tx + transport->tx_length, bytes, length); + transport->tx_length += length; + return flush_transport(transport); +} + +static int send_session_frame(void *context, const uint8_t peer_id[32], + const uint8_t *frame, size_t frame_len) +{ + fetch_client_t *client = context; + fetch_transport_t *transport = transport_by_id(client, peer_id); + uint8_t *record; + size_t prefix, written; + int rc; + if (!transport || !transport->authenticated) { errno = ENOTCONN; return -1; } + if (frame_len > FETCH_RECORD_LIMIT) { errno = EMSGSIZE; return -1; } + prefix = 1 + sleep_varint_size(frame_len); + record = malloc(prefix + frame_len); + if (!record) return -1; + record[0] = 0; + if (sleep_varint_encode(frame_len, record + 1, prefix - 1, &written)) { free(record); return -1; } + memcpy(record + prefix, frame, frame_len); + rc = queue_transport(transport, record, prefix + frame_len); + free(record); + return rc; +} + +static int send_hello(fetch_transport_t *transport) +{ + uint8_t hello[FETCH_HELLO_BYTES]; + if (transport->hello_sent || transport->utp.state != SLEEP_UTP_CONNECTED) return 0; + memcpy(hello, "THH0", 4); + memcpy(hello + 4, transport->owner->identity.pubkey, 32); + randombytes_buf(transport->local_nonce, sizeof(transport->local_nonce)); + memcpy(hello + 36, transport->local_nonce, 32); + if (sleep_sign(hello + 68, hello, 68, &transport->owner->identity) || + sleep_utp_send(&transport->utp, hello, sizeof(hello))) return -1; + transport->hello_sent = 1; + return 0; +} + +static int send_ack(fetch_transport_t *transport) +{ + uint8_t ack[FETCH_ACK_BYTES], transcript[132]; + if (transport->ack_sent) return 0; + memcpy(ack, "THA0", 4); + memcpy(ack + 4, transport->remote_nonce, 32); + memcpy(transcript, ack, 36); + memcpy(transcript + 36, transport->local_nonce, 32); + memcpy(transcript + 68, transport->owner->identity.pubkey, 32); + memcpy(transcript + 100, transport->peer_id, 32); + if (sleep_sign(ack + 36, transcript, sizeof(transcript), &transport->owner->identity) || + sleep_utp_send(&transport->utp, ack, sizeof(ack))) return -1; + transport->ack_sent = 1; + return 0; +} + +static int activate_content(fetch_client_t *client) +{ + sleep_metadata_header_t header; + uint8_t *block = NULL; + size_t length = 0; + char key_path[1200]; + int n; + if (client->content_known || !sleep_feed_has(&client->metadata, 0)) return 0; + if (sleep_feed_read(&client->metadata, 0, &block, &length)) return -1; + if (sleep_metadata_header_decode(&header, block, length) || + strcmp(header.type, "hyperdrive") || header.content_len != 32) { + free(block); errno = EPROTO; return -1; + } + free(block); + if (sleep_feed_load_remote(&client->content, header.content) || + dat_session_set_content_key(&client->session, header.content)) return -1; + n = snprintf(key_path, sizeof(key_path), "%s/content.key", client->metadata.directory); + if (n < 0 || (size_t)n >= sizeof(key_path) || persist_pubkey(key_path, header.content)) return -1; + memcpy(client->content_key, header.content, 32); + client->content_known = 1; + return 0; +} + +static int receive_records(fetch_transport_t *transport, const uint8_t *bytes, size_t length) +{ + fetch_client_t *client = transport->owner; + size_t need, capacity; + uint8_t *next; + if (length > FETCH_RECORD_LIMIT - transport->rx_length) { errno = ENOBUFS; return -1; } + need = transport->rx_length + length; + if (need > transport->rx_capacity) { + capacity = transport->rx_capacity ? transport->rx_capacity : 4096; + while (capacity < need) capacity = capacity > FETCH_RECORD_LIMIT / 2 ? FETCH_RECORD_LIMIT : capacity * 2; + next = realloc(transport->rx, capacity); + if (!next) return -1; + transport->rx = next; + transport->rx_capacity = capacity; + } + if (length) memcpy(transport->rx + transport->rx_length, bytes, length); + transport->rx_length += length; + for (;;) { + uint64_t frame_len; + size_t varint_len, prefix; + int saved_errno; + if (transport->rx_length < 2) return 0; + if (transport->rx[0] != 0) { errno = EPROTO; return -1; } + errno = 0; + if (sleep_varint_decode(transport->rx + 1, transport->rx_length - 1, + &frame_len, &varint_len)) { + saved_errno = errno; + if (saved_errno == EAGAIN) return 0; + errno = saved_errno; return -1; + } + if (frame_len > FETCH_RECORD_LIMIT) { errno = EMSGSIZE; return -1; } + prefix = 1 + varint_len; + if (frame_len > transport->rx_length - prefix) return 0; + if (dat_session_receive(&client->session, transport->peer_id, + transport->rx + prefix, (size_t)frame_len) || + activate_content(client)) return -1; + memmove(transport->rx, transport->rx + prefix + (size_t)frame_len, + transport->rx_length - prefix - (size_t)frame_len); + transport->rx_length -= prefix + (size_t)frame_len; + } +} + +static void on_utp_data(sleep_utp_t *utp, const uint8_t *bytes, size_t length) +{ + fetch_transport_t *transport = utp ? utp->context : NULL; + if (!transport || !transport->used) return; + if (transport->authenticated) { + if (receive_records(transport, bytes, length)) { + transport->owner->failed = 1; + transport->owner->failure_errno = errno ? errno : EPROTO; + } + return; + } + if (length > sizeof(transport->auth) - transport->auth_length) { + transport->owner->failed = 1; transport->owner->failure_errno = EMSGSIZE; return; + } + memcpy(transport->auth + transport->auth_length, bytes, length); + transport->auth_length += length; + for (;;) { + if (!transport->hello_received && transport->auth_length >= FETCH_HELLO_BYTES) { + const uint8_t *hello = transport->auth; + if (memcmp(hello, "THH0", 4) || memcmp(hello + 4, transport->peer_id, 32) || + sleep_verify(hello + 68, hello, 68, transport->peer_id)) { + transport->owner->failed = 1; transport->owner->failure_errno = EACCES; return; + } + memcpy(transport->remote_nonce, hello + 36, 32); + transport->hello_received = 1; + memmove(transport->auth, transport->auth + FETCH_HELLO_BYTES, + transport->auth_length - FETCH_HELLO_BYTES); + transport->auth_length -= FETCH_HELLO_BYTES; + if (send_ack(transport)) { transport->owner->failed = 1; transport->owner->failure_errno = errno; return; } + continue; + } + if (transport->hello_received && !transport->ack_received && + transport->auth_length >= FETCH_ACK_BYTES) { + const uint8_t *ack = transport->auth; + uint8_t transcript[132]; + if (memcmp(ack, "THA0", 4) || memcmp(ack + 4, transport->local_nonce, 32)) { + transport->owner->failed = 1; transport->owner->failure_errno = EPROTO; return; + } + memcpy(transcript, ack, 36); + memcpy(transcript + 36, transport->remote_nonce, 32); + memcpy(transcript + 68, transport->peer_id, 32); + memcpy(transcript + 100, transport->owner->identity.pubkey, 32); + if (sleep_verify(ack + 36, transcript, sizeof(transcript), transport->peer_id)) { + transport->owner->failed = 1; transport->owner->failure_errno = EACCES; return; + } + transport->ack_received = 1; + memmove(transport->auth, transport->auth + FETCH_ACK_BYTES, + transport->auth_length - FETCH_ACK_BYTES); + transport->auth_length -= FETCH_ACK_BYTES; + transport->authenticated = 1; + if (dat_session_peer_secure(&transport->owner->session, transport->peer_id, 1)) { + transport->owner->failed = 1; transport->owner->failure_errno = errno; return; + } + continue; + } + if (transport->authenticated && transport->auth_length) { + if (receive_records(transport, transport->auth, transport->auth_length)) { + transport->owner->failed = 1; transport->owner->failure_errno = errno; return; + } + transport->auth_length = 0; + } + return; + } +} + +static int same_endpoint(const struct sockaddr_storage *a, const struct sockaddr_storage *b) +{ + const struct sockaddr_in *aa = (const struct sockaddr_in *)a; + const struct sockaddr_in *bb = (const struct sockaddr_in *)b; + return aa->sin_family == AF_INET && bb->sin_family == AF_INET && + aa->sin_port == bb->sin_port && aa->sin_addr.s_addr == bb->sin_addr.s_addr; +} + +static void send_hello_if_ready(fetch_transport_t *transport) +{ + if (transport->utp.state == SLEEP_UTP_CONNECTED && send_hello(transport)) { + transport->owner->failed = 1; + transport->owner->failure_errno = errno; + } +} + +static void transport_packet(cesspool_t *pool, const dat_peer_t *source, + const uint8_t *bytes, size_t length) +{ + fetch_client_t *client = pool ? pool->transport_context : NULL; + sleep_utp_packet_t packet; + fetch_transport_t *transport = NULL; + cesspool_peer_t *known = NULL; + size_t i; + if (!client || sleep_utp_packet_decode(&packet, bytes, length)) return; + if (packet.type == SLEEP_UTP_SYN) { + for (i = 0; i < pool->peer_count; i++) + if (pool->peers[i].discovered && + !memcmp(pool->peers[i].discovery_topic, client->topic, 32) && + same_endpoint(&pool->peers[i].address.addr, &source->addr)) { + known = &pool->peers[i]; break; + } + if (!known || memcmp(client->identity.pubkey, known->public_key, 32) < 0) return; + transport = transport_by_id(client, known->public_key); + if (!transport) transport = transport_create(client, known->public_key); + if (!transport || transport->utp.state == SLEEP_UTP_CONNECTED) return; + transport->waiting_incoming = 0; + transport->utp.on_data = on_utp_data; + if (sleep_utp_accept(&transport->utp, &packet, + (const struct sockaddr *)&source->addr, source->addrlen)) return; + send_hello_if_ready(transport); + return; + } + for (i = 0; i < DAT_SESSION_MAX_PEERS; i++) { + fetch_transport_t *candidate = &client->transports[i]; + if (candidate->used && candidate->utp.receive_id == packet.connection_id && + same_endpoint(&candidate->utp.remote, &source->addr)) { transport = candidate; break; } + } + if (!transport) return; + if (sleep_utp_input(&transport->utp, bytes, length)) return; + send_hello_if_ready(transport); +} + +static void peer_discovered(void *context, const dat_session_peer_t *peer) +{ + fetch_client_t *client = context; + fetch_transport_t *transport = transport_create(client, peer->peer_id); + if (!transport) return; + transport->utp.on_data = on_utp_data; + if (memcmp(client->identity.pubkey, peer->peer_id, 32) > 0) { + transport->waiting_incoming = 1; + return; + } + if (sleep_utp_connect(&transport->utp, + (const struct sockaddr *)&peer->address.addr, + peer->address.addrlen)) { + transport->owner->failed = 1; + transport->owner->failure_errno = errno; + } +} + +static int open_remote_feed(sleep_feed_t *feed, const char *directory, + const char *name, const uint8_t key[32]) +{ + if (sleep_feed_open(feed, directory, name, NULL) != 0) { + if (errno != ENOENT) return -1; + return sleep_feed_open_remote(feed, directory, name, key); + } + if (sleep_feed_load_remote(feed, key)) { sleep_feed_close(feed); return -1; } + feed->persist_remote = 1; + memcpy(feed->keypair.pubkey, key, 32); + return 0; +} + +static int feed_complete(const sleep_feed_t *feed, const sleep_replication_t *replication) +{ + uint64_t i; + if (!replication->remote_length || replication->remote_length > (uint64_t)SLEEP_BITFIELD_DATA_BYTES * 8u) return 0; + for (i = 0; i < replication->remote_length; i++) if (!sleep_feed_has(feed, (size_t)i)) return 0; + return 1; +} + +static int archive_complete(const fetch_client_t *client) +{ + size_t p; + for (p = 0; p < client->session.peer_count; p++) { + const dat_session_peer_t *peer = &client->session.peers[p]; + if (peer->state != DAT_SESSION_PEER_READY) continue; + if (!feed_complete(&client->metadata, &peer->replication[DAT_SESSION_METADATA])) continue; + if (!client->content_known || + !feed_complete(&client->content, &peer->replication[DAT_SESSION_CONTENT])) continue; + return 1; + } + return 0; +} + +static void cleanup(fetch_client_t *client) +{ + size_t i; + if (client->session_ready) (void)dat_session_close(&client->session); + if (client->pool_ready) (void)cesspool_destroy(&client->pool); + for (i = 0; i < DAT_SESSION_MAX_PEERS; i++) { + fetch_transport_t *transport = &client->transports[i]; + if (transport->used) (void)sleep_utp_close(&transport->utp); + free(transport->tx); free(transport->rx); + } + sleep_feed_close(&client->metadata); + sleep_feed_close(&client->content); +} + +int dat_fetch_archive(const char *root, const uint8_t metadata_key[32], + unsigned timeout_ms) +{ + fetch_client_t *client; + char datdir[1200], path[1400]; + uint8_t unknown_content[32] = {0}; + uint16_t port = DATNET_PORT; + uint64_t deadline; + int n, rc = -1, bootstrap_count; + if (!root || !metadata_key) { errno = EINVAL; return -1; } + client = calloc(1, sizeof(*client)); + if (!client) return -1; + memcpy(client->metadata_key, metadata_key, 32); + if (sleep_crypto_init() || load_identity(&client->identity)) goto done; + if (ensure_dir_tree(root)) goto done; + n = snprintf(datdir, sizeof(datdir), "%s/.dat", root); + if (n < 0 || (size_t)n >= sizeof(datdir) || ensure_dir(datdir)) goto done; + n = snprintf(path, sizeof(path), "%s/metadata.key", datdir); + if (n < 0 || (size_t)n >= sizeof(path) || persist_pubkey(path, metadata_key)) goto done; + if (open_remote_feed(&client->metadata, datdir, "metadata", metadata_key) || + sleep_feed_open_remote(&client->content, datdir, "content", unknown_content)) goto done; + n = snprintf(path, sizeof(path), "%s/content.key", datdir); + if (n < 0 || (size_t)n >= sizeof(path)) { errno = ENAMETOOLONG; goto done; } + if (sleep_load_pubkey(path, client->content_key) == 0) { + if (sleep_feed_load_remote(&client->content, client->content_key)) goto done; + client->content.persist_remote = 1; + memcpy(client->content.keypair.pubkey, client->content_key, 32); + client->content_known = 1; + } else if (errno != ENOENT) goto done; + if (cesspool_init(&client->pool, client->identity.pubkey)) goto done; + client->pool_ready = 1; + client->pool.on_transport_packet = transport_packet; + client->pool.transport_context = client; + if (cesspool_listen(&client->pool, "0.0.0.0", port)) { + if (errno != EADDRINUSE || cesspool_listen(&client->pool, "0.0.0.0", 0)) goto done; + } + bootstrap_count = cesspool_configure_bootstraps(&client->pool); + if (bootstrap_count < 0) goto done; + if (cesspool_discovery_key(metadata_key, client->topic) || + dat_session_init(&client->session, &client->pool, metadata_key, + client->identity.pubkey, client->pool.port, + &client->metadata, metadata_key, &client->content, + client->content_known ? client->content_key : unknown_content, + send_session_frame, client)) goto done; + client->session_ready = 1; + if (!client->content_known && dat_session_expect_content_key(&client->session)) goto done; + if (dat_session_set_peer_callback(&client->session, peer_discovered, client) || + dat_session_start(&client->session, 1, 1)) goto done; + deadline = fetch_now_ms() + (timeout_ms ? timeout_ms : 30000u); + while (fetch_now_ms() < deadline) { + size_t i; + if (client->failed) { errno = client->failure_errno ? client->failure_errno : EIO; goto done; } + if (dat_session_tick(&client->session, fetch_now_ms()) < 0 && errno != EINTR) goto done; + for (i = 0; i < DAT_SESSION_MAX_PEERS; i++) { + fetch_transport_t *transport = &client->transports[i]; + if (!transport->used) continue; + if (transport->waiting_incoming && transport->utp.state == SLEEP_UTP_CLOSED) continue; + if (sleep_utp_tick(&transport->utp, fetch_now_ms()) != 0 || + transport->utp.state == SLEEP_UTP_RESET || + transport->utp.state == SLEEP_UTP_CLOSED) { + transport->used = 0; + continue; + } + if (flush_transport(transport)) goto done; + } + if (archive_complete(client)) { rc = 0; goto done; } + { + struct timespec pause = {0, 20000000}; + nanosleep(&pause, NULL); + } + } + if (!bootstrap_count && !client->session.peer_count) errno = ENETUNREACH; + else if (bootstrap_count && !client->pool.bootstrap_reachable) errno = EHOSTUNREACH; + else if (!client->session.peer_count) errno = ENOENT; + else { + size_t i, secure = 0, ready = 0, active_utp = 0, auth = 0; + for (i = 0; i < client->session.peer_count; i++) { + if (client->session.peers[i].state >= DAT_SESSION_PEER_SECURE && + client->session.peers[i].state != DAT_SESSION_PEER_CLOSED) secure++; + if (client->session.peers[i].state == DAT_SESSION_PEER_READY) ready++; + } + for (i = 0; i < DAT_SESSION_MAX_PEERS; i++) if (client->transports[i].used) { + active_utp += client->transports[i].utp.state == SLEEP_UTP_CONNECTED; + auth += client->transports[i].authenticated; + } + fprintf(stderr, "dat-fetch: bootstrap reachable; peers=%lu secure=%lu ready=%lu utp=%lu authenticated=%lu\n", + (unsigned long)client->session.peer_count, (unsigned long)secure, + (unsigned long)ready, (unsigned long)active_utp, (unsigned long)auth); + for (i = 0; i < client->session.peer_count; i++) { + const dat_session_peer_t *peer = &client->session.peers[i]; + const struct sockaddr_in *address = (const struct sockaddr_in *)&peer->address.addr; + char host[INET_ADDRSTRLEN] = "?"; + char peer_hex[65]; + size_t j; + fetch_transport_t *transport = transport_by_id(client, peer->peer_id); + for (j = 0; j < 32; j++) snprintf(peer_hex + j * 2, 3, "%02x", peer->peer_id[j]); + if (address->sin_family == AF_INET) + (void)inet_ntop(AF_INET, &address->sin_addr, host, sizeof(host)); + fprintf(stderr, "dat-fetch: peer=%s state=%d endpoint=%s:%u local-id-order=%d waiting-incoming=%d utp-state=%d\n", + peer_hex, peer->state, host, (unsigned)ntohs(address->sin_port), + memcmp(client->identity.pubkey, peer->peer_id, 32), + transport ? transport->waiting_incoming : 0, + transport ? transport->utp.state : SLEEP_UTP_CLOSED); + } + errno = ETIMEDOUT; + } +done: + if (rc != 0 && !errno) errno = EIO; + { + int saved = errno; + cleanup(client); + free(client); + errno = saved; + } + return rc; +} diff --git a/extra/dat-dpi/src/dat_net.c b/extra/dat-dpi/src/dat_net.c new file mode 100644 index 0000000..b92a7aa --- /dev/null +++ b/extra/dat-dpi/src/dat_net.c @@ -0,0 +1,166 @@ +#define _POSIX_C_SOURCE 200112L +#include "dat_net.h" +#include +#include +#ifdef _WIN32 +#include +#ifndef SIO_UDP_CONNRESET +#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR, 12) +#endif +static int datnet_winsock_ready; +#else +#include +#include +#include +#include +#include +#endif + +#ifdef _WIN32 +static int datnet_winsock_errno(int error) +{ + switch (error) { + case WSAEINTR: return EINTR; + case WSAEACCES: return EACCES; + case WSAEADDRINUSE: return EADDRINUSE; + case WSAEADDRNOTAVAIL: return EADDRNOTAVAIL; + case WSAENETDOWN: return ENETDOWN; + case WSAENETUNREACH: return ENETUNREACH; + case WSAEHOSTUNREACH: return EHOSTUNREACH; + case WSAECONNRESET: return ECONNRESET; + case WSAECONNREFUSED: return ECONNREFUSED; + case WSAETIMEDOUT: return ETIMEDOUT; + case WSAEMSGSIZE: return EMSGSIZE; + case WSAENOBUFS: return ENOBUFS; + case WSAEWOULDBLOCK: return EAGAIN; + default: return EIO; + } +} +#endif + +int datnet_init(datnet_t *net, const uint8_t topic[32], const uint8_t peerid[32]) { + if (!net || !topic || !peerid) { errno = EINVAL; return -1; } +#ifdef _WIN32 + if (!datnet_winsock_ready) { WSADATA data; if (WSAStartup(MAKEWORD(2,2), &data)) { errno = ENETDOWN; return -1; } datnet_winsock_ready = 1; } +#endif + memset(net, 0, sizeof(*net)); net->sock = DAT_INVALID_SOCKET; memcpy(net->topic, topic, 32); memcpy(net->peerid, peerid, 32); return 0; +} +int datnet_bind(datnet_t *net, const char *host, uint16_t port) { + struct sockaddr_in address; int broadcast = 1; + if (!net) { errno = EINVAL; return -1; } + memset(&address, 0, sizeof(address)); address.sin_family = AF_INET; address.sin_port = htons(port ? port : DATNET_PORT); + if (!host || !*host || !strcmp(host, "*")) address.sin_addr.s_addr = htonl(INADDR_ANY); + else { + struct addrinfo hints, *resolved = NULL; + memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_DGRAM; + if (getaddrinfo(host, NULL, &hints, &resolved) != 0 || !resolved) { + errno = EADDRNOTAVAIL; return -1; + } + address.sin_addr = ((struct sockaddr_in *)resolved->ai_addr)->sin_addr; + freeaddrinfo(resolved); + } + net->sock = socket(AF_INET, SOCK_DGRAM, 0); + if (net->sock == DAT_INVALID_SOCKET) { +#ifdef _WIN32 + errno = ENETDOWN; +#endif + return -1; + } +#ifdef _WIN32 + if (setsockopt(net->sock, SOL_SOCKET, SO_BROADCAST, (const char *)&broadcast, sizeof(broadcast))) { + int error = WSAGetLastError(); + closesocket(net->sock); net->sock = DAT_INVALID_SOCKET; + errno = datnet_winsock_errno(error); + return -1; + } + /* A peer that is not listening can send an ICMP port-unreachable response. + * Winsock reports that asynchronously as WSAECONNRESET on a UDP socket; + * that is a peer/network condition, not a dead local socket. */ + { + BOOL disable_udp_connreset = FALSE; + DWORD returned = 0; + (void)WSAIoctl(net->sock, SIO_UDP_CONNRESET, &disable_udp_connreset, + sizeof(disable_udp_connreset), NULL, 0, &returned, NULL, NULL); + } + if (bind(net->sock, (struct sockaddr *)&address, sizeof(address))) { + int error = WSAGetLastError(); + closesocket(net->sock); net->sock = DAT_INVALID_SOCKET; + errno = error == WSAEADDRINUSE ? EADDRINUSE : + error == WSAEACCES ? EACCES : EIO; + return -1; + } +#else + if (setsockopt(net->sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast))) { + int saved = errno; + close(net->sock); net->sock = DAT_INVALID_SOCKET; errno = saved; + return -1; + } + if (bind(net->sock, (struct sockaddr *)&address, sizeof(address))) { close(net->sock); net->sock = DAT_INVALID_SOCKET; return -1; } +#endif + return 0; +} +int datnet_poll(datnet_t *net, int timeout_ms) { + uint8_t packet[DATNET_MAX_PACKET], peer_id[DATNET_PEERID_BYTES]; struct sockaddr_storage source; socklen_t source_len = sizeof(source); ssize_t n; dat_peer_t peer; + if (!net || net->sock == DAT_INVALID_SOCKET) { errno = EINVAL; return -1; } + memcpy(peer_id, net->peerid, sizeof(peer_id)); +#ifdef _WIN32 + { WSAPOLLFD pfd; int ready; pfd.fd = net->sock; pfd.events = POLLRDNORM; pfd.revents = 0; + ready = WSAPoll(&pfd, 1, timeout_ms); + if (ready == SOCKET_ERROR) { errno = datnet_winsock_errno(WSAGetLastError()); return -1; } + if (ready == 0) return 0; + if (pfd.revents & (POLLHUP | POLLNVAL)) { errno = EIO; return -1; } + if (pfd.revents & POLLERR) { + int socket_error = 0, error_length = sizeof(socket_error); + if (getsockopt(net->sock, SOL_SOCKET, SO_ERROR, (char *)&socket_error, + &error_length) == SOCKET_ERROR) { + errno = datnet_winsock_errno(WSAGetLastError()); + return -1; + } + if (socket_error == WSAECONNRESET || socket_error == WSAECONNREFUSED || + socket_error == WSAEHOSTUNREACH || socket_error == WSAENETUNREACH) + return 0; + if (socket_error) { errno = datnet_winsock_errno(socket_error); return -1; } + } + if (!(pfd.revents & POLLRDNORM)) return 0; } +#else + { struct pollfd pfd; int ready; pfd.fd = net->sock; pfd.events = POLLIN; pfd.revents = 0; + ready = poll(&pfd, 1, timeout_ms); + if (ready < 0) return -1; + if (ready == 0) return 0; + if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) { errno = EIO; return -1; } + if (!(pfd.revents & POLLIN)) return 0; } +#endif +#ifdef _WIN32 + { int source_length = (int)source_len; n = recvfrom(net->sock, (char *)packet, sizeof(packet), 0, (struct sockaddr *)&source, &source_length); source_len = (socklen_t)source_length; + if (n < 0) { errno = datnet_winsock_errno(WSAGetLastError()); return -1; } } +#else + n = recvfrom(net->sock, packet, sizeof(packet), 0, (struct sockaddr *)&source, &source_len); if (n < 0) return -1; +#endif + if (n < 0) return -1; + memset(&peer, 0, sizeof(peer)); peer.fd = net->sock; peer.addr = source; peer.addrlen = source_len; + /* Cesspool decodes discovery records here; other datagrams are passed to + * its authenticated peer-transport hook. */ + if (net->on_message) net->on_message(net, &peer, packet, (size_t)n); + /* The identity bound to this UDP socket is immutable. Keep it intact even + * if a protocol callback uses an aliased view while handling the packet. */ + memcpy(net->peerid, peer_id, sizeof(peer_id)); + return 1; +} +int datnet_send(datnet_t *net, const dat_peer_t *peer, const uint8_t *message, size_t length) { + ssize_t n; if (!net || !peer || net->sock == DAT_INVALID_SOCKET || (!message && length) || length > DATNET_MAX_PACKET) { errno = EINVAL; return -1; } +#ifdef _WIN32 + n = sendto(net->sock, (const char *)message, (int)length, 0, (const struct sockaddr *)&peer->addr, (int)peer->addrlen); + if (n == SOCKET_ERROR) { errno = datnet_winsock_errno(WSAGetLastError()); return -1; } +#else + n = sendto(net->sock, message, length, 0, (const struct sockaddr *)&peer->addr, peer->addrlen); + if (n < 0) return -1; +#endif + return n == (ssize_t)length ? 0 : -1; +} +int datnet_close(datnet_t *net) { int rc = 0; if (!net) { errno = EINVAL; return -1; } if (net->sock != DAT_INVALID_SOCKET) { +#ifdef _WIN32 + rc = closesocket(net->sock); +#else + rc = close(net->sock); +#endif + } net->sock = DAT_INVALID_SOCKET; return rc; } diff --git a/extra/dat-dpi/src/dat_session.c b/extra/dat-dpi/src/dat_session.c new file mode 100644 index 0000000..885dfc8 --- /dev/null +++ b/extra/dat-dpi/src/dat_session.c @@ -0,0 +1,884 @@ +#include "dat_session.h" +#include "errno_compat.h" + +#include "sleep.h" +#include "sleep_crypto.h" +#include "sleep_proof.h" +#include +#include +#include +#ifdef DAT_LEGACY_HYPERCORE +#include +#endif + +#define DAT_SESSION_FRAME_BYTES (128u * 1024u) + +#ifdef DAT_LEGACY_HYPERCORE +static const uint64_t feed_channels[DAT_SESSION_FEED_COUNT] = {0, 1}; +#else +static const uint64_t feed_channels[DAT_SESSION_FEED_COUNT] = {1, 2}; +#endif + +static int valid_feed(enum dat_session_feed feed) +{ + return feed >= DAT_SESSION_METADATA && feed < DAT_SESSION_FEED_COUNT; +} + +static int feed_for_channel(uint64_t channel, enum dat_session_feed *feed) +{ + size_t i; + for (i = 0; i < DAT_SESSION_FEED_COUNT; i++) if (feed_channels[i] == channel) { + *feed = (enum dat_session_feed)i; + return 0; + } + errno = EPROTO; + return -1; +} + +static int discover_known_peers(dat_session_t *session) +{ + cesspool_peer_t *peers[CESSPOOL_MAX_PEERS]; + size_t count, i; + count = cesspool_topic_peers(session->cesspool, session->discovery_topic, + peers, CESSPOOL_MAX_PEERS); + for (i = 0; i < count; i++) + if (dat_session_peer_discovered(session, peers[i])) return -1; + return 0; +} + +dat_session_peer_t *dat_session_find_peer(dat_session_t *session, + const uint8_t peer_id[DAT_PEER_ID_BYTES]) +{ + size_t i; + if (!session || !peer_id) return NULL; + for (i = 0; i < session->peer_count; i++) + if (!memcmp(session->peers[i].peer_id, peer_id, DAT_PEER_ID_BYTES)) + return &session->peers[i]; + return NULL; +} + +int dat_session_init(dat_session_t *session, cesspool_t *pool, + const uint8_t archive_key[DAT_KEY_BYTES], + const uint8_t peer_id[DAT_PEER_ID_BYTES], uint16_t port, + sleep_feed_t *metadata_feed, + const uint8_t metadata_key[DAT_KEY_BYTES], + sleep_feed_t *content_feed, + const uint8_t content_key[DAT_KEY_BYTES], + dat_session_send_fn send, void *context) +{ + if (!session || !pool || !archive_key || !peer_id || !metadata_feed || + !metadata_key || !content_feed || !content_key || !send) { + errno = EINVAL; + return -1; + } + memset(session, 0, sizeof(*session)); + memcpy(session->archive_key, archive_key, DAT_KEY_BYTES); + memcpy(session->peer_id, peer_id, DAT_PEER_ID_BYTES); + memcpy(session->feed_keys[DAT_SESSION_METADATA], metadata_key, DAT_KEY_BYTES); + memcpy(session->feed_keys[DAT_SESSION_CONTENT], content_key, DAT_KEY_BYTES); + session->content_key_known = 1; + if (cesspool_discovery_key(archive_key, session->discovery_topic)) return -1; + session->cesspool = pool; + session->feeds[DAT_SESSION_METADATA] = metadata_feed; + session->feeds[DAT_SESSION_CONTENT] = content_feed; + session->send = send; + session->context = context; + session->port = port; + if (session->port == 0) session->port = pool->port; + session->state = DAT_SESSION_OPEN; + return 0; +} + +int dat_session_start(dat_session_t *session, int announce, int lookup) +{ + if (!session || session->state != DAT_SESSION_OPEN || (!announce && !lookup)) { + errno = EINVAL; + return -1; + } + session->announcing = !!announce; + session->looking_up = !!lookup; + if (cesspool_join(session->cesspool, session->discovery_topic, + session->port, announce, lookup)) { + (void)cesspool_leave(session->cesspool, session->discovery_topic); + session->state = DAT_SESSION_FAILED; + return -1; + } + session->state = DAT_SESSION_DISCOVERING; + return discover_known_peers(session); +} + +int dat_session_peer_discovered(dat_session_t *session, + const cesspool_peer_t *discovered_peer) +{ + dat_session_peer_t *peer; + if (!session || !discovered_peer || (session->state != DAT_SESSION_DISCOVERING && + session->state != DAT_SESSION_ACTIVE)) { errno = EINVAL; return -1; } + peer = dat_session_find_peer(session, discovered_peer->public_key); + if (peer) { + int retry = peer->state == DAT_SESSION_PEER_CLOSED; + peer->address = discovered_peer->address; + if (retry) { + peer->state = DAT_SESSION_PEER_DISCOVERED; + peer->transport_authenticated = 0; + free(peer->rx_buffer); + peer->rx_buffer = NULL; + peer->rx_buffer_len = peer->rx_buffer_capacity = 0; + peer->tx_offset = peer->rx_offset = 0; + peer->remote_feed_mask = 0; + peer->local_feed_mask = 0; + peer->remote_content_pending = 0; + memset(peer->remote_content_discovery, 0, + sizeof(peer->remote_content_discovery)); + peer->tx_stream_encrypted = peer->rx_stream_encrypted = 0; + if (session->on_peer) session->on_peer(session->peer_context, peer); + } + return 0; + } + if (session->peer_count >= DAT_SESSION_MAX_PEERS) { errno = ENOSPC; return -1; } + peer = &session->peers[session->peer_count]; + memset(peer, 0, sizeof(*peer)); + memcpy(peer->peer_id, discovered_peer->public_key, DAT_PEER_ID_BYTES); + peer->address = discovered_peer->address; + peer->state = DAT_SESSION_PEER_DISCOVERED; + if (sleep_replication_init(&peer->replication[DAT_SESSION_METADATA], + session->feeds[DAT_SESSION_METADATA], + feed_channels[DAT_SESSION_METADATA]) || + sleep_replication_init(&peer->replication[DAT_SESSION_CONTENT], + session->feeds[DAT_SESSION_CONTENT], + feed_channels[DAT_SESSION_CONTENT])) return -1; + session->peer_count++; + if (session->on_peer) session->on_peer(session->peer_context, peer); + return 0; +} + +int dat_session_set_peer_callback(dat_session_t *session, + dat_session_peer_fn callback, void *context) +{ + size_t i; + if (!session || session->state == DAT_SESSION_CLOSED || + session->state == DAT_SESSION_CLOSING) { errno = EINVAL; return -1; } + session->on_peer = callback; + session->peer_context = context; + if (callback) + for (i = 0; i < session->peer_count; i++) + if (session->peers[i].state == DAT_SESSION_PEER_DISCOVERED) + callback(context, &session->peers[i]); + return 0; +} + +int dat_session_set_transport_poll(dat_session_t *session, + dat_session_poll_fn poll, void *context) +{ + if (!session || session->state == DAT_SESSION_CLOSED || + session->state == DAT_SESSION_CLOSING) { errno = EINVAL; return -1; } + session->poll_transport = poll; + session->poll_context = context; + return 0; +} + +static int send_frame(dat_session_t *session, dat_session_peer_t *peer, + uint64_t channel, uint64_t type, + const uint8_t *payload, size_t payload_len) +{ + dat_wire_message_t message; + uint8_t frame[DAT_SESSION_FRAME_BYTES]; + size_t frame_len; + int send_result; + message.channel = channel; + message.type = type; + message.payload = payload; + message.payload_len = payload_len; + if (dat_wire_encode(&message, frame, sizeof(frame), &frame_len)) return -1; +#ifdef DAT_LEGACY_HYPERCORE + if (peer->tx_stream_encrypted) { + if (sleep_stream_xsalsa20_xor(frame, frame, frame_len, + peer->tx_nonce, + session->feed_keys[DAT_SESSION_METADATA], + peer->tx_offset)) return -1; + if (peer->tx_offset > UINT64_MAX - frame_len) { errno = EOVERFLOW; return -1; } + } +#endif + send_result = session->send(session->context, peer->peer_id, frame, frame_len); +#ifdef DAT_LEGACY_HYPERCORE + if (!send_result && peer->tx_stream_encrypted) peer->tx_offset += frame_len; +#endif + return send_result; +} + +static int send_have_range(dat_session_t *session, dat_session_peer_t *peer, + enum dat_session_feed feed, uint64_t start, + uint64_t length) +{ + dat_range_t range = {start, length}; + uint8_t payload[32]; + size_t payload_len; + if (!range.length) return 0; + if (dat_range_encode(&range, payload, sizeof(payload), &payload_len)) return -1; + return send_frame(session, peer, feed_channels[feed], DAT_MSG_HAVE, + payload, payload_len); +} + +static int send_have(dat_session_t *session, dat_session_peer_t *peer, + enum dat_session_feed feed) +{ + size_t index, length = sleep_feed_length(session->feeds[feed]); + size_t run_start = 0; + int in_run = 0; + if (length > SLEEP_BITFIELD_DATA_BYTES * 8u) + length = SLEEP_BITFIELD_DATA_BYTES * 8u; + for (index = 0; index < length; index++) { + int have = sleep_feed_has(session->feeds[feed], index); + if (have && !in_run) { run_start = index; in_run = 1; } + if (!have && in_run) { + if (send_have_range(session, peer, feed, run_start, index - run_start)) return -1; + in_run = 0; + } + } + if (in_run) return send_have_range(session, peer, feed, run_start, length - run_start); + return 0; +} + +static int answer_want(dat_session_t *session, dat_session_peer_t *peer, + enum dat_session_feed feed, const dat_range_t *range) +{ + uint64_t i, end, run_start = 0; + int in_run = 0; + uint64_t feed_length = sleep_feed_length(session->feeds[feed]); + if (range->start > UINT64_MAX - range->length) { + errno = EOVERFLOW; + return -1; + } + end = range->start + range->length; + if (end > feed_length) end = feed_length; + if (end > SLEEP_BITFIELD_DATA_BYTES * 8u) end = SLEEP_BITFIELD_DATA_BYTES * 8u; + for (i = range->start; i < end; i++) { + int have = i <= SIZE_MAX && sleep_feed_has(session->feeds[feed], (size_t)i); + if (have && !in_run) { run_start = i; in_run = 1; } + if (!have && in_run) { + if (send_have_range(session, peer, feed, run_start, i - run_start)) return -1; + in_run = 0; + } + } + if (in_run && send_have_range(session, peer, feed, run_start, end - run_start)) return -1; + return 0; +} + +static int announce_received_block(dat_session_t *session, + enum dat_session_feed feed, uint64_t index) +{ + dat_range_t range = {index, 1}; + uint8_t payload[32]; + size_t payload_len, i; + if (dat_range_encode(&range, payload, sizeof(payload), &payload_len)) return -1; + for (i = 0; i < session->peer_count; i++) { + dat_session_peer_t *peer = &session->peers[i]; + if (peer->state == DAT_SESSION_PEER_READY && + (peer->remote_feed_mask & (uint8_t)(1u << feed)) && + send_frame(session, peer, feed_channels[feed], DAT_MSG_HAVE, + payload, payload_len)) return -1; + } + return 0; +} + +static int pump_replication(dat_session_t *session, dat_session_peer_t *peer, + enum dat_session_feed feed) +{ + sleep_replication_t *replication = &peer->replication[feed]; + size_t index, limit; + if (peer->state != DAT_SESSION_PEER_READY || peer->paused || !replication->uploading) return 0; + limit = replication->remote_length > SLEEP_BITFIELD_DATA_BYTES * 8u ? + SLEEP_BITFIELD_DATA_BYTES * 8u : (size_t)replication->remote_length; + for (index = 0; index < limit; index++) { + int requested, available; + if (sleep_feed_has(session->feeds[feed], index)) continue; + available = sleep_bitfield_get_data(&replication->remote_bitfield, index); + if (available <= 0) continue; + requested = sleep_replication_is_requested(replication, index); + if (requested < 0) return -1; + if (requested) continue; + if (dat_session_request_block(session, peer->peer_id, feed, index, 0)) { + if (errno == ENOSPC) return 0; + return -1; + } + } + return 0; +} + +int dat_session_peer_secure(dat_session_t *session, + const uint8_t peer_id[DAT_PEER_ID_BYTES], int authenticated) +{ + dat_session_peer_t *peer; + dat_handshake_t handshake; + dat_feed_open_t feed_open; + uint8_t payload[128]; + size_t payload_len; + if (!session || !peer_id || !authenticated) { errno = EACCES; return -1; } + peer = dat_session_find_peer(session, peer_id); + if (!peer || peer->state != DAT_SESSION_PEER_DISCOVERED) { errno = EINVAL; return -1; } + peer->transport_authenticated = 1; + peer->state = DAT_SESSION_PEER_SECURE; +#ifdef DAT_LEGACY_HYPERCORE + memset(&feed_open, 0, sizeof(feed_open)); + if (sleep_crypto_init()) goto fail; + if (sleep_compute_discovery_key(session->feed_keys[DAT_SESSION_METADATA], + feed_open.discovery_key)) goto fail; + feed_open.has_nonce = 1; + randombytes_buf(feed_open.nonce, sizeof(feed_open.nonce)); + memcpy(peer->tx_nonce, feed_open.nonce, sizeof(peer->tx_nonce)); + if (dat_feed_open_encode(&feed_open, payload, sizeof(payload), &payload_len) || + send_frame(session, peer, feed_channels[DAT_SESSION_METADATA], DAT_MSG_FEED, + payload, payload_len)) goto fail; + peer->local_feed_mask |= (uint8_t)(1u << DAT_SESSION_METADATA); + peer->tx_stream_encrypted = 1; + if (dat_handshake_init(&handshake, session->peer_id, + session->content_key_known ? 0 : DAT_SESSION_CAP_METADATA_ONLY) || + dat_handshake_encode(&handshake, payload, sizeof(payload), &payload_len) || + send_frame(session, peer, 0, DAT_MSG_HANDSHAKE, payload, payload_len)) goto fail; + memset(&feed_open, 0, sizeof(feed_open)); + if (session->content_key_known && + (sleep_compute_discovery_key(session->feed_keys[DAT_SESSION_CONTENT], + feed_open.discovery_key) || + dat_feed_open_encode(&feed_open, payload, sizeof(payload), &payload_len) || + send_frame(session, peer, feed_channels[DAT_SESSION_CONTENT], DAT_MSG_FEED, + payload, payload_len))) goto fail; + if (session->content_key_known) peer->local_feed_mask |= (uint8_t)(1u << DAT_SESSION_CONTENT); + return 0; +#else + memset(&feed_open, 0, sizeof(feed_open)); + if (sleep_compute_discovery_key(session->feed_keys[DAT_SESSION_METADATA], + feed_open.discovery_key) || + dat_feed_open_encode(&feed_open, payload, sizeof(payload), &payload_len) || + send_frame(session, peer, feed_channels[DAT_SESSION_METADATA], + DAT_MSG_FEED, payload, payload_len)) goto fail; + peer->local_feed_mask |= (uint8_t)(1u << DAT_SESSION_METADATA); + if (session->content_key_known && + (sleep_compute_discovery_key(session->feed_keys[DAT_SESSION_CONTENT], + feed_open.discovery_key) || + dat_feed_open_encode(&feed_open, payload, sizeof(payload), &payload_len) || + send_frame(session, peer, feed_channels[DAT_SESSION_CONTENT], + DAT_MSG_FEED, payload, payload_len))) goto fail; + if (session->content_key_known) peer->local_feed_mask |= (uint8_t)(1u << DAT_SESSION_CONTENT); + if (dat_handshake_init(&handshake, session->peer_id, + session->content_key_known ? 0 : DAT_SESSION_CAP_METADATA_ONLY) || + dat_handshake_encode(&handshake, payload, sizeof(payload), &payload_len) || + send_frame(session, peer, 0, DAT_MSG_HANDSHAKE, payload, payload_len)) goto fail; + return 0; +#endif + +fail: + peer->state = DAT_SESSION_PEER_CLOSED; + peer->transport_authenticated = 0; + peer->tx_stream_encrypted = peer->rx_stream_encrypted = 0; + peer->tx_offset = peer->rx_offset = 0; + free(peer->rx_buffer); + peer->rx_buffer = NULL; + peer->rx_buffer_len = peer->rx_buffer_capacity = 0; + return -1; +} + +int dat_session_expect_content_key(dat_session_t *session) +{ + if (!session || session->state != DAT_SESSION_OPEN) { errno = EINVAL; return -1; } + memset(session->feed_keys[DAT_SESSION_CONTENT], 0, DAT_KEY_BYTES); + session->content_key_known = 0; + return 0; +} + +int dat_session_set_content_key(dat_session_t *session, + const uint8_t content_key[DAT_KEY_BYTES]) +{ + uint8_t expected[32]; + size_t i; + if (!session || !content_key || session->state == DAT_SESSION_CLOSED || + session->state == DAT_SESSION_CLOSING) { errno = EINVAL; return -1; } + if (session->content_key_known) { + if (!memcmp(session->feed_keys[DAT_SESSION_CONTENT], content_key, DAT_KEY_BYTES)) return 0; + errno = EKEYREJECTED; + return -1; + } + if (sleep_compute_discovery_key(content_key, expected)) return -1; + for (i = 0; i < session->peer_count; i++) { + dat_session_peer_t *peer = &session->peers[i]; + if (peer->remote_content_pending && + memcmp(peer->remote_content_discovery, expected, sizeof(expected))) { + errno = EKEYREJECTED; + return -1; + } + } + memcpy(session->feed_keys[DAT_SESSION_CONTENT], content_key, DAT_KEY_BYTES); + memcpy(session->feeds[DAT_SESSION_CONTENT]->keypair.pubkey, content_key, DAT_KEY_BYTES); + session->content_key_known = 1; + for (i = 0; i < session->peer_count; i++) { + dat_session_peer_t *peer = &session->peers[i]; + dat_feed_open_t feed_open; + uint8_t payload[128]; + size_t payload_len; + peer->remote_content_pending = 0; + if (peer->state != DAT_SESSION_PEER_READY && peer->state != DAT_SESSION_PEER_SECURE) continue; + if (!(peer->local_feed_mask & (uint8_t)(1u << DAT_SESSION_CONTENT))) { + memset(&feed_open, 0, sizeof(feed_open)); + if (sleep_compute_discovery_key(content_key, feed_open.discovery_key) || + dat_feed_open_encode(&feed_open, payload, sizeof(payload), &payload_len) || + send_frame(session, peer, feed_channels[DAT_SESSION_CONTENT], DAT_MSG_FEED, + payload, payload_len)) return -1; + peer->local_feed_mask |= (uint8_t)(1u << DAT_SESSION_CONTENT); + } + if (peer->state == DAT_SESSION_PEER_READY && + (peer->remote_feed_mask & (uint8_t)(1u << DAT_SESSION_CONTENT)) && + send_have(session, peer, DAT_SESSION_CONTENT)) return -1; + } + return 0; +} + +static int receive_data(dat_session_t *session, dat_session_peer_t *peer, + enum dat_session_feed feed, const uint8_t *payload, + size_t payload_len) +{ + dat_data_t data; + sleep_proof_t proof; + sleep_feed_t *local; + uint8_t *old_data = NULL; + uint8_t proof_cache[8192]; + const uint8_t *cached_proof; + size_t old_len = 0; + size_t cached_proof_len; + size_t i; + if (!valid_feed(feed) || dat_data_decode(&data, payload, payload_len)) return -1; + local = session->feeds[feed]; + if (data.proof_len) { + if (sleep_proof_decode(&proof, data.proof, data.proof_len)) return -1; + } else { + sleep_proof_node_t nodes[DAT_DATA_MAX_NODES]; + uint64_t remote_length = peer->replication[feed].remote_length; + if (!data.has_signature || !remote_length || data.node_count > DAT_DATA_MAX_NODES) { + errno = EPROTO; + return -1; + } + for (i = 0; i < data.node_count; i++) { + nodes[i].index = data.nodes[i].index; + nodes[i].length = data.nodes[i].length; + memcpy(nodes[i].hash, data.nodes[i].hash, 32); + } + if (sleep_proof_from_nodes(data.index, data.value, data.value_len, + nodes, data.node_count, data.signature, + remote_length, &proof)) return -1; + } + if (proof.block != data.index || !proof.has_signature || + sleep_proof_verify(&proof, data.value, data.value_len, + session->feed_keys[feed])) { + errno = EBADMSG; + return -1; + } + cached_proof = data.proof; + cached_proof_len = data.proof_len; + if (!cached_proof_len) { + if (sleep_proof_encode(&proof, proof_cache, sizeof(proof_cache), + &cached_proof_len)) return -1; + cached_proof = proof_cache; + } + if (data.index >= SLEEP_BITFIELD_DATA_BYTES * 8u || + (peer->replication[feed].remote_length && + data.index >= peer->replication[feed].remote_length)) { + errno = ERANGE; + return -1; + } + if (peer->paused) + return sleep_replication_complete(&peer->replication[feed], data.index); + if (sleep_feed_has(local, (size_t)data.index)) { + if (sleep_feed_read(local, (size_t)data.index, &old_data, &old_len)) return -1; + if (old_len != data.value_len || memcmp(old_data, data.value, old_len)) { + free(old_data); errno = EBADMSG; return -1; + } + free(old_data); + peer->payload_bytes_received += data.value_len; + if (sleep_replication_complete(&peer->replication[feed], data.index)) return -1; + return pump_replication(session, peer, feed); + } + if (sleep_feed_append_remote(local, (size_t)data.index, data.value, + data.value_len, cached_proof, + cached_proof_len)) return -1; + peer->payload_bytes_received += data.value_len; + if (sleep_replication_complete(&peer->replication[feed], data.index)) return -1; + if (announce_received_block(session, feed, data.index)) return -1; + return pump_replication(session, peer, feed); +} + +static int dispatch_request(dat_session_t *session, dat_session_peer_t *peer, + enum dat_session_feed feed, + const dat_request_t *request) +{ + if (session->on_request) + return session->on_request(session->context, peer->peer_id, feed, request); + return dat_session_send_block(session, peer->peer_id, feed, request->index); +} + +static int dat_session_process_frame(dat_session_t *session, + dat_session_peer_t *peer, + const uint8_t *frame, size_t frame_len) +{ + dat_wire_message_t message; + size_t consumed; + if (dat_wire_decode(&message, frame, frame_len, &consumed) || consumed != frame_len) return -1; + if (message.type == DAT_MSG_FEED) { + dat_feed_open_t remote_feed; + enum dat_session_feed feed; + uint8_t expected[32]; + if ((peer->state != DAT_SESSION_PEER_SECURE && peer->state != DAT_SESSION_PEER_READY) || + feed_for_channel(message.channel, &feed) || + (peer->remote_feed_mask & (uint8_t)(1u << feed)) || + dat_feed_open_decode(&remote_feed, message.payload, message.payload_len)) { + errno = EKEYREJECTED; + return -1; + } + if (feed == DAT_SESSION_CONTENT && !session->content_key_known) { + memcpy(peer->remote_content_discovery, remote_feed.discovery_key, 32); + peer->remote_content_pending = 1; + } else if (sleep_compute_discovery_key(session->feed_keys[feed], expected) || + memcmp(remote_feed.discovery_key, expected, sizeof(expected))) { + errno = EKEYREJECTED; + return -1; + } +#ifdef DAT_LEGACY_HYPERCORE + if (feed == DAT_SESSION_METADATA && message.channel == 0) { + if (!remote_feed.has_nonce || peer->rx_stream_encrypted) { errno = EPROTO; return -1; } + memcpy(peer->rx_nonce, remote_feed.nonce, sizeof(peer->rx_nonce)); + peer->rx_offset = 0; + peer->rx_stream_encrypted = 1; + } +#endif + peer->remote_feed_mask |= (uint8_t)(1u << feed); + if (peer->state == DAT_SESSION_PEER_READY) + return send_have(session, peer, feed); + return 0; + } + if (message.type == DAT_MSG_HANDSHAKE) { + dat_handshake_t remote; + if (message.channel != 0 || peer->state != DAT_SESSION_PEER_SECURE || + !(peer->remote_feed_mask & (uint8_t)(1u << DAT_SESSION_METADATA)) || + dat_handshake_decode(&remote, message.payload, message.payload_len) || + dat_handshake_protocol_matches(&remote) != 1 +#ifndef DAT_LEGACY_HYPERCORE + || (remote.has_peer_id && memcmp(remote.peer_id, peer->peer_id, + DAT_PEER_ID_BYTES)) +#endif + ) { + errno = EPROTO; + return -1; + } + #ifndef DAT_LEGACY_HYPERCORE + if (!(remote.capabilities & DAT_SESSION_CAP_METADATA_ONLY) && + peer->remote_feed_mask != (uint8_t)((1u << DAT_SESSION_FEED_COUNT) - 1u)) { + errno = EPROTO; + return -1; + } + if ((remote.capabilities & DAT_SESSION_CAP_METADATA_ONLY) && + peer->remote_feed_mask != (uint8_t)(1u << DAT_SESSION_METADATA)) { + errno = EPROTO; + return -1; + } + #endif + peer->capabilities = remote.capabilities; + peer->state = DAT_SESSION_PEER_READY; + session->state = DAT_SESSION_ACTIVE; + for (size_t feed = 0; feed < DAT_SESSION_FEED_COUNT; feed++) + if ((peer->remote_feed_mask & (uint8_t)(1u << feed)) && + send_have(session, peer, (enum dat_session_feed)feed)) return -1; + return 0; + } + { + enum dat_session_feed feed; + if (peer->state != DAT_SESSION_PEER_READY || feed_for_channel(message.channel, &feed)) { + errno = EPROTO; + return -1; + } + if (!(peer->remote_feed_mask & (uint8_t)(1u << feed))) { + errno = EPROTO; + return -1; + } + if (message.type == DAT_MSG_INFO) { + dat_info_t info; + if (dat_info_decode(&info, message.payload, message.payload_len)) return -1; + peer->replication[feed].uploading = info.uploading; + peer->replication[feed].downloading = info.downloading; + return pump_replication(session, peer, feed); + } + if (message.type == DAT_MSG_UNHAVE) { + dat_range_t range; + if (dat_range_decode(&range, message.payload, message.payload_len)) return -1; + if (sleep_replication_unhave(&peer->replication[feed], range.start, + range.length)) return -1; + return sleep_replication_unwant(&peer->replication[feed], range.start, + range.length); + } + if (message.type == DAT_MSG_HAVE || message.type == DAT_MSG_WANT) { + dat_range_t range; + if (message.type == DAT_MSG_HAVE) { + if (dat_range_decode(&range, message.payload, message.payload_len)) return -1; + if (sleep_replication_have(&peer->replication[feed], + range.start, range.length)) return -1; + return pump_replication(session, peer, feed); + } + if (dat_want_decode(&range, message.payload, message.payload_len, + sleep_feed_length(session->feeds[feed]))) return -1; + return answer_want(session, peer, feed, &range); + } + if (message.type == DAT_MSG_UNWANT) return 0; + if (message.type == DAT_MSG_REQUEST) { + dat_request_t request; + if (dat_request_decode(&request, message.payload, message.payload_len)) return -1; + return dispatch_request(session, peer, feed, &request); + } + if (message.type == DAT_MSG_DATA) + return receive_data(session, peer, feed, message.payload, message.payload_len); + if (message.type == DAT_MSG_CANCEL) return 0; + errno = EPROTONOSUPPORT; + return -1; + } +} + +static int dat_session_receive_buffered(dat_session_t *session, + dat_session_peer_t *peer, + const uint8_t *bytes, size_t length) +{ + if (length > DAT_SESSION_FRAME_BYTES * 2u || + peer->rx_buffer_len > DAT_SESSION_FRAME_BYTES * 2u - length) { + errno = EMSGSIZE; + return -1; + } + if (peer->rx_buffer_capacity < peer->rx_buffer_len + length) { + size_t capacity = peer->rx_buffer_len + length; + uint8_t *next; + if (capacity < 4096) capacity = 4096; + next = realloc(peer->rx_buffer, capacity); + if (!next) return -1; + peer->rx_buffer = next; + peer->rx_buffer_capacity = capacity; + } +#ifdef DAT_LEGACY_HYPERCORE + if (peer->rx_stream_encrypted) { + if (sleep_stream_xsalsa20_xor(peer->rx_buffer + peer->rx_buffer_len, + bytes, length, peer->rx_nonce, + session->feed_keys[DAT_SESSION_METADATA], + peer->rx_offset)) return -1; + peer->rx_offset += length; + } else if (length) { + memcpy(peer->rx_buffer + peer->rx_buffer_len, bytes, length); + } +#else + if (length) memcpy(peer->rx_buffer + peer->rx_buffer_len, bytes, length); +#endif + peer->rx_buffer_len += length; + while (peer->rx_buffer_len) { + uint64_t body_len; + size_t prefix, total; + #ifdef DAT_LEGACY_HYPERCORE + int was_encrypted = peer->rx_stream_encrypted; + #endif + if (sleep_varint_decode(peer->rx_buffer, peer->rx_buffer_len, + &body_len, &prefix)) { + if (errno == EAGAIN) return 0; + return -1; + } + if (!body_len || body_len > DAT_SESSION_FRAME_BYTES || + body_len > SIZE_MAX - prefix) { errno = EMSGSIZE; return -1; } + total = prefix + (size_t)body_len; + if (peer->rx_buffer_len < total) return 0; + if (dat_session_process_frame(session, peer, peer->rx_buffer, total)) return -1; + peer->rx_buffer_len -= total; + if (peer->rx_buffer_len) + memmove(peer->rx_buffer, peer->rx_buffer + total, peer->rx_buffer_len); +#ifdef DAT_LEGACY_HYPERCORE + /* Feed(0) turns encryption on mid-read; cipher the buffered tail from offset zero. */ + if (!was_encrypted && peer->rx_stream_encrypted && peer->rx_buffer_len) { + if (sleep_stream_xsalsa20_xor(peer->rx_buffer, peer->rx_buffer, + peer->rx_buffer_len, peer->rx_nonce, + session->feed_keys[DAT_SESSION_METADATA], 0)) return -1; + peer->rx_offset = peer->rx_buffer_len; + } +#endif + } + return 0; +} + +int dat_session_receive(dat_session_t *session, + const uint8_t peer_id[DAT_PEER_ID_BYTES], + const uint8_t *bytes, size_t length) +{ + dat_session_peer_t *peer; + if (!session || !peer_id || (!bytes && length)) { errno = EINVAL; return -1; } + peer = dat_session_find_peer(session, peer_id); + if (!peer || !peer->transport_authenticated) { errno = EACCES; return -1; } + return dat_session_receive_buffered(session, peer, bytes, length); +} + +int dat_session_request_block(dat_session_t *session, + const uint8_t peer_id[DAT_PEER_ID_BYTES], + enum dat_session_feed feed, uint64_t index, + uint64_t bytes) +{ + dat_session_peer_t *peer; + sleep_replication_request_t queued; + dat_request_t request; + uint8_t payload[64]; + size_t payload_len; + int already_requested; + if (!session || !peer_id || !valid_feed(feed)) { errno = EINVAL; return -1; } + if (index >= SLEEP_BITFIELD_DATA_BYTES * 8u) { errno = ERANGE; return -1; } + peer = dat_session_find_peer(session, peer_id); + if (!peer || peer->state != DAT_SESSION_PEER_READY || + !(peer->remote_feed_mask & (uint8_t)(1u << feed))) { errno = ENOTCONN; return -1; } + if (!peer->replication[feed].uploading) { errno = EACCES; return -1; } + already_requested = sleep_replication_is_requested(&peer->replication[feed], index); + if (already_requested < 0) return -1; + if (already_requested) return 0; + queued.index = index; queued.bytes = bytes; queued.hash = 1; queued.nodes = 1; + if (sleep_replication_request(&peer->replication[feed], &queued)) return -1; + request.index = index; request.bytes = bytes; request.want_hash = 1; request.want_nodes = 1; + if (dat_request_encode(&request, payload, sizeof(payload), &payload_len) || + send_frame(session, peer, feed_channels[feed], DAT_MSG_REQUEST, payload, payload_len)) { + (void)sleep_replication_complete(&peer->replication[feed], index); + return -1; + } + return 0; +} + +int dat_session_send_block(dat_session_t *session, + const uint8_t peer_id[DAT_PEER_ID_BYTES], + enum dat_session_feed feed, uint64_t index) +{ + dat_session_peer_t *peer; + sleep_proof_t proof; + dat_data_t data; + uint8_t *block = NULL, *proof_bytes = NULL, *payload = NULL; + size_t block_len = 0, proof_len = 0, payload_len = 0; + int rc = -1; + if (!session || !peer_id || !valid_feed(feed)) { errno = EINVAL; return -1; } + peer = dat_session_find_peer(session, peer_id); + if (!peer || peer->state != DAT_SESSION_PEER_READY || + !(peer->remote_feed_mask & (uint8_t)(1u << feed))) { errno = ENOTCONN; return -1; } + if (!peer->replication[feed].downloading) { errno = EACCES; return -1; } + if (sleep_feed_read(session->feeds[feed], (size_t)index, &block, &block_len) || + sleep_proof_create(session->feeds[feed], index, &proof)) goto done; + proof_bytes = malloc(51 + (proof.node_count + proof.root_count) * 48 + 64); + if (!proof_bytes) goto done; + if (sleep_proof_encode(&proof, proof_bytes, + 51 + (proof.node_count + proof.root_count) * 48 + 64, + &proof_len)) goto done; + data.index = index; + data.value = block; + data.value_len = block_len; + data.proof = proof_bytes; + data.proof_len = proof_len; + data.node_count = 0; + data.has_signature = proof.has_signature; + memcpy(data.signature, proof.signature, sizeof(data.signature)); + { + size_t i; + for (i = 0; i < proof.node_count + proof.root_count; i++) { + const sleep_proof_node_t *node = i < proof.node_count ? + &proof.nodes[i] : &proof.roots[i - proof.node_count]; + data.nodes[i].index = node->index; + data.nodes[i].length = node->length; + memcpy(data.nodes[i].hash, node->hash, 32); + } + data.node_count = proof.node_count + proof.root_count; + } + if (block_len > SIZE_MAX - proof_len - data.node_count * 64 - 256) { + errno = EOVERFLOW; + goto done; + } + payload = malloc(block_len + proof_len + data.node_count * 64 + 256); + if (!payload) goto done; + if (dat_data_encode(&data, payload, + block_len + proof_len + data.node_count * 64 + 256, + &payload_len)) goto done; + rc = send_frame(session, peer, feed_channels[feed], DAT_MSG_DATA, payload, payload_len); + if (!rc) peer->payload_bytes_sent += block_len; +done: + free(block); free(proof_bytes); free(payload); + return rc; +} + +int dat_session_peer_down(dat_session_t *session, const uint8_t peer_id[DAT_PEER_ID_BYTES]) +{ + dat_session_peer_t *peer; + size_t i; + int active = 0; + if (!session || !peer_id) { errno = EINVAL; return -1; } + peer = dat_session_find_peer(session, peer_id); + if (!peer) { errno = ENOENT; return -1; } + peer->state = DAT_SESSION_PEER_CLOSED; + peer->transport_authenticated = 0; + free(peer->rx_buffer); + peer->rx_buffer = NULL; + peer->rx_buffer_len = peer->rx_buffer_capacity = 0; + for (i = 0; i < DAT_SESSION_FEED_COUNT; i++) + sleep_replication_clear_requests(&peer->replication[i]); + for (i = 0; i < session->peer_count; i++) + if (session->peers[i].state == DAT_SESSION_PEER_READY) active++; + if (!active) session->state = DAT_SESSION_DISCOVERING; + return 0; +} + +int dat_session_peer_pause(dat_session_t *session, + const uint8_t peer_id[DAT_PEER_ID_BYTES], int paused) +{ + dat_session_peer_t *peer; + size_t feed; + if (!session || !peer_id) { errno = EINVAL; return -1; } + peer = dat_session_find_peer(session, peer_id); + if (!peer || peer->state != DAT_SESSION_PEER_READY) { errno = ENOTCONN; return -1; } + peer->paused = !!paused; + if (peer->paused) { + for (feed = 0; feed < DAT_SESSION_FEED_COUNT; feed++) + sleep_replication_clear_requests(&peer->replication[feed]); + return 0; + } + for (feed = 0; feed < DAT_SESSION_FEED_COUNT; feed++) + if (pump_replication(session, peer, (enum dat_session_feed)feed)) return -1; + return 0; +} + +int dat_session_tick(dat_session_t *session, uint64_t now_ms) +{ + return dat_session_poll(session, 0, now_ms); +} + +int dat_session_poll(dat_session_t *session, int timeout_ms, uint64_t now_ms) +{ + int received, wait_ms; + if (!session || (session->state != DAT_SESSION_DISCOVERING && + session->state != DAT_SESSION_ACTIVE) || timeout_ms < 0) { errno = EINVAL; return -1; } + if (session->poll_transport && + session->poll_transport(session->poll_context, timeout_ms) < 0) return -1; + wait_ms = session->poll_transport ? 0 : timeout_ms; + received = cesspool_poll(session->cesspool, wait_ms); + if (received < 0) return -1; + while (received > 0) { + received = cesspool_poll(session->cesspool, 0); + if (received < 0) return -1; + } + if (cesspool_tick(session->cesspool, now_ms)) return -1; + if (discover_known_peers(session)) return -1; + return 0; +} + +int dat_session_close(dat_session_t *session) +{ + size_t i; + int rc = 0; + if (!session || session->state == DAT_SESSION_CLOSED) { errno = EINVAL; return -1; } + session->state = DAT_SESSION_CLOSING; + for (i = 0; i < session->peer_count; i++) { + session->peers[i].state = DAT_SESSION_PEER_CLOSED; + session->peers[i].transport_authenticated = 0; + free(session->peers[i].rx_buffer); + session->peers[i].rx_buffer = NULL; + session->peers[i].rx_buffer_len = session->peers[i].rx_buffer_capacity = 0; + } + if (session->announcing || session->looking_up) { + if (cesspool_leave(session->cesspool, session->discovery_topic)) rc = -1; + } + session->state = DAT_SESSION_CLOSED; + return rc; +} diff --git a/extra/dat-dpi/src/dat_walk.c b/extra/dat-dpi/src/dat_walk.c new file mode 100644 index 0000000..71407f4 --- /dev/null +++ b/extra/dat-dpi/src/dat_walk.c @@ -0,0 +1,207 @@ +#define _POSIX_C_SOURCE 200809L +#include "dat.h" +#include "win_dirent.h" +#include +#include "win_fnmatch.h" +#include +#include +#include +#include +#include +#include +#include + +static int join_path(char*out,size_t cap,const char*a,const char*b){int n=snprintf(out,cap,"%s%s%s",a,*a&&a[strlen(a)-1]!='/'?"/":"",b);if(n<0||(size_t)n>=cap){errno=ENAMETOOLONG;return-1;}return 0;} +static int matches_archive_path(const char *path, const char *root); +typedef struct sync_context { char patterns[128][256]; size_t pattern_count; } sync_context_t; +static uint64_t stat_mtime_ms(const struct stat *st){ +#if defined(_WIN32) + if(st->st_mtime<0)return 0; + return (uint64_t)st->st_mtime*1000u; +#elif defined(__APPLE__) + if(st->st_mtimespec.tv_sec<0)return 0; + return (uint64_t)st->st_mtimespec.tv_sec*1000u+(uint64_t)st->st_mtimespec.tv_nsec/1000000u; +#else + if(st->st_mtim.tv_sec<0)return 0; + return (uint64_t)st->st_mtim.tv_sec*1000u+(uint64_t)st->st_mtim.tv_nsec/1000000u; +#endif +} +static uint64_t now_ms(void){struct timespec ts;if(clock_gettime(CLOCK_REALTIME,&ts))return (uint64_t)time(NULL)*1000u;return (uint64_t)ts.tv_sec*1000u+(uint64_t)ts.tv_nsec/1000000u;} +static int import_file(dat_archive_t*a,const char*disk,const char*name,const struct stat*st){FILE*f;uint8_t*data;size_t len=(size_t)st->st_size;if(st->st_size<0||(uint64_t)st->st_size>SIZE_MAX){errno=EOVERFLOW;return-1;}data=malloc(len?len:1);if(!data)return-1;f=fopen(disk,"rb");if(!f){free(data);return-1;}if(len&&fread(data,1,len,f)!=len){fclose(f);free(data);errno=EIO;return-1;}fclose(f);if(dat_archive_append_file(a,name,data,len,(uint32_t)st->st_mode,stat_mtime_ms(st))){free(data);return-1;}free(data);return 0;} +static int import_one(dat_archive_t*a,const char*disk,const char*name){struct stat st;if(lstat(disk,&st))return-1;if(S_ISREG(st.st_mode))return import_file(a,disk,name,&st);if(S_ISLNK(st.st_mode)){char target[1024];ssize_t n=readlink(disk,target,sizeof(target)-1);if(n<0)return-1;target[n]=0;return dat_archive_append_symlink(a,name,target,stat_mtime_ms(&st));}if(S_ISDIR(st.st_mode)){DIR*dir;struct dirent*item;int rc=0;if(*name&&dat_archive_append_directory(a,name,(uint32_t)st.st_mode,stat_mtime_ms(&st)))return-1;dir=opendir(disk);if(!dir)return-1;while((item=readdir(dir))){char child_disk[2048],child_name[2048];if(!strcmp(item->d_name,".")||!strcmp(item->d_name,"..")||!strcmp(item->d_name,".dat"))continue;if(join_path(child_disk,sizeof(child_disk),disk,item->d_name)||join_path(child_name,sizeof(child_name),name,item->d_name)||import_one(a,child_disk,child_name)){rc=-1;break;}}closedir(dir);return rc;}return 0;} +int dat_archive_import_path(dat_archive_t*a,const char*filesystem_path,const char*archive_path){if(!a||!filesystem_path||!archive_path){errno=EINVAL;return-1;}while(*archive_path=='/')archive_path++;return import_one(a,filesystem_path,archive_path);} + +static int same_file(const dat_archive_t *a, const char *disk, + const struct stat *st, const sleep_metadata_entry_t *entry) +{ + FILE *f; + uint8_t *old, buffer[8192]; + size_t old_len, n, off = 0; + if (entry->type != SLEEP_ENTRY_FILE || entry->size != (uint64_t)st->st_size || + entry->mode != (uint32_t)st->st_mode || entry->mtime != stat_mtime_ms(st)) + return 0; + if (dat_archive_read_file(a, entry, &old, &old_len)) return -1; + f = fopen(disk, "rb"); + if (!f) { free(old); return -1; } + while ((n = fread(buffer, 1, sizeof(buffer), f)) != 0) { + if (n > old_len - off || memcmp(buffer, old + off, n)) { + fclose(f); free(old); return 0; + } + off += n; + } + if (ferror(f)) { fclose(f); free(old); errno = EIO; return -1; } + fclose(f); + free(old); + return off == old_len; +} + +static int ignored_path(const sync_context_t *context, const char *name, const char *basename) +{ + size_t i; + if (*name && basename[0] == '.') return 1; + for (i = 0; i < context->pattern_count; i++) { + const char *pattern = context->patterns[i]; + size_t n = strlen(pattern); + if (pattern[0] == '/') pattern++; + if (n && context->patterns[i][n - 1] == '/' && !strncmp(name, pattern, strlen(pattern))) return 1; + if (!fnmatch(pattern, name, 0)) return 1; + } + return 0; +} + +static int sync_one(dat_archive_t *a, const char *disk, const char *name, const sync_context_t *context) +{ + struct stat st; + sleep_metadata_entry_t previous; + int found; + if (lstat(disk, &st)) return -1; + found = dat_archive_find(a, name, &previous) == 0; + if (!found && errno != ENOENT) return -1; + if (S_ISREG(st.st_mode)) { + int equal = found ? same_file(a, disk, &st, &previous) : 0; + if (equal < 0) return -1; + return equal ? 0 : import_file(a, disk, name, &st); + } + if (S_ISLNK(st.st_mode)) { + char target[1024]; ssize_t n = readlink(disk, target, sizeof(target) - 1); + if (n < 0) return -1; + target[n] = 0; + if (found && previous.type == SLEEP_ENTRY_SYMLINK && previous.mtime == stat_mtime_ms(&st) && !strcmp(previous.linkname, target)) return 0; + return dat_archive_append_symlink(a, name, target, stat_mtime_ms(&st)); + } + if (S_ISDIR(st.st_mode)) { + DIR *dir; struct dirent *item; + if (*name && (!found || previous.type != SLEEP_ENTRY_DIRECTORY || previous.mode != (uint32_t)st.st_mode || previous.mtime != stat_mtime_ms(&st)) && dat_archive_append_directory(a, name, (uint32_t)st.st_mode, stat_mtime_ms(&st))) return -1; + dir = opendir(disk); + if (!dir) return -1; + while ((item = readdir(dir))) { + char child_disk[2048], child_name[2048]; + if (!strcmp(item->d_name, ".") || !strcmp(item->d_name, "..")) continue; + if (join_path(child_disk, sizeof(child_disk), disk, item->d_name) || join_path(child_name, sizeof(child_name), name, item->d_name)) { closedir(dir); return -1; } + if (ignored_path(context, child_name, item->d_name)) continue; + if (sync_one(a, child_disk, child_name, context)) { closedir(dir); return -1; } + } + closedir(dir); + } + return 0; +} + +static int load_ignore_file(const char *root, sync_context_t *context) +{ + char path[1200], line[256]; + FILE *f; + if (join_path(path, sizeof(path), root, ".datignore")) return -1; + f = fopen(path, "r"); + if (!f) return errno == ENOENT ? 0 : -1; + while (fgets(line, sizeof(line), f)) { + size_t n = strcspn(line, "\r\n"); + line[n] = 0; + if (!line[0] || line[0] == '#') continue; + if (context->pattern_count >= sizeof(context->patterns) / sizeof(context->patterns[0])) { fclose(f); errno = EOVERFLOW; return -1; } + strcpy(context->patterns[context->pattern_count++], line); + } + if (ferror(f)) { fclose(f); errno = EIO; return -1; } + return fclose(f); +} + +static int path_missing(const char *filesystem_path, const char *archive_path, const char *entry_path) +{ + char disk[4096]; const char *relative = entry_path; size_t prefix; struct stat st; + while (*relative == '/') relative++; + prefix = strlen(archive_path); + if (prefix) { if (!strcmp(relative, archive_path)) relative = ""; else relative += prefix + 1; } + if (join_path(disk, sizeof(disk), filesystem_path, relative)) return -1; + if (!lstat(disk, &st)) return 0; + return errno == ENOENT ? 1 : -1; +} + +int dat_archive_sync_path(dat_archive_t *a, const char *filesystem_path, const char *archive_path) +{ + size_t count, i, j; + sync_context_t context; + if (!a || !filesystem_path || !archive_path) { errno = EINVAL; return -1; } + while (*archive_path == '/') archive_path++; + memset(&context, 0, sizeof(context)); + if (load_ignore_file(filesystem_path, &context) || sync_one(a, filesystem_path, archive_path, &context)) return -1; + count = dat_archive_entry_count(a); + for (i = 0; i < count; i++) { + sleep_metadata_entry_t entry, later; int newest = 1, missing; + if (dat_archive_read_entry(a, i, &entry)) return -1; + if (!matches_archive_path(entry.path, archive_path)) continue; + for (j = i + 1; j < count; j++) { if (dat_archive_read_entry(a, j, &later)) return -1; if (!strcmp(entry.path, later.path)) { newest = 0; break; } } + if (!newest || entry.type == SLEEP_ENTRY_DELETE) continue; + missing = path_missing(filesystem_path, archive_path, entry.path); + if (missing < 0) return -1; + if (missing && dat_archive_delete(a, entry.path, now_ms())) return -1; + } + return 0; +} + +static int export_entry(const dat_archive_t*a,const sleep_metadata_entry_t*e,const char*path){if(e->type==SLEEP_ENTRY_DIRECTORY){if(mkdir(path,(mode_t)e->mode)&&errno!=EEXIST)return-1;return 0;}if(e->type==SLEEP_ENTRY_SYMLINK)return symlink(e->linkname,path);if(e->type==SLEEP_ENTRY_FILE){uint8_t*data;size_t len;FILE*f;if(dat_archive_read_file(a,e,&data,&len))return-1;f=fopen(path,"wb");if(!f){free(data);return-1;}if(len&&fwrite(data,1,len,f)!=len){fclose(f);free(data);errno=EIO;return-1;}free(data);return fclose(f);}return 0;} +static int matches_archive_path(const char *path, const char *root) +{ + size_t n; + while (*path == '/') path++; + while (*root == '/') root++; + n = strlen(root); + return !n || !strcmp(path, root) || (!strncmp(path, root, n) && path[n] == '/'); +} + +int dat_archive_export_at(const dat_archive_t*a,uint64_t version,const char*archive_path,const char*filesystem_path) +{ + sleep_metadata_entry_t e, later; + size_t count,i,j,prefix; + if(!a||!archive_path||!filesystem_path){errno=EINVAL;return-1;} + while(*archive_path=='/')archive_path++; + count=dat_archive_entry_count(a); + if(version>count){errno=ERANGE;return-1;} + prefix=strlen(archive_path); + for(i=0;i<(size_t)version;i++){ + char suffix[2048],disk[4096]; + int current=1; + if(dat_archive_read_entry(a,i,&e))return-1; + if(!matches_archive_path(e.path,archive_path))continue; + for(j=i+1;j<(size_t)version;j++){ + if(dat_archive_read_entry(a,j,&later))return-1; + if(!strcmp(later.path,e.path)){current=0;break;} + } + if(!current||e.type==SLEEP_ENTRY_DELETE)continue; + { + const char *entry_path=e.path; + while(*entry_path=='/')entry_path++; + if(!prefix)sprintf(suffix,"%s",entry_path); + else if(!strcmp(entry_path,archive_path))suffix[0]=0; + else strcpy(suffix,entry_path+prefix+1); + } + if(*suffix){if(join_path(disk,sizeof(disk),filesystem_path,suffix))return-1;} + else{if(strlen(filesystem_path)>=sizeof(disk)){errno=ENAMETOOLONG;return-1;}strcpy(disk,filesystem_path);} + if(export_entry(a,&e,disk))return-1; + } + return 0; +} + +int dat_archive_export_path(const dat_archive_t*a,const char*archive_path,const char*filesystem_path) +{ + if(!a){errno=EINVAL;return-1;} + return dat_archive_export_at(a,dat_archive_version(a),archive_path,filesystem_path); +} diff --git a/extra/dat-dpi/src/dat_wire.c b/extra/dat-dpi/src/dat_wire.c new file mode 100644 index 0000000..5cc4433 --- /dev/null +++ b/extra/dat-dpi/src/dat_wire.c @@ -0,0 +1,421 @@ +#include "dat_wire.h" +#include "sleep_proto.h" +#include +#include + +static int put_varint(uint64_t value, uint8_t *out, size_t cap, size_t *off) +{ + size_t n; + if (*off > cap || sleep_varint_encode(value, out + *off, cap - *off, &n)) return -1; + *off += n; + return 0; +} + +static int put_bytes(uint32_t field, const uint8_t *data, size_t len, + uint8_t *out, size_t cap, size_t *off) +{ + if ((!data && len) || put_varint(((uint64_t)field << 3) | 2, out, cap, off) || + put_varint(len, out, cap, off)) return -1; + if (len > cap - *off) { errno = ENOSPC; return -1; } + if (len) memcpy(out + *off, data, len); + *off += len; + return 0; +} + +static int put_uint(uint32_t field, uint64_t value, + uint8_t *out, size_t cap, size_t *off) +{ + return put_varint((uint64_t)field << 3, out, cap, off) || + put_varint(value, out, cap, off) ? -1 : 0; +} + +/* Returns one field. Unknown protobuf wire types are skipped by read_field. */ +typedef struct proto_field { + uint32_t number; + uint8_t wire; + uint64_t value; + const uint8_t *bytes; + size_t length; +} proto_field_t; + +static int read_field(const uint8_t *in, size_t len, size_t *off, + proto_field_t *field) +{ + uint64_t tag, value; + size_t n; + if (*off >= len) return 0; + if (sleep_varint_decode(in + *off, len - *off, &tag, &n)) return -1; + *off += n; + field->number = (uint32_t)(tag >> 3); + field->wire = (uint8_t)(tag & 7); + field->bytes = NULL; + field->length = 0; + field->value = 0; + if (!field->number) { errno = EPROTO; return -1; } + if (field->wire == 0) { + if (sleep_varint_decode(in + *off, len - *off, &value, &n)) return -1; + *off += n; + field->value = value; + } else if (field->wire == 2) { + if (sleep_varint_decode(in + *off, len - *off, &value, &n)) return -1; + *off += n; + if (value > len - *off) { errno = EPROTO; return -1; } + field->bytes = in + *off; + field->length = (size_t)value; + *off += (size_t)value; + } else if (field->wire == 1 || field->wire == 5) { + size_t skip = field->wire == 1 ? 8 : 4; + if (skip > len - *off) { errno = EPROTO; return -1; } + *off += skip; + } else { errno = EPROTO; return -1; } + return 1; +} + +int dat_wire_encode(const dat_wire_message_t *m, uint8_t *out, size_t cap, + size_t *written) +{ + uint8_t header[10]; + size_t hlen, prefix, total; + uint64_t packed; + if (!m || !out || (!m->payload && m->payload_len) || m->type > 15 || + m->channel > (UINT64_MAX >> 4)) { errno = EINVAL; return -1; } + packed = (m->channel << 4) | m->type; + if (sleep_varint_encode(packed, header, sizeof(header), &hlen)) return -1; + if (m->payload_len > SIZE_MAX - hlen) { errno = EOVERFLOW; return -1; } + total = hlen + m->payload_len; + if (sleep_varint_encode(total, out, cap, &prefix)) return -1; + if (total > cap - prefix) { errno = ENOSPC; return -1; } + memcpy(out + prefix, header, hlen); + if (m->payload_len) memcpy(out + prefix + hlen, m->payload, m->payload_len); + if (written) *written = prefix + total; + return 0; +} + +int dat_wire_decode(dat_wire_message_t *m, const uint8_t *in, size_t len, + size_t *consumed) +{ + uint64_t frame, packed; + size_t prefix, hlen; + if (!m || !in) { errno = EINVAL; return -1; } + if (sleep_varint_decode(in, len, &frame, &prefix)) return -1; + if (frame > len - prefix || frame > SIZE_MAX) { errno = EAGAIN; return -1; } + if (!frame || sleep_varint_decode(in + prefix, (size_t)frame, &packed, &hlen)) return -1; + m->channel = packed >> 4; + m->type = packed & 15; + m->payload = in + prefix + hlen; + m->payload_len = (size_t)frame - hlen; + if (consumed) *consumed = prefix + (size_t)frame; + return 0; +} + +int dat_handshake_init(dat_handshake_t *h, const uint8_t peer_id[32], + uint64_t capabilities) +{ + size_t n; + if (!h || !peer_id) { errno = EINVAL; return -1; } + n = strlen(DAT_NETWORK_PROTOCOL); + if (!n || n > DAT_PROTOCOL_BYTES) { errno = EINVAL; return -1; } + memset(h, 0, sizeof(*h)); + memcpy(h->protocol, DAT_NETWORK_PROTOCOL, n); + memcpy(h->peer_id, peer_id, 32); + h->capabilities = capabilities; + h->has_peer_id = 1; + return 0; +} + +int dat_handshake_protocol_matches(const dat_handshake_t *h) +{ + size_t n, i; + if (!h) { errno = EINVAL; return -1; } + n = strlen(DAT_NETWORK_PROTOCOL); + if (!n || n > DAT_PROTOCOL_BYTES) { errno = EINVAL; return -1; } + if (memcmp(h->protocol, DAT_NETWORK_PROTOCOL, n)) { errno = EPROTONOSUPPORT; return 0; } + for (i = n; i < DAT_PROTOCOL_BYTES; i++) if (h->protocol[i]) { + errno = EPROTONOSUPPORT; + return 0; + } + return 1; +} + +int dat_handshake_encode(const dat_handshake_t *h, uint8_t *out, size_t cap, + size_t *written) +{ + size_t off = 0; +#ifndef DAT_LEGACY_HYPERCORE + size_t protocol_len = strlen(DAT_NETWORK_PROTOCOL); +#endif + if (!h || !out || dat_handshake_protocol_matches(h) != 1) { errno = EINVAL; return -1; } + if ((h->has_peer_id && put_bytes(1, h->peer_id, 32, out, cap, &off)) || + (h->capabilities && put_uint(2, h->capabilities, out, cap, &off))) return -1; +#ifndef DAT_LEGACY_HYPERCORE + if (put_bytes(3, (const uint8_t *)DAT_NETWORK_PROTOCOL, protocol_len, + out, cap, &off)) return -1; +#endif + if (written) *written = off; + return 0; +} + +int dat_handshake_decode(dat_handshake_t *h, const uint8_t *in, size_t len) +{ + size_t off = 0; + proto_field_t f; + int rc; + if (!h || !in) { errno = EINVAL; return -1; } + memset(h, 0, sizeof(*h)); + while ((rc = read_field(in, len, &off, &f)) > 0) { + if (f.number == 1 && f.wire == 2) { + if (f.length == sizeof(h->peer_id)) { + memcpy(h->peer_id, f.bytes, f.length); + h->has_peer_id = 1; + } + } else if (f.number == 2 && f.wire == 0) h->capabilities = f.value; +#ifndef DAT_LEGACY_HYPERCORE + else if (f.number == 3 && f.wire == 2) { + if (f.length > sizeof(h->protocol)) { errno = EPROTO; return -1; } + memcpy(h->protocol, f.bytes, f.length); + } +#endif + } + if (rc < 0) { errno = EPROTO; return -1; } + /* Hypercore v6 userData is application data, not a protocol identity. */ + memcpy(h->protocol, DAT_NETWORK_PROTOCOL, strlen(DAT_NETWORK_PROTOCOL)); + return 0; +} + +int dat_feed_open_encode(const dat_feed_open_t *feed, uint8_t *out, + size_t cap, size_t *written) +{ + size_t off = 0; + if (!feed || !out) { errno = EINVAL; return -1; } + if (put_bytes(1, feed->discovery_key, 32, out, cap, &off) || + (feed->has_nonce && put_bytes(2, feed->nonce, sizeof(feed->nonce), out, cap, &off))) return -1; + if (written) *written = off; + return 0; +} + +int dat_feed_open_decode(dat_feed_open_t *feed, const uint8_t *in, size_t len) +{ + size_t off = 0; + proto_field_t f; + int rc, found = 0; + if (!feed || !in) { errno = EINVAL; return -1; } + memset(feed, 0, sizeof(*feed)); + while ((rc = read_field(in, len, &off, &f)) > 0) { + if ((f.number == 1 || f.number == 2) && f.wire == 2) { + if ((f.number == 1 && f.length != 32) || + (f.number == 2 && f.length != sizeof(feed->nonce))) { errno = EPROTO; return -1; } + if (f.number == 1) { memcpy(feed->discovery_key, f.bytes, 32); found = 1; } + else { memcpy(feed->nonce, f.bytes, sizeof(feed->nonce)); feed->has_nonce = 1; } + } + } + if (rc < 0 || !found) { errno = EPROTO; return -1; } + return 0; +} + +int dat_range_encode(const dat_range_t *r, uint8_t *out, size_t cap, size_t *written) +{ + size_t off = 0; + if (!r || !out) { errno = EINVAL; return -1; } + if (put_uint(1, r->start, out, cap, &off) || put_uint(2, r->length, out, cap, &off)) return -1; + if (written) *written = off; + return 0; +} + +int dat_range_decode(dat_range_t *r, const uint8_t *in, size_t len) +{ + size_t off = 0; + proto_field_t f; + int rc, start = 0; + if (!r || !in) { errno = EINVAL; return -1; } + r->start = 0; r->length = 1; + while ((rc = read_field(in, len, &off, &f)) > 0) if (f.wire == 0) { + if (f.number == 1) { r->start = f.value; start = 1; } + if (f.number == 2) r->length = f.value; + } + if (rc < 0 || !start) { errno = EPROTO; return -1; } + return 0; +} + +int dat_want_decode(dat_range_t *r, const uint8_t *in, size_t len, + uint64_t feed_length) +{ + size_t off = 0; + proto_field_t f; + int rc, have_start = 0, have_length = 0; + if (!r || !in) { errno = EINVAL; return -1; } + r->start = 0; + r->length = 0; + while ((rc = read_field(in, len, &off, &f)) > 0) if (f.wire == 0) { + if (f.number == 1) { r->start = f.value; have_start = 1; } + else if (f.number == 2) { r->length = f.value; have_length = 1; } + } + if (rc < 0 || !have_start) { errno = EPROTO; return -1; } + if (!have_length && r->start < feed_length) r->length = feed_length - r->start; + return 0; +} + +int dat_info_encode(const dat_info_t *info, uint8_t *out, size_t cap, + size_t *written) +{ + size_t off = 0; + if (!info || !out) { errno = EINVAL; return -1; } + if ((!info->uploading && put_uint(1, 0, out, cap, &off)) || + (!info->downloading && put_uint(2, 0, out, cap, &off))) return -1; + if (written) *written = off; + return 0; +} + +int dat_info_decode(dat_info_t *info, const uint8_t *in, size_t len) +{ + size_t off = 0; + proto_field_t f; + int rc; + if (!info || (!in && len)) { errno = EINVAL; return -1; } + info->uploading = 1; + info->downloading = 1; + while ((rc = read_field(in, len, &off, &f)) > 0) if (f.wire == 0) { + if (f.number == 1) info->uploading = !!f.value; + else if (f.number == 2) info->downloading = !!f.value; + } + if (rc < 0) { errno = EPROTO; return -1; } + return 0; +} + +int dat_request_encode(const dat_request_t *r, uint8_t *out, size_t cap, size_t *written) +{ + size_t off = 0; + if (!r || !out) { errno = EINVAL; return -1; } + if (put_uint(1, r->index, out, cap, &off) || + (r->bytes && put_uint(2, r->bytes, out, cap, &off)) || + (r->want_hash && put_uint(3, 1, out, cap, &off)) || + (r->want_nodes && put_uint(4, 64, out, cap, &off))) return -1; + if (written) *written = off; + return 0; +} + +int dat_request_decode(dat_request_t *r, const uint8_t *in, size_t len) +{ + size_t off = 0; + proto_field_t f; + int rc, found = 0; + if (!r || !in) { errno = EINVAL; return -1; } + memset(r, 0, sizeof(*r)); + while ((rc = read_field(in, len, &off, &f)) > 0) if (f.wire == 0) { + if (f.number == 1) { r->index = f.value; found = 1; } + else if (f.number == 2) r->bytes = f.value; + else if (f.number == 3) r->want_hash = !!f.value; + else if (f.number == 4) r->want_nodes = !!f.value; + } + if (rc < 0 || !found) { errno = EPROTO; return -1; } + return 0; +} + +static int encode_node(const dat_data_node_t *node, uint8_t *out, + size_t cap, size_t *written) +{ + size_t off = 0; + if (put_uint(1, node->index, out, cap, &off) || + put_bytes(2, node->hash, 32, out, cap, &off) || + put_uint(3, node->length, out, cap, &off)) return -1; + *written = off; + return 0; +} + +static int decode_node(dat_data_node_t *node, const uint8_t *in, size_t len) +{ + size_t off = 0; + proto_field_t f; + int rc, have_index = 0, have_hash = 0, have_length = 0; + while ((rc = read_field(in, len, &off, &f)) > 0) { + if (f.number == 1 && f.wire == 0) { node->index = f.value; have_index = 1; } + else if (f.number == 2 && f.wire == 2 && f.length == 32) { memcpy(node->hash, f.bytes, 32); have_hash = 1; } + else if (f.number == 3 && f.wire == 0) { node->length = f.value; have_length = 1; } + } + if (rc < 0 || !have_index || !have_hash || !have_length) { errno = EPROTO; return -1; } + return 0; +} + +int dat_data_encode(const dat_data_t *d, uint8_t *out, size_t cap, size_t *written) +{ + size_t off = 0, i, node_len; + uint8_t node[64]; + if (!d || !out || (!d->value && d->value_len) || (!d->proof && d->proof_len) || + d->node_count > DAT_DATA_MAX_NODES) { errno = EINVAL; return -1; } + if (put_uint(1, d->index, out, cap, &off) || + put_bytes(2, d->value, d->value_len, out, cap, &off)) return -1; + for (i = 0; i < d->node_count; i++) { + if (encode_node(&d->nodes[i], node, sizeof(node), &node_len) || + put_bytes(3, node, node_len, out, cap, &off)) return -1; + } + if (d->has_signature && put_bytes(4, d->signature, sizeof(d->signature), out, cap, &off)) return -1; + /* Field 5 carries C's complete multi-root proof; older protobuf readers ignore it. */ + if (d->proof_len && put_bytes(5, d->proof, d->proof_len, out, cap, &off)) return -1; + if (written) *written = off; + return 0; +} + +int dat_data_decode(dat_data_t *d, const uint8_t *in, size_t len) +{ + size_t off = 0; + proto_field_t f; + int rc, have_index = 0; + if (!d || !in) { errno = EINVAL; return -1; } + memset(d, 0, sizeof(*d)); + while ((rc = read_field(in, len, &off, &f)) > 0) { + if (f.number == 1 && f.wire == 0) { d->index = f.value; have_index = 1; } + else if (f.number == 2 && f.wire == 2) { d->value = f.bytes; d->value_len = f.length; } + else if (f.number == 3 && f.wire == 2) { + if (d->node_count >= DAT_DATA_MAX_NODES || + decode_node(&d->nodes[d->node_count], f.bytes, f.length)) return -1; + d->node_count++; + } else if (f.number == 4 && f.wire == 2) { + if (f.length != 64) { errno = EPROTO; return -1; } + memcpy(d->signature, f.bytes, 64); d->has_signature = 1; + } else if (f.number == 5 && f.wire == 2) { d->proof = f.bytes; d->proof_len = f.length; } + } + if (rc < 0 || !have_index) { errno = EPROTO; return -1; } + return 0; +} + +int dat_cancel_encode(const dat_cancel_t *c, uint8_t *out, size_t cap, size_t *written) +{ + size_t off = 0; + if (!c || !out) { errno = EINVAL; return -1; } + if (put_uint(1, c->index, out, cap, &off)) return -1; + if (written) *written = off; + return 0; +} + +int dat_cancel_decode(dat_cancel_t *c, const uint8_t *in, size_t len) +{ + dat_request_t r; + if (!c || dat_request_decode(&r, in, len)) return -1; + c->index = r.index; + return 0; +} + +int dat_extension_encode(const dat_extension_t *e, uint8_t *out, size_t cap, + size_t *written) +{ + size_t off = 0; + if (!e || !out || (!e->message && e->message_len)) { errno = EINVAL; return -1; } + if (put_varint(e->id, out, cap, &off)) return -1; + if (e->message_len > cap - off) { errno = ENOSPC; return -1; } + if (e->message_len) memcpy(out + off, e->message, e->message_len); + off += e->message_len; + if (written) *written = off; + return 0; +} + +int dat_extension_decode(dat_extension_t *e, const uint8_t *in, size_t len) +{ + uint64_t id; + size_t n, off = 0; + if (!e || !in || sleep_varint_decode(in, len, &id, &n)) return -1; + off = n; + memset(e, 0, sizeof(*e)); + e->id = id; + e->message = in + off; + e->message_len = len - off; + return 0; +} diff --git a/extra/dat-dpi/src/s7.c b/extra/dat-dpi/src/s7.c new file mode 100644 index 0000000..725890b --- /dev/null +++ b/extra/dat-dpi/src/s7.c @@ -0,0 +1,96503 @@ +/* s7, a Scheme interpreter + * + * derived from TinyScheme 1.39, but not a single byte of that code remains + * SPDX-License-Identifier: 0BSD + * + * Bill Schottstaedt, bil@ccrma.stanford.edu + * + * Mike Scholz provided the FreeBSD support (complex trig funcs, etc) + * Rick Taube, Andrew Burnson, Donny Ward, Greg Santucci, and Christos Vagias provided the MS Visual C++ support + * Kjetil Matheussen provided the mingw support + * + * Documentation is in s7.h and s7.html. + * s7test.scm is a regression test. + * repl.scm is a vt100-based listener. + * nrepl.scm is a notcurses-based listener. + * cload.scm and lib*.scm tie in various C libraries. + * lint.scm checks Scheme code for infelicities. + * r7rs.scm implements some of r7rs (small). + * write.scm currrently has pretty-print. + * mockery.scm has the mock-data definitions. + * reactive.scm has reactive-set and friends. + * stuff.scm has some stuff. + * profile.scm has code to display profile data. + * debug.scm has debugging aids. + * case.scm has case*, an extension of case to pattern matching. + * timing tests are in the s7 tools directory + * + * s7.c is organized as follows: + * structs and type flags + * internal debugging stuff + * constants + * GC + * stacks + * symbols and keywords + * lets + * continuations + * numbers + * characters + * strings + * ports + * format + * lists + * vectors + * hash-tables + * c-objects + * functions + * equal? + * generic length, copy, reverse, fill!, append + * error handlers + * sundry leftovers + * the optimizers + * multiple-values, quasiquote + * eval + * *s7* + * initialization and free + * repl + * + * naming conventions: s7_* usually are C accessible (s7.h), g_* are scheme accessible, + * H_* are documentation strings, Q_* are procedure signatures, scheme "?" corresponds to C "is_", scheme "->" to C "_to_", + * *_1 are ancillary functions, big_* refer to gmp, *_nr means no return, inline_* means always-inline. + * + * ---------------- compile time switches ---------------- + */ + +#if defined __has_include +# if __has_include ("mus-config.h") +# include "mus-config.h" +# endif +#else + #include "mus-config.h" +#endif + +/* + * Your config file goes here, or just replace that #include line with the defines you need. + * The compile-time switches involve booleans, complex numbers, and multiprecision arithmetic. + * Currently we assume we have setjmp.h (used by the error handlers). + * + * Complex number support, which is problematic in C++, Solaris, and netBSD + * is on the HAVE_COMPLEX_NUMBERS switch. In OSX or Linux, if you're not using C++, + * + * #define HAVE_COMPLEX_NUMBERS 1 + * #define HAVE_COMPLEX_TRIG 1 + * + * In C++ I use: + * + * #define HAVE_COMPLEX_NUMBERS 1 + * #define HAVE_COMPLEX_TRIG 0 + * + * In Windows and tcc, both are 0. + * + * Some systems (FreeBSD) have complex.h, but some random subset of the trig funcs, so + * HAVE_COMPLEX_NUMBERS means we can find + * cimag creal cabs csqrt carg conj + * and HAVE_COMPLEX_TRIG means we have + * cacos cacosh casin casinh catan catanh ccos ccosh cexp clog cpow csin csinh ctan ctanh + * + * When HAVE_COMPLEX_NUMBERS is 0, the complex functions are stubs that simply return their + * argument -- this will be very confusing for the s7 user because, for example, (sqrt -2) + * will return something bogus (it might not signal an error). + * + * so the incoming (non-s7-specific) compile-time switches are + * HAVE_COMPLEX_NUMBERS, HAVE_COMPLEX_TRIG, SIZEOF_VOID_P + * if SIZEOF_VOID_P is not defined, we look for __SIZEOF_POINTER__ instead, + * the default is to assume that we're running on a 64-bit machine. + * + * To get multiprecision arithmetic, set WITH_GMP to 1. + * You'll also need libgmp, libmpfr, and libmpc (version 0.8.0 or later) + * + * and we use these predefined macros: __cplusplus, _MSC_VER, __GNUC__, __clang__, __ANDROID__ + * + * if WITH_SYSTEM_EXTRAS is 1 (default is 1 unless _MSC_VER), various OS and file related functions are included. + * if you want this file to compile into a stand-alone interpreter, define WITH_MAIN, + * to use nrepl also define WITH_NOTCURSES + * + * -O3 is often slower than -O2 (at least according to callgrind) + * -march=native seems to improve tree-vectorization which is important in Snd + * -ffast-math makes a mess of NaNs, and does not appear to be faster + * -fno-math-errno -fno-signed-zeros appear to be slightly faster, and I don't see any errors + * I also tried -fno-signaling-nans -fno-trapping-math -fassociative-math, but at least one of them is much slower + * this code doesn't compile anymore in gcc 4.3 + */ + +#if (defined(__GNUC__) || defined(__clang__) || defined(__TINYC__)) /* s7 uses PRId64 so (for example) g++ 4.4 is too old */ + #define WITH_GCC 1 +#else + #define WITH_GCC 0 +#endif + + +/* ---------------- initial sizes ---------------- */ + +#ifndef INITIAL_HEAP_SIZE + #define INITIAL_HEAP_SIZE 64000 /* 29-Jul-21 -- seems faster */ +#endif +/* the heap grows as needed, this is its initial size. If the initial heap is small, s7 can run in about 2.5 Mbytes of memory. + * There are many cases where a bigger heap is faster (but harware cache size probably matters more). + * The heap size must be a multiple of 32. Each object takes 48 bytes. + */ + +#ifndef SYMBOL_TABLE_SIZE + #define SYMBOL_TABLE_SIZE 32749 +#endif +/* names are hashed into the symbol table (a vector) and collisions are chained as lists */ +/* 16381: thash +80 [string_to_symbol_p_p] +40 if 24001, tlet +80 [symbol_p_p], +32 24001 */ + +#ifndef INITIAL_STACK_SIZE + #define INITIAL_STACK_SIZE 4096 /* was 2048 17-Mar-21 */ +#endif +/* the stack grows as needed, each frame takes 4 entries, this is its initial size. (*s7* 'stack-top) divides size by 4 */ + +#define STACK_RESIZE_TRIGGER (INITIAL_STACK_SIZE / 2) + +#ifndef INITIAL_PROTECTED_OBJECTS_SIZE + #define INITIAL_PROTECTED_OBJECTS_SIZE 16 +#endif +/* a vector of objects that are (semi-permanently) protected from the GC, grows as needed */ + +#ifndef GC_TEMPS_SIZE + #define GC_TEMPS_SIZE 256 +#endif +/* the number of recent objects that are temporarily gc-protected; 8 works for s7test and snd-test. + * For the FFI, this sets the lag between a call on s7_cons and the first moment when its result + * might be vulnerable to the GC. + */ + + +/* ---------------- scheme choices ---------------- */ + +#ifndef WITH_GMP + #define WITH_GMP 0 + /* this includes multiprecision arithmetic for all numeric types and functions, using gmp, mpfr, and mpc + * WITH_GMP adds the following functions: bignum and bignum?, and (*s7* 'bignum-precision) + */ +#endif + +#ifndef DEFAULT_BIGNUM_PRECISION + #define DEFAULT_BIGNUM_PRECISION 128 /* (*s7* 'bignum-precision) initial value, must be >= 2 */ +#endif + +#ifndef WITH_PURE_S7 + #define WITH_PURE_S7 0 +#endif +#if WITH_PURE_S7 + #define WITH_EXTRA_EXPONENT_MARKERS 0 + #define WITH_IMMUTABLE_UNQUOTE 1 + /* also omitted: *-ci* functions, char-ready?, cond-expand, multiple-values-bind|set!, call-with-values + * and a lot more (inexact/exact, integer-length, etc) -- see s7.html. + */ +#endif + +#ifndef WITH_EXTRA_EXPONENT_MARKERS + #define WITH_EXTRA_EXPONENT_MARKERS 0 +#endif +/* if 1, s7 recognizes "d", "f", "l", and "s" as exponent markers, in addition to "e" (also "D", "F", "L", "S") */ + +#ifndef WITH_SYSTEM_EXTRAS + #define WITH_SYSTEM_EXTRAS (!_MSC_VER) + /* this adds several functions that access file info, directories, times, etc */ +#endif + +#ifndef WITH_IMMUTABLE_UNQUOTE + #define WITH_IMMUTABLE_UNQUOTE 0 + /* this removes the name "unquote" */ +#endif + +#ifndef WITH_C_LOADER + #if WITH_GCC && (!__MINGW32__) && (!__CYGWIN__) + #define WITH_C_LOADER 1 + /* (load file.so [e]) looks for ([e] 'init_func) and if found, calls it as the shared object init function. + * If WITH_SYSTEM_EXTRAS is 0, the caller needs to supply system and delete-file so that cload.scm works. + */ + #else + #define WITH_C_LOADER 0 + /* I think dlopen et al are available in MS C, but I have no way to test them; see load_shared_object below */ + #endif +#endif + +#ifndef WITH_HISTORY + #define WITH_HISTORY 0 + /* this includes a circular buffer of previous evaluations for debugging, ((owlet) 'error-history) and (*s7* 'history-size) */ +#endif + +#ifndef DEFAULT_HISTORY_SIZE + #define DEFAULT_HISTORY_SIZE 8 + /* this is the default length of the eval history buffer */ +#endif +#if WITH_HISTORY + #define MAX_HISTORY_SIZE 1048576 +#endif + +#ifndef DEFAULT_PRINT_LENGTH + #define DEFAULT_PRINT_LENGTH 12 /* (*s7* 'print-length) initial value, was 32 but Snd uses 12, 23-Jul-21 */ +#endif + +#ifndef WITH_NUMBER_SEPARATOR + #define WITH_NUMBER_SEPARATOR 0 +#endif + +/* in case mus-config.h forgets these */ +#ifdef _MSC_VER + #ifndef HAVE_COMPLEX_NUMBERS + #define HAVE_COMPLEX_NUMBERS 0 + #endif + #ifndef HAVE_COMPLEX_TRIG + #define HAVE_COMPLEX_TRIG 0 + #endif +#else + #ifndef HAVE_COMPLEX_NUMBERS + #if __TINYC__ + #define HAVE_COMPLEX_NUMBERS 0 + #else + #define HAVE_COMPLEX_NUMBERS 1 + #endif + #endif + #if __cplusplus || __TINYC__ + #ifndef HAVE_COMPLEX_TRIG + #define HAVE_COMPLEX_TRIG 0 + #endif + #else + #ifndef HAVE_COMPLEX_TRIG + #define HAVE_COMPLEX_TRIG 1 + #endif + #endif +#endif + +#ifndef WITH_MULTITHREAD_CHECKS + #define WITH_MULTITHREAD_CHECKS 0 + /* debugging aid if using s7 in a multithreaded program -- this code courtesy of Kjetil Matheussen */ +#endif + +#ifndef WITH_WARNINGS + #define WITH_WARNINGS 0 + /* int+int overflows to real, etc: this adds warnings which are expensive even though they are never called (procedure overhead) */ +#endif + +#ifndef S7_DEBUGGING + #define S7_DEBUGGING 0 +#endif + +#undef DEBUGGING +#define DEBUGGING typo! +#define HAVE_GMP typo! + +#define SHOW_EVAL_OPS 0 +#ifndef _GNU_SOURCE + #define _GNU_SOURCE /* for qsort_r, grumble... */ +#endif + +#ifndef _MSC_VER + #include + #include + #include + #include + #include +#else + /* in Snd these are in mus-config.h */ + #ifndef MUS_CONFIG_H_LOADED + #if _MSC_VER < 1900 + #define snprintf _snprintf + #endif + #if _MSC_VER > 1200 + #define _CRT_SECURE_NO_DEPRECATE 1 + #define _CRT_NONSTDC_NO_DEPRECATE 1 + #define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1 + #endif + #endif + #include + #pragma warning(disable: 4244) /* conversion might cause loss of data warning */ +#endif + +#if WITH_GCC && (!S7_DEBUGGING) + #define Inline inline __attribute__((__always_inline__)) +#else + #ifdef _MSC_VER + #define Inline __forceinline + #else + #define Inline inline + #endif +#endif + +#ifndef WITH_VECTORIZE + #define WITH_VECTORIZE 1 +#endif + +#if (WITH_VECTORIZE) && (defined(__GNUC__) && __GNUC__ >= 5) /* is this included -in -O2 now? */ + #define Vectorized __attribute__((optimize("tree-vectorize"))) +#else + #define Vectorized +#endif + +#if WITH_GCC + #define Sentinel __attribute__((sentinel)) +#else + #define Sentinel +#endif + +#ifdef _MSC_VER + #define noreturn _Noreturn /* deprecated in C23 */ +#else + #define noreturn __attribute__((noreturn)) + /* this is ok in gcc/g++/clang and tcc; pure attribute is rarely applicable here, and does not seem to be helpful (maybe safe_strlen) */ +#endif + +#ifndef S7_ALIGNED + #define S7_ALIGNED 0 + /* memclr, local_strcmp and local_memset */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER + #define MS_WINDOWS 1 +#else + #define MS_WINDOWS 0 +#endif + +#if defined(_MSC_VER) || defined(__MINGW32__) + #define Jmp_Buf jmp_buf + #define SetJmp(A, B) setjmp(A) + #define LongJmp(A, B) longjmp(A, B) +#else + #define Jmp_Buf sigjmp_buf + #define SetJmp(A, B) sigsetjmp(A, B) + #define LongJmp(A, B) siglongjmp(A, B) + /* we need sigsetjmp, not setjmp for nrepl's interrupt (something to do with signal masks??) + * unfortunately sigsetjmp is noticeably slower than setjmp, especially when s7_optimize_1 is called a lot. + * In one case, the sigsetjmp version runs in 24 seconds, but the setjmp version takes 10 seconds, and + * yet callgrind says there is almost no difference? I removed setjmp from s7_optimize. + */ +#endif + +#if (!MS_WINDOWS) + #include +#endif + +#if __cplusplus + #include +#else + #include +#endif + +/* there is also apparently __STDC_NO_COMPLEX__ */ +#if HAVE_COMPLEX_NUMBERS + #if __cplusplus + #include + #else + #include + #ifndef __SUNPRO_C + #if defined(__sun) && defined(__SVR4) + #undef _Complex_I + #define _Complex_I 1.0i + #endif + #endif + #endif + + #ifndef CMPLX + #if (!(defined(__cplusplus))) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)) && !defined(__INTEL_COMPILER) + #define CMPLX(x, y) __builtin_complex ((double) (x), (double) (y)) + #else + #define CMPLX(r, i) ((r) + ((i) * (s7_complex)_Complex_I)) + #endif + #endif +#endif + +#if (defined(__GNUC__)) + #define s7_complex_i 1.0i +#else + #define s7_complex_i (s7_complex)_Complex_I /* a float, but we want a double */ +#endif + +#include "s7.h" + +#ifndef M_PI + #define M_PI 3.1415926535897932384626433832795029L +#endif + +#ifndef INFINITY + #ifndef HUGE_VAL + #define INFINITY (1.0/0.0) /* -log(0.0) is triggering dumb complaints from cppcheck */ + /* there is sometimes a function, infinity(), MSC apparently uses HUGE_VALF, gcc has __builtin_huge_val() */ + #else + #define INFINITY HUGE_VAL + #endif +#endif + +#ifndef NAN + #define NAN (INFINITY / INFINITY) /* apparently ieee754 suggests 0.0/0.0 */ +#endif + +#if ((!__NetBSD__) && ((_MSC_VER) || (!defined(__STC__)) || (defined(__STDC_VERSION__) && (__STDC_VERSION__ < 199901L)))) + #define __func__ __FUNCTION__ +#endif + +#if (((defined(SIZEOF_VOID_P)) && (SIZEOF_VOID_P == 4)) || ((defined(__SIZEOF_POINTER__)) && (__SIZEOF_POINTER__ == 4)) || (!defined(__LP64__))) + #define NUMBER_NAME_SIZE 2 /* pointless */ + #define POINTER_32 true +#else + #define NUMBER_NAME_SIZE 22 /* leave 1 for uint8_t name len (byte 0), 1 for terminating nul */ + #define POINTER_32 false +#endif + +#define WRITE_REAL_PRECISION 16 +typedef long double long_double; + +#define ld64 PRId64 +#define p64 PRIdPTR + +#define MAX_FLOAT_FORMAT_PRECISION 128 + +/* types */ +enum {T_FREE = 0, + T_PAIR, T_NIL, T_UNUSED, T_UNDEFINED, T_UNSPECIFIED, T_EOF, T_BOOLEAN, T_CHARACTER, T_SYNTAX, T_SYMBOL, + T_INTEGER, T_RATIO, T_REAL, T_COMPLEX, T_BIG_INTEGER, T_BIG_RATIO, T_BIG_REAL, T_BIG_COMPLEX, + T_STRING, T_C_OBJECT, T_VECTOR, T_INT_VECTOR, T_FLOAT_VECTOR, T_BYTE_VECTOR, + T_CATCH, T_DYNAMIC_WIND, T_HASH_TABLE, T_LET, T_ITERATOR, + T_STACK, T_COUNTER, T_SLOT, T_C_POINTER, T_OUTPUT_PORT, T_INPUT_PORT, T_RANDOM_STATE, T_CONTINUATION, T_GOTO, + T_CLOSURE, T_CLOSURE_STAR, T_MACRO, T_MACRO_STAR, T_BACRO, T_BACRO_STAR, + T_C_MACRO, T_C_FUNCTION_STAR, T_C_FUNCTION, T_C_RST_NO_REQ_FUNCTION, + NUM_TYPES}; +/* T_UNUSED, T_STACK, T_SLOT, T_DYNAMIC_WIND, T_CATCH, and T_COUNTER are internal */ + +static const char *s7_type_names[] = + {"free", "pair", "nil", "unused", "undefined", "unspecified", "eof_object", "boolean", "character", "syntax", "symbol", + "integer", "ratio", "real", "complex", "big_integer", "big_ratio", "big_real", "big_complex", + "string", "c_object", "vector", "int_vector", "float_vector", "byte_vector", + "catch", "dynamic_wind", "hash_table", "let", "iterator", + "stack", "counter", "slot", "c_pointer", "output_port", "input_port", "random_state", "continuation", "goto", + "closure", "closure*", "macro", "macro*", "bacro", "bacro*", + "c_macro", "c_function*", "c_function", "c_rst_no_req_function", + }; + +/* 1:T_PAIR, 2:T_NIL, 3:T_UNUSED, 4:T_UNDEFINED, 5:T_UNSPECIFIED, 6:T_EOF, 7:T_BOOLEAN, 8:T_CHARACTER, 9:T_SYNTAX, 10:T_SYMBOL, + 11:T_INTEGER, 12:T_RATIO, 13:T_REAL, 14:T_COMPLEX, 15:T_BIG_INTEGER, 16:T_BIG_RATIO, 17:T_BIG_REAL, 18:T_BIG_COMPLEX, + 19:T_STRING, 20:T_C_OBJECT, 21:T_VECTOR, 22:T_INT_VECTOR, 23:T_FLOAT_VECTOR, 24:T_BYTE_VECTOR, + 25:T_CATCH, 26:T_DYNAMIC_WIND, 27:T_HASH_TABLE, 28:T_LET, 29:T_ITERATOR, + 30:T_STACK, 31:T_COUNTER, 32:T_SLOT, 33:T_C_POINTER, 34:T_OUTPUT_PORT, 35:T_INPUT_PORT, 36:T_RANDOM_STATE, 37:T_CONTINUATION, 38:T_GOTO, + 39:T_CLOSURE, 40:T_CLOSURE_STAR, 41:T_MACRO, 42:T_MACRO_STAR, 43:T_BACRO, 44:T_BACRO_STAR, + 45:T_C_MACRO, 46:T_C_FUNCTION_STAR, 47:T_C_FUNCTION, 48:T_C_RST_NO_REQ_FUNCTION, + 49:NUM_TYPES +*/ + +typedef struct block_t { + union { + void *data; + s7_pointer d_ptr; + s7_int *i_ptr; + } dx; + int32_t index; + union { + bool needs_free; + uint32_t tag; + } ln; + s7_int size; + union { + struct block_t *next; + char *documentation; + s7_pointer ksym; + s7_int nx_int; + s7_int *ix_ptr; + struct { + uint32_t i1, i2; + } ix; + } nx; + union { + s7_pointer ex_ptr; + void *ex_info; + s7_int ckey; + } ex; +} block_t; + +#define NUM_BLOCK_LISTS 18 +#define TOP_BLOCK_LIST 17 +#define BLOCK_LIST 0 + +#define block_data(p) p->dx.data +#define block_index(p) p->index +#define block_set_index(p, Index) p->index = Index +#define block_size(p) p->size +#define block_set_size(p, Size) p->size = Size +#define block_next(p) p->nx.next +#define block_info(p) p->ex.ex_info + +typedef block_t hash_entry_t; /* I think this means we waste 8 bytes per entry but can use the mallocate functions */ +#define hash_entry_key(p) p->dx.d_ptr +#define hash_entry_value(p) (p)->ex.ex_ptr +#define hash_entry_set_value(p, Val) p->ex.ex_ptr = Val +#define hash_entry_next(p) block_next(p) +#define hash_entry_raw_hash(p) block_size(p) +#define hash_entry_set_raw_hash(p, Hash) block_set_size(p, Hash) + +typedef block_t vdims_t; +#define vdims_rank(p) p->size +#define vector_elements_should_be_freed(p) p->ln.needs_free +#define vdims_dims(p) p->dx.i_ptr +#define vdims_offsets(p) p->nx.ix_ptr +#define vdims_original(p) p->ex.ex_ptr + + +typedef enum {TOKEN_EOF, TOKEN_LEFT_PAREN, TOKEN_RIGHT_PAREN, TOKEN_DOT, TOKEN_ATOM, TOKEN_QUOTE, TOKEN_DOUBLE_QUOTE, TOKEN_BACK_QUOTE, + TOKEN_COMMA, TOKEN_AT_MARK, TOKEN_SHARP_CONST, TOKEN_VECTOR, TOKEN_BYTE_VECTOR, TOKEN_INT_VECTOR, TOKEN_FLOAT_VECTOR} token_t; + +typedef enum {NO_ARTICLE, INDEFINITE_ARTICLE} article_t; +typedef enum {DWIND_INIT, DWIND_BODY, DWIND_FINISH} dwind_t; +enum {NO_SAFETY = 0, IMMUTABLE_VECTOR_SAFETY, MORE_SAFETY_WARNINGS}; /* (*s7* 'safety) settings */ + +typedef enum {FILE_PORT, STRING_PORT, FUNCTION_PORT} port_type_t; + +typedef struct { + int32_t (*read_character)(s7_scheme *sc, s7_pointer port); /* function to read a character, int32_t for EOF */ + void (*write_character)(s7_scheme *sc, uint8_t c, s7_pointer port); /* function to write a character */ + void (*write_string)(s7_scheme *sc, const char *str, s7_int len, s7_pointer port); /* function to write a string of known length */ + token_t (*read_semicolon)(s7_scheme *sc, s7_pointer port); /* internal skip-to-semicolon reader */ + int32_t (*read_white_space)(s7_scheme *sc, s7_pointer port); /* internal skip white space reader */ + s7_pointer (*read_name)(s7_scheme *sc, s7_pointer pt); /* internal get-next-name reader */ + s7_pointer (*read_sharp)(s7_scheme *sc, s7_pointer pt); /* internal get-next-sharp-constant reader */ + s7_pointer (*read_line)(s7_scheme *sc, s7_pointer pt, bool eol_case); /* function to read a string up to \n */ + void (*displayer)(s7_scheme *sc, const char *s, s7_pointer pt); + void (*close_port)(s7_scheme *sc, s7_pointer p); /* close-in|output-port */ +} port_functions_t; + +typedef struct { + bool needs_free, is_closed; + port_type_t ptype; + FILE *file; + char *filename; + block_t *filename_block; + uint32_t line_number, file_number; + s7_int filename_length; + block_t *block; + s7_pointer orig_str; /* GC protection for string port string or function port function */ + const port_functions_t *pf; + s7_pointer (*input_function)(s7_scheme *sc, s7_read_t read_choice, s7_pointer port); + void (*output_function)(s7_scheme *sc, uint8_t c, s7_pointer port); +} port_t; + +typedef enum {o_d_v, o_d_vd, o_d_vdd, o_d_vid, o_d_id, o_d_7pi, o_d_7pii, o_d_7piid, o_d_7piii, o_d_7piiid, + o_d_ip, o_d_pd, o_d_7pid, o_d, o_d_d, o_d_dd, o_d_7dd, o_d_ddd, o_d_dddd, + o_i_i, o_i_7i, o_i_ii, o_i_7ii, o_i_iii, o_i_7pi, o_i_7pii, o_i_7piii, o_d_p, + o_b_p, o_b_7p, o_b_pp, o_b_7pp, o_b_pp_unchecked, o_b_pi, o_b_ii, o_b_7ii, o_b_dd, + o_p, o_p_p, o_p_ii, o_p_d, o_p_dd, o_i_7d, o_i_7p, o_d_7d, o_p_pp, o_p_ppp, o_p_pi, o_p_pi_unchecked, + o_p_ppi, o_p_i, o_p_pii, o_p_pip, o_p_pip_unchecked, o_p_piip, o_b_i, o_b_d} opt_func_t; + +typedef struct opt_funcs_t { + opt_func_t typ; + void *func; + struct opt_funcs_t *next; +} opt_funcs_t; + +typedef struct { + const char *name; + int32_t name_length; + uint32_t id; + const char *doc; + opt_funcs_t *opt_data; /* vunion-functions (see below) */ + s7_pointer generic_ff, setter, signature, pars; + s7_pointer (*chooser)(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool ops); + /* arg_defaults|names call_args only T_C_FUNCTION_STAR -- call args for GC protection */ + union { + s7_pointer *arg_defaults; + s7_pointer bool_setter; + } dam; + union { + s7_pointer *arg_names; + s7_pointer c_sym; + } sam; + union { + s7_pointer call_args; + void (*marker)(s7_pointer p, s7_int len); + } cam; +} c_proc_t; + + +typedef struct { + s7_int type, outer_type; + s7_pointer scheme_name, getter, setter; + void (*mark)(void *val); + void (*free)(void *value); /* this will go away someday (use gc_free) */ + bool (*eql)(void *val1, void *val2); /* this will go away someday (use equal) */ +#if (!DISABLE_DEPRECATED) + char *(*print)(s7_scheme *sc, void *value); +#endif + s7_pointer (*equal) (s7_scheme *sc, s7_pointer args); + s7_pointer (*equivalent) (s7_scheme *sc, s7_pointer args); + s7_pointer (*ref) (s7_scheme *sc, s7_pointer args); + s7_pointer (*set) (s7_scheme *sc, s7_pointer args); + s7_pointer (*length) (s7_scheme *sc, s7_pointer args); + s7_pointer (*reverse) (s7_scheme *sc, s7_pointer args); + s7_pointer (*copy) (s7_scheme *sc, s7_pointer args); + s7_pointer (*fill) (s7_scheme *sc, s7_pointer args); + s7_pointer (*to_list) (s7_scheme *sc, s7_pointer args); + s7_pointer (*to_string) (s7_scheme *sc, s7_pointer args); + s7_pointer (*gc_mark) (s7_scheme *sc, s7_pointer args); + s7_pointer (*gc_free) (s7_scheme *sc, s7_pointer args); +} c_object_t; + + +typedef s7_int (*hash_map_t)(s7_scheme *sc, s7_pointer table, s7_pointer key); /* hash-table object->location mapper */ +typedef hash_entry_t *(*hash_check_t)(s7_scheme *sc, s7_pointer table, s7_pointer key); /* hash-table object equality function */ +static hash_map_t default_hash_map[NUM_TYPES]; + +typedef s7_int (*s7_i_7pi_t)(s7_scheme *sc, s7_pointer p, s7_int i1); +typedef s7_int (*s7_i_7pii_t)(s7_scheme *sc, s7_pointer p, s7_int i1, s7_int i2); +typedef s7_int (*s7_i_7piii_t)(s7_scheme *sc, s7_pointer p, s7_int i1, s7_int i2, s7_int i3); +typedef s7_int (*s7_i_iii_t)(s7_int i1, s7_int i2, s7_int i3); +typedef s7_int (*s7_i_7i_t)(s7_scheme *sc, s7_int i1); +typedef s7_int (*s7_i_7ii_t)(s7_scheme *sc, s7_int i1, s7_int i2); +typedef bool (*s7_b_pp_t)(s7_pointer p1, s7_pointer p2); +typedef bool (*s7_b_7pp_t)(s7_scheme *sc, s7_pointer p1, s7_pointer p2); +typedef bool (*s7_b_7p_t)(s7_scheme *sc, s7_pointer p1); +typedef bool (*s7_b_pi_t)(s7_scheme *sc, s7_pointer p1, s7_int i2); +typedef bool (*s7_b_d_t)(s7_double p1); +typedef bool (*s7_b_i_t)(s7_int p1); +typedef bool (*s7_b_ii_t)(s7_int p1, s7_int p2); +typedef bool (*s7_b_7ii_t)(s7_scheme *sc, s7_int p1, s7_int p2); +typedef bool (*s7_b_dd_t)(s7_double p1, s7_double p2); +typedef s7_pointer (*s7_p_t)(s7_scheme *sc); +typedef s7_pointer (*s7_p_ppi_t)(s7_scheme *sc, s7_pointer p1, s7_pointer p2, s7_int i1); +typedef s7_pointer (*s7_p_pi_t)(s7_scheme *sc, s7_pointer p1, s7_int i1); +typedef s7_pointer (*s7_p_pii_t)(s7_scheme *sc, s7_pointer p1, s7_int i1, s7_int i2); +typedef s7_pointer (*s7_p_pip_t)(s7_scheme *sc, s7_pointer p1, s7_int i1, s7_pointer p2); +typedef s7_pointer (*s7_p_piip_t)(s7_scheme *sc, s7_pointer p1, s7_int i1, s7_int i2, s7_pointer p3); +typedef s7_pointer (*s7_p_i_t)(s7_scheme *sc, s7_int i); +typedef s7_pointer (*s7_p_ii_t)(s7_scheme *sc, s7_int i1, s7_int i2); +typedef s7_pointer (*s7_p_dd_t)(s7_scheme *sc, s7_double x1, s7_double x2); +typedef s7_double (*s7_d_7d_t)(s7_scheme *sc, s7_double p1); +typedef s7_double (*s7_d_7dd_t)(s7_scheme *sc, s7_double p1, s7_double p2); +typedef s7_double (*s7_d_7pii_t)(s7_scheme *sc, s7_pointer p1, s7_int i1, s7_int i2); +typedef s7_double (*s7_d_7piid_t)(s7_scheme *sc, s7_pointer p1, s7_int i1, s7_int i2, s7_double x1); +typedef s7_double (*s7_d_7piii_t)(s7_scheme *sc, s7_pointer p1, s7_int i1, s7_int i2, s7_int i3); +typedef s7_double (*s7_d_7piiid_t)(s7_scheme *sc, s7_pointer p1, s7_int i1, s7_int i2, s7_int i3, s7_double x1); + +typedef struct opt_info opt_info; + +typedef union { + s7_int i; + s7_double x; + s7_pointer p; + void *obj; + opt_info *o1; + s7_function call; + s7_double (*d_f)(void); + s7_double (*d_d_f)(s7_double x); + s7_double (*d_7d_f)(s7_scheme *sc, s7_double x); + s7_double (*d_dd_f)(s7_double x1, s7_double x2); + s7_double (*d_7dd_f)(s7_scheme *sc, s7_double x1, s7_double x2); + s7_double (*d_ddd_f)(s7_double x1, s7_double x2, s7_double x3); + s7_double (*d_dddd_f)(s7_double x1, s7_double x2, s7_double x3, s7_double x4); + s7_double (*d_v_f)(void *obj); + s7_double (*d_vd_f)(void *obj, s7_double fm); + s7_double (*d_vdd_f)(void *obj, s7_double x1, s7_double x2); + s7_double (*d_vid_f)(void *obj, s7_int i, s7_double fm); + s7_double (*d_id_f)(s7_int i, s7_double fm); + s7_double (*d_7pi_f)(s7_scheme *sc, s7_pointer obj, s7_int i1); + s7_double (*d_7pid_f)(s7_scheme *sc, s7_pointer obj, s7_int i1, s7_double x); + s7_double (*d_7pii_f)(s7_scheme *sc, s7_pointer obj, s7_int i1, s7_int i2); + s7_double (*d_7piid_f)(s7_scheme *sc, s7_pointer obj, s7_int i1, s7_int i2, s7_double x); + s7_double (*d_7piii_f)(s7_scheme *sc, s7_pointer obj, s7_int i1, s7_int i2, s7_int i3); + s7_double (*d_7piiid_f)(s7_scheme *sc, s7_pointer obj, s7_int i1, s7_int i2, s7_int i3, s7_double x); + s7_double (*d_ip_f)(s7_int i1, s7_pointer p); + s7_double (*d_pd_f)(s7_pointer obj, s7_double x); + s7_double (*d_p_f)(s7_pointer p); + s7_int (*i_7d_f)(s7_scheme *sc, s7_double i1); + s7_int (*i_7p_f)(s7_scheme *sc, s7_pointer i1); + s7_int (*i_i_f)(s7_int i1); + s7_int (*i_7i_f)(s7_scheme *sc, s7_int i1); + s7_int (*i_ii_f)(s7_int i1, s7_int i2); + s7_int (*i_7ii_f)(s7_scheme *sc, s7_int i1, s7_int i2); + s7_int (*i_iii_f)(s7_int i1, s7_int i2, s7_int i3); + s7_int (*i_7pi_f)(s7_scheme *sc, s7_pointer p, s7_int i1); + s7_int (*i_7pii_f)(s7_scheme *sc, s7_pointer p, s7_int i1, s7_int i2); + s7_int (*i_7piii_f)(s7_scheme *sc, s7_pointer p, s7_int i1, s7_int i2, s7_int i3); + bool (*b_i_f)(s7_int p); + bool (*b_d_f)(s7_double p); + bool (*b_p_f)(s7_pointer p); + bool (*b_pp_f)(s7_pointer p1, s7_pointer p2); + bool (*b_7pp_f)(s7_scheme *sc, s7_pointer p1, s7_pointer p2); + bool (*b_7p_f)(s7_scheme *sc, s7_pointer p1); + bool (*b_pi_f)(s7_scheme *sc, s7_pointer p1, s7_int i2); + bool (*b_ii_f)(s7_int i1, s7_int i2); + bool (*b_7ii_f)(s7_scheme *sc, s7_int i1, s7_int i2); + bool (*b_dd_f)(s7_double x1, s7_double x2); + s7_pointer (*p_f)(s7_scheme *sc); + s7_pointer (*p_p_f)(s7_scheme *sc, s7_pointer p); + s7_pointer (*p_pp_f)(s7_scheme *sc, s7_pointer p1, s7_pointer p2); + s7_pointer (*p_ppp_f)(s7_scheme *sc, s7_pointer p, s7_pointer p2, s7_pointer p3); + s7_pointer (*p_pi_f)(s7_scheme *sc, s7_pointer p1, s7_int i1); + s7_pointer (*p_pii_f)(s7_scheme *sc, s7_pointer p1, s7_int i1, s7_int i2); + s7_pointer (*p_ppi_f)(s7_scheme *sc, s7_pointer p1, s7_pointer p2, s7_int i1); + s7_pointer (*p_pip_f)(s7_scheme *sc, s7_pointer p1, s7_int i1, s7_pointer p2); + s7_pointer (*p_piip_f)(s7_scheme *sc, s7_pointer p1, s7_int i1, s7_int i2, s7_pointer p3); + s7_pointer (*p_i_f)(s7_scheme *sc, s7_int i); + s7_pointer (*p_ii_f)(s7_scheme *sc, s7_int x1, s7_int x2); + s7_pointer (*p_d_f)(s7_scheme *sc, s7_double x); + s7_pointer (*p_dd_f)(s7_scheme *sc, s7_double x1, s7_double x2); + s7_double (*fd)(opt_info *o); + s7_int (*fi)(opt_info *o); + bool (*fb)(opt_info *o); + s7_pointer (*fp)(opt_info *o); +} vunion; + +#define NUM_VUNIONS 15 +struct opt_info { + vunion v[NUM_VUNIONS]; + s7_scheme *sc; +}; + +#define O_WRAP (NUM_VUNIONS - 1) + +#if WITH_GMP +typedef struct bigint {mpz_t n; struct bigint *nxt;} bigint; +typedef struct bigrat {mpq_t q; struct bigrat *nxt;} bigrat; +typedef struct bigflt {mpfr_t x; struct bigflt *nxt;} bigflt; +typedef struct bigcmp {mpc_t z; struct bigcmp *nxt;} bigcmp; + +typedef struct { + mpfr_t error, ux, x0, x1; + mpz_t i, i0, i1, n; + mpz_t p0, q0, r, r1, p1, q1, old_p1, old_q1; + mpfr_t val, e0, e1, e0p, e1p, old_e0, old_e1, old_e0p; + mpq_t q; +} rat_locals_t; +#endif + +typedef intptr_t opcode_t; + + +/* -------------------------------- cell structure -------------------------------- */ + +typedef struct s7_cell { + union { + uint64_t flag; /* type info */ + int64_t signed_flag; + uint8_t type_field; + uint16_t sflag; + struct { + uint32_t unused_low_flag; + uint16_t opt_choice; + uint16_t high_flag; + } opts; + } tf; + union { + + union { /* integers, floats */ + s7_int integer_value; + s7_double real_value; + + struct { /* ratios */ + s7_int numerator; + s7_int denominator; + } fraction_value; + + struct { /* complex numbers */ + s7_double rl; + s7_double im; + } complex_value; + +#if WITH_GMP + bigint *bgi; /* bignums */ + bigrat *bgr; + bigflt *bgf; + bigcmp *bgc; +#endif + } number; + + struct { + s7_int unused1, unused2; /* always int64_t so this is 16 bytes */ + uint8_t name[24]; + } number_name; + + struct { /* ports */ + port_t *port; + uint8_t *data; + s7_int size, point; + block_t *block; + } prt; + + struct{ /* characters */ + uint8_t c, up_c; + int32_t length; + bool alpha_c, digit_c, space_c, upper_c, lower_c; + char c_name[12]; + } chr; + + struct { /* c-pointers */ + void *c_pointer; + s7_pointer c_type, info, weak1, weak2; + } cptr; + + struct { /* vectors */ + s7_int length; + union { + s7_pointer *objects; + s7_int *ints; + s7_double *floats; + uint8_t *bytes; + } elements; + block_t *block; + s7_pointer (*vget)(s7_scheme *sc, s7_pointer vec, s7_int loc); + union { + s7_pointer (*vset)(s7_scheme *sc, s7_pointer vec, s7_int loc, s7_pointer val); + s7_pointer fset; + } setv; + } vector; + + struct { /* stacks (internal) struct must match vector above for length/objects */ + s7_int length; + s7_pointer *objects; + block_t *block; + int64_t top, flags; + } stk; + + struct { /* hash-tables */ + s7_int mask; + hash_entry_t **elements; /* a pointer into block below: takes up a field in object.hasher but is faster (50 in thash) */ + hash_check_t hash_func; + hash_map_t *loc; + block_t *block; + } hasher; + + struct { /* iterators */ + s7_pointer obj, cur; + union { + s7_int loc; + s7_pointer lcur; + } lc; + union { + s7_int len; + s7_pointer slow; + hash_entry_t *hcur; + } lw; + s7_pointer (*next)(s7_scheme *sc, s7_pointer iterator); + } iter; + + struct { + c_proc_t *c_proc; /* C functions, macros */ + s7_function ff; + s7_int required_args, optional_args, all_args; /* these could be uint32_t */ + } fnc; + + struct { /* pairs */ + s7_pointer car, cdr, opt1; + union + { + s7_pointer opt2; + s7_int n; + } o2; + union { + s7_pointer opt3; + s7_int n; + uint8_t opt_type; + } o3; + } cons; + + struct { /* special purpose pairs (symbol-table etc) */ + s7_pointer unused_car, unused_cdr; + uint64_t hash; + const char *fstr; + uint64_t location; /* line/file/position, also used in symbol_table as raw_len */ + } sym_cons; + + struct { /* scheme functions */ + s7_pointer args, body, env, setter; /* args can be a symbol, as well as a list, setter can be #f as well as a procedure/closure */ + int32_t arity; + } func; + + struct { /* strings */ + s7_int length; + char *svalue; + uint64_t hash; /* string hash-index */ + block_t *block; + block_t *gensym_block; + } string; + + struct { /* symbols */ + s7_pointer name, global_slot, local_slot; + int64_t id; /* which let last bound the symbol -- for faster symbol lookup */ + uint32_t ctr; /* how many times has symbol been bound */ + uint32_t tag; /* symbol as member of a set (tree-set-memq etc), high 32 bits are in symbol_info (the string block) */ + } sym; + + struct { /* syntax */ + s7_pointer symbol; + opcode_t op; + int32_t min_args, max_args; + const char *documentation; + } syn; + + struct { /* slots (bindings) */ + s7_pointer sym, val, nxt, pending_value, expr; /* pending_value is also the setter field which works by a whisker */ + } slt; + + struct { /* lets (environments) */ + s7_pointer slots, nxt; + int64_t id; /* id of rootlet is -1 */ + union { + struct { + s7_pointer function; /* *function* (symbol) if this is a funclet */ + uint32_t line, file; /* *function* location if it is known */ + } efnc; + struct { + s7_pointer dox1, dox2; /* do loop variables */ + } dox; + s7_int key; /* sc->baffle_ctr type */ + } edat; + } envr; + + struct { /* special stuff like # */ + s7_pointer car, cdr; /* unique_car|cdr, for sc->nil these are sc->unspecified for faster assoc etc */ + int64_t unused_let_id; /* let_id(sc->nil) is -1, so this needs to align with envr.id above, only used by sc->nil, so free elsewhere */ + const char *name; + s7_int len; + } unq; + + struct { /* #<...> */ + char *name; /* not const because the GC frees it */ + s7_int len; + } undef; + + struct { /* # */ + const char *name; + s7_int len; + } eof; + + struct { /* counter (internal) */ + s7_pointer result, list, env, slots; /* env = counter_let (curlet after map/for-each let created) */ + uint64_t cap; /* sc->capture_let_counter for let reuse */ + } ctr; + + struct { /* random-state */ +#if WITH_GMP + gmp_randstate_t state; +#else + uint64_t seed, carry; +#endif + } rng; + + struct { /* additional object types (C) */ + s7_int type; + void *value; /* the value the caller associates with the c_object */ + s7_pointer e; /* the method list, if any (openlet) */ + s7_scheme *sc; + } c_obj; + + struct { /* continuations */ + block_t *block; + s7_pointer stack, op_stack; + s7_pointer *stack_start, *stack_end; + } cwcc; + + struct { /* call-with-exit */ + uint64_t goto_loc, op_stack_loc; + bool active; + s7_pointer name; + } rexit; + + struct { /* catch */ + uint64_t goto_loc, op_stack_loc; + s7_pointer tag; + s7_pointer handler; + Jmp_Buf *cstack; + } rcatch; /* C++ reserves "catch" I guess */ + + struct { /* dynamic-wind */ + s7_pointer in, out, body; + dwind_t state; + } winder; + } object; + +#if S7_DEBUGGING + int32_t alloc_line, uses, explicit_free_line, gc_line, holders; + int64_t alloc_type, debugger_bits; + const char *alloc_func, *gc_func, *root; + s7_pointer holder; +#endif +} s7_cell; + + +typedef struct s7_big_cell { + s7_cell cell; + int64_t big_hloc; +} s7_big_cell; +typedef struct s7_big_cell *s7_big_pointer; + +typedef struct heap_block_t { + intptr_t start, end; + int64_t offset; + struct heap_block_t *next; +} heap_block_t; + +typedef struct { + s7_pointer *objs; + int32_t size, top, ref, size2; + bool has_hits; + int32_t *refs; + s7_pointer cycle_port, init_port; + s7_int cycle_loc, init_loc, ctr; + bool *defined; +} shared_info_t; + +typedef struct { + s7_int loc, curly_len, ctr; + char *curly_str; + s7_pointer args, orig_str, curly_arg, port, strport; +} format_data_t; + +typedef struct gc_obj_t { + s7_pointer p; + struct gc_obj_t *nxt; +} gc_obj_t; + +typedef struct { + s7_pointer *list; + s7_int size, loc; +} gc_list_t; + +typedef struct { + s7_int size, top, excl_size, excl_top; + s7_pointer *funcs, *let_names, *files; + s7_int *timing_data, *excl, *lines; +} profile_data_t; + + +/* -------------------------------- s7_scheme struct -------------------------------- */ +struct s7_scheme { + s7_pointer code; /* layout of first 4 entries should match stack frame layout */ + s7_pointer curlet; + s7_pointer args; + opcode_t cur_op; + s7_pointer value, cur_code; + token_t tok; + + s7_pointer stack; /* stack is a vector */ + uint32_t stack_size; + s7_pointer *stack_start, *stack_end, *stack_resize_trigger; + + s7_pointer *op_stack, *op_stack_now, *op_stack_end; + uint32_t op_stack_size, max_stack_size; + + s7_cell **heap, **free_heap, **free_heap_top, **free_heap_trigger, **previous_free_heap_top; + int64_t heap_size, gc_freed, gc_total_freed, max_heap_size, gc_temps_size; + s7_double gc_resize_heap_fraction, gc_resize_heap_by_4_fraction; + s7_int gc_calls, gc_total_time, gc_start, gc_end; + heap_block_t *heap_blocks; + +#if WITH_HISTORY + s7_pointer eval_history1, eval_history2, error_history, history_sink, history_pairs, old_cur_code; + bool using_history1; +#endif + +#if WITH_MULTITHREAD_CHECKS + int32_t lock_count; + pthread_mutex_t lock; +#endif + + gc_obj_t *semipermanent_objects, *semipermanent_lets; + s7_pointer protected_objects, protected_setters, protected_setter_symbols; /* vectors of gc-protected objects */ + s7_int *protected_objects_free_list; /* to avoid a linear search for a place to store an object in sc->protected_objects */ + s7_int protected_objects_size, protected_setters_size, protected_setters_loc; + s7_int protected_objects_free_list_loc; + + s7_pointer nil; /* empty list */ + s7_pointer T; /* #t */ + s7_pointer F; /* #f */ + s7_pointer undefined; /* # */ + s7_pointer unspecified; /* # */ + s7_pointer no_value; /* the (values) value */ + s7_pointer unused; /* a marker for an unoccupied slot in sc->protected_objects (and other similar stuff) */ + + s7_pointer symbol_table; /* symbol table */ + s7_pointer rootlet, shadow_rootlet; /* rootlet */ + s7_int rootlet_entries; + s7_pointer unlet; /* original bindings of predefined functions */ + + s7_pointer input_port; /* current-input-port */ + s7_pointer *input_port_stack; /* input port stack (load and read internally) */ + uint32_t input_port_stack_size, input_port_stack_loc; + + s7_pointer output_port; /* current-output-port */ + s7_pointer error_port; /* current-error-port */ + s7_pointer owlet; /* owlet */ + s7_pointer error_type, error_data, error_code, error_line, error_file, error_position; /* owlet slots */ + s7_pointer standard_input, standard_output, standard_error; + + s7_pointer sharp_readers; /* the binding pair for the global *#readers* list */ + s7_pointer load_hook; /* *load-hook* hook object */ + s7_pointer autoload_hook; /* *autoload-hook* hook object */ + s7_pointer unbound_variable_hook; /* *unbound-variable-hook* hook object */ + s7_pointer missing_close_paren_hook, rootlet_redefinition_hook; + s7_pointer error_hook, read_error_hook; /* *error-hook* hook object, and *read-error-hook* */ + bool gc_off, gc_in_progress; /* gc_off: if true, the GC won't run */ + uint32_t gc_stats, gensym_counter, f_class, add_class, multiply_class, subtract_class, num_eq_class; + int32_t format_column, error_argnum; + uint64_t capture_let_counter; + bool short_print, is_autoloading, in_with_let, object_out_locked, has_openlets, is_expanding, accept_all_keyword_arguments, muffle_warnings; + bool got_tc, got_rec, not_tc; + s7_int rec_tc_args, continuation_counter; + int64_t let_number; + unsigned char number_separator; + s7_double default_rationalize_error, equivalent_float_epsilon, hash_table_float_epsilon; + s7_int default_hash_table_length, initial_string_port_length, print_length, objstr_max_len, history_size, true_history_size, output_port_data_size; + s7_int max_vector_length, max_string_length, max_list_length, max_vector_dimensions, max_format_length, max_port_data_size, rec_loc, rec_len; + s7_pointer stacktrace_defaults; + + s7_pointer rec_stack, rec_testp, rec_f1p, rec_f2p, rec_f3p, rec_f4p, rec_f5p, rec_f6p, rec_f7p, rec_f8p, rec_f9p; + s7_pointer rec_resp, rec_slot1, rec_slot2, rec_slot3, rec_p1, rec_p2; + s7_pointer *rec_els; + s7_function rec_testf, rec_f1f, rec_f2f, rec_f3f, rec_f4f, rec_f5f, rec_f6f, rec_f7f, rec_f8f, rec_f9f, rec_resf, rec_fn; + s7_int (*rec_fi1)(opt_info *o); + s7_int (*rec_fi2)(opt_info *o); + s7_int (*rec_fi3)(opt_info *o); + s7_int (*rec_fi4)(opt_info *o); + s7_int (*rec_fi5)(opt_info *o); + s7_int (*rec_fi6)(opt_info *o); + bool (*rec_fb1)(opt_info *o); + bool (*rec_fb2)(opt_info *o); + + opt_info *rec_test_o, *rec_result_o, *rec_a1_o, *rec_a2_o, *rec_a3_o, *rec_a4_o, *rec_a5_o, *rec_a6_o; + s7_i_ii_t rec_i_ii_f; + s7_d_dd_t rec_d_dd_f; + s7_pointer rec_val1, rec_val2; + + int32_t float_format_precision; + vdims_t *wrap_only; + + char *typnam; + int32_t typnam_len, print_width; + s7_pointer *singletons; + block_t *unentry; /* hash-table lookup failure indicator */ + + #define INITIAL_FILE_NAMES_SIZE 8 + s7_pointer *file_names; + int32_t file_names_size, file_names_top; + + #define INITIAL_STRBUF_SIZE 1024 + s7_int strbuf_size; + char *strbuf; + + char *read_line_buf; + s7_int read_line_buf_size; + + s7_pointer w, x, y, z; + s7_pointer temp1, temp2, temp3, temp4, temp5, temp7, temp8, temp9, temp10; + s7_pointer t1_1, t2_1, t2_2, t3_1, t3_2, t3_3, t4_1, u1_1; + s7_pointer elist_1, elist_2, elist_3, elist_4, elist_5, elist_6, elist_7; + s7_pointer plist_1, plist_2, plist_2_2, plist_3, qlist_2, qlist_3, clist_1, clist_2, dlist_1, mlist_1, mlist_2; /* dlist|clist and ulist can't overlap */ + + Jmp_Buf *goto_start; + bool longjmp_ok; + int32_t setjmp_loc; + + void (*begin_hook)(s7_scheme *sc, bool *val); + opcode_t begin_op; + + bool debug_or_profile, profiling_gensyms; + s7_int current_line, s7_call_line, debug, profile, profile_position; + s7_pointer profile_prefix; + profile_data_t *profile_data; + const char *current_file, *s7_call_file, *s7_call_name; + + shared_info_t *circle_info; + format_data_t **fdats; + int32_t num_fdats, safety; + gc_list_t *strings, *vectors, *input_ports, *output_ports, *input_string_ports, *continuations, *c_objects, *hash_tables; + gc_list_t *gensyms, *undefineds, *multivectors, *weak_refs, *weak_hash_iterators, *opt1_funcs; +#if (WITH_GMP) + gc_list_t *big_integers, *big_ratios, *big_reals, *big_complexes, *big_random_states; + mpz_t mpz_1, mpz_2, mpz_3, mpz_4; + mpq_t mpq_1, mpq_2, mpq_3; + mpfr_t mpfr_1, mpfr_2, mpfr_3; + mpc_t mpc_1, mpc_2; + rat_locals_t *ratloc; + bigint *bigints; + bigrat *bigrats; + bigflt *bigflts; + bigcmp *bigcmps; +#endif + s7_pointer *setters; + s7_int setters_size, setters_loc; + s7_pointer *tree_pointers; + int32_t tree_pointers_size, tree_pointers_top, semipermanent_cells, num_to_str_size; + s7_pointer format_ports; + uint32_t alloc_pointer_k, alloc_function_k, alloc_symbol_k; + s7_cell *alloc_pointer_cells; + c_proc_t *alloc_function_cells; + uint32_t alloc_big_pointer_k; + s7_big_cell *alloc_big_pointer_cells; + s7_pointer string_wrappers, integer_wrappers, real_wrappers; + uint8_t *alloc_symbol_cells; + char *num_to_str; + + block_t *block_lists[NUM_BLOCK_LISTS]; + size_t alloc_string_k; + char *alloc_string_cells; + + c_object_t **c_object_types; + int32_t c_object_types_size, num_c_object_types; + s7_pointer type_to_typers[NUM_TYPES]; + + uint32_t syms_tag, syms_tag2; + int32_t bignum_precision; + s7_int baffle_ctr, map_call_ctr; + s7_pointer default_random_state; + + s7_pointer sort_body, sort_begin, sort_v1, sort_v2; + opcode_t sort_op; + s7_int sort_body_len; + s7_b_7pp_t sort_f; + opt_info *sort_o; + bool (*sort_fb)(opt_info *o); + + #define INT_TO_STR_SIZE 32 + char int_to_str1[INT_TO_STR_SIZE], int_to_str2[INT_TO_STR_SIZE], int_to_str3[INT_TO_STR_SIZE], int_to_str4[INT_TO_STR_SIZE], int_to_str5[INT_TO_STR_SIZE]; + + s7_pointer abs_symbol, acos_symbol, acosh_symbol, add_symbol, angle_symbol, append_symbol, apply_symbol, apply_values_symbol, arity_symbol, + ash_symbol, asin_symbol, asinh_symbol, assoc_symbol, assq_symbol, assv_symbol, atan_symbol, atanh_symbol, autoload_symbol, autoloader_symbol, + bacro_symbol, bacro_star_symbol, bignum_symbol, byte_vector_symbol, byte_vector_ref_symbol, byte_vector_set_symbol, byte_vector_to_string_symbol, + c_pointer_symbol, c_pointer_info_symbol, c_pointer_to_list_symbol, c_pointer_type_symbol, c_pointer_weak1_symbol, c_pointer_weak2_symbol, + caaaar_symbol, caaadr_symbol, caaar_symbol, caadar_symbol, caaddr_symbol, caadr_symbol, + caar_symbol, cadaar_symbol, cadadr_symbol, cadar_symbol, caddar_symbol, cadddr_symbol, caddr_symbol, cadr_symbol, + call_cc_symbol, call_with_current_continuation_symbol, call_with_exit_symbol, call_with_input_file_symbol, + call_with_input_string_symbol, call_with_output_file_symbol, call_with_output_string_symbol, car_symbol, + catch_symbol, cdaaar_symbol, cdaadr_symbol, cdaar_symbol, cdadar_symbol, cdaddr_symbol, cdadr_symbol, cdar_symbol, + cddaar_symbol, cddadr_symbol, cddar_symbol, cdddar_symbol, cddddr_symbol, cdddr_symbol, cddr_symbol, cdr_symbol, + ceiling_symbol, char_downcase_symbol, char_eq_symbol, char_geq_symbol, char_gt_symbol, char_leq_symbol, char_lt_symbol, + char_position_symbol, char_to_integer_symbol, char_upcase_symbol, cload_directory_symbol, close_input_port_symbol, + close_output_port_symbol, complex_symbol, cons_symbol, copy_symbol, cos_symbol, cosh_symbol, coverlet_symbol, + curlet_symbol, current_error_port_symbol, current_input_port_symbol, current_output_port_symbol, cutlet_symbol, cyclic_sequences_symbol, + denominator_symbol, dilambda_symbol, display_symbol, divide_symbol, documentation_symbol, dynamic_wind_symbol, dynamic_unwind_symbol, + num_eq_symbol, error_symbol, eval_string_symbol, eval_symbol, exact_to_inexact_symbol, exit_symbol, exp_symbol, expt_symbol, + features_symbol, file__symbol, fill_symbol, float_vector_ref_symbol, float_vector_set_symbol, float_vector_symbol, floor_symbol, + flush_output_port_symbol, for_each_symbol, format_symbol, funclet_symbol, _function__symbol, + gc_symbol, gcd_symbol, gensym_symbol, geq_symbol, get_output_string_symbol, gt_symbol, + hash_table_entries_symbol, hash_table_key_typer_symbol, hash_table_ref_symbol, hash_table_set_symbol, hash_table_symbol, + hash_table_value_typer_symbol, help_symbol, + imag_part_symbol, immutable_symbol, inexact_to_exact_symbol, inlet_symbol, int_vector_ref_symbol, int_vector_set_symbol, int_vector_symbol, + integer_decode_float_symbol, integer_to_char_symbol, + is_aritable_symbol, is_bignum_symbol, is_boolean_symbol, is_byte_symbol, is_byte_vector_symbol, + is_c_object_symbol, c_object_type_symbol, is_c_pointer_symbol, is_char_alphabetic_symbol, is_char_lower_case_symbol, is_char_numeric_symbol, + is_char_symbol, is_char_upper_case_symbol, is_char_whitespace_symbol, is_complex_symbol, is_constant_symbol, + is_continuation_symbol, is_defined_symbol, is_dilambda_symbol, is_eof_object_symbol, is_eq_symbol, is_equal_symbol, + is_eqv_symbol, is_even_symbol, is_exact_symbol, is_float_vector_symbol, is_funclet_symbol, + is_gensym_symbol, is_goto_symbol, is_hash_table_symbol, is_immutable_symbol, + is_inexact_symbol, is_infinite_symbol, is_input_port_symbol, is_int_vector_symbol, is_integer_symbol, is_iterator_symbol, + is_keyword_symbol, is_let_symbol, is_list_symbol, is_macro_symbol, is_equivalent_symbol, is_nan_symbol, is_negative_symbol, + is_null_symbol, is_number_symbol, is_odd_symbol, is_openlet_symbol, is_output_port_symbol, is_pair_symbol, + is_port_closed_symbol, is_positive_symbol, is_procedure_symbol, is_proper_list_symbol, is_provided_symbol, + is_random_state_symbol, is_rational_symbol, is_real_symbol, is_sequence_symbol, is_string_symbol, is_subvector_symbol, + is_symbol_symbol, is_syntax_symbol, is_vector_symbol, is_weak_hash_table_symbol, is_zero_symbol, + is_float_symbol, is_integer_or_real_at_end_symbol, is_integer_or_any_at_end_symbol, is_unspecified_symbol, is_undefined_symbol, + iterate_symbol, iterator_is_at_end_symbol, iterator_sequence_symbol, + keyword_to_symbol_symbol, + lcm_symbol, length_symbol, leq_symbol, let_ref_fallback_symbol, let_ref_symbol, let_set_fallback_symbol, + let_set_symbol, let_temporarily_symbol, libraries_symbol, list_ref_symbol, list_set_symbol, list_symbol, list_tail_symbol, list_values_symbol, + load_path_symbol, load_symbol, log_symbol, logand_symbol, logbit_symbol, logior_symbol, lognot_symbol, logxor_symbol, lt_symbol, + macro_symbol, macro_star_symbol, magnitude_symbol, make_byte_vector_symbol, make_float_vector_symbol, make_hash_table_symbol, + make_weak_hash_table_symbol, make_int_vector_symbol, make_iterator_symbol, string_to_keyword_symbol, make_list_symbol, make_string_symbol, + make_vector_symbol, map_symbol, max_symbol, member_symbol, memq_symbol, memv_symbol, min_symbol, modulo_symbol, multiply_symbol, + name_symbol, nan_symbol, nan_payload_symbol, newline_symbol, not_symbol, number_to_string_symbol, numerator_symbol, + object_to_string_symbol, object_to_let_symbol, open_input_file_symbol, open_input_function_symbol, open_input_string_symbol, + open_output_file_symbol, open_output_function_symbol, open_output_string_symbol, openlet_symbol, outlet_symbol, owlet_symbol, + pair_filename_symbol, pair_line_number_symbol, peek_char_symbol, pi_symbol, port_filename_symbol, port_line_number_symbol, + port_file_symbol, port_position_symbol, procedure_source_symbol, provide_symbol, + qq_append_symbol, quotient_symbol, + random_state_symbol, random_state_to_list_symbol, random_symbol, rationalize_symbol, read_byte_symbol, + read_char_symbol, read_line_symbol, read_string_symbol, read_symbol, real_part_symbol, remainder_symbol, + require_symbol, reverse_symbol, reverseb_symbol, rootlet_symbol, round_symbol, + setter_symbol, set_car_symbol, set_cdr_symbol, + set_current_error_port_symbol, set_current_input_port_symbol, set_current_output_port_symbol, + signature_symbol, sin_symbol, sinh_symbol, sort_symbol, sqrt_symbol, + stacktrace_symbol, string_append_symbol, string_copy_symbol, string_downcase_symbol, string_eq_symbol, string_fill_symbol, + string_geq_symbol, string_gt_symbol, string_leq_symbol, string_lt_symbol, string_position_symbol, string_ref_symbol, + string_set_symbol, string_symbol, string_to_number_symbol, string_to_symbol_symbol, string_upcase_symbol, + sublet_symbol, substring_symbol, subtract_symbol, subvector_symbol, subvector_position_symbol, subvector_vector_symbol, + symbol_symbol, symbol_to_dynamic_value_symbol, + symbol_to_keyword_symbol, symbol_to_string_symbol, symbol_to_value_symbol, + tan_symbol, tanh_symbol, throw_symbol, string_to_byte_vector_symbol, + tree_count_symbol, tree_leaves_symbol, tree_memq_symbol, tree_set_memq_symbol, tree_is_cyclic_symbol, truncate_symbol, type_of_symbol, + unlet_symbol, + values_symbol, varlet_symbol, vector_append_symbol, vector_dimension_symbol, vector_dimensions_symbol, vector_fill_symbol, + vector_rank_symbol, vector_ref_symbol, vector_set_symbol, vector_symbol, vector_typer_symbol, + weak_hash_table_symbol, with_input_from_file_symbol, with_input_from_string_symbol, with_output_to_file_symbol, with_output_to_string_symbol, + write_byte_symbol, write_char_symbol, write_string_symbol, write_symbol, + local_documentation_symbol, local_signature_symbol, local_setter_symbol, local_iterator_symbol; + s7_pointer hash_code_symbol, dummy_equal_hash_table; +#if (!WITH_PURE_S7) + s7_pointer is_char_ready_symbol, char_ci_leq_symbol, char_ci_lt_symbol, char_ci_eq_symbol, char_ci_geq_symbol, char_ci_gt_symbol, + let_to_list_symbol, integer_length_symbol, string_ci_leq_symbol, string_ci_lt_symbol, string_ci_eq_symbol, + string_ci_geq_symbol, string_ci_gt_symbol, string_to_list_symbol, vector_to_list_symbol, string_length_symbol, + list_to_string_symbol, list_to_vector_symbol, vector_length_symbol; +#endif + + /* syntax symbols et al */ + s7_pointer else_symbol, lambda_symbol, lambda_star_symbol, let_symbol, quote_symbol, quasiquote_symbol, unquote_symbol, macroexpand_symbol, + define_expansion_symbol, define_expansion_star_symbol, with_let_symbol, if_symbol, autoload_error_symbol, + when_symbol, unless_symbol, begin_symbol, cond_symbol, case_symbol, and_symbol, or_symbol, do_symbol, number_to_real_symbol, + define_symbol, define_star_symbol, define_constant_symbol, with_baffle_symbol, define_macro_symbol, no_setter_symbol, + define_macro_star_symbol, define_bacro_symbol, define_bacro_star_symbol, letrec_symbol, letrec_star_symbol, let_star_symbol, + rest_keyword, allow_other_keys_keyword, readable_keyword, display_keyword, write_keyword, value_symbol, type_symbol, + baffled_symbol, set_symbol, body_symbol, class_name_symbol, feed_to_symbol, format_error_symbol, immutable_error_symbol, + wrong_number_of_args_symbol, read_error_symbol, string_read_error_symbol, syntax_error_symbol, division_by_zero_symbol, bad_result_symbol, + io_error_symbol, invalid_escape_function_symbol, wrong_type_arg_symbol, out_of_range_symbol, out_of_memory_symbol, + missing_method_symbol, unbound_variable_symbol, if_keyword, symbol_table_symbol, profile_in_symbol, trace_in_symbol; + + /* signatures of sequences used as applicable objects: ("hi" 1) */ + s7_pointer string_signature, vector_signature, float_vector_signature, int_vector_signature, byte_vector_signature, + c_object_signature, let_signature, hash_table_signature, pair_signature; + /* common signatures */ + s7_pointer pcl_bc, pcl_bs, pcl_bt, pcl_c, pcl_e, pcl_f, pcl_i, pcl_n, pcl_r, pcl_s, pcl_v, pl_bc, pl_bn, pl_bt, pl_p, pl_sf, pl_tl, pl_nn; + + /* optimizer s7_functions */ + s7_pointer add_2, add_3, add_1x, add_x1, subtract_1, subtract_2, subtract_3, subtract_x1, subtract_2f, subtract_f2, simple_char_eq, + char_equal_2, char_greater_2, char_less_2, char_position_csi, string_equal_2, substring_uncopied, display_2, display_f, + string_greater_2, string_less_2, symbol_to_string_uncopied, get_output_string_uncopied, string_equal_2c, string_c1, string_append_2, + vector_ref_2, vector_ref_3, vector_set_3, vector_set_4, read_char_1, dynamic_wind_unchecked, dynamic_wind_body, dynamic_wind_init, append_2, + fv_ref_2, fv_ref_3, fv_set_3, fv_set_unchecked, iv_ref_2, iv_ref_3, iv_set_3, bv_ref_2, bv_ref_3, bv_set_3, vector_2, vector_3, + list_0, list_1, list_2, list_3, list_4, list_set_i, hash_table_ref_2, hash_table_2, list_ref_at_0, list_ref_at_1, list_ref_at_2, + format_f, format_no_column, format_just_control_string, format_as_objstr, values_uncopied, int_log2, + memq_2, memq_3, memq_4, memq_any, tree_set_memq_syms, simple_inlet, sublet_curlet, profile_out, simple_list_values, + lint_let_ref, lint_let_set, geq_2, add_i_random, is_defined_in_rootlet; + + s7_pointer multiply_2, invert_1, invert_x, divide_2, divide_by_2, max_2, min_2, max_3, min_3, + num_eq_2, num_eq_xi, num_eq_ix, less_xi, less_xf, less_x0, less_2, greater_xi, greater_xf, greater_2, + leq_xi, leq_2, leq_ixx, geq_xi, geq_xf, random_i, random_f, random_1, + mul_2_ff, mul_2_ii, mul_2_if, mul_2_fi, mul_2_xi, mul_2_ix, mul_2_fx, mul_2_xf, + add_2_ff, add_2_ii, add_2_if, add_2_fi, add_2_xi, add_2_ix, add_2_fx, add_2_xf; + s7_pointer seed_symbol, carry_symbol; + + /* object->let symbols */ + s7_pointer active_symbol, data_symbol, weak_symbol, dimensions_symbol, info_symbol, c_type_symbol, source_symbol, c_object_ref_symbol, + at_end_symbol, sequence_symbol, position_symbol, entries_symbol, function_symbol, open_symbol, alias_symbol, port_type_symbol, + file_symbol, file_info_symbol, line_symbol, c_object_let_symbol, class_symbol, current_value_symbol, closed_symbol, + is_mutable_symbol, size_symbol, original_vector_symbol, pointer_symbol; + +#if WITH_SYSTEM_EXTRAS + s7_pointer is_directory_symbol, file_exists_symbol, delete_file_symbol, getenv_symbol, system_symbol, directory_to_list_symbol, file_mtime_symbol; +#endif + s7_pointer open_input_function_choices[S7_NUM_READ_CHOICES]; + s7_pointer closed_input_function, closed_output_function; + s7_pointer vector_set_function, string_set_function, list_set_function, hash_table_set_function, let_set_function, c_object_set_function, last_function; + s7_pointer wrong_type_arg_info, out_of_range_info, sole_arg_wrong_type_info, sole_arg_out_of_range_info; + + #define NUM_SAFE_PRELISTS 8 + #define NUM_SAFE_LISTS 64 /* 36 is the biggest normally (lint.scm), 49 in s7test, 57 in snd-test */ + s7_pointer safe_lists[NUM_SAFE_LISTS]; + int32_t current_safe_list; + + s7_pointer autoload_table, s7_starlet, s7_starlet_symbol, let_temp_hook; + const char ***autoload_names; + s7_int *autoload_names_sizes; + bool **autoloaded_already; + s7_int autoload_names_loc, autoload_names_top; + int32_t format_depth; + bool undefined_identifier_warnings, undefined_constant_warnings, stop_at_error; + + opt_funcs_t *alloc_opt_func_cells; + int32_t alloc_opt_func_k; + + int32_t pc; + #define OPTS_SIZE 256 /* pqw-vox needs 178 */ + opt_info *opts[OPTS_SIZE + 1]; /* this form is a lot faster than opt_info**! */ + + #define INITIAL_SAVED_POINTERS_SIZE 256 + void **saved_pointers; + s7_int saved_pointers_loc, saved_pointers_size; + + s7_pointer type_names[NUM_TYPES]; + +#if S7_DEBUGGING + int32_t *tc_rec_calls; + int32_t last_gc_line; + bool printing_gc_info; +#endif +}; + + +static noreturn void error_nr(s7_scheme *sc, s7_pointer type, s7_pointer info); +static s7_pointer wrap_string(s7_scheme *sc, const char *str, s7_int len); + +#if S7_DEBUGGING + static void gdb_break(void) {}; +#endif + +/* an experiment */ +#ifndef DISABLE_FILE_OUTPUT + #define DISABLE_FILE_OUTPUT 0 +#endif + +#if S7_DEBUGGING || POINTER_32 || WITH_WARNINGS || DISABLE_FILE_OUTPUT + static s7_scheme *cur_sc = NULL; /* intended for gdb (see gdbinit), but also used elsewhere unfortunately */ +#endif + +#if DISABLE_FILE_OUTPUT +static FILE *old_fopen(const char *pathname, const char *mode) {return(fopen(pathname, mode));} + +#define fwrite local_fwrite +#define fopen local_fopen /* open only used for file_probe (O_RDONLY), creat and write not used */ + +static s7_pointer set_elist_1(s7_scheme *sc, s7_pointer x1); + +static size_t local_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) +{ + error_nr(cur_sc, cur_sc->io_error_symbol, + set_elist_1(cur_sc, wrap_string(cur_sc, "writing a file is not allowed in this version of s7", 51))); +} + +static FILE *local_fopen(const char *pathname, const char *mode) +{ + if ((mode[0] == 'w') || (mode[0] == 'a')) + error_nr(cur_sc, cur_sc->io_error_symbol, + set_elist_1(cur_sc, wrap_string(cur_sc, "opening a file is not allowed in this version of s7", 51))); + return(old_fopen(pathname, mode)); +} +#endif + + +#if POINTER_32 +#if (!DISABLE_FILE_OUTPUT) + static s7_pointer set_elist_1(s7_scheme *sc, s7_pointer x1); +#endif +static void *Malloc(size_t bytes) +{ + void *p = malloc(bytes); + if (!p) error_nr(cur_sc, cur_sc->out_of_memory_symbol, set_elist_1(cur_sc, wrap_string(cur_sc, "malloc failed", 13))); + return(p); +} + +static void *Calloc(size_t nmemb, size_t size) +{ + void *p = calloc(nmemb, size); + if (!p) error_nr(cur_sc, cur_sc->out_of_memory_symbol, set_elist_1(cur_sc, wrap_string(cur_sc, "calloc failed", 13))); + return(p); +} + +static void *Realloc(void *ptr, size_t size) +{ + void *p = realloc(ptr, size); + if (!p) error_nr(cur_sc, cur_sc->out_of_memory_symbol, set_elist_1(cur_sc, wrap_string(cur_sc, "realloc failed", 14))); + return(p); +} +#else +#define Malloc malloc +#define Calloc calloc +#define Realloc realloc +#endif + + +/* -------------------------------- mallocate -------------------------------- */ +static void add_saved_pointer(s7_scheme *sc, void *p) +{ + if (sc->saved_pointers_loc == sc->saved_pointers_size) + { + sc->saved_pointers_size *= 2; + sc->saved_pointers = (void **)Realloc(sc->saved_pointers, sc->saved_pointers_size * sizeof(void *)); + } + sc->saved_pointers[sc->saved_pointers_loc++] = p; +} + +static const int32_t intlen_bits[256] = + {0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}; + +static void memclr(void *s, size_t n) +{ + uint8_t *s2; +#if S7_ALIGNED + s2 = (uint8_t *)s; +#else +#if (defined(__x86_64__) || defined(__i386__)) + if (n >= 8) + { + int64_t *s1 = (int64_t *)s; + size_t n8 = n >> 3; + do {*s1++ = 0;} while (--n8 > 0); /* LOOP_4 here is slower */ + n &= 7; + s2 = (uint8_t *)s1; + } + else s2 = (uint8_t *)s; +#else + s2 = (uint8_t *)s; +#endif +#endif + while (n > 0) + { + *s2++ = 0; + n--; + } +} + +#define LOOP_4(Code) do {Code; Code; Code; Code;} while (0) +#define LOOP_8(Code) do {Code; Code; Code; Code; Code; Code; Code; Code;} while (0) +#define STEP_8(Var) (((Var) & 0x7) == 0) +#define STEP_64(Var) (((Var) & 0x3f) == 0) + +#if POINTER_32 +#define memclr64 memclr +#else +static Vectorized void memclr64(void *p, size_t bytes) +{ + size_t n = bytes >> 3; + int64_t *vals = (int64_t *)p; + for (size_t i = 0; i < n; ) + LOOP_8(vals[i++] = 0); +} +#endif + +static void init_block_lists(s7_scheme *sc) +{ + for (int32_t i = 0; i < NUM_BLOCK_LISTS; i++) + sc->block_lists[i] = NULL; +} + +static inline void liberate(s7_scheme *sc, block_t *p) +{ + if (block_index(p) != TOP_BLOCK_LIST) + { + block_next(p) = (struct block_t *)sc->block_lists[block_index(p)]; + sc->block_lists[block_index(p)] = p; + } + else + { + if (block_data(p)) + { + free(block_data(p)); + block_data(p) = NULL; + } + block_next(p) = (struct block_t *)sc->block_lists[BLOCK_LIST]; + sc->block_lists[BLOCK_LIST] = p; + } +} + +static inline void liberate_block(s7_scheme *sc, block_t *p) +{ + block_next(p) = (struct block_t *)sc->block_lists[BLOCK_LIST]; /* BLOCK_LIST==0 */ + sc->block_lists[BLOCK_LIST] = p; +} + +static void fill_block_list(s7_scheme *sc) +{ + #define BLOCK_MALLOC_SIZE 256 + block_t *b = (block_t *)Malloc(BLOCK_MALLOC_SIZE * sizeof(block_t)); /* batch alloc means blocks in this batch can't be freed, only returned to the list */ + add_saved_pointer(sc, b); + sc->block_lists[BLOCK_LIST] = b; + for (int32_t i = 0; i < BLOCK_MALLOC_SIZE - 1; b++, i++) + block_next(b) = (block_t *)(b + 1); + block_next(b) = NULL; +} + +static inline block_t *mallocate_block(s7_scheme *sc) +{ + block_t *p; + if (!sc->block_lists[BLOCK_LIST]) + fill_block_list(sc); /* this is much faster than allocating blocks as needed */ + p = sc->block_lists[BLOCK_LIST]; + sc->block_lists[BLOCK_LIST] = (block_t *)(block_next(p)); + block_set_index(p, BLOCK_LIST); + return(p); +} + +static inline char *permalloc(s7_scheme *sc, size_t len) +{ + #define ALLOC_STRING_SIZE (65536 * 8) /* going up to 16 made no difference in timings */ + #define ALLOC_MAX_STRING (512 * 8) /* was 256 -- sets max size of block space lost at the end, but smaller = more direct malloc calls */ + char *result; + size_t next_k; + + len = (len + 7) & (~7); /* 8-byte aligned -- more than half the time, len is already 8-byte aligned */ + next_k = sc->alloc_string_k + len; + if (next_k > ALLOC_STRING_SIZE) + { + if (len >= ALLOC_MAX_STRING) + { + result = (char *)Malloc(len); + add_saved_pointer(sc, result); + return(result); + } + sc->alloc_string_cells = (char *)Malloc(ALLOC_STRING_SIZE); /* get a new block */ + add_saved_pointer(sc, sc->alloc_string_cells); + sc->alloc_string_k = 0; + next_k = len; + } + result = &(sc->alloc_string_cells[sc->alloc_string_k]); + sc->alloc_string_k = next_k; + return(result); +} + +static Inline block_t *inline_mallocate(s7_scheme *sc, size_t bytes) +{ + block_t *p; + if (bytes > 0) + { + int32_t index; + if (bytes <= 8) /* presetting a version of intlen_bits with 3's gave only a small speed-up */ + index = 3; + else + { + if (bytes <= 256) + index = intlen_bits[bytes - 1]; + else index = (bytes <= 65536) ? (8 + intlen_bits[(bytes - 1) >> 8]) : TOP_BLOCK_LIST; /* expansion to (1 << 17) made no difference */ + } + p = sc->block_lists[index]; + if (p) + sc->block_lists[index] = (block_t *)block_next(p); + else + { + if (index < (TOP_BLOCK_LIST - 1)) + { + p = sc->block_lists[index + 1]; + if (p) + { + /* we are "borrowing" a block from the next larger bin -- this saves space but costs a bit of time. + * in a tauto run repeating every call 1000 times, the old form ends up at 129M and 31.1 secs, + * whereas the borrowing form ends at 116M and 31.5 secs, but most of my tests show a slight + * speed-up, probably because grabbing a block here is faster than making a new one. + * Worst case is tlet: 8 slower in callgrind. + */ + sc->block_lists[index + 1] = (block_t *)block_next(p); + block_set_size(p, bytes); + return(p); + }} + p = mallocate_block(sc); + block_data(p) = (index < TOP_BLOCK_LIST) ? (void *)permalloc(sc, (size_t)(1 << index)) : Malloc(bytes); + block_set_index(p, index); + }} + else p = mallocate_block(sc); + block_set_size(p, bytes); + return(p); +} + +static block_t *mallocate(s7_scheme *sc, size_t bytes) {return(inline_mallocate(sc, bytes));} + +static block_t *callocate(s7_scheme *sc, size_t bytes) +{ + block_t *p = inline_mallocate(sc, bytes); + if ((block_data(p)) && (block_index(p) != BLOCK_LIST)) + { + if ((bytes & (~0x3f)) > 0) + memclr64((void *)block_data(p), bytes & (~0x3f)); + if ((bytes & 0x3f) > 0) + memclr((void *)((uint8_t *)block_data(p) + (bytes & (~0x3f))), bytes & 0x3f); + } + return(p); +} + +static block_t *reallocate(s7_scheme *sc, block_t *op, size_t bytes) +{ + block_t *np = inline_mallocate(sc, bytes); + if (block_data(op)) /* presumably block_data(np) is not null */ + memcpy((uint8_t *)(block_data(np)), (uint8_t *)(block_data(op)), block_size(op)); + liberate(sc, op); + return(np); +} + +/* we can't export mallocate et al without also exporting block_t or accessors for it + * that is, the block_t* pointer returned can't be used as if it were the void* pointer returned by malloc + * ideally we'd have a way to release excessive mallocate bins, but they are permalloc'd individually + */ + + +/* -------------------------------------------------------------------------------- */ +typedef enum {P_DISPLAY, P_WRITE, P_READABLE, P_KEY, P_CODE} use_write_t; + +static s7_pointer too_many_arguments_string, not_enough_arguments_string, cant_bind_immutable_string, + a_boolean_string, a_byte_vector_string, a_format_port_string, a_let_string, a_list_string, a_non_constant_symbol_string, + a_non_negative_integer_string, a_normal_procedure_string, a_normal_real_string, a_number_string, a_procedure_string, a_procedure_or_a_macro_string, + a_proper_list_string, a_random_state_object_string, a_rational_string, a_sequence_string, a_symbol_string, a_thunk_string, a_valid_radix_string, + an_association_list_string, an_eq_func_string, an_input_file_port_string, an_input_port_string, an_input_string_port_string, an_open_input_port_string, + an_open_output_port_string, an_output_port_or_f_string, an_output_file_port_string, an_output_port_string, an_output_string_port_string, + an_unsigned_byte_string, caaar_a_list_string, caadr_a_list_string, caar_a_list_string, cadar_a_list_string, caddr_a_list_string, + cadr_a_list_string, car_a_list_string, cdaar_a_list_string, cdadr_a_list_string, cdar_a_list_string, cddar_a_list_string, + cdddr_a_list_string, cddr_a_list_string, cdr_a_list_string, immutable_error_string, it_is_infinite_string, it_is_nan_string, + it_is_negative_string, it_is_too_large_string, it_is_too_small_string, parameter_set_twice_string, result_is_too_large_string, + something_applicable_string, too_many_indices_string, intermediate_too_large_string, + format_string_1, format_string_2, format_string_3, format_string_4, keyword_value_missing_string; + +static bool t_number_p[NUM_TYPES], t_small_real_p[NUM_TYPES], t_rational_p[NUM_TYPES], t_real_p[NUM_TYPES], t_big_number_p[NUM_TYPES]; +static bool t_simple_p[NUM_TYPES], t_structure_p[NUM_TYPES]; +static bool t_any_macro_p[NUM_TYPES], t_any_closure_p[NUM_TYPES], t_has_closure_let[NUM_TYPES]; +static bool t_mappable_p[NUM_TYPES], t_sequence_p[NUM_TYPES], t_vector_p[NUM_TYPES]; +static bool t_procedure_p[NUM_TYPES], t_applicable_p[NUM_TYPES], t_macro_setter_p[NUM_TYPES]; +#if S7_DEBUGGING +static bool t_freeze_p[NUM_TYPES]; /* free_cell sanity check */ +static bool t_ext_p[NUM_TYPES]; /* make sure internal types don't leak out */ +#endif + +static void init_types(void) +{ + for (int32_t i = 0; i < NUM_TYPES; i++) + { + t_number_p[i] = false; + t_small_real_p[i] = false; + t_real_p[i] = false; + t_rational_p[i] = false; + t_simple_p[i] = false; + t_structure_p[i] = false; + t_any_macro_p[i] = false; + t_any_closure_p[i] = false; + t_has_closure_let[i] = false; + t_sequence_p[i] = false; + t_mappable_p[i] = false; + t_vector_p[i] = false; + t_applicable_p[i] = false; + t_procedure_p[i] = false; + t_macro_setter_p[i] = false; +#if S7_DEBUGGING + t_freeze_p[i] = false; + t_ext_p[i] = false; +#endif + } + t_number_p[T_INTEGER] = true; + t_number_p[T_RATIO] = true; + t_number_p[T_REAL] = true; + t_number_p[T_COMPLEX] = true; + t_number_p[T_BIG_INTEGER] = true; + t_number_p[T_BIG_RATIO] = true; + t_number_p[T_BIG_REAL] = true; + t_number_p[T_BIG_COMPLEX] = true; + + t_rational_p[T_INTEGER] = true; + t_rational_p[T_RATIO] = true; + t_rational_p[T_BIG_INTEGER] = true; + t_rational_p[T_BIG_RATIO] = true; + + t_small_real_p[T_INTEGER] = true; + t_small_real_p[T_RATIO] = true; + t_small_real_p[T_REAL] = true; + + t_real_p[T_INTEGER] = true; + t_real_p[T_RATIO] = true; + t_real_p[T_REAL] = true; + t_real_p[T_BIG_INTEGER] = true; + t_real_p[T_BIG_RATIO] = true; + t_real_p[T_BIG_REAL] = true; + + t_big_number_p[T_BIG_INTEGER] = true; + t_big_number_p[T_BIG_RATIO] = true; + t_big_number_p[T_BIG_REAL] = true; + t_big_number_p[T_BIG_COMPLEX] = true; + + t_structure_p[T_PAIR] = true; + t_structure_p[T_VECTOR] = true; + t_structure_p[T_HASH_TABLE] = true; + t_structure_p[T_SLOT] = true; + t_structure_p[T_LET] = true; + t_structure_p[T_ITERATOR] = true; + t_structure_p[T_C_POINTER] = true; + t_structure_p[T_C_OBJECT] = true; + + t_sequence_p[T_NIL] = true; + t_sequence_p[T_PAIR] = true; + t_sequence_p[T_STRING] = true; + t_sequence_p[T_VECTOR] = true; + t_sequence_p[T_INT_VECTOR] = true; + t_sequence_p[T_FLOAT_VECTOR] = true; + t_sequence_p[T_BYTE_VECTOR] = true; + t_sequence_p[T_HASH_TABLE] = true; + t_sequence_p[T_LET] = true; + t_sequence_p[T_C_OBJECT] = true; /* this assumes the object has a length method? */ + + t_mappable_p[T_PAIR] = true; + t_mappable_p[T_STRING] = true; + t_mappable_p[T_VECTOR] = true; + t_mappable_p[T_INT_VECTOR] = true; + t_mappable_p[T_FLOAT_VECTOR] = true; + t_mappable_p[T_BYTE_VECTOR] = true; + t_mappable_p[T_HASH_TABLE] = true; + t_mappable_p[T_LET] = true; + t_mappable_p[T_C_OBJECT] = true; + t_mappable_p[T_ITERATOR] = true; + t_mappable_p[T_C_MACRO] = true; + t_mappable_p[T_MACRO] = true; + t_mappable_p[T_BACRO] = true; + t_mappable_p[T_MACRO_STAR] = true; + t_mappable_p[T_BACRO_STAR] = true; + t_mappable_p[T_CLOSURE] = true; + t_mappable_p[T_CLOSURE_STAR] = true; + + t_vector_p[T_VECTOR] = true; + t_vector_p[T_INT_VECTOR] = true; + t_vector_p[T_FLOAT_VECTOR] = true; + t_vector_p[T_BYTE_VECTOR] = true; + + t_applicable_p[T_PAIR] = true; + t_applicable_p[T_STRING] = true; + t_applicable_p[T_VECTOR] = true; + t_applicable_p[T_INT_VECTOR] = true; + t_applicable_p[T_FLOAT_VECTOR] = true; + t_applicable_p[T_BYTE_VECTOR] = true; + t_applicable_p[T_HASH_TABLE] = true; + t_applicable_p[T_ITERATOR] = true; + t_applicable_p[T_LET] = true; + t_applicable_p[T_C_OBJECT] = true; + t_applicable_p[T_C_MACRO] = true; + t_applicable_p[T_MACRO] = true; + t_applicable_p[T_BACRO] = true; + t_applicable_p[T_MACRO_STAR] = true; + t_applicable_p[T_BACRO_STAR] = true; + t_applicable_p[T_SYNTAX] = true; + t_applicable_p[T_C_FUNCTION] = true; + t_applicable_p[T_C_FUNCTION_STAR] = true; + t_applicable_p[T_C_RST_NO_REQ_FUNCTION] = true; + t_applicable_p[T_CLOSURE] = true; + t_applicable_p[T_CLOSURE_STAR] = true; + t_applicable_p[T_GOTO] = true; + t_applicable_p[T_CONTINUATION] = true; + + /* t_procedure_p[T_C_OBJECT] = true; */ + t_procedure_p[T_C_FUNCTION] = true; + t_procedure_p[T_C_FUNCTION_STAR] = true; + t_procedure_p[T_C_RST_NO_REQ_FUNCTION] = true; + t_procedure_p[T_CLOSURE] = true; + t_procedure_p[T_CLOSURE_STAR] = true; + t_procedure_p[T_GOTO] = true; + t_procedure_p[T_CONTINUATION] = true; + + for (int32_t i = T_CLOSURE; i < NUM_TYPES; i++) t_macro_setter_p[i] = true; + t_macro_setter_p[T_SYMBOL] = true; /* (slot setter); apparently T_LET and T_C_OBJECT are not possible here */ + + t_any_macro_p[T_C_MACRO] = true; + t_any_macro_p[T_MACRO] = true; + t_any_macro_p[T_BACRO] = true; + t_any_macro_p[T_MACRO_STAR] = true; + t_any_macro_p[T_BACRO_STAR] = true; + + t_any_closure_p[T_CLOSURE] = true; + t_any_closure_p[T_CLOSURE_STAR] = true; + + t_has_closure_let[T_MACRO] = true; + t_has_closure_let[T_BACRO] = true; + t_has_closure_let[T_MACRO_STAR] = true; + t_has_closure_let[T_BACRO_STAR] = true; + t_has_closure_let[T_CLOSURE] = true; + t_has_closure_let[T_CLOSURE_STAR] = true; + + t_simple_p[T_NIL] = true; + /* t_simple_p[T_UNDEFINED] = true; */ /* only # itself will work with eq? */ + t_simple_p[T_EOF] = true; + t_simple_p[T_BOOLEAN] = true; + t_simple_p[T_CHARACTER] = true; + t_simple_p[T_SYMBOL] = true; + t_simple_p[T_SYNTAX] = true; + t_simple_p[T_C_MACRO] = true; + t_simple_p[T_C_FUNCTION] = true; + t_simple_p[T_C_FUNCTION_STAR] = true; + t_simple_p[T_C_RST_NO_REQ_FUNCTION] = true; + /* not completely sure about the next ones */ + /* t_simple_p[T_LET] = true; */ /* this needs let_equal in member et al, 29-Nov-22 */ + t_simple_p[T_INPUT_PORT] = true; + t_simple_p[T_OUTPUT_PORT] = true; + +#if S7_DEBUGGING + t_freeze_p[T_STRING] = true; + t_freeze_p[T_BYTE_VECTOR] = true; + t_freeze_p[T_VECTOR] = true; + t_freeze_p[T_FLOAT_VECTOR] = true; + t_freeze_p[T_INT_VECTOR] = true; + t_freeze_p[T_UNDEFINED] = true; + t_freeze_p[T_C_OBJECT] = true; + t_freeze_p[T_HASH_TABLE] = true; + t_freeze_p[T_C_FUNCTION] = true; + t_freeze_p[T_CONTINUATION] = true; + t_freeze_p[T_INPUT_PORT] = true; + t_freeze_p[T_OUTPUT_PORT] = true; +#if WITH_GMP + t_freeze_p[T_BIG_INTEGER] = true; + t_freeze_p[T_BIG_RATIO] = true; + t_freeze_p[T_BIG_REAL] = true; + t_freeze_p[T_BIG_COMPLEX] = true; + t_freeze_p[T_RANDOM_STATE] = true; +#endif + t_ext_p[T_UNUSED] = true; + t_ext_p[T_STACK] = true; + t_ext_p[T_SLOT] = true; + t_ext_p[T_DYNAMIC_WIND] = true; + t_ext_p[T_CATCH] = true; + t_ext_p[T_COUNTER] = true; +#if (!WITH_GMP) + t_ext_p[T_BIG_INTEGER] = true; + t_ext_p[T_BIG_RATIO] = true; + t_ext_p[T_BIG_REAL] = true; + t_ext_p[T_BIG_COMPLEX] = true; +#endif +#endif +} + +#if WITH_HISTORY +#define current_code(Sc) car(Sc->cur_code) +#define set_current_code(Sc, Code) do {Sc->cur_code = cdr(Sc->cur_code); set_car(Sc->cur_code, T_Ext(Code));} while (0) +#define replace_current_code(Sc, Code) set_car(Sc->cur_code, T_Ext(Code)) +#define mark_current_code(Sc) do {int32_t _i_; s7_pointer _p_; for (_p_ = Sc->cur_code, _i_ = 0; _i_ < Sc->history_size; _i_++, _p_ = cdr(_p_)) gc_mark(car(_p_));} while (0) +#else +#define current_code(Sc) Sc->cur_code +#define set_current_code(Sc, Code) Sc->cur_code = T_Ext(Code) +#define replace_current_code(Sc, Code) Sc->cur_code = T_Ext(Code) +#define mark_current_code(Sc) gc_mark(Sc->cur_code) +#endif + +#define full_type(p) ((p)->tf.flag) +#define typesflag(p) ((p)->tf.sflag) +#define TYPE_MASK 0xff + +#if S7_DEBUGGING + static void print_gc_info(s7_scheme *sc, s7_pointer obj, int32_t line); + static s7_pointer check_ref(s7_pointer p, uint8_t expected_type, const char *func, int32_t line, const char *func1, const char *func2); + static s7_pointer check_ref7(s7_pointer p, const char *func, int32_t line); + static s7_pointer check_ref11(s7_pointer p, const char *func, int32_t line); + static s7_pointer check_ref16(s7_pointer p, const char *func, int32_t line); + static s7_pointer check_ref19(s7_pointer p, const char *func, int32_t line); + static s7_pointer check_nref(s7_pointer p, const char *func, int32_t line); + static s7_pointer check_opcode(s7_pointer p, const char *func, int32_t line); + static s7_pointer check_let_ref(s7_pointer p, uint64_t role, const char *func, int32_t line); + + #define unchecked_type(p) ((p)->tf.type_field) +#if WITH_GCC + #define type(p) ({uint8_t _t_; _t_ = (p)->tf.type_field; if ((!cur_sc->printing_gc_info) && ((_t_ == T_FREE) || (_t_ >= NUM_TYPES))) print_gc_info(cur_sc, p, __LINE__); _t_;}) +#else + #define type(p) (p)->tf.type_field +#endif + #define set_full_type(p, f) set_type_1(p, f, __func__, __LINE__) + /* these check most s7_cell field references (and many type bits) for consistency */ + #define T_App(P) check_ref11(P, __func__, __LINE__) /* applicable or #f */ + #define T_Arg(P) check_ref10(P, __func__, __LINE__) /* closure arg (list, symbol) */ + #define T_BVc(P) check_ref(P, T_BYTE_VECTOR, __func__, __LINE__, "sweep", NULL) + #define T_Bgf(P) check_ref(P, T_BIG_RATIO, __func__, __LINE__, "sweep", NULL) + #define T_Bgi(P) check_ref(P, T_BIG_INTEGER, __func__, __LINE__, "sweep", NULL) + #define T_Bgr(P) check_ref(P, T_BIG_REAL, __func__, __LINE__, "sweep", NULL) + #define T_Bgz(P) check_ref(P, T_BIG_COMPLEX, __func__, __LINE__, "sweep", NULL) + #define T_CMac(P) check_ref(P, T_C_MACRO, __func__, __LINE__, NULL, NULL) + #define T_Cat(P) check_ref(P, T_CATCH, __func__, __LINE__, NULL, NULL) + #define T_Chr(P) check_ref(P, T_CHARACTER, __func__, __LINE__, NULL, NULL) + #define T_Clo(P) check_ref5(P, __func__, __LINE__) /* has closure let */ + #define T_Cmp(P) check_ref(P, T_COMPLEX, __func__, __LINE__, NULL, NULL) + #define T_Con(P) check_ref(P, T_CONTINUATION, __func__, __LINE__, "sweep", "process_continuation") + #define T_Ctr(P) check_ref(P, T_COUNTER, __func__, __LINE__, NULL, NULL) + #define T_Dyn(P) check_ref(P, T_DYNAMIC_WIND, __func__, __LINE__, NULL, NULL) + #define T_Eof(P) check_ref(P, T_EOF, __func__, __LINE__, "sweep", NULL) + #define T_Ext(P) check_ref19(P, __func__, __LINE__) /* not an internal type */ + #define T_Fnc(P) check_ref6(P, __func__, __LINE__) /* any c_function|c_macro */ + #define T_Frc(P) check_ref2(P, T_RATIO, T_INTEGER, __func__, __LINE__, NULL, NULL) + #define T_Fst(P) check_ref(P, T_C_FUNCTION_STAR, __func__, __LINE__, NULL, NULL) + #define T_Fvc(P) check_ref(P, T_FLOAT_VECTOR, __func__, __LINE__, "sweep", NULL) + #define T_Got(P) check_ref(P, T_GOTO, __func__, __LINE__, NULL, NULL) + #define T_Hsh(P) check_ref(P, T_HASH_TABLE, __func__, __LINE__, "sweep", "free_hash_table") + #define T_Int(P) check_ref(P, T_INTEGER, __func__, __LINE__, NULL, NULL) + #define T_Itr(P) check_ref(P, T_ITERATOR, __func__, __LINE__, "sweep", "process_iterator") + #define T_Ivc(P) check_ref(P, T_INT_VECTOR, __func__, __LINE__, "sweep", NULL) + #define T_Key(P) check_ref18(P, __func__, __LINE__) /* keyword */ + #define T_Let(P) check_ref(P, T_LET, __func__, __LINE__, NULL, NULL) + #define T_Lid(P) check_ref16(P, __func__, __LINE__) /* let/nil */ + #define T_Lst(P) check_ref2(P, T_PAIR, T_NIL, __func__, __LINE__, "gc", NULL) + #define T_Mac(P) check_ref17(P, __func__, __LINE__) /* a non-C macro */ + #define T_Met(P) check_ref9(P, __func__, __LINE__) /* anything that might contain a method */ + #define T_Nmv(P) check_ref15(P, __func__, __LINE__) /* not multiple-value, not free */ + #define T_Num(P) check_ref7(P, __func__, __LINE__) /* any number (not bignums) */ + #define T_Nvc(P) check_ref(P, T_VECTOR, __func__, __LINE__, "sweep", NULL) + #define T_Obj(P) check_ref(P, T_C_OBJECT, __func__, __LINE__, "sweep", "s7_c_object_value") + #define T_Op(P) check_opcode(P, __func__, __LINE__) + #define T_Pair(P) check_ref(P, T_PAIR, __func__, __LINE__, NULL, NULL) + #define T_Pcs(P) check_ref2(P, T_PAIR, T_CLOSURE_STAR, __func__, __LINE__, NULL, NULL) + #define T_Pos(P) check_nref(P, __func__, __LINE__) /* not free */ + #define T_Prc(P) check_ref14(P, __func__, __LINE__) /* any procedure (3-arg setters) or #f|#t */ + #define T_Pri(P) check_ref3i(P, __func__, __LINE__) /* input_port or #f */ + #define T_Pro(P) check_ref3o(P, __func__, __LINE__) /* output_port or #f */ + #define T_Prt(P) check_ref3(P, __func__, __LINE__) /* input|output_port */ + #define T_Ptr(P) check_ref(P, T_C_POINTER, __func__, __LINE__, NULL, NULL) + #define T_Ran(P) check_ref(P, T_RANDOM_STATE, __func__, __LINE__, NULL, NULL) + #define T_Rel(P) check_ref(P, T_REAL, __func__, __LINE__, NULL, NULL) + #define T_Seq(P) check_ref8(P, __func__, __LINE__) /* any sequence or structure */ + #define T_Sld(P) check_ref2(P, T_SLOT, T_UNDEFINED,__func__, __LINE__, NULL, NULL) + #define T_Sln(P) check_ref12(P, __func__, __LINE__) /* slot or nil */ + #define T_Slt(P) check_ref(P, T_SLOT, __func__, __LINE__, NULL, NULL) + #define T_Stk(P) check_ref(P, T_STACK, __func__, __LINE__, NULL, NULL) + #define T_Str(P) check_ref(P, T_STRING, __func__, __LINE__, "sweep", NULL) + #define T_SVec(P) check_ref13(P, __func__, __LINE__) /* subvector */ + #define T_Sym(P) check_ref(P, T_SYMBOL, __func__, __LINE__, "sweep", "remove_gensym_from_symbol_table") + #define T_Syn(P) check_ref(P, T_SYNTAX, __func__, __LINE__, NULL, NULL) + #define T_Undf(P) check_ref(P, T_UNDEFINED, __func__, __LINE__, "sweep", NULL) + #define T_Vec(P) check_ref4(P, __func__, __LINE__) /* any vector */ +#else + /* if not debugging, all those checks go away */ + #define T_App(P) P + #define T_Arg(P) P + #define T_BVc(P) P + #define T_Bgf(P) P + #define T_Bgi(P) P + #define T_Bgr(P) P + #define T_Bgz(P) P + #define T_CMac(P) P + #define T_Cat(P) P + #define T_Chr(P) P + #define T_Clo(P) P + #define T_Cmp(P) P + #define T_Con(P) P + #define T_Ctr(P) P + #define T_Dyn(P) P + #define T_Eof(P) P + #define T_Ext(P) P + #define T_Fnc(P) P + #define T_Frc(P) P + #define T_Fst(P) P + #define T_Fvc(P) P + #define T_Got(P) P + #define T_Hsh(P) P + #define T_Int(P) P + #define T_Itr(P) P + #define T_Ivc(P) P + #define T_Key(P) P + #define T_Let(P) P + #define T_Lid(P) P + #define T_Lst(P) P + #define T_Mac(P) P + #define T_Met(P) P + #define T_Nmv(P) P + #define T_Num(P) P + #define T_Nvc(P) P + #define T_Obj(P) P + #define T_Op(P) P + #define T_Pair(P) P + #define T_Pcs(P) P + #define T_Pos(P) P + #define T_Prc(P) P + #define T_Pri(P) P + #define T_Pro(P) P + #define T_Prt(P) P + #define T_Ptr(P) P + #define T_Ran(P) P + #define T_Rel(P) P + #define T_Seq(P) P + #define T_Sld(P) P + #define T_Sln(P) P + #define T_Slt(P) P + #define T_Stk(P) P + #define T_Str(P) P + #define T_SVec(P) P + #define T_Sym(P) P + #define T_Syn(P) P + #define T_Undf(P) P + #define T_Vec(P) P + + #define unchecked_type(p) ((p)->tf.type_field) + #define type(p) ((p)->tf.type_field) + #define set_full_type(p, f) full_type(p) = f +#endif +#define signed_type(p) (p)->tf.signed_flag + +#define is_number(P) t_number_p[type(P)] +#define is_small_real(P) t_small_real_p[type(P)] +#define is_real(P) t_real_p[type(P)] +#define is_rational(P) t_rational_p[type(P)] +#define is_big_number(p) t_big_number_p[type(p)] +#define is_t_integer(p) (type(p) == T_INTEGER) +#define is_t_ratio(p) (type(p) == T_RATIO) +#define is_t_real(p) (type(p) == T_REAL) +#define is_t_complex(p) (type(p) == T_COMPLEX) +#define is_t_big_integer(p) (type(p) == T_BIG_INTEGER) +#define is_t_big_ratio(p) (type(p) == T_BIG_RATIO) +#define is_t_big_real(p) (type(p) == T_BIG_REAL) +#define is_t_big_complex(p) (type(p) == T_BIG_COMPLEX) + +#define is_boolean(p) (type(p) == T_BOOLEAN) + +#define is_free(p) (type(p) == T_FREE) +#define is_free_and_clear(p) (full_type(p) == T_FREE) +#define is_simple(P) t_simple_p[type(P)] /* eq? */ +#define has_structure(P) ((t_structure_p[type(P)]) && ((!is_normal_vector(P)) || (!has_simple_elements(P)))) + +#define is_any_macro(P) t_any_macro_p[type(P)] +#define is_any_closure(P) t_any_closure_p[type(P)] +#define is_any_procedure(P) (type(P) >= T_CLOSURE) +#define has_closure_let(P) t_has_closure_let[type(P)] + +#define is_simple_sequence(P) (t_sequence_p[type(P)]) +#define is_sequence(P) ((t_sequence_p[type(P)]) || (has_methods(P))) +#define is_mutable_sequence(P) (((t_sequence_p[type(P)]) || (has_methods(P))) && (!is_immutable(P))) +#define is_mappable(P) (t_mappable_p[type(P)]) +#define is_applicable(P) (t_applicable_p[type(P)]) +/* this misses #() which is not applicable to anything, and "", and inapplicable c-objects like random-state */ +#define is_procedure(p) ((t_procedure_p[type(p)]) || ((is_c_object(p)) && (is_safe_procedure(p)))) +#define is_t_procedure(p) (t_procedure_p[type(p)]) + +/* the layout of these bits does matter in several cases -- don't shadow SYNTACTIC_PAIR and OPTIMIZED_PAIR */ +#define TYPE_BITS 8 + +#define set_type_bit(p, b) full_type(p) |= (b) +#define clear_type_bit(p, b) full_type(p) &= (~(b)) +#define has_type_bit(p, b) ((full_type(p) & (b)) != 0) + +#define set_type0_bit(p, b) typesflag(p) |= (b) /* I don't think these type0's matter -- *_type_bit is the same speed */ +#define clear_type0_bit(p, b) typesflag(p) &= (~(b)) +#define has_type0_bit(p, b) ((typesflag(p) & (b)) != 0) + +#define set_type1_bit(p, b) (p)->tf.opts.high_flag |= (b) +#define clear_type1_bit(p, b) (p)->tf.opts.high_flag &= (~(b)) +#define has_type1_bit(p, b) (((p)->tf.opts.high_flag & (b)) != 0) + +#define T_SYNTACTIC (1 << (TYPE_BITS + 1)) +#define is_symbol_and_syntactic(p) (typesflag(T_Ext(p)) == (uint16_t)(T_SYMBOL | T_SYNTACTIC)) +#define is_syntactic_symbol(p) has_type0_bit(T_Sym(p), T_SYNTACTIC) +#define is_syntactic_pair(p) has_type0_bit(T_Pair(p), T_SYNTACTIC) +#define clear_syntactic(p) clear_type0_bit(T_Pair(p), T_SYNTACTIC) +#define set_syntactic_pair(p) full_type(T_Pair(p)) = (T_PAIR | T_SYNTACTIC | (full_type(p) & (0xffffffffffff0000 & ~T_OPTIMIZED))) /* used only in pair_set_syntax_op */ +/* this marks symbols that represent syntax objects, it should be in the second byte */ + +#define T_SIMPLE_ARG_DEFAULTS (1 << (TYPE_BITS + 2)) +#define lambda_has_simple_defaults(p) has_type0_bit(T_Pair(closure_body(p)), T_SIMPLE_ARG_DEFAULTS) +#define lambda_set_simple_defaults(p) set_type0_bit(T_Pair(p), T_SIMPLE_ARG_DEFAULTS) +/* are all lambda* default values simple? This is set on closure_body, so it doesn't mess up closure_is_ok_1 */ + +#define T_LIST_IN_USE T_SIMPLE_ARG_DEFAULTS +#define list_is_in_use(p) has_type0_bit(T_Pair(p), T_LIST_IN_USE) +#define set_list_in_use(p) set_type0_bit(T_Pair(p), T_LIST_IN_USE) +#define clear_list_in_use(p) do {clear_type0_bit(T_Pair(p), T_LIST_IN_USE); sc->current_safe_list = 0;} while (0) +/* since the safe lists are not in the heap, if the list_in_use bit is off, the list won't be GC-protected even if + * it is gc_marked explicitly. This happens, for example, in copy_proper_list where we try to protect the original list + * by sc->temp5 = lst; then in the GC, gc_mark(sc->temp5); but the safe_list probably is already marked, so its contents are not protected. + */ + +#define T_ONE_FORM T_SIMPLE_ARG_DEFAULTS +#define set_closure_has_one_form(p) set_type0_bit(T_Clo(p), T_ONE_FORM) +#define T_MULTIFORM (1 << (TYPE_BITS + 0)) +#define set_closure_has_multiform(p) set_type0_bit(T_Clo(p), T_MULTIFORM) +#define T_ONE_FORM_FX_ARG (T_ONE_FORM | T_MULTIFORM) +#define set_closure_one_form_fx_arg(p) set_type0_bit(T_Clo(p), T_ONE_FORM_FX_ARG) +/* can't use T_HAS_FX here because closure_is_ok wants to examine typesflag */ + +#define T_OPTIMIZED (1 << (TYPE_BITS + 3)) +#define set_optimized(p) set_type0_bit(T_Pair(p), T_OPTIMIZED) +#define clear_optimized(p) clear_type0_bit(T_Pair(p), T_OPTIMIZED | T_SYNTACTIC | T_HAS_FX | T_HAS_FN) +#define is_optimized(p) (typesflag(T_Ext(p)) == (uint16_t)(T_PAIR | T_OPTIMIZED)) +/* optimizer flag for an expression that has optimization info, it should be in the second byte */ + +#define T_SCOPE_SAFE T_OPTIMIZED +#define is_scope_safe(p) has_type0_bit(T_Fnc(p), T_SCOPE_SAFE) +#define set_scope_safe(p) set_type0_bit(T_Fnc(p), T_SCOPE_SAFE) + +#define T_SAFE_CLOSURE (1 << (TYPE_BITS + 4)) +#define is_safe_closure(p) has_type0_bit(T_Clo(p), T_SAFE_CLOSURE) +#define set_safe_closure(p) set_type0_bit(T_Clo(p), T_SAFE_CLOSURE) +#define is_safe_closure_body(p) has_type0_bit(T_Pair(p), T_SAFE_CLOSURE) +#define set_safe_closure_body(p) set_type0_bit(T_Pair(p), T_SAFE_CLOSURE) +#define clear_safe_closure_body(p) clear_type0_bit(T_Pair(p), T_SAFE_CLOSURE) + +/* optimizer flag for a closure body that is completely simple (every expression is safe) + * set_safe_closure happens in define_funchcecked letrec_setup_closures etc, clear only in procedure_source, bits only here + * this has to be separate from T_SAFE_PROCEDURE, and should be in the second byte (closure_is_ok_1 checks typesflag). + * define -> optimize_lambda sets safe -> define_funchecked -> make_funclet for the let + * similarly, named let -> optimize_lambda, then let creates the let if safe + * thereafter, optimizer uses OP_SAFE_CLOSURE* which calls update_let* + */ + +#define T_DONT_EVAL_ARGS (1 << (TYPE_BITS + 5)) +#define dont_eval_args(p) has_type0_bit(T_Ext(p), T_DONT_EVAL_ARGS) +/* this marks things that don't evaluate their arguments */ + +#define T_EXPANSION (1 << (TYPE_BITS + 6)) +#define is_expansion(p) has_type0_bit(T_Pos(p), T_EXPANSION) +#define clear_expansion(p) clear_type0_bit(T_Sym(p), T_EXPANSION) +/* this marks the symbol and its run-time macro value, distinguishing it from an ordinary macro */ + +#define T_MULTIPLE_VALUE (1 << (TYPE_BITS + 7)) +#define is_multiple_value(p) has_type0_bit(T_Pos(p), T_MULTIPLE_VALUE) /* not T_Ext -- can be a slot */ +#if S7_DEBUGGING +#define set_multiple_value(p) do {if (!in_heap(p)) {fprintf(stderr, "%s[%d]: mv\n", __func__, __LINE__); abort();} set_type0_bit(T_Pair(p), T_MULTIPLE_VALUE);} while (0) +#else +#define set_multiple_value(p) set_type0_bit(T_Pair(p), T_MULTIPLE_VALUE) +#endif +#define clear_multiple_value(p) clear_type0_bit(T_Pair(p), T_MULTIPLE_VALUE) +#define multiple_value(p) p +/* this bit marks a list (from "values") that is waiting for a chance to be spliced into its caller's argument list */ + +#define T_MATCHED T_MULTIPLE_VALUE +#define is_matched_pair(p) has_type0_bit(T_Pair(p), T_MATCHED) +#define clear_match_pair(p) clear_type0_bit(T_Pair(p), T_MATCHED) +#define set_match_pair(p) set_type0_bit(T_Pair(p), T_MATCHED) +#define set_match_symbol(p) set_type0_bit(T_Sym(p), T_MATCHED) +#define is_matched_symbol(p) has_type0_bit(T_Sym(p), T_MATCHED) +#define clear_match_symbol(p) clear_type0_bit(T_Sym(p), T_MATCHED) + +#define T_GLOBAL (1 << (TYPE_BITS + 8)) +#define T_LOCAL (1 << (TYPE_BITS + 12)) +#define is_global(p) has_type_bit(T_Sym(p), T_GLOBAL) +#define set_global(p) do {if ((full_type(T_Sym(p)) & T_LOCAL) == 0) full_type(p) |= T_GLOBAL;} while (0) +/* T_LOCAL marks a symbol that has been used locally */ +/* T_GLOBAL marks something defined (bound) at the top-level, and never defined locally */ + +#define REPORT_ROOTLET_REDEF 0 +#if REPORT_ROOTLET_REDEF + /* to find who is stomping on our symbols: */ + static void set_local_1(s7_scheme *sc, s7_pointer symbol, const char *func, int32_t line); + #define set_local(Symbol) set_local_1(sc, Symbol, __func__, __LINE__) +#else + #define set_local(p) full_type(T_Sym(p)) = ((full_type(p) | T_LOCAL) & ~(T_DONT_EVAL_ARGS | T_GLOBAL | T_SYNTACTIC)) +#endif + +#define T_LOW_COUNT T_LOCAL +#define has_low_count(p) has_type_bit(T_Pair(p), T_LOW_COUNT) +#define set_has_low_count(p) set_type_bit(T_Pair(p), T_LOW_COUNT) + +#define T_TC T_LOCAL +#define has_tc(p) has_type_bit(T_Pair(p), T_TC) +#define set_has_tc(p) set_type_bit(T_Pair(p), T_TC) + +#define T_UNSAFE_DO T_GLOBAL +#define is_unsafe_do(p) has_type_bit(T_Pair(p), T_UNSAFE_DO) +#define set_unsafe_do(p) set_type_bit(T_Pair(p), T_UNSAFE_DO) +/* marks do-loops that resist optimization */ + +#define T_DOX_SLOT1 T_GLOBAL +#define has_dox_slot1(p) has_type_bit(T_Let(p), T_DOX_SLOT1) +#define set_has_dox_slot1(p) set_type_bit(T_Let(p), T_DOX_SLOT1) +/* marks a let that includes the dox_slot1 */ + +#define T_COLLECTED (1 << (TYPE_BITS + 9)) +#define is_collected(p) has_type_bit(T_Seq(p), T_COLLECTED) +#define is_collected_unchecked(p) has_type_bit(p, T_COLLECTED) +#define set_collected(p) set_type_bit(T_Seq(p), T_COLLECTED) +/* #define clear_collected(p) clear_type_bit(T_Seq(p), T_COLLECTED) */ +/* this is a transient flag used by the printer to catch cycles. It affects only objects that have structure. + * We can't use a low bit (bit 7 for example), because collect_shared_info inspects the object's type. + */ + +#define T_LOCATION (1 << (TYPE_BITS + 10)) +#define has_location(p) has_type_bit(T_Pair(p), T_LOCATION) +#define set_has_location(p) set_type_bit(T_Pair(p), T_LOCATION) +/* pair in question has line/file/position info added during read, or the environment has function placement info + * this bit should not be in the first byte -- SYNTACTIC_PAIR ignores it. + */ + +#define T_LOADER_PORT T_LOCATION +#define is_loader_port(p) has_type_bit(T_Pri(p), T_LOADER_PORT) +#define set_loader_port(p) set_type_bit(T_Pri(p), T_LOADER_PORT) +#define clear_loader_port(p) clear_type_bit(T_Pri(p), T_LOADER_PORT) +/* this bit marks a port used by the loader so that random load-time reads do not screw up the load process */ + +#define T_HAS_SETTER T_LOCATION +#define slot_has_setter(p) has_type_bit(T_Slt(p), T_HAS_SETTER) +#define slot_set_has_setter(p) set_type_bit(T_Slt(p), T_HAS_SETTER) +/* marks a slot that has a setter or symbol that might have a setter */ + +#define T_WITH_LET_LET T_LOCATION +#define is_with_let_let(p) has_type_bit(T_Let(p), T_WITH_LET_LET) +#define set_with_let_let(p) set_type_bit(T_Let(p), T_WITH_LET_LET) +/* marks a let that is the argument to with-let */ + +#define T_SIMPLE_DEFAULTS T_LOCATION +#define c_func_has_simple_defaults(p) has_type_bit(T_Fst(p), T_SIMPLE_DEFAULTS) +#define c_func_set_simple_defaults(p) set_type_bit(T_Fst(p), T_SIMPLE_DEFAULTS) +#define c_func_clear_simple_defaults(p) clear_type_bit(T_Fst(p), T_SIMPLE_DEFAULTS) +/* flag c_func_star arg defaults that need GC protection */ + +#define T_NO_SETTER T_LOCATION +#define closure_no_setter(p) has_type_bit(T_Clo(p), T_NO_SETTER) +#define closure_set_no_setter(p) set_type_bit(T_Clo(p), T_NO_SETTER) + +#define T_SHARED (1 << (TYPE_BITS + 11)) +#define is_shared(p) has_type_bit(T_Seq(p), T_SHARED) +#define set_shared(p) set_type_bit(T_Seq(p), T_SHARED) +#define is_collected_or_shared(p) has_type_bit(p, T_COLLECTED | T_SHARED) +#define clear_collected_and_shared(p) clear_type_bit(p, T_COLLECTED | T_SHARED) /* this can clear free cells = calloc */ +/* T_LOCAL is bit 12 */ + +#define T_SAFE_PROCEDURE (1 << (TYPE_BITS + 13)) +#define is_safe_procedure(p) has_type_bit(T_App(p), T_SAFE_PROCEDURE) +#define is_safe_or_scope_safe_procedure(p) ((full_type(T_Fnc(p)) & (T_SCOPE_SAFE | T_SAFE_PROCEDURE)) != 0) +/* applicable objects that do not return or modify their arg list directly (no :rest arg in particular), + * and that can't call themselves either directly or via s7_call, and that don't mess with the stack. + */ + +#define T_CHECKED (1 << (TYPE_BITS + 14)) +#define set_checked(p) set_type_bit(T_Pair(p), T_CHECKED) +#define is_checked(p) has_type_bit(T_Pair(p), T_CHECKED) +#define clear_checked(p) clear_type_bit(T_Pair(p), T_CHECKED) +#define set_checked_slot(p) set_type_bit(T_Slt(p), T_CHECKED) +#define is_checked_slot(p) has_type_bit(T_Slt(p), T_CHECKED) +#define clear_checked_slot(p) clear_type_bit(T_Slt(p), T_CHECKED) + +#define T_ALL_INTEGER T_CHECKED +#define is_all_integer(p) has_type_bit(T_Sym(p), T_ALL_INTEGER) +#define set_all_integer(p) set_type_bit(T_Sym(p), T_ALL_INTEGER) + +#define T_UNSAFE (1 << (TYPE_BITS + 15)) +#define set_unsafe(p) set_type_bit(T_Pair(p), T_UNSAFE) +#define set_unsafely_optimized(p) full_type(T_Pair(p)) = (full_type(p) | T_UNSAFE | T_OPTIMIZED) +#define is_unsafe(p) has_type_bit(T_Pair(p), T_UNSAFE) +#define clear_unsafe(p) clear_type_bit(T_Pair(p), T_UNSAFE) +#define is_safely_optimized(p) ((full_type(T_Pair(p)) & (T_OPTIMIZED | T_UNSAFE)) == T_OPTIMIZED) +/* optimizer flag saying "this expression is not completely self-contained. It might involve the stack, etc" */ + +#define T_CLEAN_SYMBOL T_UNSAFE +#define is_clean_symbol(p) has_type_bit(T_Sym(p), T_CLEAN_SYMBOL) +#define set_clean_symbol(p) set_type_bit(T_Sym(p), T_CLEAN_SYMBOL) +/* set if we know the symbol name can be printed without quotes (slashification) */ + +#define T_HAS_STEPPER T_UNSAFE +#define has_stepper(p) has_type_bit(T_Slt(p), T_HAS_STEPPER) +#define set_has_stepper(p) set_type_bit(T_Slt(p), T_HAS_STEPPER) + +#define T_DOX_SLOT2 T_UNSAFE +#define has_dox_slot2(p) has_type_bit(T_Let(p), T_DOX_SLOT2) +#define set_has_dox_slot2(p) set_type_bit(T_Let(p), T_DOX_SLOT2) +/* marks a let that includes the dox_slot2 */ + +#define T_IMMUTABLE (1 << (TYPE_BITS + 16)) +#define is_immutable(p) has_type_bit(T_Pos(p), T_IMMUTABLE) +#define set_immutable(p) set_type_bit(T_Pos(p), T_IMMUTABLE) /* can be a slot, so not T_Ext */ +#define set_immutable_let(p) set_type_bit(T_Lid(p), T_IMMUTABLE) +#define set_immutable_slot(p) set_type_bit(T_Slt(p), T_IMMUTABLE) +#define is_immutable_port(p) has_type_bit(T_Prt(p), T_IMMUTABLE) +#define is_immutable_symbol(p) has_type_bit(T_Sym(p), T_IMMUTABLE) +#define is_immutable_slot(p) has_type_bit(T_Slt(p), T_IMMUTABLE) +#define is_immutable_pair(p) has_type_bit(T_Pair(p), T_IMMUTABLE) +#define is_immutable_vector(p) has_type_bit(T_Vec(p), T_IMMUTABLE) +#define is_immutable_string(p) has_type_bit(T_Str(p), T_IMMUTABLE) +#define is_immutable_hash_table(p) has_type_bit(T_Hsh(p), T_IMMUTABLE) +#define is_immutable_let(p) has_type_bit(T_Let(p), T_IMMUTABLE) +/* T_IMMUTABLE is compatible with T_MUTABLE -- the latter is an internal bit for locally mutable numbers */ + +#define T_SETTER (1 << (TYPE_BITS + 17)) +#define set_is_setter(p) set_type_bit(T_Sym(p), T_SETTER) +#define is_setter(p) has_type_bit(T_Sym(p), T_SETTER) +/* optimizer flag for a procedure that sets some variable (set-car! for example) */ + +#define T_ALLOW_OTHER_KEYS T_SETTER +#define set_allow_other_keys(p) set_type_bit(T_Pair(p), T_ALLOW_OTHER_KEYS) +#define allows_other_keys(p) has_type_bit(T_Pair(p), T_ALLOW_OTHER_KEYS) +#define c_function_set_allow_other_keys(p) set_type_bit(T_Fst(p), T_ALLOW_OTHER_KEYS) +#define c_function_allows_other_keys(p) has_type_bit(T_Fst(p), T_ALLOW_OTHER_KEYS) +/* marks arglist (or c_function*) that allows keyword args other than those in the parameter list; + * we can't allow (define* (f :allow-other-keys)...) because there's only one nil, and besides, it does say "other". + */ + +#define T_LET_REMOVED T_SETTER +#define let_set_removed(p) set_type_bit(T_Let(p), T_LET_REMOVED) +#define let_removed(p) has_type_bit(T_Let(p), T_LET_REMOVED) +/* mark lets that have been removed from the heap or checked for that possibility */ + +#define T_HAS_EXPRESSION T_SETTER +#define slot_set_has_expression(p) set_type_bit(T_Slt(p), T_HAS_EXPRESSION) +#define slot_has_expression(p) has_type_bit(T_Slt(p), T_HAS_EXPRESSION) + +#define T_MUTABLE (1 << (TYPE_BITS + 18)) +#define is_mutable_number(p) has_type_bit(T_Num(p), T_MUTABLE) +#define is_mutable_integer(p) has_type_bit(T_Int(p), T_MUTABLE) +#define clear_mutable_number(p) clear_type_bit(T_Num(p), T_MUTABLE) +#define clear_mutable_integer(p) clear_type_bit(T_Int(p), T_MUTABLE) +/* used for mutable numbers, can occur with T_IMMUTABLE (outside view vs inside) */ + +#define T_HAS_KEYWORD T_MUTABLE +#define has_keyword(p) has_type_bit(T_Sym(p), T_HAS_KEYWORD) +#define set_has_keyword(p) set_type_bit(T_Sym(p), T_HAS_KEYWORD) + +#define T_MARK_SEQ T_MUTABLE +#define is_mark_seq(p) has_type_bit(T_Itr(p), T_MARK_SEQ) +#define set_mark_seq(p) set_type_bit(T_Itr(p), T_MARK_SEQ) +/* used in iterators for GC mark of sequence */ + +#define T_HAS_LOOP_END T_MUTABLE +#define has_loop_end(p) has_type_bit(T_Slt(p), T_HAS_LOOP_END) +#define loop_end_fits(Slot, Len) ((has_loop_end(Slot)) && (denominator(slot_value(Slot)) <= Len)) +#define set_has_loop_end(p) set_type_bit(T_Slt(p), T_HAS_LOOP_END) +/* marks a slot that holds a do-loop's step-or-end variable, numerator=current, denominator=end */ + +#define T_NO_CELL_OPT T_MUTABLE +#define set_no_cell_opt(p) set_type_bit(T_Pair(p), T_NO_CELL_OPT) +#define no_cell_opt(p) has_type_bit(T_Pair(p), T_NO_CELL_OPT) + +#define T_IS_ELIST T_MUTABLE +#define set_is_elist(p) set_type_bit(T_Lst(p), T_IS_ELIST) +#define is_elist(p) has_type_bit(T_Lst(p), T_IS_ELIST) + +#define T_NO_INT_OPT T_SETTER +#define set_no_int_opt(p) set_type_bit(T_Pair(p), T_NO_INT_OPT) +#define no_int_opt(p) has_type_bit(T_Pair(p), T_NO_INT_OPT) + +#define T_NO_FLOAT_OPT T_UNSAFE +#define set_no_float_opt(p) set_type_bit(T_Pair(p), T_NO_FLOAT_OPT) +#define no_float_opt(p) has_type_bit(T_Pair(p), T_NO_FLOAT_OPT) + +#define T_NO_BOOL_OPT T_SAFE_STEPPER +#define set_no_bool_opt(p) set_type_bit(T_Pair(p), T_NO_BOOL_OPT) +#define no_bool_opt(p) has_type_bit(T_Pair(p), T_NO_BOOL_OPT) + +#define T_INTEGER_KEYS T_SETTER +#define set_has_integer_keys(p) set_type_bit(T_Pair(p), T_INTEGER_KEYS) +#define has_integer_keys(p) has_type_bit(T_Pair(p), T_INTEGER_KEYS) + +#define T_SAFE_STEPPER (1 << (TYPE_BITS + 19)) +#define is_safe_stepper(p) has_type_bit(T_Slt(p), T_SAFE_STEPPER) +#define set_safe_stepper(p) set_type_bit(T_Slt(p), T_SAFE_STEPPER) +#define clear_safe_stepper(p) clear_type_bit(T_Slt(p), T_SAFE_STEPPER) +#define is_safe_stepper_expr(p) has_type_bit(T_Pair(p), T_SAFE_STEPPER) +#define set_safe_stepper_expr(p) set_type_bit(T_Pair(p), T_SAFE_STEPPER) + +#define T_NUMBER_NAME T_SAFE_STEPPER +#define has_number_name(p) has_type_bit(T_Num(p), T_NUMBER_NAME) +#define set_has_number_name(p) set_type_bit(T_Num(p), T_NUMBER_NAME) +/* marks numbers that have a saved version of their string representation; this only matters in teq.scm, maybe tread.scm */ + +#define T_MAYBE_SAFE T_SAFE_STEPPER +#define is_maybe_safe(p) has_type_bit(T_Fnc(p), T_MAYBE_SAFE) +#define set_maybe_safe(p) set_type_bit(T_Fnc(p), T_MAYBE_SAFE) + +#define T_PAIR_MACRO T_SAFE_STEPPER +#define has_pair_macro(p) has_type_bit(T_Mac(p), T_PAIR_MACRO) +#define set_has_pair_macro(p) set_type_bit(T_Mac(p), T_PAIR_MACRO) + +#define T_HAS_LET_SET_FALLBACK T_SAFE_STEPPER +#define T_HAS_LET_REF_FALLBACK T_MUTABLE +#define has_let_ref_fallback(p) ((full_type(T_Lid(p)) & (T_HAS_LET_REF_FALLBACK | T_HAS_METHODS)) == (T_HAS_LET_REF_FALLBACK | T_HAS_METHODS)) +#define has_let_set_fallback(p) ((full_type(T_Lid(p)) & (T_HAS_LET_SET_FALLBACK | T_HAS_METHODS)) == (T_HAS_LET_SET_FALLBACK | T_HAS_METHODS)) +#define set_has_let_ref_fallback(p) set_type_bit(T_Let(p), T_HAS_LET_REF_FALLBACK) +#define set_has_let_set_fallback(p) set_type_bit(T_Let(p), T_HAS_LET_SET_FALLBACK) +#define has_let_fallback(p) has_type_bit(T_Lid(p), (T_HAS_LET_REF_FALLBACK | T_HAS_LET_SET_FALLBACK)) +#define set_all_methods(p, e) full_type(T_Let(p)) |= (full_type(e) & (T_HAS_METHODS | T_HAS_LET_REF_FALLBACK | T_HAS_LET_SET_FALLBACK)) + +#define T_WEAK_HASH T_SAFE_STEPPER +#define set_weak_hash_table(p) set_type_bit(T_Hsh(p), T_WEAK_HASH) +#define is_weak_hash_table(p) has_type_bit(T_Hsh(p), T_WEAK_HASH) + +#define T_ALL_FLOAT T_SAFE_STEPPER +#define is_all_float(p) has_type_bit(T_Sym(p), T_ALL_FLOAT) +#define set_all_float(p) set_type_bit(T_Sym(p), T_ALL_FLOAT) +#define set_all_integer_and_float(p) set_type_bit(T_Sym(p), (T_ALL_INTEGER | T_ALL_FLOAT)) + +#define T_COPY_ARGS (1 << (TYPE_BITS + 20)) +#define needs_copied_args(p) has_type_bit(T_Ext(p), T_COPY_ARGS) /* set via explicit T_COPY_ARGS, on T_Pos see s7_apply_function */ +#define set_needs_copied_args(p) set_type_bit(T_Pair(p), T_COPY_ARGS) +#define clear_needs_copied_args(p) clear_type_bit(T_Pair(p), T_COPY_ARGS) +/* this marks something that might mess with its argument list, it should not be in the second byte */ + +#define T_GENSYM (1 << (TYPE_BITS + 21)) +#define is_gensym(p) has_type_bit(T_Sym(p), T_GENSYM) +/* symbol is from gensym (GC-able etc) */ + +#define T_FUNCLET T_GENSYM +#define is_funclet(p) has_type_bit(T_Let(p), T_FUNCLET) +#define set_funclet(p) set_type_bit(T_Let(p), T_FUNCLET) +/* this marks a funclet */ + +#define T_HASH_CHOSEN T_GENSYM +#define hash_chosen(p) has_type_bit(T_Hsh(p), T_HASH_CHOSEN) +#define hash_set_chosen(p) set_type_bit(T_Hsh(p), T_HASH_CHOSEN) +#define hash_clear_chosen(p) clear_type_bit(T_Hsh(p), T_HASH_CHOSEN) + +#define T_DOCUMENTED T_GENSYM +#define is_documented(p) has_type_bit(T_Str(p), T_DOCUMENTED) +#define set_documented(p) set_type_bit(T_Str(p), T_DOCUMENTED) +/* this marks a symbol that has documentation (bit is set on name cell) */ + +#define T_FX_TREED T_GENSYM +#define is_fx_treed(p) has_type_bit(T_Pair(p), T_FX_TREED) +#define set_fx_treed(p) set_type_bit(T_Pair(p), T_FX_TREED) + +#define T_SUBVECTOR T_GENSYM +#define is_subvector(p) has_type_bit(T_Vec(p), T_SUBVECTOR) + +#define T_HAS_PENDING_VALUE T_GENSYM +#define slot_set_has_pending_value(p) set_type_bit(T_Slt(p), T_HAS_PENDING_VALUE) +#define slot_has_pending_value(p) has_type_bit(T_Slt(p), T_HAS_PENDING_VALUE) +#define slot_clear_has_pending_value(p) clear_type_bit(T_Slt(p), T_HAS_PENDING_VALUE) +#define slot_has_setter_or_pending_value(p) has_type_bit(p, T_HAS_SETTER | T_HAS_PENDING_VALUE) + +#define T_HAS_METHODS (1 << (TYPE_BITS + 22)) +#define has_methods(p) has_type_bit(T_Pos(p), T_HAS_METHODS) /* display slot hits T_Ext here */ +#define is_openlet(p) has_type_bit(T_Let(p), T_HAS_METHODS) +#define has_active_methods(sc, p) ((has_type_bit(T_Ext(p), T_HAS_METHODS)) && (sc->has_openlets)) /* g_char # */ +#define set_has_methods(p) set_type_bit(T_Met(p), T_HAS_METHODS) +#define clear_has_methods(p) clear_type_bit(T_Met(p), T_HAS_METHODS) +/* this marks an environment or closure that is "open" for generic functions etc, don't reuse this bit */ + +#define T_ITER_OK (1LL << (TYPE_BITS + 23)) +#define iter_ok(p) has_type_bit(T_Itr(p), T_ITER_OK) +#define clear_iter_ok(p) clear_type_bit(T_Itr(p), T_ITER_OK) + +#define T_LOOP_END_POSSIBLE T_ITER_OK +#define loop_end_possible(p) has_type_bit(T_Pair(p), T_LOOP_END_POSSIBLE) +#define set_loop_end_possible(p) set_type_bit(T_Pair(p), T_LOOP_END_POSSIBLE) + +#define T_IN_ROOTLET T_ITER_OK +#define in_rootlet(p) has_type_bit(T_Slt(p), T_IN_ROOTLET) +#define set_in_rootlet(p) set_type_bit(T_Slt(p), T_IN_ROOTLET) + +#define T_BOOL_FUNCTION T_ITER_OK +#define is_bool_function(p) has_type_bit(T_Prc(p), T_BOOL_FUNCTION) +#define set_is_bool_function(p) set_type_bit(T_Fnc(p), T_BOOL_FUNCTION) + +#define T_SYMBOL_FROM_SYMBOL T_ITER_OK +#define is_symbol_from_symbol(p) has_type_bit(T_Sym(p), T_SYMBOL_FROM_SYMBOL) +#define set_is_symbol_from_symbol(p) set_type_bit(T_Sym(p), T_SYMBOL_FROM_SYMBOL) +#define clear_symbol_from_symbol(p) clear_type1_bit(T_Sym(p), T_SYMBOL_FROM_SYMBOL) + +/* it's faster here to use the high_flag bits rather than typeflag bits */ +#define BIT_ROOM 16 +#define T_FULL_SYMCONS (1LL << (TYPE_BITS + BIT_ROOM + 24)) +#define T_SYMCONS (1 << 0) +#define is_possibly_constant(p) has_type1_bit(T_Sym(p), T_SYMCONS) +#define set_possibly_constant(p) set_type1_bit(T_Sym(p), T_SYMCONS) +#define is_probably_constant(p) has_type_bit(T_Sym(p), (T_FULL_SYMCONS | T_IMMUTABLE)) + +#define T_HAS_LET_ARG T_SYMCONS +#define has_let_arg(p) has_type1_bit(T_Prc(p), T_HAS_LET_ARG) +#define set_has_let_arg(p) set_type1_bit(T_Prc(p), T_HAS_LET_ARG) +/* p is a setter procedure, "let arg" refers to the setter's optional third (let) argument */ + +#define T_HASH_VALUE_TYPE T_SYMCONS +#define has_hash_value_type(p) has_type1_bit(T_Hsh(p), T_HASH_VALUE_TYPE) +#define set_has_hash_value_type(p) set_type1_bit(T_Hsh(p), T_HASH_VALUE_TYPE) + +#define T_INT_OPTABLE T_SYMCONS +#define is_int_optable(p) has_type1_bit(T_Pair(p), T_INT_OPTABLE) +#define set_is_int_optable(p) set_type1_bit(T_Pair(p), T_INT_OPTABLE) + +/* symbol free here */ +#define T_FULL_HAS_LET_FILE (1LL << (TYPE_BITS + BIT_ROOM + 25)) +#define T_HAS_LET_FILE (1 << 1) +#define has_let_file(p) has_type1_bit(T_Let(p), T_HAS_LET_FILE) +#define set_has_let_file(p) set_type1_bit(T_Let(p), T_HAS_LET_FILE) +#define clear_has_let_file(p) clear_type1_bit(T_Let(p), T_HAS_LET_FILE) + +#define T_TYPED_VECTOR T_HAS_LET_FILE +#define is_typed_vector(p) has_type1_bit(T_Vec(p), T_TYPED_VECTOR) +#define set_typed_vector(p) set_type1_bit(T_Vec(p), T_TYPED_VECTOR) +#define clear_typed_vector(p) clear_type1_bit(T_Vec(p), T_TYPED_VECTOR) + +#define T_TYPED_HASH_TABLE T_HAS_LET_FILE +#define is_typed_hash_table(p) has_type1_bit(T_Hsh(p), T_TYPED_HASH_TABLE) +#define set_is_typed_hash_table(p) set_type1_bit(T_Hsh(p), T_TYPED_HASH_TABLE) + +#define T_BOOL_SETTER T_HAS_LET_FILE +#define c_function_has_bool_setter(p) has_type1_bit(T_Fnc(p), T_BOOL_SETTER) +#define c_function_set_has_bool_setter(p) set_type1_bit(T_Fnc(p), T_BOOL_SETTER) + +#define T_REST_SLOT T_HAS_LET_FILE +#define is_rest_slot(p) has_type1_bit(T_Slt(p), T_REST_SLOT) +#define set_is_rest_slot(p) set_type1_bit(T_Slt(p), T_REST_SLOT) + +#define T_NO_DEFAULTS T_HAS_LET_FILE +#define T_FULL_NO_DEFAULTS T_FULL_HAS_LET_FILE +#define has_no_defaults(p) has_type1_bit(T_Pcs(p), T_NO_DEFAULTS) +#define set_has_no_defaults(p) set_type1_bit(T_Pcs(p), T_NO_DEFAULTS) +/* pair=closure* body, transferred to closure* */ + +#define T_FULL_DEFINER (1LL << (TYPE_BITS + BIT_ROOM + 26)) +#define T_DEFINER (1 << 2) +#define is_definer(p) has_type1_bit(T_Sym(p), T_DEFINER) +#define set_is_definer(p) set_type1_bit(T_Sym(p), T_DEFINER) +#define is_func_definer(p) has_type1_bit(T_Fnc(p), T_DEFINER) +#define set_func_is_definer(p) do {set_type1_bit(T_Fnc(initial_value(p)), T_DEFINER); set_type1_bit(T_Sym(p), T_DEFINER);} while (0) +#define is_syntax_definer(p) has_type1_bit(T_Syn(p), T_DEFINER) +#define set_syntax_is_definer(p) do {set_type1_bit(T_Syn(initial_value(p)), T_DEFINER); set_type1_bit(T_Sym(p), T_DEFINER);} while (0) +/* this marks "definers" like define and define-macro */ + +#define T_MACLET T_DEFINER +#define is_maclet(p) has_type1_bit(T_Let(p), T_MACLET) +#define set_maclet(p) set_type1_bit(T_Let(p), T_MACLET) +/* this marks a maclet */ + +#define T_HAS_FX T_DEFINER +#define set_has_fx(p) set_type1_bit(T_Pair(p), T_HAS_FX) +#define has_fx(p) has_type1_bit(T_Pair(p), T_HAS_FX) +#define clear_has_fx(p) clear_type1_bit(T_Pair(p), T_HAS_FX) + +#define T_SLOT_DEFAULTS T_DEFINER +#define slot_defaults(p) has_type1_bit(T_Slt(p), T_SLOT_DEFAULTS) +#define set_slot_defaults(p) set_type1_bit(T_Slt(p), T_SLOT_DEFAULTS) + +#define T_WEAK_HASH_ITERATOR T_DEFINER +#define is_weak_hash_iterator(p) has_type1_bit(T_Itr(p), T_WEAK_HASH_ITERATOR) +#define set_weak_hash_iterator(p) set_type1_bit(T_Itr(p), T_WEAK_HASH_ITERATOR) +#define clear_weak_hash_iterator(p) clear_type1_bit(T_Itr(p), T_WEAK_HASH_ITERATOR) + +#define T_HASH_KEY_TYPE T_DEFINER +#define has_hash_key_type(p) has_type1_bit(T_Hsh(p), T_HASH_KEY_TYPE) +#define set_has_hash_key_type(p) set_type1_bit(T_Hsh(p), T_HASH_KEY_TYPE) + +#define T_FULL_BINDER (1LL << (TYPE_BITS + BIT_ROOM + 27)) +#define T_BINDER (1 << 3) +#define set_syntax_is_binder(p) do {set_type1_bit(T_Syn(initial_value(p)), T_BINDER); set_type1_bit(T_Sym(p), T_BINDER);} while (0) +#define is_definer_or_binder(p) has_type1_bit(T_Sym(p), T_DEFINER | T_BINDER) +/* this marks "binders" like let */ + +#define T_SEMISAFE T_BINDER +#define is_semisafe(p) has_type1_bit(T_Fnc(p), T_SEMISAFE) +#define set_is_semisafe(p) set_type1_bit(T_Fnc(p), T_SEMISAFE) + +/* #define T_TREE_COLLECTED T_FULL_BINDER */ +#define T_SHORT_TREE_COLLECTED T_BINDER +#define tree_is_collected(p) has_type1_bit(T_Pair(p), T_SHORT_TREE_COLLECTED) +#define tree_set_collected(p) set_type1_bit(T_Pair(p), T_SHORT_TREE_COLLECTED) +#define tree_clear_collected(p) clear_type1_bit(T_Pair(p), T_SHORT_TREE_COLLECTED) + +#define T_SIMPLE_VALUES T_BINDER +#define has_simple_values(p) has_type1_bit(T_Hsh(p), T_SIMPLE_VALUES) +#define set_has_simple_values(p) set_type1_bit(T_Hsh(p), T_SIMPLE_VALUES) +#define clear_has_simple_values(p) clear_type1_bit(T_Hsh(p), T_SIMPLE_VALUES) + +#define T_VERY_SAFE_CLOSURE (1LL << (TYPE_BITS + BIT_ROOM + 28)) +#define T_SHORT_VERY_SAFE_CLOSURE (1 << 4) +#define is_very_safe_closure(p) has_type1_bit(T_Clo(p), T_SHORT_VERY_SAFE_CLOSURE) +#define set_very_safe_closure(p) set_type1_bit(T_Clo(p), T_SHORT_VERY_SAFE_CLOSURE) +#define closure_bits(p) (full_type(T_Pair(p)) & (T_SAFE_CLOSURE | T_VERY_SAFE_CLOSURE | T_FULL_NO_DEFAULTS)) +#define is_very_safe_closure_body(p) has_type1_bit(T_Pair(p), T_SHORT_VERY_SAFE_CLOSURE) +#define set_very_safe_closure_body(p) set_type1_bit(T_Pair(p), T_SHORT_VERY_SAFE_CLOSURE) + +#define T_BAFFLE_LET T_SHORT_VERY_SAFE_CLOSURE +#define is_baffle_let(p) has_type1_bit(T_Let(p), T_BAFFLE_LET) +#define set_baffle_let(p) set_type1_bit(T_Let(p), T_BAFFLE_LET) + +#define T_CYCLIC (1LL << (TYPE_BITS + BIT_ROOM + 29)) +#define T_SHORT_CYCLIC (1 << 5) +#define is_cyclic(p) has_type1_bit(T_Seq(p), T_SHORT_CYCLIC) +#define set_cyclic(p) set_type1_bit(T_Seq(p), T_SHORT_CYCLIC) + +#define T_CYCLIC_SET (1LL << (TYPE_BITS + BIT_ROOM + 30)) +#define T_SHORT_CYCLIC_SET (1 << 6) +#define is_cyclic_set(p) has_type1_bit(T_Seq(p), T_SHORT_CYCLIC_SET) +#define set_cyclic_set(p) set_type1_bit(T_Seq(p), T_SHORT_CYCLIC_SET) +#define clear_cyclic_bits(p) clear_type_bit(p, T_COLLECTED | T_SHARED | T_CYCLIC | T_CYCLIC_SET) + +#define T_KEYWORD (1LL << (TYPE_BITS + BIT_ROOM + 31)) +#define T_SHORT_KEYWORD (1 << 7) +#define is_keyword(p) has_type1_bit(T_Sym(p), T_SHORT_KEYWORD) +#define is_symbol_and_keyword(p) ((is_symbol(p)) && (is_keyword(p))) +/* this bit distinguishes a symbol from a symbol that is also a keyword */ + +#define T_FX_TREEABLE T_SHORT_KEYWORD +#define is_fx_treeable(p) has_type1_bit(T_Pair(p), T_FX_TREEABLE) +#define set_is_fx_treeable(p) set_type1_bit(T_Pair(p), T_FX_TREEABLE) + +#define T_FULL_SIMPLE_ELEMENTS (1LL << (TYPE_BITS + BIT_ROOM + 32)) +#define T_SIMPLE_ELEMENTS (1 << 8) +#define has_simple_elements(p) has_type1_bit(T_Nvc(p), T_SIMPLE_ELEMENTS) +#define set_has_simple_elements(p) set_type1_bit(T_Nvc(p), T_SIMPLE_ELEMENTS) +#define c_function_has_simple_elements(p) has_type1_bit(T_Fnc(p), T_SIMPLE_ELEMENTS) +#define c_function_set_has_simple_elements(p) set_type1_bit(T_Fnc(p), T_SIMPLE_ELEMENTS) +/* c_func case here refers to boolean? et al -- structure element type declaration that ensures a simple object */ + +#define T_SIMPLE_KEYS T_SIMPLE_ELEMENTS +#define has_simple_keys(p) has_type1_bit(T_Hsh(p), T_SIMPLE_KEYS) +#define set_has_simple_keys(p) set_type1_bit(T_Hsh(p), T_SIMPLE_KEYS) +#define clear_has_simple_keys(p) clear_type1_bit(T_Hsh(p), T_SIMPLE_KEYS) + +#define T_SAFE_SETTER T_SIMPLE_ELEMENTS +#define is_safe_setter(p) has_type1_bit(T_Sym(p), T_SAFE_SETTER) +#define set_is_safe_setter(p) set_type1_bit(T_Sym(p), T_SAFE_SETTER) + +#define T_FLOAT_OPTABLE T_SIMPLE_ELEMENTS +#define is_float_optable(p) has_type1_bit(T_Pair(p), T_FLOAT_OPTABLE) +#define set_is_float_optable(p) set_type1_bit(T_Pair(p), T_FLOAT_OPTABLE) + +#define T_FULL_CASE_KEY (1LL << (TYPE_BITS + BIT_ROOM + 33)) +#define T_CASE_KEY (1 << 9) +#define is_case_key(p) has_type1_bit(T_Ext(p), T_CASE_KEY) +#define set_case_key(p) set_type1_bit(T_Sym(p), T_CASE_KEY) + +#define T_OPT1_FUNC_LISTED T_CASE_KEY +#define opt1_func_listed(p) has_type1_bit(T_Pair(p), T_OPT1_FUNC_LISTED) +#define set_opt1_func_listed(p) set_type1_bit(T_Pair(p), T_OPT1_FUNC_LISTED) + +#define T_FULL_HAS_GX (1LL << (TYPE_BITS + BIT_ROOM + 34)) +#define T_HAS_GX (1 << 10) +#define has_gx(p) has_type1_bit(T_Pair(p), T_HAS_GX) +#define set_has_gx(p) set_type1_bit(T_Pair(p), T_HAS_GX) + +#define T_FULL_UNKNOPT (1LL << (TYPE_BITS + BIT_ROOM + 35)) +#define T_UNKNOPT (1 << 11) +#define is_unknopt(p) has_type1_bit(T_Pair(p), T_UNKNOPT) +#define set_is_unknopt(p) set_type1_bit(T_Pair(p), T_UNKNOPT) + +#define T_MAC_OK T_UNKNOPT +#define mac_is_ok(p) has_type1_bit(T_Pair(p), T_MAC_OK) +#define set_mac_is_ok(p) set_type1_bit(T_Pair(p), T_MAC_OK) +/* marks a macro (via (macro...)) that has been checked -- easier (and slower) than making 4 or 5 more ops, op_macro_unchecked and so on */ + +#define T_FULL_SAFETY_CHECKED (1LL << (TYPE_BITS + BIT_ROOM + 36)) +#define T_SAFETY_CHECKED (1 << 12) +#define is_safety_checked(p) has_type1_bit(T_Pair(p), T_SAFETY_CHECKED) +#define set_safety_checked(p) do {if (in_heap(p)) set_type1_bit(T_Pair(p), T_SAFETY_CHECKED);} while (0) + +#define T_FULL_HAS_FN (1LL << (TYPE_BITS + BIT_ROOM + 37)) +#define T_HAS_FN (1 << 13) +#define set_has_fn(p) set_type1_bit(T_Pair(p), T_HAS_FN) +#define has_fn(p) has_type1_bit(T_Pair(p), T_HAS_FN) + +#define T_GC_MARK 0x8000000000000000 +#define is_marked(p) has_type_bit(p, T_GC_MARK) +#define set_mark(p) set_type_bit(T_Pos(p), T_GC_MARK) +#define clear_mark(p) clear_type_bit(p, T_GC_MARK) +/* using the sign bit, bit 23 (or 55) == 31 (or 63) for this makes a big difference in the GC */ + +#define T_UNHEAP 0x4000000000000000 +#define T_SHORT_UNHEAP (1 << 14) +#define in_heap(p) (((T_Pos(p))->tf.opts.high_flag & T_SHORT_UNHEAP) == 0) +#define unheap(sc, p) set_type1_bit(T_Pos(p), T_SHORT_UNHEAP) + +#define is_eof(p) ((T_Ext(p)) == eof_object) +#define is_true(Sc, p) ((T_Ext(p)) != Sc->F) +#define is_false(Sc, p) ((T_Ext(p)) == Sc->F) + +#ifdef _MSC_VER + static s7_pointer make_boolean(s7_scheme *sc, bool val) {if (val) return(sc->T); return(sc->F);} +#else + #define make_boolean(sc, Val) ((Val) ? sc->T : sc->F) +#endif + +#define is_pair(p) (type(p) == T_PAIR) +#define is_mutable_pair(p) ((is_pair(p)) && (!is_immutable(p))) /* same speed: ((full_type(T_Pos(p)) & (TYPE_MASK | T_IMMUTABLE)) == T_PAIR) */ +#define is_null(p) ((T_Pos(p)) == sc->nil) /* can be a slot */ +#define is_not_null(p) ((T_Pos(p)) != sc->nil) +#define is_list(p) ((is_pair(p)) || (type(p) == T_NIL)) +#define is_quoted_pair(p) ((is_pair(p)) && (car(p) == sc->quote_symbol)) +#define is_unquoted_pair(p) ((is_pair(p)) && (car(p) != sc->quote_symbol)) +#define is_quoted_symbol(p) ((is_pair(p)) && (car(p) == sc->quote_symbol) && (is_symbol(cadr(p)))) + + +/* pair line/file/position */ +#define PAIR_LINE_BITS 24 +#define PAIR_FILE_BITS 12 +#define PAIR_POSITION_BITS 28 +#define PAIR_LINE_OFFSET 0 +#define PAIR_FILE_OFFSET PAIR_LINE_BITS +#define PAIR_POSITION_OFFSET (PAIR_LINE_BITS + PAIR_FILE_BITS) +#define PAIR_LINE_MASK ((1 << PAIR_LINE_BITS) - 1) +#define PAIR_FILE_MASK ((1 << PAIR_FILE_BITS) - 1) +#define PAIR_POSITION_MASK ((1 << PAIR_POSITION_BITS) - 1) + +#define port_location(Pt) (((port_line_number(Pt) & PAIR_LINE_MASK) << PAIR_LINE_OFFSET) | \ + ((port_file_number(Pt) & PAIR_FILE_MASK) << PAIR_FILE_OFFSET) | \ + ((port_position(Pt) & PAIR_POSITION_MASK) << PAIR_POSITION_OFFSET)) + +#define location_to_line(Loc) ((Loc >> PAIR_LINE_OFFSET) & PAIR_LINE_MASK) +#define location_to_file(Loc) ((Loc >> PAIR_FILE_OFFSET) & PAIR_FILE_MASK) +#define location_to_position(Loc) ((Loc >> PAIR_POSITION_OFFSET) & PAIR_POSITION_MASK) + +#define pair_line_number(p) location_to_line(pair_location(p)) +#define pair_file_number(p) location_to_file(pair_location(p)) +#define pair_position(p) location_to_position(pair_location(p)) + +#if (!S7_DEBUGGING) +#define pair_location(p) (p)->object.sym_cons.location +#define pair_set_location(p, X) (p)->object.sym_cons.location = X +#define pair_raw_hash(p) (p)->object.sym_cons.hash +#define pair_set_raw_hash(p, X) (p)->object.sym_cons.hash = X +#define pair_raw_len(p) (p)->object.sym_cons.location +#define pair_set_raw_len(p, X) (p)->object.sym_cons.location = X +#define pair_raw_name(p) (p)->object.sym_cons.fstr +#define pair_set_raw_name(p, X) (p)->object.sym_cons.fstr = X +/* opt1 == raw_hash, opt2 == raw_name, opt3 == line|ctr + len, but hash/name/len only apply to the symbol table so there's no collision */ + +#define opt1(p, r) ((p)->object.cons.opt1) +#define set_opt1(p, x, r) (p)->object.cons.opt1 = x +#define opt2(p, r) ((p)->object.cons.o2.opt2) +#define set_opt2(p, x, r) (p)->object.cons.o2.opt2 = (s7_pointer)(x) +#define opt2_n(p, r) ((p)->object.cons.o2.n) +#define set_opt2_n(p, x, r) (p)->object.cons.o2.n = x +#define opt3(p, r) ((p)->object.cons.o3.opt3) +#define set_opt3(p, x, r) do {(p)->object.cons.o3.opt3 = x; clear_type_bit(p, T_LOCATION);} while (0) +#define opt3_n(p, r) ((p)->object.cons.o3.n) +#define set_opt3_n(p, x, r) do {(p)->object.cons.o3.n = x; clear_type_bit(p, T_LOCATION);} while (0) + +#else + +/* the 3 opt fields hold most of the varigated optimizer info, so they are used in many conflicting ways. + * the bits and funcs here try to track each such use, and report any cross-talk or collisions. + * all of this machinery vanishes if debugging is turned off. + */ +#define OPT1_SET (1 << 0) +#define OPT2_SET (1 << 1) +#define OPT3_SET (1 << 2) + +#define OPT1_FAST (1 << 3) /* fast list in member/assoc circular list check */ +#define OPT1_CFUNC (1 << 4) /* c-function */ +#define OPT1_CLAUSE (1 << 5) /* case clause */ +#define OPT1_LAMBDA (1 << 6) /* lambda(*) */ +#define OPT1_SYM (1 << 7) /* symbol */ +#define OPT1_PAIR (1 << 8) /* pair */ +#define OPT1_CON (1 << 9) /* constant from eval's point of view */ /* 10 was opt1_goto, unused */ +#define OPT1_ANY (1 << 11) /* anything -- deliberate unchecked case */ +#define OPT1_HASH (1 << 12) /* hash code used in the symbol table (pair_raw_hash) */ +#define OPT1_MASK (OPT1_FAST | OPT1_CFUNC | OPT1_CLAUSE | OPT1_LAMBDA | OPT1_SYM | OPT1_PAIR | OPT1_CON | OPT1_ANY | OPT1_HASH) + +#define opt1_is_set(p) (((T_Pair(p))->debugger_bits & OPT1_SET) != 0) +#define set_opt1_is_set(p) (T_Pair(p))->debugger_bits |= OPT1_SET +#define opt1_role_matches(p, Role) (((T_Pair(p))->debugger_bits & OPT1_MASK) == Role) +#define set_opt1_role(p, Role) (T_Pair(p))->debugger_bits = (Role | ((p)->debugger_bits & ~OPT1_MASK)) +#define opt1(p, Role) opt1_1(sc, T_Pair(p), Role, __func__, __LINE__) +#define set_opt1(p, x, Role) set_opt1_1(T_Pair(p), x, Role, __func__, __LINE__) + +#define OPT2_KEY (1 << 13) /* case key */ +#define OPT2_SLOW (1 << 14) /* slow list in member/assoc circular list check */ +#define OPT2_SYM (1 << 15) /* symbol */ +#define OPT2_PAIR (1 << 16) /* pair */ +#define OPT2_CON (1 << 17) /* constant as above */ +#define OPT2_FX (1 << 18) /* fx (fx_*) func (sc, form) */ +#define OPT2_FN (1 << 19) /* fn (s7_function) func (sc, arglist) */ +#define OPT2_LAMBDA (1 << 20) /* lambda form */ +#define OPT2_NAME (1 << 21) /* named used by symbol table (pair_raw_name) */ +#define OPT2_DIRECT (1LL << 32) +#define OPT2_INT (1LL << 33) +#define OPT2_MASK (OPT2_KEY | OPT2_SLOW | OPT2_SYM | OPT2_PAIR | OPT2_CON | OPT2_FX | \ + OPT2_FN | OPT2_LAMBDA | OPT2_DIRECT | OPT2_NAME | OPT2_INT) + +#define opt2_is_set(p) (((T_Pair(p))->debugger_bits & OPT2_SET) != 0) +#define set_opt2_is_set(p) (T_Pair(p))->debugger_bits |= OPT2_SET +#define opt2_role_matches(p, Role) (((T_Pair(p))->debugger_bits & OPT2_MASK) == Role) +#define set_opt2_role(p, Role) (T_Pair(p))->debugger_bits = (Role | ((p)->debugger_bits & ~OPT2_MASK)) +#define opt2(p, Role) opt2_1(sc, T_Pair(p), Role, __func__, __LINE__) +#define set_opt2(p, x, Role) set_opt2_1(sc, T_Pair(p), (s7_pointer)(x), Role, __func__, __LINE__) +#define opt2_n(p, Role) opt2_n_1(sc, T_Pair(p), Role, __func__, __LINE__) +#define set_opt2_n(p, x, Role) set_opt2_n_1(sc, T_Pair(p), x, Role, __func__, __LINE__) + +#define OPT3_ARGLEN (1 << 22) /* arglist length */ +#define OPT3_SYM (1 << 23) /* expression symbol access */ +#define OPT3_AND (1 << 24) /* and second clause */ +#define OPT3_DIRECT (1 << 25) /* direct call info */ +#define OPT3_ANY (1 << 26) +#define OPT3_LET (1 << 27) /* let or #f */ +#define OPT3_CON (1 << 28) +#define OPT3_LOCATION (1 << 29) +#define OPT3_LEN (1 << 30) +#define OPT3_BYTE (1LL << 31) +#define OPT3_INT (1LL << 34) +#define OPT3_MASK (OPT3_ARGLEN | OPT3_SYM | OPT3_AND | OPT3_ANY | OPT3_LET | OPT3_BYTE | \ + OPT3_LOCATION | OPT3_LEN | OPT3_DIRECT | OPT3_CON | OPT3_INT) + +#define opt3_is_set(p) (((T_Pair(p))->debugger_bits & OPT3_SET) != 0) +#define set_opt3_is_set(p) (T_Pair(p))->debugger_bits |= OPT3_SET +#define opt3_role_matches(p, Role) (((T_Pair(p))->debugger_bits & OPT3_MASK) == Role) +#define set_opt3_role(p, Role) (T_Pair(p))->debugger_bits = (Role | ((p)->debugger_bits & ~OPT3_MASK)) +#define opt3(p, Role) opt3_1(sc, T_Pair(p), Role, __func__, __LINE__) +#define set_opt3(p, x, Role) set_opt3_1(T_Pair(p), x, Role) +#define opt3_n(p, Role) opt3_n_1(sc, T_Pair(p), Role, __func__, __LINE__) +#define set_opt3_n(p, x, Role) set_opt3_n_1(T_Pair(p), x, Role) + +#define pair_location(p) opt3_location_1(sc, T_Pair(p), __func__, __LINE__) +#define pair_set_location(p, X) set_opt3_location_1(T_Pair(p), X) +#define pair_raw_hash(p) opt1_hash_1(sc, T_Pair(p), __func__, __LINE__) +#define pair_set_raw_hash(p, X) set_opt1_hash_1(T_Pair(p), X) +#define pair_raw_len(p) opt3_len_1(sc, T_Pair(p), __func__, __LINE__) +#define pair_set_raw_len(p, X) set_opt3_len_1(T_Pair(p), X) +#define pair_raw_name(p) opt2_name_1(sc, T_Pair(p), __func__, __LINE__) +#define pair_set_raw_name(p, X) set_opt2_name_1(T_Pair(p), X) + +#define L_HIT (1LL << 40) /* "L_SET" is taken */ +#define L_FUNC (1LL << 41) +#define L_DOX (1LL << 42) +#define L_MASK (L_FUNC | L_DOX) +#endif + +#define opt1_fast(P) T_Lst(opt1(P, OPT1_FAST)) +#define set_opt1_fast(P, X) set_opt1(P, T_Pair(X), OPT1_FAST) +#define opt1_cfunc(P) T_Pos(opt1(P, OPT1_CFUNC)) +#define set_opt1_cfunc(P, X) set_opt1(P, T_Fnc(X), OPT1_CFUNC) +#define opt1_lambda_unchecked(P) opt1(P, OPT1_LAMBDA) /* can be free/null? from s7_call? */ +#define opt1_lambda(P) T_Clo(opt1(P, OPT1_LAMBDA)) +#define set_opt1_lambda(P, X) set_opt1(P, T_Clo(X), OPT1_LAMBDA) +#define set_opt1_lambda_add(P, X) do {set_opt1(P, T_Clo(X), OPT1_LAMBDA); add_opt1_func(sc, P);} while (0) +#define opt1_clause(P) T_Pos(opt1(P, OPT1_CLAUSE)) +#define set_opt1_clause(P, X) set_opt1(P, T_Pos(X), OPT1_CLAUSE) +#define opt1_sym(P) T_Sym(opt1(P, OPT1_SYM)) +#define set_opt1_sym(P, X) set_opt1(P, T_Sym(X), OPT1_SYM) +#define opt1_pair(P) T_Lst(opt1(P, OPT1_PAIR)) +#define set_opt1_pair(P, X) set_opt1(P, T_Lst(X), OPT1_PAIR) +#define opt1_con(P) T_Pos(opt1(P, OPT1_CON)) +#define set_opt1_con(P, X) set_opt1(P, T_Pos(X), OPT1_CON) /* can be # */ +#define opt1_any(P) opt1(P, OPT1_ANY) /* can be free in closure_is_ok */ +#define set_opt1_any(P, X) set_opt1(P, X, OPT1_ANY) + +#define opt2_any(P) opt2(P, OPT2_KEY) +#define set_opt2_any(P, X) set_opt2(P, X, OPT2_KEY) +#define opt2_int(P) opt2_n(P, OPT2_INT) +#define set_opt2_int(P, X) set_opt2_n(P, X, OPT2_INT) +#define opt2_slow(P) T_Lst(opt2(P, OPT2_SLOW)) +#define set_opt2_slow(P, X) set_opt2(P, T_Pair(X), OPT2_SLOW) +#define opt2_sym(P) T_Sym(opt2(P, OPT2_SYM)) +#define set_opt2_sym(P, X) set_opt2(P, T_Sym(X), OPT2_SYM) +#define opt2_pair(P) T_Lst(opt2(P, OPT2_PAIR)) +#define set_opt2_pair(P, X) set_opt2(P, T_Lst(X), OPT2_PAIR) +#define opt2_con(P) T_Pos(opt2(P, OPT2_CON)) +#define set_opt2_con(P, X) set_opt2(P, T_Pos(X), OPT2_CON) +#define opt2_lambda(P) T_Pair(opt2(P, OPT2_LAMBDA)) +#define set_opt2_lambda(P, X) set_opt2(P, T_Pair(X), OPT2_LAMBDA) +#define opt2_direct(P) opt2(P, OPT2_DIRECT) +#define set_opt2_direct(P, X) set_opt2(P, (s7_pointer)(X), OPT2_DIRECT) + +#define opt3_arglen(P) opt3_n(P, OPT3_ARGLEN) +#define set_opt3_arglen(P, X) set_opt3_n(P, X, OPT3_ARGLEN) +#define opt3_int(P) opt3_n(P, OPT3_INT) +#define set_opt3_int(P, X) set_opt3_n(P, X, OPT3_INT) +#define opt3_sym(P) T_Sym(opt3(P, OPT3_SYM)) +#define set_opt3_sym(P, X) set_opt3(P, T_Sym(X), OPT3_SYM) +#define opt3_con(P) T_Pos(opt3(P, OPT3_CON)) +#define set_opt3_con(P, X) set_opt3(P, T_Pos(X), OPT3_CON) +#define opt3_pair(P) T_Pair(opt3(P, OPT3_AND)) +#define set_opt3_pair(P, X) set_opt3(P, T_Pair(X), OPT3_AND) +#define opt3_any(P) opt3(P, OPT3_ANY) +#define set_opt3_any(P, X) set_opt3(P, X, OPT3_ANY) +#define opt3_let(P) T_Lid(opt3(P, OPT3_LET)) +#define set_opt3_let(P, X) set_opt3(P, T_Lid(X), OPT3_LET) +#define opt3_direct(P) opt3(P, OPT3_DIRECT) +#define set_opt3_direct(P, X) set_opt3(P, (s7_pointer)(X), OPT3_DIRECT) + +#if S7_DEBUGGING +#define opt3_byte(p) opt3_byte_1(sc, T_Pair(p), OPT3_BYTE, __func__, __LINE__) +#define set_opt3_byte(p, x) set_opt3_byte_1(T_Pair(p), x, OPT3_BYTE, __func__, __LINE__) +#else +#define opt3_byte(P) T_Pair(P)->object.cons.o3.opt_type /* op_if_is_type, opt_type == opt3 in cons */ +#define set_opt3_byte(P, X) do {T_Pair(P)->object.cons.o3.opt_type = X; clear_type_bit(P, T_LOCATION);} while (0) +#endif + +#define pair_macro(P) opt2_sym(P) +#define set_pair_macro(P, Name) set_opt2_sym(P, Name) + +#define fn_proc(f) ((s7_function)(opt2(f, OPT2_FN))) +#define fx_proc(f) ((s7_function)(opt2(f, OPT2_FX))) +#define fn_proc_unchecked(f) ((s7_function)(T_Pair(f)->object.cons.o2.opt2)) +#define fx_proc_unchecked(f) ((s7_function)(T_Pair(f)->object.cons.o2.opt2)) /* unused */ + +#define set_fx(f, _X_) do {s7_pointer X; X = (s7_pointer)(_X_); set_opt2(f, X, OPT2_FX); if (X) set_has_fx(f); else clear_has_fx(f);} while (0) +#define set_fx_direct(f, X) do {set_opt2(f, (s7_pointer)(X), OPT2_FX); set_has_fx(f);} while (0) +#define set_fn(f, _X_) do {s7_pointer X; X = (s7_pointer)(_X_); set_opt2(f, X, OPT2_FN); if (X) set_has_fn(f); else clear_has_fx(f);} while (0) +#define set_fn_direct(f, X) do {set_opt2(f, (s7_pointer)(X), OPT2_FN); set_has_fn(f);} while (0) + +#if WITH_GCC +#define fx_call(Sc, F) ({s7_pointer _P_; _P_ = F; fx_proc(_P_)(Sc, car(_P_));}) +#define fc_call(Sc, F) ({s7_pointer _P_; _P_ = F; fn_proc(_P_)(Sc, cdr(_P_));}) +#define fn_call(Sc, F) ({s7_pointer _P_; _P_ = F; fn_proc(_P_)(Sc, cdr(_P_));}) +#else +#define fx_call(Sc, F) fx_proc(F)(Sc, car(F)) +#define fc_call(Sc, F) fn_proc(F)(Sc, cdr(F)) +#define fn_call(Sc, F) fn_proc(F)(Sc, cdr(F)) +#endif +/* fx_call can affect the stack and sc->value */ + +#define car(p) (T_Pair(p))->object.cons.car +#define set_car(p, Val) car(p) = T_Pos(Val) /* can be a slot or # */ +#define cdr(p) (T_Pair(p))->object.cons.cdr +#define set_cdr(p, Val) cdr(p) = T_Pos(Val) +#define unchecked_car(p) (T_Pos(p))->object.cons.car +#define unchecked_cdr(p) (T_Pos(p))->object.cons.cdr + +#define caar(p) car(car(p)) +#define cadr(p) car(cdr(p)) +#define set_cadr(p, Val) car(cdr(p)) = T_Pos(Val) +#define cdar(p) cdr(car(p)) +#define set_cdar(p, Val) cdr(car(p)) = T_Pos(Val) +#define cddr(p) cdr(cdr(p)) + +#define caaar(p) car(car(car(p))) +#define cadar(p) car(cdr(car(p))) +#define cdadr(p) cdr(car(cdr(p))) +#define caddr(p) car(cdr(cdr(p))) +#define set_caddr(p, Val) car(cdr(cdr(p))) = T_Pos(Val) +#define caadr(p) car(car(cdr(p))) +#define cdaar(p) cdr(car(car(p))) +#define cdddr(p) cdr(cdr(cdr(p))) +#define set_cdddr(p, Val) cdr(cdr(cdr(p))) = T_Pos(Val) +#define cddar(p) cdr(cdr(car(p))) + +#define caaadr(p) car(car(car(cdr(p)))) +#define caadar(p) car(car(cdr(car(p)))) +#define cadaar(p) car(cdr(car(car(p)))) +#define cadddr(p) car(cdr(cdr(cdr(p)))) +#define caaddr(p) car(car(cdr(cdr(p)))) +#define cddddr(p) cdr(cdr(cdr(cdr(p)))) +#define caddar(p) car(cdr(cdr(car(p)))) +#define cdadar(p) cdr(car(cdr(car(p)))) +#define cdaddr(p) cdr(car(cdr(cdr(p)))) +#define caaaar(p) car(car(car(car(p)))) +#define cadadr(p) car(cdr(car(cdr(p)))) +#define cdaadr(p) cdr(car(car(cdr(p)))) +#define cdaaar(p) cdr(car(car(car(p)))) +#define cdddar(p) cdr(cdr(cdr(car(p)))) +#define cddadr(p) cdr(cdr(car(cdr(p)))) +#define cddaar(p) cdr(cdr(car(car(p)))) + +#define cadaddr(p) cadr(caddr(p)) +#define caddadr(p) caddr(cadr(p)) +#define caddaddr(p) caddr(caddr(p)) + +#if WITH_GCC + /* slightly tricky because cons can be called recursively, macro here is faster than inline function */ + #define cons(Sc, A, B) ({s7_pointer _X_, _A_, _B_; _A_ = A; _B_ = B; new_cell(Sc, _X_, T_PAIR | T_SAFE_PROCEDURE); set_car(_X_, _A_); set_cdr(_X_, _B_); _X_;}) +#else + #define cons(Sc, A, B) s7_cons(Sc, A, B) +#endif + +#define list_1(Sc, A) cons(Sc, A, Sc->nil) +#define list_1_unchecked(Sc, A) cons_unchecked(Sc, A, Sc->nil) +#define list_2(Sc, A, B) cons_unchecked(Sc, A, cons(Sc, B, Sc->nil)) +#define list_2_unchecked(Sc, A, B) cons_unchecked(Sc, A, cons_unchecked(Sc, B, Sc->nil)) +#define list_3(Sc, A, B, C) cons_unchecked(Sc, A, cons_unchecked(Sc, B, cons(Sc, C, Sc->nil))) +#define list_4(Sc, A, B, C, D) cons_unchecked(Sc, A, cons_unchecked(Sc, B, cons_unchecked(Sc, C, cons(Sc, D, Sc->nil)))) +#define with_list_t1(A) (set_car(sc->t1_1, A), sc->t1_1) /* this is slower than explicit code, esp t3, procedures are same as this */ +#define with_list_t2(A, B) (set_car(sc->t2_1, A), set_car(sc->t2_2, B), sc->t2_1) +#define with_list_t3(A, B, C) (set_car(sc->t3_1, A), set_car(sc->t3_2, B), set_car(sc->t3_3, C), sc->t3_1) +#define with_list_t4(A, B, C, D) (set_car(sc->t4_1, A), set_car(sc->t3_1, B), set_car(sc->t3_2, C), set_car(sc->t3_3, D), sc->t4_1) + +#define is_string(p) (type(p) == T_STRING) +#define is_mutable_string(p) ((full_type(T_Ext(p)) & (TYPE_MASK | T_IMMUTABLE)) == T_STRING) +#define string_value(p) (T_Str(p))->object.string.svalue +#define string_length(p) (T_Str(p))->object.string.length +#define string_hash(p) (T_Str(p))->object.string.hash +#define string_block(p) (T_Str(p))->object.string.block +#define unchecked_string_block(p) p->object.string.block + +#define character(p) (T_Chr(p))->object.chr.c +#define is_character(p) (type(p) == T_CHARACTER) +#define upper_character(p) (T_Chr(p))->object.chr.up_c +#define is_char_alphabetic(p) (T_Chr(p))->object.chr.alpha_c +#define is_char_numeric(p) (T_Chr(p))->object.chr.digit_c +#define is_char_whitespace(p) (T_Chr(p))->object.chr.space_c +#define is_char_uppercase(p) (T_Chr(p))->object.chr.upper_c +#define is_char_lowercase(p) (T_Chr(p))->object.chr.lower_c +#define character_name(p) (T_Chr(p))->object.chr.c_name +#define character_name_length(p) (T_Chr(p))->object.chr.length + +#define optimize_op(P) (T_Ext(P))->tf.opts.opt_choice +#define set_optimize_op(P, Op) (T_Ext(P))->tf.opts.opt_choice = (Op) /* not T_Pair -- needs legit cur_sc in init_chars|strings */ +#define OP_HOP_MASK 0xfffe +#define optimize_op_match(P, Q) ((is_optimized(P)) && ((optimize_op(P) & OP_HOP_MASK) == (Q))) +#define op_no_hop(P) (optimize_op(P) & OP_HOP_MASK) +#define op_has_hop(P) ((optimize_op(P) & 1) != 0) +#define clear_optimize_op(P) set_optimize_op(P, OP_UNOPT) +#define set_safe_optimize_op(P, Q) do {set_optimized(P); set_optimize_op(P, Q);} while (0) +#define set_unsafe_optimize_op(P, Q) do {set_unsafely_optimized(P); set_optimize_op(P, Q);} while (0) + +#define is_symbol(p) (type(p) == T_SYMBOL) +#define is_normal_symbol(p) ((is_symbol(p)) && (!is_keyword(p))) +#define is_safe_symbol(p) ((is_symbol(p)) && (is_slot(s7_slot(sc, p)))) +#define symbol_name_cell(p) T_Str((T_Sym(p))->object.sym.name) +#define symbol_set_name_cell(p, S) (T_Sym(p))->object.sym.name = T_Str(S) +#define symbol_name(p) string_value(symbol_name_cell(p)) +#define symbol_name_length(p) string_length(symbol_name_cell(p)) +#define gensym_block(p) symbol_name_cell(p)->object.string.gensym_block +#define pointer_map(p) (s7_int)((intptr_t)(p) >> 8) +#define symbol_id(p) (T_Sym(p))->object.sym.id +#define symbol_set_id_unchecked(p, X) (T_Sym(p))->object.sym.id = X +#if S7_DEBUGGING +static void symbol_set_id(s7_pointer p, s7_int id) +{ + if (id < symbol_id(p)) + { + fprintf(stderr, "%s[%d]: id mismatch: sym: %s %" ld64 ", let: %" ld64 "\n", __func__, __LINE__, symbol_name(p), symbol_id(p), id); + abort(); + } + (T_Sym(p))->object.sym.id = id; +} +#else +#define symbol_set_id(p, X) (T_Sym(p))->object.sym.id = X +#endif +/* we need 64-bits here, since we don't want this thing to wrap around, and lets are created at a great rate + * callgrind says this is faster than a uint32_t! + */ +#define symbol_info(p) (symbol_name_cell(p))->object.string.block +#define symbol_type(p) (block_size(symbol_info(p)) & 0xff) /* boolean function bool type */ +#define symbol_set_type(p, Type) block_size(symbol_info(p)) = ((block_size(symbol_info(p)) & ~0xff) | ((Type) & 0xff)) +#define symbol_clear_type(p) block_size(symbol_info(p)) = 0 +#define s7_starlet_symbol(p) ((uint8_t)((block_size(symbol_info(p)) >> 8) & 0xff)) /* *s7* id */ +#define s7_starlet_symbol_set(p, F) block_size(symbol_info(p)) = ((block_size(symbol_info(p)) & ~0xff00) | (((F) & 0xff) << 8)) + +#define initial_slot(p) T_Sld(symbol_info(p)->ex.ex_ptr) +#define set_initial_slot(p, Val) symbol_info(p)->ex.ex_ptr = T_Sld(Val) +#define global_slot(p) T_Sld((T_Sym(p))->object.sym.global_slot) +#define set_global_slot(p, Val) (T_Sym(p))->object.sym.global_slot = T_Sld(Val) +#define local_slot(p) T_Sln((T_Sym(p))->object.sym.local_slot) +#define set_local_slot(p, Val) (T_Sym(p))->object.sym.local_slot = T_Slt(Val) + +#define initial_value(p) slot_value(initial_slot(T_Sym(p))) +#define local_value(p) slot_value(local_slot(T_Sym(p))) +#define unchecked_local_value(p) local_slot(p)->object.slt.val +#define global_value(p) slot_value(global_slot(T_Sym(p))) + +#define keyword_symbol(p) symbol_info(T_Key(p))->nx.ksym /* keyword only, so does not collide with documentation */ +#define keyword_symbol_unchecked(p) symbol_info(p)->nx.ksym +#define keyword_set_symbol(p, Val) symbol_info(T_Key(p))->nx.ksym = T_Sym(Val) +#define symbol_help(p) symbol_info(p)->nx.documentation +#define symbol_set_help(p, Doc) symbol_info(p)->nx.documentation = Doc +#define symbol_tag(p) (T_Sym(p))->object.sym.tag +#define symbol_set_tag(p, Val) (T_Sym(p))->object.sym.tag = Val +#define symbol_ctr(p) (T_Sym(p))->object.sym.ctr /* needs to be in the symbol object (not symbol_info) for speed */ +#define symbol_clear_ctr(p) (T_Sym(p))->object.sym.ctr = 0 +#define symbol_increment_ctr(p) (T_Sym(p))->object.sym.ctr++ +#define symbol_tag2(p) symbol_info(p)->ln.tag +#define symbol_set_tag2(p, Val) symbol_info(p)->ln.tag = Val +#define symbol_has_help(p) (is_documented(symbol_name_cell(p))) +#define symbol_set_has_help(p) set_documented(symbol_name_cell(p)) +/* symbol_info->dx is free */ + +#define symbol_set_local_slot_unchecked(Symbol, Id, Slot) \ + do {(Symbol)->object.sym.local_slot = T_Sln(Slot); symbol_set_id_unchecked(Symbol, Id); symbol_increment_ctr(Symbol);} while (0) +#define symbol_set_local_slot_unchecked_and_unincremented(Symbol, Id, Slot) \ + do {(Symbol)->object.sym.local_slot = T_Sln(Slot); symbol_set_id_unchecked(Symbol, Id);} while (0) +#define symbol_set_local_slot(Symbol, Id, Slot) \ + do {set_local_slot(Symbol, Slot); symbol_set_id(Symbol, Id); symbol_increment_ctr(Symbol);} while (0) +#define symbol_set_local_slot_unincremented(Symbol, Id, Slot) \ + do {set_local_slot(Symbol, Slot); symbol_set_id(Symbol, Id);} while (0) +/* set slot before id in case Slot is an expression that tries to find the current Symbol slot (using its old Id obviously) */ + +#define is_slot(p) (type(p) == T_SLOT) +#define slot_symbol(p) T_Sym((T_Slt(p))->object.slt.sym) +#define slot_set_symbol(p, Sym) (T_Slt(p))->object.slt.sym = T_Sym(Sym) +#define slot_value(p) T_Nmv((T_Slt(p))->object.slt.val) +#if S7_DEBUGGING +#define slot_set_value(slot, value) \ + do { \ + if (is_immutable_slot(slot)) {fprintf(stderr, "setting immutable slot %s\n", symbol_name(slot_symbol(slot))); if (cur_sc->stop_at_error) abort();} \ + (T_Slt(slot))->object.slt.val = T_Nmv(value); \ + } while (0) +#else +#define slot_set_value(p, Val) (T_Slt(p))->object.slt.val = T_Nmv(Val) +#endif +#define slot_set_symbol_and_value(Slot, Symbol, Value) do {slot_set_symbol(Slot, Symbol); slot_set_value(Slot, Value);} while (0) +#define slot_set_value_with_hook(Slot, Value) \ + do {if (hook_has_functions(sc->rootlet_redefinition_hook)) slot_set_value_with_hook_1(sc, Slot, T_Nmv(Value)); else slot_set_value(Slot, T_Nmv(Value));} while (0) +#define next_slot(p) T_Sln((T_Slt(p))->object.slt.nxt) +#define slot_set_next(p, Val) (T_Slt(p))->object.slt.nxt = T_Sln(Val) +#define slot_set_pending_value(p, Val) do {(T_Slt(p))->object.slt.pending_value = T_Nmv(Val); slot_set_has_pending_value(p);} while (0) +#define slot_simply_set_pending_value(p, Val) (T_Slt(p))->object.slt.pending_value = T_Nmv(Val) +#if S7_DEBUGGING +static s7_pointer slot_pending_value(s7_pointer p) \ + {if (slot_has_pending_value(p)) return(p->object.slt.pending_value); fprintf(stderr, "%s[%d]: slot: no pending value\n", __func__, __LINE__); abort(); return(NULL);} +static s7_pointer slot_expression(s7_pointer p) \ + {if (slot_has_expression(p)) return(p->object.slt.expr); fprintf(stderr, "%s[%d]: slot: no expression\n", __func__, __LINE__); abort(); return(NULL);} +#else +#define slot_pending_value(p) (T_Slt(p))->object.slt.pending_value +#define slot_expression(p) (T_Slt(p))->object.slt.expr +#endif +#define slot_pending_value_unchecked(p) (T_Slt(p))->object.slt.pending_value + +#define slot_set_expression(p, Val) do {(T_Slt(p))->object.slt.expr = T_Ext(Val); slot_set_has_expression(p);} while (0) +#define slot_just_set_expression(p, Val) (T_Slt(p))->object.slt.expr = T_Ext(Val) +#define slot_setter(p) T_Prc(T_Slt(p)->object.slt.pending_value) +#define slot_set_setter_1(p, Val) (T_Slt(p))->object.slt.pending_value = T_Prc(Val) +#if S7_DEBUGGING +#define tis_slot(p) ((p) && (T_Slt(p))) +#else +#define tis_slot(p) (p) /* used for loop through let slots which end in nil, not for general slot recognition */ +#endif +#define slot_end NULL +#define is_slot_end(p) (!(p)) + +#define is_syntax(p) (type(p) == T_SYNTAX) +#define syntax_symbol(p) T_Sym((T_Syn(p))->object.syn.symbol) +#define syntax_set_symbol(p, Sym) (T_Syn(p))->object.syn.symbol = T_Sym(Sym) +#define syntax_opcode(p) (T_Syn(p))->object.syn.op +#define syntax_min_args(p) (T_Syn(p))->object.syn.min_args +#define syntax_max_args(p) (T_Syn(p))->object.syn.max_args +#define syntax_documentation(p) (T_Syn(p))->object.syn.documentation +#define pair_set_syntax_op(p, X) do {set_optimize_op(p, X); set_syntactic_pair(p);} while (0) +#define symbol_syntax_op_checked(p) ((is_syntactic_pair(p)) ? optimize_op(p) : symbol_syntax_op(car(p))) +#define symbol_syntax_op(p) syntax_opcode(global_value(p)) +#define is_syntax_or_qq(p) ((is_syntax(p)) || ((p) == initial_value(sc->quasiquote_symbol))) + +#define INITIAL_ROOTLET_SIZE 512 +#define let_id(p) (T_Lid(p))->object.envr.id +#define let_set_id(p, Id) (T_Lid(p))->object.envr.id = Id +#define is_let(p) (type(p) == T_LET) +#define is_let_unchecked(p) (unchecked_type(p) == T_LET) +#define let_slots(p) T_Sln((T_Let(p))->object.envr.slots) +#define let_outlet(p) T_Lid((T_Let(p))->object.envr.nxt) +#define let_set_outlet(p, ol) (T_Let(p))->object.envr.nxt = T_Lid(ol) +#if S7_DEBUGGING + #define let_set_slots(p, Slot) do {if ((!in_heap(p)) && (Slot) && (in_heap(Slot))) fprintf(stderr, "%s[%d]: let+slot mismatch\n", __func__, __LINE__); \ + T_Let(p)->object.envr.slots = T_Sln(Slot);} while (0) + #define C_Let(p, role) check_let_ref(p, role, __func__, __LINE__) + #define S_Let(p, role) check_let_set(p, role, __func__, __LINE__) +#else + #define let_set_slots(p, Slot) (T_Let(p))->object.envr.slots = T_Sln(Slot) + #define C_Let(p, role) p + #define S_Let(p, role) p +#endif +#define funclet_function(p) T_Sym((C_Let(p, L_FUNC))->object.envr.edat.efnc.function) +#define funclet_set_function(p, F) (S_Let(p, L_FUNC))->object.envr.edat.efnc.function = T_Sym(F) +#define set_curlet(Sc, P) Sc->curlet = T_Lid(P) + +#define let_baffle_key(p) (T_Let(p))->object.envr.edat.key +#define set_let_baffle_key(p, K) (T_Let(p))->object.envr.edat.key = K + +#define let_line(p) (C_Let(p, L_FUNC))->object.envr.edat.efnc.line +#define let_set_line(p, L) (S_Let(p, L_FUNC))->object.envr.edat.efnc.line = L +#define let_file(p) (C_Let(p, L_FUNC))->object.envr.edat.efnc.file +#define let_set_file(p, F) (S_Let(p, L_FUNC))->object.envr.edat.efnc.file = F + +#define let_dox_slot1(p) T_Slt((C_Let(p, L_DOX))->object.envr.edat.dox.dox1) +#define let_set_dox_slot1(p, S) do {(S_Let(p, L_DOX))->object.envr.edat.dox.dox1 = T_Slt(S); set_has_dox_slot1(p);} while (0) +#define let_dox_slot2(p) T_Sld((C_Let(p, L_DOX))->object.envr.edat.dox.dox2) +#define let_set_dox_slot2(p, S) do {(S_Let(p, L_DOX))->object.envr.edat.dox.dox2 = T_Slt(S); set_has_dox_slot2(p);} while (0) +#define let_dox_slot2_unchecked(p) T_Sld(C_Let(p, L_DOX)->object.envr.edat.dox.dox2) +#define let_set_dox_slot2_unchecked(p, S) do {S_Let(p, L_DOX)->object.envr.edat.dox.dox2 = T_Sld(S); set_has_dox_slot2(p);} while (0) +#define let_dox1_value(p) slot_value(let_dox_slot1(p)) +#define let_dox2_value(p) slot_value(let_dox_slot2(p)) + +#define unique_name(p) (p)->object.unq.name /* not T_Uniq(p) here -- see make_unique */ +#define unique_name_length(p) (p)->object.unq.len +#define is_unspecified(p) (type(p) == T_UNSPECIFIED) +#define unique_car(p) (p)->object.unq.car +#define unique_cdr(p) (p)->object.unq.cdr + +#define is_undefined(p) (type(p) == T_UNDEFINED) +#define undefined_name(p) (T_Undf(p))->object.undef.name +#define undefined_name_length(p) (T_Undf(p))->object.undef.len +#define undefined_set_name_length(p, L) (T_Undf(p))->object.undef.len = L +#define eof_name(p) (T_Eof(p))->object.eof.name +#define eof_name_length(p) (T_Eof(p))->object.eof.len + +#define is_any_vector(p) t_vector_p[type(p)] +#define is_normal_vector(p) (type(p) == T_VECTOR) +#define vector_length(p) (p)->object.vector.length +#define unchecked_vector_elements(p) (p)->object.vector.elements.objects +#define unchecked_vector_element(p, i) ((p)->object.vector.elements.objects[i]) +#define vector_element(p, i) ((T_Nvc(p))->object.vector.elements.objects[i]) +#define vector_elements(p) (T_Nvc(p))->object.vector.elements.objects +#define any_vector_elements(p) (T_Vec(p))->object.vector.elements.objects +#define vector_getter(p) (T_Vec(p))->object.vector.vget +#define vector_setter(p) (T_Vec(p))->object.vector.setv.vset +#define vector_block(p) (T_Vec(p))->object.vector.block +#define unchecked_vector_block(p) p->object.vector.block + +#define typed_vector_typer(p) T_Prc((T_Vec(p))->object.vector.setv.fset) +#define typed_vector_set_typer(p, Fnc) (T_Vec(p))->object.vector.setv.fset = T_Prc(Fnc) +#define typed_vector_gc_mark(p) ((is_c_function(typed_vector_typer(p))) ? c_function_marker(typed_vector_typer(p)) : mark_typed_vector_1) +#define typed_vector_typer_call(sc, p, Args) \ + ((is_c_function(typed_vector_typer(p))) ? c_function_call(typed_vector_typer(p))(sc, Args) : s7_apply_function(sc, typed_vector_typer(p), Args)) + +#define is_int_vector(p) (type(p) == T_INT_VECTOR) +#define int_vector(p, i) ((T_Ivc(p))->object.vector.elements.ints[i]) +#define int_vector_ints(p) (T_Ivc(p))->object.vector.elements.ints + +#define is_float_vector(p) (type(p) == T_FLOAT_VECTOR) +#define float_vector(p, i) ((T_Fvc(p))->object.vector.elements.floats[i]) +#define float_vector_floats(p) (T_Fvc(p))->object.vector.elements.floats + +#define is_byte_vector(p) (type(p) == T_BYTE_VECTOR) +#define byte_vector_length(p) (T_BVc(p))->object.vector.length +#define byte_vector_bytes(p) (T_BVc(p))->object.vector.elements.bytes +#define byte_vector(p, i) ((T_BVc(p))->object.vector.elements.bytes[i]) +#define is_string_or_byte_vector(p) ((type(p) == T_STRING) || (type(p) == T_BYTE_VECTOR)) + +#define vector_dimension_info(p) ((vdims_t *)(T_Vec(p))->object.vector.block->ex.ex_info) +#define vector_set_dimension_info(p, d) (T_Vec(p))->object.vector.block->ex.ex_info = (void *)d +#define vector_ndims(p) vdims_rank(vector_dimension_info(p)) +#define vector_dimension(p, i) vdims_dims(vector_dimension_info(p))[i] +#define vector_dimensions(p) vdims_dims(vector_dimension_info(p)) +#define vector_offset(p, i) vdims_offsets(vector_dimension_info(p))[i] +#define vector_offsets(p) vdims_offsets(vector_dimension_info(p)) +#define vector_rank(p) ((vector_dimension_info(p)) ? vector_ndims(p) : 1) +#define vector_has_dimension_info(p) (vector_dimension_info(p)) + +#define subvector_vector(p) T_Vec(((vector_dimension_info(T_SVec(p))) ? vdims_original(vector_dimension_info(p)) : (p)->object.vector.block->nx.ksym)) +#define subvector_set_vector(p, vect) (T_SVec(p))->object.vector.block->nx.ksym = T_Vec(vect) + +#define rootlet_element(p, i) unchecked_vector_element(p, i) +#define rootlet_elements(p) unchecked_vector_elements(p) +#define rootlet_block(p) unchecked_vector_block(p) + +#define stack_element(p, i) unchecked_vector_element(T_Stk(p), i) +#define stack_elements(p) unchecked_vector_elements(T_Stk(p)) +#define stack_block(p) unchecked_vector_block(T_Stk(p)) +#define stack_top(Sc) ((Sc)->stack_end - (Sc)->stack_start) +#define temp_stack_top(p) (T_Stk(p))->object.stk.top +/* #define stack_flags(p) (T_Stk(p))->object.stk.flags */ +#define stack_clear_flags(p) (T_Stk(p))->object.stk.flags = 0 +#define stack_has_pairs(p) (((T_Stk(p))->object.stk.flags & 1) != 0) +#define stack_set_has_pairs(p) (T_Stk(p))->object.stk.flags |= 1 +#define stack_has_counters(p) (((T_Stk(p))->object.stk.flags & 2) != 0) +#define stack_set_has_counters(p) (T_Stk(p))->object.stk.flags |= 2 + +#define is_hash_table(p) (type(p) == T_HASH_TABLE) +#define is_mutable_hash_table(p) ((full_type(T_Ext(p)) & (TYPE_MASK | T_IMMUTABLE)) == T_HASH_TABLE) +#define hash_table_mask(p) (T_Hsh(p))->object.hasher.mask +#define hash_table_block(p) (T_Hsh(p))->object.hasher.block +#define unchecked_hash_table_block(p) p->object.hasher.block +#define hash_table_set_block(p, b) (T_Hsh(p))->object.hasher.block = b +#define hash_table_element(p, i) (T_Hsh(p))->object.hasher.elements[i] +#define hash_table_elements(p) (T_Hsh(p))->object.hasher.elements /* block data (dx) */ +#define hash_table_entries(p) hash_table_block(p)->nx.nx_int +#define hash_table_checker(p) (T_Hsh(p))->object.hasher.hash_func +#define hash_table_mapper(p) (T_Hsh(p))->object.hasher.loc +#define hash_table_procedures(p) T_Lst(hash_table_block(p)->ex.ex_ptr) +#define hash_table_set_procedures(p, Lst) hash_table_block(p)->ex.ex_ptr = T_Lst(Lst) /* both the checker/mapper: car/cdr, and the two typers (opt/opt2) */ +#define hash_table_procedures_checker(p) car(hash_table_procedures(p)) +#define hash_table_procedures_mapper(p) cdr(hash_table_procedures(p)) +#define hash_table_set_procedures_checker(p, f) set_car(hash_table_procedures(p), f) +#define hash_table_set_procedures_mapper(p, f) set_cdr(hash_table_procedures(p), f) +#define hash_table_key_typer(p) T_Prc(opt1_any(hash_table_procedures(p))) +#define hash_table_key_typer_unchecked(p) hash_table_block(p)->ex.ex_ptr->object.cons.opt1 +#define hash_table_set_key_typer(p, Fnc) set_opt1_any(hash_table_procedures(T_Hsh(p)), T_Prc(Fnc)) +#define hash_table_value_typer(p) T_Prc(opt2_any(hash_table_procedures(p))) +#define hash_table_value_typer_unchecked(p) hash_table_block(p)->ex.ex_ptr->object.cons.o2.opt2 +#define hash_table_set_value_typer(p, Fnc) set_opt2_any(hash_table_procedures(T_Hsh(p)), T_Prc(Fnc)) +#define weak_hash_iters(p) hash_table_block(p)->ln.tag + +#if S7_DEBUGGING +#define T_Itr_Pos(p) titr_pos(sc, T_Itr(p), __func__, __LINE__) +#define T_Itr_Len(p) titr_len(sc, T_Itr(p), __func__, __LINE__) +#define T_Itr_Hash(p) titr_hash(sc, T_Itr(p), __func__, __LINE__) +#define T_Itr_Let(p) titr_let(sc, T_Itr(p), __func__, __LINE__) +#define T_Itr_Pair(p) titr_pair(sc, T_Itr(p), __func__, __LINE__) +#else +#define T_Itr_Pos(p) p +#define T_Itr_Len(p) p +#define T_Itr_Hash(p) p +#define T_Itr_Let(p) p +#define T_Itr_Pair(p) p +#endif + +#define is_iterator(p) (type(p) == T_ITERATOR) +#define iterator_sequence(p) (T_Itr(p))->object.iter.obj +#define iterator_position(p) (T_Itr_Pos(p))->object.iter.lc.loc +#define iterator_length(p) (T_Itr_Len(p))->object.iter.lw.len +#define iterator_next(p) (T_Itr(p))->object.iter.next +#define iterator_is_at_end(p) (!iter_ok(p)) /* ((full_type(T_Itr(p)) & T_ITER_OK) == 0) */ +#define iterator_slow(p) T_Lst((T_Itr_Pair(p))->object.iter.lw.slow) +#define iterator_set_slow(p, Val) (T_Itr_Pair(p))->object.iter.lw.slow = T_Lst(Val) +#define iterator_hash_current(p) (T_Itr_Hash(p))->object.iter.lw.hcur +#define iterator_current(p) (T_Itr(p))->object.iter.cur +#define iterator_current_slot(p) T_Sln((T_Itr_Let(p))->object.iter.lc.lcur) +#define iterator_set_current_slot(p, Val) (T_Itr_Let(p))->object.iter.lc.lcur = T_Sln(Val) +#define iterator_let_cons(p) (T_Itr_Let(p))->object.iter.cur + +#define ITERATOR_END eof_object +#define ITERATOR_END_NAME "#" + +#define is_input_port(p) (type(p) == T_INPUT_PORT) +#define is_output_port(p) (type(p) == T_OUTPUT_PORT) +#define port_port(p) (T_Prt(p))->object.prt.port +#define is_string_port(p) (port_type(p) == STRING_PORT) +#define is_file_port(p) (port_type(p) == FILE_PORT) +#define is_function_port(p) (port_type(p) == FUNCTION_PORT) +#define port_filename_block(p) port_port(p)->filename_block +#define port_filename(p) port_port(p)->filename +#define port_filename_length(p) port_port(p)->filename_length +#define port_file(p) port_port(p)->file +#define port_data_block(p) port_port(p)->block +#define unchecked_port_data_block(p) p->object.prt.port->block +#define port_line_number(p) port_port(p)->line_number +#define port_file_number(p) port_port(p)->file_number +#define port_data(p) (T_Prt(p))->object.prt.data +#define port_data_size(p) (T_Prt(p))->object.prt.size +#define port_position(p) (T_Prt(p))->object.prt.point +#define port_block(p) (T_Prt(p))->object.prt.block +#define port_type(p) port_port(p)->ptype +#define port_is_closed(p) port_port(p)->is_closed +#define port_set_closed(p, Val) port_port(p)->is_closed = Val /* this can't be a type bit because sweep checks it after the type has been cleared */ +#define port_needs_free(p) port_port(p)->needs_free +#define port_next(p) port_block(p)->nx.next +#define port_output_function(p) port_port(p)->output_function /* these two are for function ports */ +#define port_input_function(p) port_port(p)->input_function +#define port_string_or_function(p) port_port(p)->orig_str +#define port_set_string_or_function(p, S) port_port(p)->orig_str = S + +#define current_input_port(Sc) T_Pri(Sc->input_port) +#define set_current_input_port(Sc, P) Sc->input_port = T_Pri(P) +#define current_output_port(Sc) T_Pro(Sc->output_port) +#define set_current_output_port(Sc, P) Sc->output_port = T_Pro(P) +#define current_error_port(Sc) T_Pro(Sc->error_port) +#define set_current_error_port(Sc, P) Sc->error_port = T_Pro(P) + +#define port_read_character(p) port_port(p)->pf->read_character +#define port_read_line(p) port_port(p)->pf->read_line +#define port_display(p) port_port(p)->pf->displayer +#define port_write_character(p) port_port(p)->pf->write_character +#define port_write_string(p) port_port(p)->pf->write_string +#define port_read_semicolon(p) port_port(p)->pf->read_semicolon +#define port_read_white_space(p) port_port(p)->pf->read_white_space +#define port_read_name(p) port_port(p)->pf->read_name +#define port_read_sharp(p) port_port(p)->pf->read_sharp +#define port_close(p) port_port(p)->pf->close_port + +#define is_c_function(f) (type(f) >= T_C_FUNCTION) +#define is_c_function_star(f) (type(f) == T_C_FUNCTION_STAR) +#define is_any_c_function(f) (type(f) >= T_C_FUNCTION_STAR) +#define is_safe_c_function(f) ((is_c_function(f)) && (is_safe_procedure(f))) +#define c_function_data(f) (T_Fnc(f))->object.fnc.c_proc +#define c_function_call(f) (T_Fnc(f))->object.fnc.ff +#define c_function_min_args(f) (T_Fnc(f))->object.fnc.required_args +#define c_function_optional_args(f) (T_Fnc(f))->object.fnc.optional_args +#define c_function_max_args(f) (T_Fnc(f))->object.fnc.all_args +#define c_function_is_aritable(f, N) ((c_function_min_args(f) <= N) && (c_function_max_args(f) >= N)) +#define c_function_name(f) c_function_data(f)->name +#define c_function_name_length(f) c_function_data(f)->name_length +#define c_function_documentation(f) c_function_data(f)->doc +#define c_function_signature(f) c_function_data(f)->signature +#define c_function_setter(f) T_Prc(c_function_data(f)->setter) +#define c_function_set_setter(f, Val) c_function_data(f)->setter = T_Prc(Val) +#define c_function_class(f) c_function_data(f)->id +#define c_function_chooser(f) c_function_data(f)->chooser +#define c_function_base(f) T_Fnc(c_function_data(f)->generic_ff) +#define c_function_set_base(f, Val) c_function_data(f)->generic_ff = T_Fnc(Val) +#define c_function_marker(f) c_function_data(f)->cam.marker /* the mark function for the vector (mark_vector_1 etc) */ +#define c_function_set_marker(f, Val) c_function_data(f)->cam.marker = Val +#define c_function_symbol(f) c_function_data(f)->sam.c_sym + +#define c_function_bool_setter(f) c_function_data(f)->dam.bool_setter +#define c_function_set_bool_setter(f, Val) c_function_data(f)->dam.bool_setter = Val + +#define c_function_arg_defaults(f) c_function_data(T_Fst(f))->dam.arg_defaults +#define c_function_call_args(f) c_function_data(T_Fst(f))->cam.call_args +#define c_function_arg_names(f) c_function_data(T_Fst(f))->sam.arg_names + +#define set_c_function(X, f) do {set_opt1_cfunc(X, f); set_fn_direct(X, c_function_call(f));} while (0) +#define c_function_opt_data(f) c_function_data(f)->opt_data + +#define is_c_macro(p) (type(p) == T_C_MACRO) +#define c_macro_data(f) (T_CMac(f))->object.fnc.c_proc +#define c_macro_call(f) (T_CMac(f))->object.fnc.ff +#define c_macro_name(f) c_macro_data(f)->name +#define c_macro_name_length(f) c_macro_data(f)->name_length +#define c_macro_min_args(f) (T_CMac(f))->object.fnc.required_args +#define c_macro_max_args(f) (T_CMac(f))->object.fnc.all_args +#define c_macro_setter(f) T_Prc(c_macro_data(f)->setter) +#define c_macro_set_setter(f, Val) c_macro_data(f)->setter = T_Prc(Val) +#define could_be_macro_setter(Obj) t_macro_setter_p[type(Obj)] + +#define is_random_state(p) (type(p) == T_RANDOM_STATE) +#define random_gmp_state(p) (p)->object.rng.state /* sweep sees free cell in big_random_state gc_list and needs to call gmprandclear on its value */ +#define random_seed(p) (T_Ran(p))->object.rng.seed +#define random_carry(p) (T_Ran(p))->object.rng.carry + +#define continuation_block(p) (T_Con(p))->object.cwcc.block +#define continuation_stack(p) T_Stk(T_Con(p)->object.cwcc.stack) +#define continuation_set_stack(p, Val) (T_Con(p))->object.cwcc.stack = T_Stk(Val) +#define continuation_stack_end(p) (T_Con(p))->object.cwcc.stack_end +#define continuation_stack_start(p) (T_Con(p))->object.cwcc.stack_start +#define continuation_stack_top(p) (continuation_stack_end(p) - continuation_stack_start(p)) +#define continuation_op_stack(p) (T_Con(p))->object.cwcc.op_stack +#define continuation_stack_size(p) continuation_block(p)->nx.ix.i1 +#define continuation_op_loc(p) continuation_block(p)->nx.ix.i2 +#define continuation_op_size(p) continuation_block(p)->ln.tag +#define continuation_key(p) continuation_block(p)->ex.ckey +/* this can overflow int32_t -- baffle_key is s7_int, so ckey should be also */ +#define continuation_name(p) continuation_block(p)->dx.d_ptr + +#define call_exit_goto_loc(p) (T_Got(p))->object.rexit.goto_loc +#define call_exit_op_loc(p) (T_Got(p))->object.rexit.op_stack_loc +#define call_exit_active(p) (T_Got(p))->object.rexit.active +#define call_exit_name(p) (T_Got(p))->object.rexit.name + +#define is_continuation(p) (type(p) == T_CONTINUATION) +#define is_goto(p) (type(p) == T_GOTO) +#define is_macro(p) (type(p) == T_MACRO) +#define is_macro_star(p) (type(p) == T_MACRO_STAR) +#define is_bacro(p) (type(p) == T_BACRO) +#define is_bacro_star(p) (type(p) == T_BACRO_STAR) +#define is_either_macro(p) ((is_macro(p)) || (is_macro_star(p))) +#define is_either_bacro(p) ((is_bacro(p)) || (is_bacro_star(p))) + +#define is_closure(p) (type(p) == T_CLOSURE) +#define is_closure_star(p) (type(p) == T_CLOSURE_STAR) +#define closure_args(p) T_Arg((T_Clo(p))->object.func.args) +#define closure_set_args(p, Val) (T_Clo(p))->object.func.args = T_Arg(Val) +#define closure_body(p) (T_Pair((T_Clo(p))->object.func.body)) +#define closure_set_body(p, Val) (T_Clo(p))->object.func.body = T_Pair(Val) +#define closure_let(p) T_Lid((T_Clo(p))->object.func.env) +#define closure_set_let(p, L) (T_Clo(p))->object.func.env = T_Lid(L) +#define closure_arity(p) (T_Clo(p))->object.func.arity +#define closure_set_arity(p, A) (T_Clo(p))->object.func.arity = A + +#define closure_setter(p) (T_Prc((T_Clo(p))->object.func.setter)) +#define closure_set_setter(p, Val) (T_Clo(p))->object.func.setter = T_Prc(Val) +#define closure_map_list(p) (T_Pair((T_Clo(p))->object.func.setter)) +#define closure_set_map_list(p, Val) (T_Clo(p))->object.func.setter = T_Pair(Val) +#define closure_setter_or_map_list(p) (T_Clo(p)->object.func.setter) +/* closure_map_list refers to a cyclic list detector in map; since in this case map makes a new closure for its own use, + * closure_map_list doesn't collide with closure_setter. + */ + +#define CLOSURE_ARITY_NOT_SET 0x40000000 +#define MAX_ARITY 0x20000000 +#define closure_arity_unknown(p) (closure_arity(p) == CLOSURE_ARITY_NOT_SET) +#define is_thunk(Sc, Fnc) ((type(Fnc) >= T_GOTO) && (s7_is_aritable(Sc, Fnc, 0))) + +#define hook_has_functions(p) (is_pair(s7_hook_functions(sc, T_Clo(p)))) + +#define catch_tag(p) (T_Cat(p))->object.rcatch.tag +#define catch_goto_loc(p) (T_Cat(p))->object.rcatch.goto_loc +#define catch_op_loc(p) (T_Cat(p))->object.rcatch.op_stack_loc +#define catch_cstack(p) (T_Cat(p))->object.rcatch.cstack +#define catch_handler(p) T_Ext((T_Cat(p))->object.rcatch.handler) +#define catch_set_handler(p, val) (T_Cat(p))->object.rcatch.handler = T_Ext(val) + +#define dynamic_wind_state(p) (T_Dyn(p))->object.winder.state +#define dynamic_wind_in(p) (T_Dyn(p))->object.winder.in +#define dynamic_wind_out(p) (T_Dyn(p))->object.winder.out +#define dynamic_wind_body(p) (T_Dyn(p))->object.winder.body + +#define is_c_object(p) (type(p) == T_C_OBJECT) +#define c_object_value(p) (T_Obj(p))->object.c_obj.value +#define c_object_type(p) (T_Obj(p))->object.c_obj.type +#define c_object_let(p) T_Lid((T_Obj(p))->object.c_obj.e) +#define c_object_set_let(p, L) (T_Obj(p))->object.c_obj.e = T_Lid(L) +#define c_object_s7(p) (T_Obj(p))->object.c_obj.sc + +#define c_object_info(Sc, p) Sc->c_object_types[c_object_type(T_Obj(p))] +#define c_object_free(Sc, p) c_object_info(Sc, p)->free +#define c_object_mark(Sc, p) c_object_info(Sc, p)->mark +#define c_object_gc_mark(Sc, p) c_object_info(Sc, p)->gc_mark +#define c_object_gc_free(Sc, p) c_object_info(Sc, p)->gc_free +#define c_object_ref(Sc, p) c_object_info(Sc, p)->ref +#define c_object_getf(Sc, p) c_object_info(Sc, p)->getter +#define c_object_set(Sc, p) c_object_info(Sc, p)->set +#define c_object_setf(Sc, p) c_object_info(Sc, p)->setter +#if (!DISABLE_DEPRECATED) + #define c_object_print(Sc, p) c_object_info(Sc, p)->print +#endif +#define c_object_len(Sc, p) c_object_info(Sc, p)->length +#define c_object_eql(Sc, p) c_object_info(Sc, p)->eql +#define c_object_equal(Sc, p) c_object_info(Sc, p)->equal +#define c_object_equivalent(Sc, p) c_object_info(Sc, p)->equivalent +#define c_object_fill(Sc, p) c_object_info(Sc, p)->fill +#define c_object_copy(Sc, p) c_object_info(Sc, p)->copy +#define c_object_reverse(Sc, p) c_object_info(Sc, p)->reverse +#define c_object_to_list(Sc, p) c_object_info(Sc, p)->to_list +#define c_object_to_string(Sc, p) c_object_info(Sc, p)->to_string +#define c_object_scheme_name(Sc, p) T_Str(c_object_info(Sc, p)->scheme_name) + +#define c_pointer(p) (T_Ptr(p))->object.cptr.c_pointer +#define c_pointer_type(p) (T_Ptr(p))->object.cptr.c_type +#define c_pointer_info(p) (T_Ptr(p))->object.cptr.info +#define c_pointer_weak1(p) (T_Ptr(p))->object.cptr.weak1 +#define c_pointer_weak2(p) (T_Ptr(p))->object.cptr.weak2 +#define c_pointer_set_weak1(p, q) (T_Ptr(p))->object.cptr.weak1 = T_Ext(q) +#define c_pointer_set_weak2(p, q) (T_Ptr(p))->object.cptr.weak2 = T_Ext(q) +#define is_c_pointer(p) (type(p) == T_C_POINTER) + +#define is_counter(p) (type(p) == T_COUNTER) +#define counter_result(p) (T_Ctr(p))->object.ctr.result +#define counter_set_result(p, Val) (T_Ctr(p))->object.ctr.result = T_Ext(Val) +#define counter_list(p) (T_Ctr(p))->object.ctr.list +#define counter_set_list(p, Val) (T_Ctr(p))->object.ctr.list = T_Ext(Val) +#define counter_capture(p) (T_Ctr(p))->object.ctr.cap +#define counter_set_capture(p, Val) (T_Ctr(p))->object.ctr.cap = Val +#define counter_let(p) T_Lid((T_Ctr(p))->object.ctr.env) +#define counter_set_let(p, L) (T_Ctr(p))->object.ctr.env = T_Lid(L) +#define counter_slots(p) T_Sln(T_Ctr(p)->object.ctr.slots) +#define counter_set_slots(p, Val) (T_Ctr(p))->object.ctr.slots = T_Sln(Val) + +#if S7_DEBUGGING +#define init_temp(p, Val) do {if (p != sc->unused) fprintf(stderr, "%s[%d]: temp %s\n", __func__, __LINE__, display(p)); p = T_Ext(Val);} while (0) +#else +#define init_temp(p, Val) p = Val +#endif + +#if __cplusplus && HAVE_COMPLEX_NUMBERS + using namespace std; /* the code has to work in C as well as C++, so we can't scatter std:: all over the place */ + typedef complex s7_complex; + static s7_double Real(complex x) {return(real(x));} /* protect the C++ name */ + static s7_double Imag(complex x) {return(imag(x));} +#endif + +#define integer(p) (T_Int(p))->object.number.integer_value +#define set_integer(p, x) integer(p) = x +#define real(p) (T_Rel(p))->object.number.real_value +#define set_real(p, x) real(p) = x +#define numerator(p) (T_Frc(p))->object.number.fraction_value.numerator +#define denominator(p) (T_Frc(p))->object.number.fraction_value.denominator +#define fraction(p) (((long_double)numerator(p)) / ((long_double)denominator(p))) +#define inverted_fraction(p) (((long_double)denominator(p)) / ((long_double)numerator(p))) +#define real_part(p) (T_Cmp(p))->object.number.complex_value.rl +#define set_real_part(p, x) real_part(p) = x +#define imag_part(p) (T_Cmp(p))->object.number.complex_value.im +#define set_imag_part(p, x) imag_part(p) = x +#if HAVE_COMPLEX_NUMBERS + #define to_c_complex(p) CMPLX(real_part(p), imag_part(p)) +#endif + +#if WITH_GMP +#define big_integer(p) ((T_Bgi(p))->object.number.bgi->n) +#define big_integer_nxt(p) (p)->object.number.bgi->nxt +#define big_integer_bgi(p) (p)->object.number.bgi +#define big_ratio(p) ((T_Bgf(p))->object.number.bgr->q) +#define big_ratio_nxt(p) (p)->object.number.bgr->nxt +#define big_ratio_bgr(p) (p)->object.number.bgr +#define big_real(p) ((T_Bgr(p))->object.number.bgf->x) +#define big_real_nxt(p) (p)->object.number.bgf->nxt +#define big_real_bgf(p) (p)->object.number.bgf +#define big_complex(p) ((T_Bgz(p))->object.number.bgc->z) +#define big_complex_nxt(p) (p)->object.number.bgc->nxt +#define big_complex_bgc(p) (p)->object.number.bgc +#endif + +#if S7_DEBUGGING +const char *display(s7_pointer obj); +const char *display(s7_pointer obj) +{ + const char *res; + if (!has_methods(obj)) + return(string_value(s7_object_to_string(cur_sc, obj, false))); + clear_type_bit(obj, T_HAS_METHODS); /* clear_has_methods calls T_Met -> check_ref9 */ + res = string_value(s7_object_to_string(cur_sc, obj, false)); + set_type_bit(obj, T_HAS_METHODS); /* same for set_has_methods */ + return(res); +} +#else +#define display(Obj) string_value(s7_object_to_string(sc, Obj, false)) +#endif +#define display_80(Obj) string_value(object_to_truncated_string(sc, Obj, 80)) + +#if S7_DEBUGGING +static void set_type_1(s7_pointer p, uint64_t f, const char *func, int32_t line) +{ + p->alloc_line = line; + p->alloc_func = func; + p->alloc_type = f; + p->explicit_free_line = 0; + p->uses++; + if (((f) & TYPE_MASK) == T_FREE) + fprintf(stderr, "%d: set free, %p type to %" PRIx64 "\n", __LINE__, p, (int64_t)(f)); + else + if (((f) & TYPE_MASK) >= NUM_TYPES) + fprintf(stderr, "%d: set invalid type, %p type to %" PRIx64 "\n", __LINE__, p, (int64_t)(f)); + else + { + if (((full_type(p) & T_IMMUTABLE) != 0) && ((full_type(p) != (uint64_t)(f)))) + { + fprintf(stderr, "%s[%d]: set immutable %p type %d to %" ld64 "\n", __func__, __LINE__, p, unchecked_type(p), (int64_t)(f)); + abort(); + } + if (((full_type(p) & T_UNHEAP) != 0) && (((f) & T_UNHEAP) == 0)) + fprintf(stderr, "%s[%d]: clearing unheap in set type!\n", __func__, __LINE__); + } + full_type(p) = f; +} +#endif + +#define number_name(p) (char *)((T_Num(p))->object.number_name.name + 1) +#define number_name_length(p) (T_Num(p))->object.number_name.name[0] + +static void set_number_name(s7_pointer p, const char *name, int32_t len) +{ + /* if no number name: teq +110 tread +30 tform +90 */ + if ((len >= 0) && (len < NUMBER_NAME_SIZE) && (!is_mutable_number(p))) + { + set_has_number_name(p); + number_name_length(p) = (uint8_t)len; + memcpy((void *)number_name(p), (const void *)name, len); + (number_name(p))[len] = 0; + } +} + +static s7_int s7_int_min = 0; +static int32_t s7_int_digits_by_radix[17]; + +#define S7_INT_BITS 63 + +#define S7_INT64_MAX 9223372036854775807LL +#define S7_INT64_MIN (int64_t)(-S7_INT64_MAX - 1LL) + +#define S7_INT32_MAX 2147483647LL +#define S7_INT32_MIN (-S7_INT32_MAX - 1LL) + +static void init_int_limits(void) +{ +#if WITH_GMP + #define S7_LOG_INT64_MAX 36.736800 +#else + /* actually not safe = (log (- (expt 2 63) 1)) and (log (- (expt 2 31) 1)) (using 63 and 31 bits) */ + #define S7_LOG_INT64_MAX 43.668274 +#endif + s7_int_min = S7_INT64_MIN; /* see comment in s7_make_ratio -- we're trying to hack around a gcc bug (9.2.1 Ubuntu) */ + s7_int_digits_by_radix[0] = 0; + s7_int_digits_by_radix[1] = 0; + for (int32_t i = 2; i < 17; i++) + s7_int_digits_by_radix[i] = (int32_t)(floor(S7_LOG_INT64_MAX / log((double)i))); +} + +static s7_pointer make_permanent_integer(s7_int i) +{ + s7_pointer p = (s7_pointer)Calloc(1, sizeof(s7_cell)); /* Calloc to clear name */ + full_type(p) = T_IMMUTABLE | T_INTEGER | T_UNHEAP; + integer(p) = i; + return(p); +} + +#define NUM_CHARS 256 +#ifndef NUM_SMALL_INTS + #define NUM_SMALL_INTS 8192 + /* 65536: tshoot -6, tvect -50, dup -26, trclo -27, tmap -48, tsort -14, tlet -16, trec -58, thash -40 */ +#else +#if (NUM_SMALL_INTS < NUM_CHARS) /* g_char_to_integer assumes this is at least NUM_CHARS, as does the byte_vector stuff (256) */ + #error NUM_SMALL_INTS is less than NUM_CHARS which will not work +#endif +#endif + +static bool t_number_separator_p[NUM_CHARS]; +static s7_pointer *small_ints = NULL; +#define small_int(Val) small_ints[Val] +#define is_small_int(n) ((n & ~(NUM_SMALL_INTS - 1)) == 0) /* ((n >= 0) && (n < NUM_SMALL_INTS)) is slower */ + +static s7_pointer real_zero, real_NaN, complex_NaN, real_pi, real_one, arity_not_set, max_arity, real_infinity, real_minus_infinity; +static s7_pointer int_zero, int_one, int_two, int_three, minus_one, minus_two, mostfix, leastfix; + +static void init_small_ints(void) +{ + const char *ones[10] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}; + s7_cell *cells = (s7_cell *)Malloc(NUM_SMALL_INTS * sizeof(s7_cell)); /* was calloc 14-Apr-22 */ + small_ints = (s7_pointer *)Malloc(NUM_SMALL_INTS * sizeof(s7_pointer)); + for (int32_t i = 0; i < NUM_SMALL_INTS; i++) + { + s7_pointer p; + small_ints[i] = &cells[i]; + p = small_ints[i]; + full_type(p) = T_IMMUTABLE | T_INTEGER | T_UNHEAP; + integer(p) = i; + } + for (int32_t i = 0; i < 10; i++) + set_number_name(small_ints[i], ones[i], 1); + + /* setup a few other numbers while we're here */ + #define EXTRA_NUMBERS 11 + cells = (s7_cell *)Calloc(EXTRA_NUMBERS, sizeof(s7_cell)); + + #define init_integer(Ptr, Num, Name, Name_Len) \ + do {set_full_type(Ptr, T_INTEGER | T_IMMUTABLE | T_UNHEAP); set_integer(Ptr, Num); set_number_name(Ptr, Name, Name_Len);} while (0) + #define init_integer_no_name(Ptr, Num) \ + do {set_full_type(Ptr, T_INTEGER | T_IMMUTABLE | T_UNHEAP); set_integer(Ptr, Num);} while (0) + + #define init_real(Ptr, Num, Name, Name_Len) \ + do {set_full_type(Ptr, T_REAL | T_IMMUTABLE | T_UNHEAP); set_real(Ptr, Num); set_number_name(Ptr, Name, Name_Len);} while (0) + #define init_real_no_name(Ptr, Num) \ + do {set_full_type(Ptr, T_REAL | T_IMMUTABLE | T_UNHEAP); set_real(Ptr, Num);} while (0) + + #define init_complex(Ptr, Real, Imag, Name, Name_Len) \ + do {set_full_type(Ptr, T_COMPLEX | T_IMMUTABLE | T_UNHEAP); set_real_part(Ptr, Real); set_imag_part(Ptr, Imag); set_number_name(Ptr, Name, Name_Len);} while (0) + + real_zero = &cells[0]; init_real(real_zero, 0.0, "0.0", 3); + real_one = &cells[1]; init_real(real_one, 1.0, "1.0", 3); + real_NaN = &cells[2]; init_real(real_NaN, NAN, "+nan.0", 6); + complex_NaN = &cells[10]; init_complex(complex_NaN, NAN, NAN, "+nan.0+nan.0i", 13); + real_infinity = &cells[3]; init_real(real_infinity, INFINITY, "+inf.0", 6); + real_minus_infinity = &cells[4]; init_real(real_minus_infinity, -INFINITY, "-inf.0", 6); + real_pi = &cells[5]; init_real_no_name(real_pi, 3.1415926535897932384626433832795029L); + + arity_not_set = &cells[6]; init_integer_no_name(arity_not_set, CLOSURE_ARITY_NOT_SET); + max_arity = &cells[7]; init_integer_no_name(max_arity, MAX_ARITY); + minus_one = &cells[8]; init_integer(minus_one, -1, "-1", 2); + minus_two = &cells[9]; init_integer(minus_two, -2, "-2", 2); + int_zero = small_ints[0]; + int_one = small_ints[1]; + int_two = small_ints[2]; + int_three = small_ints[3]; + + mostfix = make_permanent_integer(S7_INT64_MAX); + leastfix = make_permanent_integer(s7_int_min); + set_number_name(mostfix, "9223372036854775807", 19); + set_number_name(leastfix, "-9223372036854775808", 20); + + for (int32_t i = 0; i < NUM_CHARS; i++) t_number_separator_p[i] = true; + t_number_separator_p[(uint8_t)'i'] = false; + t_number_separator_p[(uint8_t)'+'] = false; + t_number_separator_p[(uint8_t)'-'] = false; + t_number_separator_p[(uint8_t)'/'] = false; + t_number_separator_p[(uint8_t)'@'] = false; + t_number_separator_p[(uint8_t)'.'] = false; + t_number_separator_p[(uint8_t)'e'] = false; + t_number_separator_p[(uint8_t)'E'] = false; +} + +#define clamp_length(NLen, Len) (((NLen) < (Len)) ? (NLen) : (Len)) + + +/* -------------------------------------------------------------------------------- */ +#if (defined(__FreeBSD__)) || ((defined(__linux__)) && (__GLIBC__ >= 2) && (__GLIBC_MINOR__ > 17)) || (defined(__OpenBSD__)) || (defined(__NetBSD__)) + static inline s7_int my_clock(void) + { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + /* coarse: 0.057u 0.007s, monotonic: 0.083u 0.007s, clock(): 0.624u 0.372s -- coarse since Linux 2.6.32, glibc > 2.17 + * FreeBSD has CLOCK_MONOTONIC_FAST in place of COARSE, OpenBSD and netBSD have neither + * clock_getres places 1 in tv_nsec in linux, so I assume I divide billion/tv_nsec + * MacOSX has clock_get_time, and after Sierra 10.12 has clock_gettime + * apparently we include /usr/include/AvailabilityMacros.h, then #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12 + * Windows has QueryPerformanceCounter or something + * maybe just check for POSIX compatibility? + */ + return(ts.tv_sec * 1000000000 + ts.tv_nsec); /* accumulated into s7_int so this should be ok: s7.h gives it 64 bits */ + } + + static s7_int ticks_per_second(void) + { + struct timespec ts; + clock_getres(CLOCK_MONOTONIC, &ts); + return((ts.tv_nsec == 0) ? 1000000000 : (1000000000 / ts.tv_nsec)); + } +#else + #define my_clock clock /* but this is cpu time? */ + #define ticks_per_second() CLOCKS_PER_SEC +#endif + +#ifndef GC_TRIGGER_SIZE + #define GC_TRIGGER_SIZE 64 +#endif + +#if S7_DEBUGGING +static void try_to_call_gc_1(s7_scheme *sc, const char *func, int32_t line); +#define try_to_call_gc(Sc) try_to_call_gc_1(Sc, __func__, __LINE__) +#else +static void try_to_call_gc(s7_scheme *sc); +#endif + +#define GC_STATS 1 +#define HEAP_STATS 2 +#define STACK_STATS 4 +#define PROTECTED_OBJECTS_STATS 8 + +#define show_gc_stats(Sc) ((Sc->gc_stats & GC_STATS) != 0) +#define show_stack_stats(Sc) ((Sc->gc_stats & STACK_STATS) != 0) +#define show_heap_stats(Sc) ((Sc->gc_stats & HEAP_STATS) != 0) +#define show_protected_objects_stats(Sc) ((Sc->gc_stats & PROTECTED_OBJECTS_STATS) != 0) + + +/* new_cell has to include the new cell's type. In the free list, it is 0 (T_FREE). If we remove it here, + * but then hit some error before setting the type, the GC sweep thinks it is a free cell already and + * does not return it to the free list: a memory leak. + */ +#if (!S7_DEBUGGING) +#define new_cell(Sc, Obj, Type) \ + do { \ + if (Sc->free_heap_top <= Sc->free_heap_trigger) try_to_call_gc(Sc); \ + Obj = (*(--(Sc->free_heap_top))); \ + set_full_type(Obj, Type); \ + } while (0) + +#define new_cell_no_check(Sc, Obj, Type) do {Obj = (*(--(Sc->free_heap_top))); set_full_type(Obj, Type);} while (0) + /* since sc->free_heap_trigger is GC_TRIGGER_SIZE above the free heap base, we don't need + * to check it repeatedly after the first such check. + */ +#else + +#define new_cell(Sc, Obj, Type) \ + do { \ + if (Sc->free_heap_top <= Sc->free_heap_trigger) try_to_call_gc(Sc); \ + Obj = (*(--(Sc->free_heap_top))); \ + Obj->debugger_bits = 0; Obj->gc_func = NULL; Obj->gc_line = 0; \ + set_full_type(Obj, Type); \ + } while (0) + +#define new_cell_no_check(Sc, Obj, Type) \ + do { \ + Obj = (*(--(Sc->free_heap_top))); \ + if (Sc->free_heap_top < Sc->free_heap) {fprintf(stderr, "%s[%d]: free heap exhausted\n", __func__, __LINE__); abort();}\ + Obj->debugger_bits = 0; Obj->gc_func = NULL; Obj->gc_line = 0; \ + set_full_type(Obj, Type); \ + } while (0) +#endif + +/* #define gc_if_at_trigger(Sc) if (Sc->free_heap_top <= Sc->free_heap_trigger) try_to_call_gc(Sc) */ + +#if WITH_GCC +#define make_integer(Sc, N) \ + ({ s7_int _N_; _N_ = (N); (is_small_int(_N_) ? small_int(_N_) : ({ s7_pointer _I_; new_cell(Sc, _I_, T_INTEGER); integer(_I_) = _N_; _I_;}) ); }) +#define make_integer_unchecked(Sc, N) \ + ({ s7_int _N_; _N_ = (N); (is_small_int(_N_) ? small_int(_N_) : ({ s7_pointer _I_; new_cell_no_check(Sc, _I_, T_INTEGER); integer(_I_) = _N_; _I_;}) ); }) + +#define make_real(Sc, X) ({ s7_pointer _R_; s7_double _N_ = (X); new_cell(Sc, _R_, T_REAL); set_real(_R_, _N_); _R_;}) +#define make_real_unchecked(Sc, X) ({ s7_pointer _R_; s7_double _N_ = (X); new_cell_no_check(Sc, _R_, T_REAL); set_real(_R_, _N_); _R_;}) + +#define make_complex_not_0i(Sc, R, I) ({ s7_pointer _C_; new_cell(Sc, _C_, T_COMPLEX); set_real_part(_C_, R); set_imag_part(_C_, I); _C_;}) +#define make_complex(Sc, R, I) \ + ({ s7_double _im_; _im_ = (I); ((_im_ == 0.0) ? make_real(Sc, R) : \ + ({ s7_pointer _C_; new_cell(Sc, _C_, T_COMPLEX); set_real_part(_C_, R); set_imag_part(_C_, _im_); _C_;}) ); }) + +#define real_to_double(Sc, X, Caller) ({ s7_pointer _x_; _x_ = (X); ((type(_x_) == T_REAL) ? real(_x_) : s7_number_to_real_with_caller(Sc, _x_, Caller)); }) +#define rational_to_double(Sc, X) ({ s7_pointer _x_; _x_ = (X); ((type(_x_) == T_INTEGER) ? (s7_double)integer(_x_) : fraction(_x_)); }) + +#else + +#define make_integer(Sc, N) s7_make_integer(Sc, N) +#define make_integer_unchecked(Sc, N) s7_make_integer(Sc, N) +#define make_real(Sc, X) s7_make_real(Sc, X) +#define make_real_unchecked(Sc, X) s7_make_real(Sc, X) +#define make_complex(Sc, R, I) s7_make_complex(Sc, R, I) +#define make_complex_not_0i(Sc, R, I) s7_make_complex(Sc, R, I) +#define real_to_double(Sc, X, Caller) s7_number_to_real_with_caller(Sc, X, Caller) +#define rational_to_double(Sc, X) s7_number_to_real(Sc, X) +#endif + +static s7_pointer wrap_integer(s7_scheme *sc, s7_int x) +{ + s7_pointer p; + if (is_small_int(x)) return(small_int(x)); + p = car(sc->integer_wrappers); + integer(p) = x; + sc->integer_wrappers = cdr(sc->integer_wrappers); + return(p); +} + +static s7_pointer wrap_real(s7_scheme *sc, s7_double x) +{ + s7_pointer p = car(sc->real_wrappers); + real(p) = x; + sc->real_wrappers = cdr(sc->real_wrappers); + return(p); +} + + +/* -------------------------------------------------------------------------------- + * local versions of some standard C library functions + * timing tests involving these are very hard to interpret, local_memset is faster using int64_t than int32_t + */ + +static void local_memset(void *s, uint8_t val, size_t n) +{ + uint8_t *s2; +#if S7_ALIGNED + s2 = (uint8_t *)s; +#else +#if (defined(__x86_64__) || defined(__i386__)) + if (n >= 8) + { + int64_t *s1 = (int64_t *)s; + size_t n8 = n >> 3; + int64_t ival = val | (val << 8) | (val << 16) | (((uint64_t)val) << 24); /* uint64_t casts make gcc/clang/fsanitize happy */ + ival = (((uint64_t)ival) << 32) | ival; + if ((n8 & 0x3) == 0) + while (n8 > 0) {LOOP_4(*s1++ = ival); n8 -= 4;} + else do {*s1++ = ival;} while (--n8 > 0); + n &= 7; + s2 = (uint8_t *)s1; + } + else s2 = (uint8_t *)s; +#else + s2 = (uint8_t *)s; +#endif +#endif + while (n > 0) + { + *s2++ = val; + n--; + } +} + +static inline s7_int safe_strlen(const char *str) /* this is safer than strlen, and slightly faster */ +{ + const char *tmp = str; + if ((!tmp) || (!(*tmp))) return(0); + for (; *tmp; ++tmp); + return(tmp - str); +} + +static char *copy_string_with_length(const char *str, s7_int len) +{ + char *newstr; + if ((S7_DEBUGGING) && ((len <= 0) || (!str))) fprintf(stderr, "%s[%d]: len: %" ld64 ", str: %s\n", __func__, __LINE__, len, str); + if (len > (1LL << 48)) return(NULL); /* squelch an idiotic warning */ + newstr = (char *)Malloc(len + 1); + memcpy((void *)newstr, (const void *)str, len); /* we check len != 0 above -- 24-Jan-22 */ + newstr[len] = '\0'; + return(newstr); +} + +static char *copy_string(const char *str) {return(copy_string_with_length(str, safe_strlen(str)));} + +#if 0 +static bool local_strcmp(const char *s1, const char *s2) +{ + while (true) + { + if (*s1 != *s2++) return(false); + if (*s1++ == 0) return(true); + } + return(true); +} +#else +#define local_strcmp(S1, S2) (strcmp(S1, S2) == 0) +/* I think libc strcmp is much faster than it used to be, and beats the code above */ +#endif + +#define c_strings_are_equal(Str1, Str2) (local_strcmp(Str1, Str2)) +/* scheme strings can have embedded nulls */ + +static bool safe_strcmp(const char *s1, const char *s2) +{ + if ((!s1) || (!s2)) return(s1 == s2); + return(local_strcmp(s1, s2)); +} + +static bool local_strncmp(const char *s1, const char *s2, size_t n) /* not strncmp because scheme strings can have embedded nulls */ +{ +#if ((!S7_ALIGNED) && (defined(__x86_64__) || defined(__i386__))) /* unaligned accesses are safe on i386 hardware, sez everyone */ + if (n >= 8) + { + size_t n8 = n >> 3; + int64_t *is1 = (int64_t *)s1, *is2 = (int64_t *)s2; + do {if (*is1++ != *is2++) return(false);} while (--n8 > 0); /* in tbig LOOP_4 is slower? */ + s1 = (const char *)is1; + s2 = (const char *)is2; + n &= 7; + } +#endif + while (n > 0) + { + if (*s1++ != *s2++) return(false); + n--; + } + return(true); +} + +#define strings_are_equal_with_length(Str1, Str2, Len) (local_strncmp(Str1, Str2, Len)) + +static Sentinel size_t catstrs(char *dst, size_t len, ...) /* NULL-terminated arg list */ +{ + const char *dend = (const char *)(dst + len - 1); /* -1 for null at end? */ + char *d = dst; + va_list ap; + while ((*d) && (d < dend)) d++; /* stop at NULL or end-of-buffer */ + va_start(ap, len); + for (const char *s = va_arg(ap, const char *); s != NULL; s = va_arg(ap, const char *)) + while ((*s) && (d < dend)) {*d++ = *s++;} + *d = '\0'; + va_end (ap); + return(d - dst); +} + +static Sentinel size_t catstrs_direct(char *dst, const char *s1, ...) +{ /* NULL-terminated arg list, dst is destination only (assumed empty), all args known to fit in dst */ + char *d = dst; + va_list ap; + va_start(ap, s1); + for (const char *s = s1; s != NULL; s = va_arg(ap, const char *)) + while (*s) {*d++ = *s++;} + *d = '\0'; + va_end (ap); + return(d - dst); +} + +static char *pos_int_to_str(s7_scheme *sc, s7_int num, s7_int *len, char endc) +{ + char *p = (char *)(sc->int_to_str3 + INT_TO_STR_SIZE - 1); + char *op = p; + *p-- = '\0'; + if (endc != '\0') *p-- = endc; + do {*p-- = "0123456789"[num % 10]; num /= 10;} while (num); + (*len) = op - p; /* this includes the trailing #\null */ + return((char *)(p + 1)); +} + +static char *pos_int_to_str_direct(s7_scheme *sc, s7_int num) +{ + char *p = (char *)(sc->int_to_str4 + INT_TO_STR_SIZE - 1); + *p-- = '\0'; + do {*p-- = "0123456789"[num % 10]; num /= 10;} while (num); + return((char *)(p + 1)); +} + +static char *pos_int_to_str_direct_1(s7_scheme *sc, s7_int num) +{ + char *p = (char *)(sc->int_to_str5 + INT_TO_STR_SIZE - 1); + *p-- = '\0'; + do {*p-- = "0123456789"[num % 10]; num /= 10;} while (num); + return((char *)(p + 1)); +} + +#if WITH_GCC + #if S7_DEBUGGING + static s7_pointer lookup_1(s7_scheme *sc, const s7_pointer symbol); + #define lookup(Sc, Sym) check_null_sym(Sc, lookup_1(Sc, Sym), Sym, __LINE__, __func__) + static s7_pointer check_null_sym(s7_scheme *sc, s7_pointer p, s7_pointer sym, int32_t line, const char *func); + #define lookup_unexamined(Sc, Sym) lookup_1(Sc, Sym) + #define lookup_checked(Sc, Sym) ({s7_pointer _x_; _x_ = lookup_1(Sc, Sym); ((_x_) ? _x_ : unbound_variable(Sc, Sym));}) + #else + static inline s7_pointer lookup(s7_scheme *sc, const s7_pointer symbol); + #define lookup_unexamined(Sc, Sym) lookup(Sc, Sym) + #define lookup_checked(Sc, Sym) ({s7_pointer _x_; _x_ = lookup(Sc, Sym); ((_x_) ? _x_ : unbound_variable(Sc, Sym));}) + #endif +#else + static inline s7_pointer lookup(s7_scheme *sc, const s7_pointer symbol); + #define lookup_unexamined(Sc, Sym) s7_symbol_value(Sc, Sym) /* changed 3-Nov-22 -- we're using lookup_unexamined below to avoid the unbound_variable check */ + #define lookup_checked(Sc, Sym) lookup(Sc, Sym) +#endif + + +/* ---------------- evaluator ops ---------------- */ +/* C=constant, S=symbol, A=fx-callable, Q=quote, N=any number of next >= 1, FX=list of A's, P=parlous?, O=one form, M=multiform */ +enum {OP_UNOPT, OP_GC_PROTECT, /* must be an even number of ops here, op_gc_protect used below as lower boundary marker */ + + OP_SAFE_C_NC, HOP_SAFE_C_NC, OP_SAFE_C_S, HOP_SAFE_C_S, + OP_SAFE_C_SS, HOP_SAFE_C_SS, OP_SAFE_C_SC, HOP_SAFE_C_SC, OP_SAFE_C_CS, HOP_SAFE_C_CS, OP_SAFE_C_CQ, HOP_SAFE_C_CQ, + OP_SAFE_C_SSS, HOP_SAFE_C_SSS, OP_SAFE_C_SCS, HOP_SAFE_C_SCS, OP_SAFE_C_SSC, HOP_SAFE_C_SSC, OP_SAFE_C_CSS, HOP_SAFE_C_CSS, + OP_SAFE_C_SCC, HOP_SAFE_C_SCC, OP_SAFE_C_CSC, HOP_SAFE_C_CSC, OP_SAFE_C_CCS, HOP_SAFE_C_CCS, + OP_SAFE_C_NS, HOP_SAFE_C_NS, OP_SAFE_C_opNCq, HOP_SAFE_C_opNCq, OP_SAFE_C_opSq, HOP_SAFE_C_opSq, + OP_SAFE_C_opSSq, HOP_SAFE_C_opSSq, OP_SAFE_C_opSCq, HOP_SAFE_C_opSCq, + OP_SAFE_C_opCSq, HOP_SAFE_C_opCSq, OP_SAFE_C_S_opSq, HOP_SAFE_C_S_opSq, + OP_SAFE_C_C_opSCq, HOP_SAFE_C_C_opSCq, OP_SAFE_C_S_opSCq, HOP_SAFE_C_S_opSCq, OP_SAFE_C_S_opCSq, HOP_SAFE_C_S_opCSq, + OP_SAFE_C_opSq_S, HOP_SAFE_C_opSq_S, OP_SAFE_C_opSq_C, HOP_SAFE_C_opSq_C, + OP_SAFE_C_opSq_opSq, HOP_SAFE_C_opSq_opSq, OP_SAFE_C_S_opSSq, HOP_SAFE_C_S_opSSq, OP_SAFE_C_C_opSq, HOP_SAFE_C_C_opSq, + OP_SAFE_C_opCSq_C, HOP_SAFE_C_opCSq_C, OP_SAFE_C_opSSq_C, HOP_SAFE_C_opSSq_C, OP_SAFE_C_C_opSSq, HOP_SAFE_C_C_opSSq, + OP_SAFE_C_opSSq_opSSq, HOP_SAFE_C_opSSq_opSSq, OP_SAFE_C_opSSq_opSq, HOP_SAFE_C_opSSq_opSq, OP_SAFE_C_opSq_opSSq, HOP_SAFE_C_opSq_opSSq, + OP_SAFE_C_opSSq_S, HOP_SAFE_C_opSSq_S, OP_SAFE_C_opCSq_S, HOP_SAFE_C_opCSq_S, OP_SAFE_C_opSCq_C, HOP_SAFE_C_opSCq_C, + OP_SAFE_C_op_opSSqq_S, HOP_SAFE_C_op_opSSqq_S, OP_SAFE_C_op_opSqq, HOP_SAFE_C_op_opSqq, + OP_SAFE_C_op_S_opSqq, HOP_SAFE_C_op_S_opSqq, OP_SAFE_C_op_opSq_Sq, HOP_SAFE_C_op_opSq_Sq, OP_SAFE_C_opSq_CS, HOP_SAFE_C_opSq_CS, + + OP_SAFE_C_A, HOP_SAFE_C_A, OP_SAFE_C_AA, HOP_SAFE_C_AA, OP_SAFE_C_SA, HOP_SAFE_C_SA, OP_SAFE_C_AS, HOP_SAFE_C_AS, + OP_SAFE_C_CA, HOP_SAFE_C_CA, OP_SAFE_C_AC, HOP_SAFE_C_AC, OP_SAFE_C_AAA, HOP_SAFE_C_AAA, OP_SAFE_C_4A, HOP_SAFE_C_4A, + OP_SAFE_C_NA, HOP_SAFE_C_NA, OP_SAFE_C_ALL_CA, HOP_SAFE_C_ALL_CA, + OP_SAFE_C_SSA, HOP_SAFE_C_SSA, OP_SAFE_C_SAS, HOP_SAFE_C_SAS, OP_SAFE_C_SAA, HOP_SAFE_C_SAA, + OP_SAFE_C_CSA, HOP_SAFE_C_CSA, OP_SAFE_C_SCA, HOP_SAFE_C_SCA, OP_SAFE_C_ASS, HOP_SAFE_C_ASS, + OP_SAFE_C_CAC, HOP_SAFE_C_CAC, OP_SAFE_C_AGG, HOP_SAFE_C_AGG, + OP_SAFE_C_opAq, HOP_SAFE_C_opAq, OP_SAFE_C_opAAq, HOP_SAFE_C_opAAq, OP_SAFE_C_opAAAq, HOP_SAFE_C_opAAAq, + OP_SAFE_C_S_opAq, HOP_SAFE_C_S_opAq, OP_SAFE_C_opAq_S, HOP_SAFE_C_opAq_S, OP_SAFE_C_S_opAAq, HOP_SAFE_C_S_opAAq, + OP_SAFE_C_STAR, HOP_SAFE_C_STAR, OP_SAFE_C_STAR_A, HOP_SAFE_C_STAR_A, + OP_SAFE_C_STAR_AA, HOP_SAFE_C_STAR_AA, OP_SAFE_C_STAR_NA, HOP_SAFE_C_STAR_NA, + + OP_SAFE_C_P, HOP_SAFE_C_P, OP_SAFE_C_PP, HOP_SAFE_C_PP, OP_SAFE_C_FF, HOP_SAFE_C_FF, OP_SAFE_C_SP, HOP_SAFE_C_SP, + OP_SAFE_C_CP, HOP_SAFE_C_CP, OP_SAFE_C_AP, HOP_SAFE_C_AP, OP_SAFE_C_PA, HOP_SAFE_C_PA, OP_SAFE_C_PS, HOP_SAFE_C_PS, + OP_SAFE_C_PC, HOP_SAFE_C_PC, OP_SAFE_C_SSP, HOP_SAFE_C_SSP, OP_ANY_C_NP, HOP_ANY_C_NP, OP_SAFE_C_3P, HOP_SAFE_C_3P, + + OP_THUNK, HOP_THUNK, OP_THUNK_O, HOP_THUNK_O, OP_THUNK_ANY, HOP_THUNK_ANY, + OP_SAFE_THUNK, HOP_SAFE_THUNK, OP_SAFE_THUNK_A, HOP_SAFE_THUNK_A, OP_SAFE_THUNK_ANY, HOP_SAFE_THUNK_ANY, + + OP_CLOSURE_S, HOP_CLOSURE_S, OP_CLOSURE_S_O, HOP_CLOSURE_S_O, + OP_CLOSURE_A, HOP_CLOSURE_A, OP_CLOSURE_A_O, HOP_CLOSURE_A_O, OP_CLOSURE_P, HOP_CLOSURE_P, + OP_CLOSURE_AP, HOP_CLOSURE_AP, OP_CLOSURE_PA, HOP_CLOSURE_PA, OP_CLOSURE_PP, HOP_CLOSURE_PP, + OP_CLOSURE_FA, HOP_CLOSURE_FA, OP_CLOSURE_SS, HOP_CLOSURE_SS, OP_CLOSURE_SS_O, HOP_CLOSURE_SS_O, + OP_CLOSURE_SC, HOP_CLOSURE_SC, OP_CLOSURE_SC_O, HOP_CLOSURE_SC_O, + OP_CLOSURE_3S, HOP_CLOSURE_3S, OP_CLOSURE_3S_O, HOP_CLOSURE_3S_O, OP_CLOSURE_4S, HOP_CLOSURE_4S, OP_CLOSURE_4S_O, HOP_CLOSURE_4S_O, OP_CLOSURE_5S, HOP_CLOSURE_5S, + OP_CLOSURE_AA, HOP_CLOSURE_AA, OP_CLOSURE_AA_O, HOP_CLOSURE_AA_O, OP_CLOSURE_3A, HOP_CLOSURE_3A, OP_CLOSURE_4A, HOP_CLOSURE_4A, + OP_CLOSURE_NA, HOP_CLOSURE_NA, OP_CLOSURE_ASS, HOP_CLOSURE_ASS, OP_CLOSURE_SAS, HOP_CLOSURE_SAS ,OP_CLOSURE_AAS, HOP_CLOSURE_AAS, + OP_CLOSURE_SAA, HOP_CLOSURE_SAA, OP_CLOSURE_ASA, HOP_CLOSURE_ASA, OP_CLOSURE_NS, HOP_CLOSURE_NS, + + OP_SAFE_CLOSURE_S, HOP_SAFE_CLOSURE_S, OP_SAFE_CLOSURE_S_O, HOP_SAFE_CLOSURE_S_O, + OP_SAFE_CLOSURE_S_A, HOP_SAFE_CLOSURE_S_A, OP_SAFE_CLOSURE_S_TO_S, HOP_SAFE_CLOSURE_S_TO_S, OP_SAFE_CLOSURE_S_TO_SC, HOP_SAFE_CLOSURE_S_TO_SC, + OP_SAFE_CLOSURE_P, HOP_SAFE_CLOSURE_P, OP_SAFE_CLOSURE_P_A, HOP_SAFE_CLOSURE_P_A, + OP_SAFE_CLOSURE_AP, HOP_SAFE_CLOSURE_AP, OP_SAFE_CLOSURE_PA, HOP_SAFE_CLOSURE_PA, OP_SAFE_CLOSURE_PP, HOP_SAFE_CLOSURE_PP, + OP_SAFE_CLOSURE_A, HOP_SAFE_CLOSURE_A, OP_SAFE_CLOSURE_A_O, HOP_SAFE_CLOSURE_A_O, OP_SAFE_CLOSURE_A_A, HOP_SAFE_CLOSURE_A_A, + OP_SAFE_CLOSURE_A_TO_SC, HOP_SAFE_CLOSURE_A_TO_SC, + OP_SAFE_CLOSURE_SS, HOP_SAFE_CLOSURE_SS, OP_SAFE_CLOSURE_SS_O, HOP_SAFE_CLOSURE_SS_O, OP_SAFE_CLOSURE_SS_A, HOP_SAFE_CLOSURE_SS_A, + OP_SAFE_CLOSURE_SC, HOP_SAFE_CLOSURE_SC, OP_SAFE_CLOSURE_SC_O, HOP_SAFE_CLOSURE_SC_O, + OP_SAFE_CLOSURE_AA, HOP_SAFE_CLOSURE_AA, OP_SAFE_CLOSURE_AA_O, HOP_SAFE_CLOSURE_AA_O, OP_SAFE_CLOSURE_AA_A, HOP_SAFE_CLOSURE_AA_A, + OP_SAFE_CLOSURE_SAA, HOP_SAFE_CLOSURE_SAA, OP_SAFE_CLOSURE_SSA, HOP_SAFE_CLOSURE_SSA, + OP_SAFE_CLOSURE_AGG, HOP_SAFE_CLOSURE_AGG, OP_SAFE_CLOSURE_3A, HOP_SAFE_CLOSURE_3A, OP_SAFE_CLOSURE_NA, HOP_SAFE_CLOSURE_NA, + OP_SAFE_CLOSURE_3S, HOP_SAFE_CLOSURE_3S, OP_SAFE_CLOSURE_NS, HOP_SAFE_CLOSURE_NS, /* safe_closure_4s gained very little */ + OP_SAFE_CLOSURE_3S_A, HOP_SAFE_CLOSURE_3S_A, + /* ssa|saa|ns|na|3s|agg|3a|sc|ap|pa|pp_a ? thunk_o? op_closure_ns? */ + + OP_ANY_CLOSURE_3P, HOP_ANY_CLOSURE_3P, OP_ANY_CLOSURE_4P, HOP_ANY_CLOSURE_4P, OP_ANY_CLOSURE_NP, HOP_ANY_CLOSURE_NP, + OP_ANY_CLOSURE_SYM, HOP_ANY_CLOSURE_SYM, OP_ANY_CLOSURE_A_SYM, HOP_ANY_CLOSURE_A_SYM, + + OP_CLOSURE_STAR_A, HOP_CLOSURE_STAR_A, OP_CLOSURE_STAR_NA, HOP_CLOSURE_STAR_NA, + OP_SAFE_CLOSURE_STAR_A, HOP_SAFE_CLOSURE_STAR_A, OP_SAFE_CLOSURE_STAR_AA, HOP_SAFE_CLOSURE_STAR_AA, + OP_SAFE_CLOSURE_STAR_AA_O, HOP_SAFE_CLOSURE_STAR_AA_O, OP_SAFE_CLOSURE_STAR_A1, HOP_SAFE_CLOSURE_STAR_A1, + OP_SAFE_CLOSURE_STAR_KA, HOP_SAFE_CLOSURE_STAR_KA, OP_CLOSURE_STAR_KA, HOP_CLOSURE_STAR_KA, OP_SAFE_CLOSURE_STAR_3A, HOP_SAFE_CLOSURE_STAR_3A, + OP_SAFE_CLOSURE_STAR_NA, HOP_SAFE_CLOSURE_STAR_NA, OP_SAFE_CLOSURE_STAR_NA_0, HOP_SAFE_CLOSURE_STAR_NA_0, + OP_SAFE_CLOSURE_STAR_NA_1, HOP_SAFE_CLOSURE_STAR_NA_1, OP_SAFE_CLOSURE_STAR_NA_2, HOP_SAFE_CLOSURE_STAR_NA_2, + + OP_C_SS, HOP_C_SS, OP_C_S, HOP_C_S, OP_READ_S, HOP_READ_S, OP_C_P, HOP_C_P, OP_C_AP, HOP_C_AP, + OP_C_A, HOP_C_A, OP_C_AA, HOP_C_AA, OP_C, HOP_C, OP_C_NA, HOP_C_NA, + + OP_CL_S, HOP_CL_S, OP_CL_SS, HOP_CL_SS, OP_CL_A, HOP_CL_A, OP_CL_AA, HOP_CL_AA, + OP_CL_NA, HOP_CL_NA, OP_CL_FA, HOP_CL_FA, OP_CL_SAS, HOP_CL_SAS, + /* end of h_opts */ + + OP_APPLY_SS, OP_APPLY_SA, OP_APPLY_SL, OP_MACRO_D, OP_MACRO_STAR_D, + OP_WITH_IO, OP_WITH_IO_1, OP_WITH_OUTPUT_TO_STRING, OP_WITH_IO_C, OP_CALL_WITH_OUTPUT_STRING, + OP_S, OP_S_G, OP_S_A, OP_S_AA, OP_A_A, OP_A_AA, OP_P_S, OP_P_S_1, OP_MAP_FOR_EACH_FA, OP_MAP_FOR_EACH_FAA, + OP_F, OP_F_A, OP_F_AA, OP_F_NP, OP_F_NP_1, + + OP_IMPLICIT_GOTO, OP_IMPLICIT_GOTO_A, OP_IMPLICIT_CONTINUATION_A, OP_IMPLICIT_ITERATE, + OP_IMPLICIT_VECTOR_REF_A, OP_IMPLICIT_VECTOR_REF_AA, OP_IMPLICIT_VECTOR_SET_3, OP_IMPLICIT_VECTOR_SET_4, + OP_IMPLICIT_STRING_REF_A, OP_IMPLICIT_C_OBJECT_REF_A, OP_IMPLICIT_PAIR_REF_A, OP_IMPLICIT_PAIR_REF_AA, + OP_IMPLICIT_HASH_TABLE_REF_A, OP_IMPLICIT_HASH_TABLE_REF_AA, + OP_IMPLICIT_LET_REF_C, OP_IMPLICIT_LET_REF_A, OP_IMPLICIT_S7_STARLET_REF_S, OP_IMPLICIT_S7_STARLET_SET, + OP_UNKNOWN, OP_UNKNOWN_NS, OP_UNKNOWN_NA, OP_UNKNOWN_S, OP_UNKNOWN_GG, OP_UNKNOWN_A, OP_UNKNOWN_AA, OP_UNKNOWN_NP, + + OP_SYMBOL, OP_CONSTANT, OP_PAIR_SYM, OP_PAIR_PAIR, OP_PAIR_ANY, HOP_HASH_TABLE_INCREMENT, OP_CLEAR_OPTS, + + OP_READ_INTERNAL, OP_EVAL, OP_EVAL_ARGS, OP_EVAL_ARGS1, OP_EVAL_ARGS2, OP_EVAL_ARGS3, OP_EVAL_ARGS4, OP_EVAL_ARGS5, + OP_APPLY, OP_EVAL_MACRO, OP_LAMBDA, OP_QUOTE, OP_QUOTE_UNCHECKED, OP_MACROEXPAND, OP_CALL_CC, OP_CALL_WITH_EXIT, OP_CALL_WITH_EXIT_O, + OP_C_CATCH, OP_C_CATCH_ALL, OP_C_CATCH_ALL_O, OP_C_CATCH_ALL_A, + + OP_DEFINE, OP_DEFINE1, OP_BEGIN, OP_BEGIN_HOOK, OP_BEGIN_NO_HOOK, OP_BEGIN_UNCHECKED, OP_BEGIN_2_UNCHECKED, OP_BEGIN_NA, OP_BEGIN_AA, + OP_IF, OP_IF1, OP_WHEN, OP_UNLESS, OP_SET, OP_SET1, OP_SET2, + OP_LET, OP_LET1, OP_LET_STAR, OP_LET_STAR1, OP_LET_STAR2, OP_LET_STAR_SHADOWED, + OP_LETREC, OP_LETREC1, OP_LETREC_STAR, OP_LETREC_STAR1, + OP_LET_TEMPORARILY, OP_LET_TEMP_UNCHECKED, OP_LET_TEMP_INIT1, OP_LET_TEMP_INIT2, OP_LET_TEMP_DONE, OP_LET_TEMP_DONE1, + OP_LET_TEMP_S7, OP_LET_TEMP_NA, OP_LET_TEMP_A, OP_LET_TEMP_SETTER, OP_LET_TEMP_UNWIND, OP_LET_TEMP_S7_UNWIND, OP_LET_TEMP_SETTER_UNWIND, + OP_LET_TEMP_A_A, OP_LET_TEMP_S7_DIRECT, OP_LET_TEMP_S7_DIRECT_UNWIND, + OP_COND, OP_COND1, OP_FEED_TO_1, OP_COND_SIMPLE, OP_COND1_SIMPLE, OP_COND_SIMPLE_O, OP_COND1_SIMPLE_O, + OP_AND, OP_OR, + OP_DEFINE_MACRO, OP_DEFINE_MACRO_STAR, OP_DEFINE_EXPANSION, OP_DEFINE_EXPANSION_STAR, OP_MACRO, OP_MACRO_STAR, + OP_CASE, + OP_READ_LIST, OP_READ_NEXT, OP_READ_DOT, OP_READ_QUOTE, + OP_READ_QUASIQUOTE, OP_READ_UNQUOTE, OP_READ_APPLY_VALUES, + OP_READ_VECTOR, OP_READ_BYTE_VECTOR, OP_READ_INT_VECTOR, OP_READ_FLOAT_VECTOR, OP_READ_DONE, + OP_LOAD_RETURN_IF_EOF, OP_LOAD_CLOSE_AND_POP_IF_EOF, OP_EVAL_DONE, OP_SPLICE_VALUES, OP_NO_VALUES, + OP_CATCH, OP_DYNAMIC_WIND, OP_DYNAMIC_UNWIND, OP_DYNAMIC_UNWIND_PROFILE, OP_PROFILE_IN, + OP_DEFINE_CONSTANT, OP_DEFINE_CONSTANT1, + OP_DO, OP_DO_END, OP_DO_END1, OP_DO_STEP, OP_DO_STEP2, OP_DO_INIT, + OP_DEFINE_STAR, OP_LAMBDA_STAR, OP_LAMBDA_STAR_DEFAULT, OP_ERROR_QUIT, OP_UNWIND_INPUT, OP_UNWIND_OUTPUT, OP_ERROR_HOOK_QUIT, + OP_WITH_LET, OP_WITH_LET1, OP_WITH_LET_UNCHECKED, OP_WITH_LET_S, + OP_WITH_BAFFLE, OP_WITH_BAFFLE_UNCHECKED, OP_EXPANSION, + OP_FOR_EACH, OP_FOR_EACH_1, OP_FOR_EACH_2, OP_FOR_EACH_3, + OP_MAP, OP_MAP_1, OP_MAP_2, OP_MAP_GATHER, OP_MAP_GATHER_1, OP_MAP_GATHER_2, OP_MAP_GATHER_3, OP_MAP_UNWIND, + OP_BARRIER, OP_DEACTIVATE_GOTO, + OP_DEFINE_BACRO, OP_DEFINE_BACRO_STAR, OP_BACRO, OP_BACRO_STAR, + OP_GET_OUTPUT_STRING, + OP_SORT, OP_SORT1, OP_SORT2, OP_SORT3, OP_SORT_PAIR_END, OP_SORT_VECTOR_END, OP_SORT_STRING_END, + OP_EVAL_STRING, + OP_MEMBER_IF, OP_ASSOC_IF, OP_MEMBER_IF1, OP_ASSOC_IF1, + OP_LAMBDA_UNCHECKED, OP_LET_UNCHECKED, OP_CATCH_1, OP_CATCH_2, OP_CATCH_ALL, + + OP_SET_UNCHECKED, OP_SET_S_C, OP_SET_S_S, OP_SET_S_P, OP_SET_S_A, + OP_SET_NORMAL, OP_SET_opSq_A, OP_SET_opSAq_A, OP_SET_opSAq_P, OP_SET_opSAq_P_1, OP_SET_opSAAq_A, OP_SET_opSAAq_P, OP_SET_opSAAq_P_1, + OP_SET_FROM_SETTER, OP_SET_FROM_LET_TEMP, OP_SET_SAFE, + OP_INCREMENT_BY_1, OP_DECREMENT_BY_1, OP_INCREMENT_SA, OP_INCREMENT_SAA, OP_SET_CONS, + + OP_LETREC_UNCHECKED, OP_LETREC_STAR_UNCHECKED, OP_COND_UNCHECKED, + OP_LAMBDA_STAR_UNCHECKED, OP_DO_UNCHECKED, OP_DEFINE_UNCHECKED, OP_DEFINE_STAR_UNCHECKED, OP_DEFINE_FUNCHECKED, OP_DEFINE_CONSTANT_UNCHECKED, + OP_DEFINE_WITH_SETTER, + + OP_LET_NO_VARS, OP_NAMED_LET, OP_NAMED_LET_NO_VARS, OP_NAMED_LET_A, OP_NAMED_LET_AA, OP_NAMED_LET_NA, OP_NAMED_LET_STAR, + OP_LET_NA_OLD, OP_LET_NA_NEW, OP_LET_2A_OLD, OP_LET_2A_NEW, OP_LET_3A_OLD, OP_LET_3A_NEW, + OP_LET_opaSSq_OLD, OP_LET_opaSSq_NEW, OP_LET_ONE_OLD, OP_LET_ONE_NEW, OP_LET_ONE_P_OLD, OP_LET_ONE_P_NEW, + OP_LET_ONE_OLD_1, OP_LET_ONE_NEW_1, OP_LET_ONE_P_OLD_1, OP_LET_ONE_P_NEW_1, + OP_LET_A_OLD, OP_LET_A_NEW, OP_LET_A_P_OLD, OP_LET_A_P_NEW, + OP_LET_A_A_OLD, OP_LET_A_A_NEW, OP_LET_A_NA_OLD, OP_LET_A_NA_NEW, OP_LET_A_OLD_2, OP_LET_A_NEW_2, + OP_LET_STAR_NA, OP_LET_STAR_NA_A, + + OP_CASE_A_E_S, OP_CASE_A_I_S, OP_CASE_A_G_S, OP_CASE_A_E_G, OP_CASE_A_G_G, OP_CASE_A_S_G, + OP_CASE_P_E_S, OP_CASE_P_I_S, OP_CASE_P_G_S, OP_CASE_P_E_G, OP_CASE_P_G_G, + OP_CASE_E_S, OP_CASE_I_S, OP_CASE_G_S, OP_CASE_E_G, OP_CASE_G_G, + OP_CASE_A_I_S_A, OP_CASE_A_E_S_A, OP_CASE_A_G_S_A, OP_CASE_A_S_G_A, + + OP_IF_UNCHECKED, OP_AND_P, OP_AND_P1, OP_AND_AP, OP_AND_PAIR_P, + OP_AND_SAFE_P1, OP_AND_SAFE_P2, OP_AND_SAFE_P3, OP_AND_SAFE_P_REST, OP_AND_2A, OP_AND_3A, OP_AND_N, OP_AND_S_2, + OP_OR_P, OP_OR_P1, OP_OR_AP, OP_OR_2A, OP_OR_3A, OP_OR_N, OP_OR_S_2, OP_OR_S_TYPE_2, + OP_WHEN_S, OP_WHEN_A, OP_WHEN_P, OP_WHEN_AND_AP, OP_WHEN_AND_2A, OP_WHEN_AND_3A, OP_UNLESS_S, OP_UNLESS_A, OP_UNLESS_P, + + OP_IF_A_C_C, OP_IF_A_A, OP_IF_A_A_A, OP_IF_S_A_A, OP_IF_AND2_S_A, OP_IF_NOT_A_A, OP_IF_NOT_A_A_A, + OP_IF_B_A, OP_IF_B_P, OP_IF_B_R, OP_IF_B_A_P, OP_IF_B_P_A, OP_IF_B_P_P, OP_IF_B_N_N, + OP_IF_A_A_P, OP_IF_A_P_A, OP_IF_S_P_A, OP_IF_S_A_P, OP_IF_S_P, OP_IF_S_P_P, OP_IF_S_R, OP_IF_S_N, OP_IF_S_N_N, + OP_IF_opSq_P, OP_IF_opSq_P_P, OP_IF_opSq_R, OP_IF_opSq_N, OP_IF_opSq_N_N, + OP_IF_IS_TYPE_S_P, OP_IF_IS_TYPE_S_P_P, OP_IF_IS_TYPE_S_R, OP_IF_IS_TYPE_S_N, OP_IF_IS_TYPE_S_N_N, OP_IF_IS_TYPE_S_P_A, OP_IF_IS_TYPE_S_A_A, OP_IF_IS_TYPE_S_A_P, + OP_IF_A_P, OP_IF_A_P_P, OP_IF_A_R, OP_IF_A_N, OP_IF_A_N_N, + OP_IF_AND2_P, OP_IF_AND2_P_P, OP_IF_AND2_R, OP_IF_AND2_N, OP_IF_AND2_N_N, + OP_IF_AND3_P, OP_IF_AND3_P_P, OP_IF_AND3_R, OP_IF_AND3_N, OP_IF_AND3_N_N, /* or3 got few hits */ + OP_IF_P_P, OP_IF_P_P_P, OP_IF_P_R, OP_IF_P_N, OP_IF_P_N_N, + OP_IF_ANDP_P, OP_IF_ANDP_P_P, OP_IF_ANDP_R, OP_IF_ANDP_N, OP_IF_ANDP_N_N, + OP_IF_ORP_P, OP_IF_ORP_P_P, OP_IF_ORP_R, OP_IF_ORP_N, OP_IF_ORP_N_N, + OP_IF_OR2_P, OP_IF_OR2_P_P, OP_IF_OR2_R, OP_IF_OR2_N, OP_IF_OR2_N_N, + OP_IF_PP, OP_IF_PPP, OP_IF_PN, OP_IF_PR, OP_IF_PRR, OP_WHEN_PP, OP_UNLESS_PP, + + OP_COND_NA_NA, OP_COND_NA_NP, OP_COND_NA_NP_1, OP_COND_NA_2E, OP_COND_NA_3E, OP_COND_NA_NP_O, + OP_COND_FEED, OP_COND_FEED_1, + + OP_SIMPLE_DO, OP_SIMPLE_DO_STEP, OP_SAFE_DOTIMES, OP_SAFE_DOTIMES_STEP, OP_SAFE_DOTIMES_STEP_O, + OP_SAFE_DO, OP_SAFE_DO_STEP, OP_DOX, OP_DOX_STEP, OP_DOX_STEP_O, OP_DOX_NO_BODY, OP_DOX_PENDING_NO_BODY, OP_DOX_INIT, + OP_DOTIMES_P, OP_DOTIMES_STEP_O, + OP_DO_NO_VARS, OP_DO_NO_VARS_NO_OPT, OP_DO_NO_VARS_NO_OPT_1, + OP_DO_NO_BODY_NA_VARS, OP_DO_NO_BODY_NA_VARS_STEP, OP_DO_NO_BODY_NA_VARS_STEP_1, + + OP_SAFE_C_P_1, OP_SAFE_C_PP_1, OP_SAFE_C_PP_3_MV, OP_SAFE_C_PP_5, OP_SAFE_C_PP_6_MV, + OP_SAFE_C_3P_1, OP_SAFE_C_3P_2, OP_SAFE_C_3P_3, OP_SAFE_C_3P_1_MV, OP_SAFE_C_3P_2_MV, OP_SAFE_C_3P_3_MV, + OP_SAFE_C_SP_1, OP_SAFE_C_SP_MV, OP_SAFE_CONS_SP_1, OP_SAFE_ADD_SP_1, OP_SAFE_MULTIPLY_SP_1, + OP_SAFE_C_PS_1, OP_SAFE_C_PC_1, OP_SAFE_C_PS_MV, OP_SAFE_C_PC_MV, + OP_EVAL_MACRO_MV, OP_MACROEXPAND_1, OP_APPLY_LAMBDA, + OP_ANY_C_NP_1, OP_ANY_C_NP_MV, OP_SAFE_C_SSP_1, OP_SAFE_C_SSP_MV, + OP_C_P_1, OP_C_P_MV, OP_C_AP_1, OP_C_AP_MV, OP_ANY_C_NP_2, OP_SAFE_C_PA_1, OP_SAFE_C_PA_MV, + OP_SET_WITH_LET_1, OP_SET_WITH_LET_2, + + OP_CLOSURE_AP_1, OP_CLOSURE_PA_1, OP_CLOSURE_PP_1, OP_CLOSURE_P_1, + OP_SAFE_CLOSURE_P_1, OP_SAFE_CLOSURE_P_A_1, OP_SAFE_CLOSURE_AP_1, OP_SAFE_CLOSURE_PA_1, OP_SAFE_CLOSURE_PP_1, + OP_ANY_CLOSURE_3P_1, OP_ANY_CLOSURE_3P_2, OP_ANY_CLOSURE_3P_3, OP_ANY_CLOSURE_NP_1, OP_ANY_CLOSURE_NP_MV, + OP_ANY_CLOSURE_4P_1, OP_ANY_CLOSURE_4P_2, OP_ANY_CLOSURE_4P_3, OP_ANY_CLOSURE_4P_4, OP_ANY_CLOSURE_NP_2, + + OP_TC_AND_A_OR_A_LA, OP_TC_OR_A_AND_A_LA, OP_TC_AND_A_OR_A_LAA, OP_TC_OR_A_AND_A_LAA, OP_TC_AND_A_OR_A_L3A, OP_TC_OR_A_AND_A_L3A, + OP_TC_OR_A_A_AND_A_A_LA, OP_TC_OR_A_AND_A_A_L3A, OP_TC_AND_A_OR_A_A_LA, OP_TC_OR_A_AND_A_A_LA, + OP_TC_WHEN_LA, OP_TC_WHEN_LAA, OP_TC_WHEN_L3A, OP_TC_LET_WHEN_LAA, OP_TC_LET_UNLESS_LAA, + OP_TC_COND_A_Z_A_Z_LAA, OP_TC_COND_A_Z_A_LAA_Z, OP_TC_COND_A_Z_A_LAA_LAA, OP_TC_LET_COND, + OP_TC_IF_A_Z_LA, OP_TC_IF_A_Z_LAA, OP_TC_IF_A_Z_L3A, OP_TC_IF_A_L3A_Z, OP_TC_IF_A_LA_Z, OP_TC_IF_A_LAA_Z, + OP_TC_IF_A_Z_IF_A_Z_LA, OP_TC_IF_A_Z_IF_A_LA_Z, OP_TC_IF_A_Z_IF_A_Z_LAA, OP_TC_IF_A_Z_IF_A_LAA_Z, OP_TC_IF_A_Z_IF_A_L3A_L3A, + OP_TC_COND_A_Z_A_Z_LA, OP_TC_COND_A_Z_A_LA_Z, OP_TC_COND_A_Z_LA, OP_TC_COND_A_LA_Z, OP_TC_COND_A_Z_LAA, OP_TC_COND_A_LAA_Z, + OP_TC_LET_IF_A_Z_LA, OP_TC_LET_IF_A_Z_LAA, OP_TC_IF_A_Z_LET_IF_A_Z_LAA, + OP_TC_CASE_LA, OP_TC_AND_A_IF_A_Z_LA, OP_TC_AND_A_IF_A_LA_Z, + + OP_RECUR_IF_A_A_opA_LAq, OP_RECUR_IF_A_opA_LAq_A, OP_RECUR_IF_A_A_opLA_Aq, OP_RECUR_IF_A_opLA_Aq_A, + OP_RECUR_IF_A_A_opLA_LAq, OP_RECUR_IF_A_opLA_LAq_A, + OP_RECUR_IF_A_A_opA_LA_LAq, OP_RECUR_IF_A_opA_LA_LAq_A, + OP_RECUR_IF_A_A_opLA_LA_LAq, OP_RECUR_IF_A_A_IF_A_A_opLA_LAq, OP_RECUR_IF_A_A_IF_A_A_opLAA_LAAq, + OP_RECUR_IF_A_A_opA_LAAq, OP_RECUR_IF_A_opA_LAAq_A, OP_RECUR_IF_A_A_opA_L3Aq, + OP_RECUR_IF_A_A_LopL3A_L3A_L3Aq, OP_RECUR_IF_A_A_AND_A_LAA_LAA, + OP_RECUR_IF_A_A_IF_A_LAA_opA_LAAq, /* same as cond case below */ + OP_RECUR_COND_A_A_opA_LAq, OP_RECUR_COND_A_A_opA_LAAq, + OP_RECUR_COND_A_A_A_A_opLA_LAq, OP_RECUR_COND_A_A_A_A_opLAA_LAAq, OP_RECUR_COND_A_A_A_A_opA_LAAq, + OP_RECUR_COND_A_A_A_LAA_LopA_LAAq, OP_RECUR_COND_A_A_A_LAA_opA_LAAq, + OP_RECUR_AND_A_OR_A_LAA_LAA, + + NUM_OPS}; + +#define is_tc_op(Op) ((Op >= OP_TC_AND_A_OR_A_LA) && (Op <= OP_TC_CASE_LA)) + +typedef enum{E_C_P, E_C_PP, E_C_CP, E_C_SP, E_C_PC, E_C_PS} combine_op_t; + +static const char* op_names[NUM_OPS] = + { + "unopt", "gc_protect", + + "safe_c_nc", "h_safe_c_nc", "safe_c_s", "h_safe_c_s", + "safe_c_ss", "h_safe_c_ss", "safe_c_sc", "h_safe_c_sc", "safe_c_cs", "h_safe_c_cs", "safe_c_cq", "h_safe_c_cq", + "safe_c_sss", "h_safe_c_sss", "safe_c_scs", "h_safe_c_scs", "safe_c_ssc", "h_safe_c_ssc", "safe_c_css", "h_safe_c_css", + "safe_c_scc", "h_safe_c_scc", "safe_c_csc", "h_safe_c_csc", "safe_c_ccs", "h_safe_c_ccs", + "safe_c_ns", "h_safe_c_ns", "safe_c_opncq", "h_safe_c_opncq", "safe_c_opsq", "h_safe_c_opsq", + "safe_c_opssq", "h_safe_c_opssq", "safe_c_opscq", "h_safe_c_opscq", + "safe_c_opcsq", "h_safe_c_opcsq", "safe_c_s_opsq", "h_safe_c_s_opsq", + "safe_c_c_opscq", "h_safe_c_c_opscq", "safe_c_s_opscq", "h_safe_c_s_opscq", "safe_c_s_opcsq", "h_safe_c_s_opcsq", + "safe_c_opsq_s", "h_safe_c_opsq_s", "safe_c_opsq_c", "h_safe_c_opsq_c", + "safe_c_opsq_opsq", "h_safe_c_opsq_opsq", "safe_c_s_opssq", "h_safe_c_s_opssq", "safe_c_c_opsq", "h_safe_c_c_opsq", + "safe_c_opcsq_c", "h_safe_c_opcsq_c", "safe_c_opssq_c", "h_safe_c_opssq_c", "safe_c_c_opssq", "h_safe_c_c_opssq", + "safe_c_opssq_opssq", "h_safe_c_opssq_opssq", "safe_c_opssq_opsq", "h_safe_c_opssq_opsq", "safe_c_opsq_opssq", "h_safe_c_opsq_opssq", + "safe_c_opssq_s", "h_safe_c_opssq_s", "safe_c_opcsq_s", "h_safe_c_opcsq_s", "safe_c_opscq_c", "h_safe_c_opscq_c", + "safe_c_op_opssqq_s", "h_safe_c_op_opssqq_s", "safe_c_op_opsqq", "h_safe_c_op_opsqq", + "safe_c_op_s_opsqq", "h_safe_c_op_s_opsqq", "safe_c_op_opsq_sq", "h_safe_c_op_opsq_sq", "safe_c_opsq_cs", "h_safe_c_opsq_cs", + + "safe_c_a", "h_safe_c_a", "safe_c_aa", "h_safe_c_aa", "safe_c_sa", "h_safe_c_sa", "safe_c_as", "h_safe_c_as", + "safe_c_ca", "h_safe_c_ca", "safe_c_ac", "h_safe_c_ac", "safe_c_aaa", "h_safe_c_aaa", "safe_c_4a", "h_safe_c_4a", + "safe_c_na", "h_safe_c_na", "safe_c_all_ca", "h_safe_c_all_ca", + "safe_c_ssa", "h_safe_c_ssa", "safe_c_sas", "h_safe_c_sas", "safe_c_saa", "h_safe_c_saa", + "safe_c_csa", "h_safe_c_csa", "safe_c_sca", "h_safe_c_sca", "safe_c_ass", "h_safe_c_ass", + "safe_c_cac", "h_safe_c_cac", "safe_c_agg", "h_safe_c_agg", + "safe_c_opaq", "h_safe_c_opaq", "safe_c_opaaq", "h_safe_c_opaaq", "safe_c_opaaaq", "h_safe_c_opaaaq", + "safe_c_s_opaq", "h_safe_c_s_opaq", "safe_c_opaq_s", "h_safe_c_opaq_s", "safe_c_s_opaaq", "h_safe_c_s_opaaq", + "safe_c*", "h_safe_c*", "safe_c*_a", "h_safe_c*_a", "safe_c*_aa", "h_safe_c*_aa", "safe_c*_na", "h_safe_c*_na", + + "safe_c_p", "h_safe_c_p", "safe_c_pp", "h_safe_c_pp", "safe_c_ff", "h_safe_c_ff", "safe_c_sp", "h_safe_c_sp", + "safe_c_cp", "h_safe_c_cp", "safe_c_ap", "h_safe_c_ap", "safe_c_pa", "h_safe_c_pa", "safe_c_ps", "h_safe_c_ps", + "safe_c_pc", "h_safe_c_pc", "safe_c_ssp", "h_safe_c_ssp", "any_c_np", "h_any_c_np", "safe_c_3p", "h_safe_c_3p", + + "thunk", "h_thunk", "thunk_o", "h_thunk_o", "thunk_any", "h_thunk_any", + "safe_thunk", "h_safe_thunk", "safe_thunk_a", "h_safe_thunk_a", "safe_thunk_any", "h_safe_thunk_any", + + "closure_s", "h_closure_s", "closure_s_o", "h_closure_s_o", + "closure_a", "h_closure_a", "closure_a_o", "h_closure_a_o", "closure_p", "h_closure_p", + "closure_ap", "h_closure_ap", "closure_pa", "h_closure_pa", "closure_pp", "h_closure_pp", + "closure_fa", "h_closure_fa", "closure_ss", "h_closure_ss", "closure_ss_o", "h_closure_ss_o", + "closure_sc", "h_closure_sc", "closure_sc_o", "h_closure_sc_o", + "closure_3s", "h_closure_3s", "closure_3s_o", "h_closure_3s_o", "closure_4s", "h_closure_4s", "closure_4s_o", "h_closure_4s_o", "closure_5s", "h_closure_5s", + "closure_aa", "h_closure_aa", "closure_aa_o", "h_closure_aa_o", "closure_3a", "h_closure_3a", "closure_4a", "h_closure_4a", + "closure_na", "h_closure_na", "closure_ass", "h_closure_ass", "closure_sas", "h_closure_sas ","closure_aas", "h_closure_aas", + "closure_saa", "h_closure_saa", "closure_asa", "h_closure_asa", "closure_ns", "h_closure_ns", + + "safe_closure_s", "h_safe_closure_s", "safe_closure_s_o", "h_safe_closure_s_o", + "safe_closure_s_a", "h_safe_closure_s_a", "safe_closure_s_to_s", "h_safe_closure_s_to_s", "safe_closure_s_to_sc", "h_safe_closure_s_to_sc", + "safe_closure_p", "h_safe_closure_p", "safe_closure_p_a", "h_safe_closure_p_a", + "safe_closure_ap", "h_safe_closure_ap", "safe_closure_pa", "h_safe_closure_pa", "safe_closure_pp", "h_safe_closure_pp", + "safe_closure_a", "h_safe_closure_a", "safe_closure_a_o", "h_safe_closure_a_o", "safe_closure_a_a", "h_safe_closure_a_a", + "safe_closure_a_to_sc", "h_safe_closure_a_to_sc", + "safe_closure_ss", "h_safe_closure_ss", "safe_closure_ss_o", "h_safe_closure_ss_o", "safe_closure_ss_a", "h_safe_closure_ss_a", + "safe_closure_sc", "h_safe_closure_sc", "safe_closure_sc_o", "h_safe_closure_sc_o", + "safe_closure_aa", "h_safe_closure_aa", "safe_closure_aa_o", "h_safe_closure_aa_o", "safe_closure_aa_a", "h_safe_closure_aa_a", + "safe_closure_saa", "h_safe_closure_saa", "safe_closure_ssa", "h_safe_closure_ssa", + "safe_closure_agg", "h_safe_closure_agg", "safe_closure_3a", "h_safe_closure_3a", "safe_closure_na", "h_safe_closure_na", + "safe_closure_3s", "h_safe_closure_3s", "safe_closure_ns", "h_safe_closure_ns", + "safe_closure_3s_a", "h_safe_closure_3s_a", + + "any_closure_3p", "h_any_closure_3p", "any_closure_4p", "h_any_closure_4p", "any_closure_np", "h_any_closure_np", + "any_closure_sym", "h_any_closure_sym", "any_closure_a_sym", "h_any_closure_a_sym", + + "closure*_a", "h_closure*_a", "closure*_na", "h_closure*_na", + "safe_closure*_a", "h_safe_closure*_a", "safe_closure*_aa", "h_safe_closure*_aa", + "safe_closure*_aa_o", "h_safe_closure*_aa_o", "safe_closure*_a1", "h_safe_closure*_a1", + "safe_closure*_ka", "h_safe_closure*_ka", "closure*_ka", "h_closure*_ka", "safe_closure*_3a", "h_safe_closure*_3a", + "safe_closure*_na", "h_safe_closure*_na", "safe_closure*_na_0", "h_safe_closure*_na_0", + "safe_closure*_na_1", "h_safe_closure*_na_1", "safe_closure*_na_2", "h_safe_closure*_na_2", + + "c_ss", "h_c_ss", "c_s", "h_c_s", "read_s", "h_read_s", "c_p", "h_c_p", "c_ap", "h_c_ap", + "c_a", "h_c_a", "c_aa", "h_c_aa", "c", "h_c", "c_na", "h_c_na", + + "cl_s", "h_cl_s", "cl_ss", "h_cl_ss", "cl_a", "h_cl_a", "cl_aa", "h_cl_aa", + "cl_na", "h_cl_na", "cl_fa", "h_cl_fa", "cl_sas", "h_cl_sas", + + "apply_ss", "apply_sa", "apply_sl", "macro_d", "macro*_d", + "with_input_from_string", "with_input_from_string_1", "with_output_to_string", "with_input_from_string_c", "call_with_output_string", + "s", "s_g", "s_a", "s_aa", "a_a", "a_aa", "p_s", "p_s_1", "map_for_each_fa", "map_for_each_faa", + "f", "f_a", "f_aa", "f_np", "f_np_1", + + "implicit_goto", "implicit_goto_a", "implicit_continuation_a","implicit_iterate", + "implicit_vector_ref_a", "implicit_vector_ref_aa", "implicit_vector_set_3", "implicit_vector_set_4", + "implicit_string_ref_a", "implicit_c_object_ref_a", "implicit_pair_ref_a", "implicit_pair_ref_aa", + "implicit_hash_table_ref_a", "implicit_hash_table_ref_aa", + "implicit_let_ref_c", "implicit_let_ref_a", "implicit_*s7*_ref_s", "implicit_*s7*_set", + "unknown_thunk", "unknown_ns", "unknown_na", "unknown_s", "unknown_gg", "unknown_a", "unknown_aa", "unknown_np", + + "symbol", "constant", "pair_sym", "pair_pair", "pair_any", "h_hash_table_increment", "clear_opts", + + "read_internal", "eval", "eval_args", "eval_args1", "eval_args2", "eval_args3", "eval_args4", "eval_args5", + "apply", "eval_macro", "lambda", "quote", "quote_unchecked", "macroexpand", "call/cc", "call_with_exit", "call_with_exit_o", + "c_catch", "c_catch_all", "c_catch_all_o", "c_catch_all_a", + + "define", "define1", "begin", "begin_hook", "begin_no_hook", "begin_unchecked", "begin_2_unchecked", "begin_na", "begin_aa", + "if", "if1", "when", "unless", "set", "set1", "set2", + "let", "let1", "let*", "let*1", "let*2", "let*-shadowed", + "letrec", "letrec1", "letrec*", "letrec*1", + "let_temporarily", "let_temp_unchecked", "let_temp_init1", "let_temp_init2", "let_temp_done", "let_temp_done1", + "let_temp_s7", "let_temp_na", "let_temp_a", "let_temp_setter", "let_temp_unwind", "let_temp_s7_unwind", "let_temp_setter_unwind", + "let_temp_a_a", "let_temp_s7_direct", "let_temp_s7_direct_unwind", + "cond", "cond1", "feed_to_1", "cond_simple", "cond1_simple", "cond_simple_o", "cond1_simple_o", + "and", "or", + "define_macro", "define_macro*", "define_expansion", "define_expansion*", "macro", "macro*", + "case", "read_list", "read_next", "read_dot", "read_quote", + "read_quasiquote", "read_unquote", "read_apply_values", + "read_vector", "read_byte_vector", "read_int_vector", "read_float_vector", "read_done", + "load_return_if_eof", "load_close_and_pop_if_eof", "eval_done", "splice_values", "no_values", + "catch", "dynamic_wind", "dynamic_unwind", "dynamic_unwind_profile", "profile_in", + "define_constant", "define_constant1", + "do", "do_end", "do_end1", "do_step", "do_step2", "do_init", + "define*", "lambda*", "lambda*_default", "error_quit", "unwind_input", "unwind_output", "error_hook_quit", + "with_let", "with_let1", "with_let_unchecked", "with_let_s", + "with_baffle", "with_baffle_unchecked", "expansion", + "for_each", "for_each_1", "for_each_2", "for_each_3", + "map", "map_1", "map_2", "map_gather", "map_gather_1", "map_gather_2", "map_gather_3", "map_unwind", + "barrier", "deactivate_goto", + "define_bacro", "define_bacro*", "bacro", "bacro*", + "get_output_string", + "sort", "sort1", "sort2", "sort3", "sort_pair_end", "sort_vector_end", "sort_string_end", + "eval_string", + "member_if", "assoc_if", "member_if1", "assoc_if1", + "lambda_unchecked", "let_unchecked", "catch_1", "catch_2", "catch_all", + "set_unchecked", "set_s_c", "set_s_s", "set_s_p", "set_a", + "set_normal", "set_opsq_a", "set_opsaq_a", "set_opsaq_p", "set_opsaq_p_1", "set_opsaaq_a", "set_opsaaq_p", "set_opsaaq_p_1", + "set_from_setter", "set_from_let_temp", "set_safe", + "increment_1", "decrement_1", "increment_sa", "increment_saa", "set_cons", + "letrec_unchecked", "letrec*_unchecked", "cond_unchecked", + "lambda*_unchecked", "do_unchecked", "define_unchecked", "define*_unchecked", "define_funchecked", "define_constant_unchecked", + "define_with_setter", + + "let_no_vars", "named_let", "named_let_no_vars", "named_let_a", "named_let_aa", "named_let_na", "named_let*", + "let_na_old", "let_na_new", "let_2a_old", "let_2a_new", "let_3a_old", "let_3a_new", + "let_opassq_old", "let_opassq_new", "let_one_old", "let_one_new", "let_one_p_old", "let_one_p_new", + "let_one_old_1", "let_one_new_1", "let_one_p_old_1", "let_one_p_new_1", + "let_a_old", "let_a_new", "let_a_p_old", "let_a_p_new", + "let_a_a_old", "let_a_a_new", "let_a_na_old", "let_a_na_new", "let_a_old_2", "let_a_new_2", + "let*_na", "let*_na_a", + + "case_a_e_s", "case_a_i_s", "case_a_g_s", "case_a_e_g", "case_a_g_g", "case_a_s_g", + "case_p_e_s", "case_p_i_s", "case_p_g_s", "case_p_e_g", "case_p_g_g", + "case_e_s", "case_i_s", "case_g_s", "case_e_g", "case_g_g", + "case_a_i_s_a", "case_a_e_s_a", "case_a_g_s_a", "case_a_s_g_a", + + "if_unchecked", "and_p", "and_p1", "and_ap", "and_pair_p", + "and_safe_p1", "op_and_safe_p2", "and_safe_p3", "and_safe_p_rest", "and_2a", "and_3a", "and_n", "and_s_2", + "or_p", "or_p1", "or_ap", "or_2a", "or_3a", "or_n", "or_s_2", "or_s_type_2", + "when_s", "when_a", "when_p", "when_and_ap", "when_and_2a", "when_and_3a", "unless_s", "unless_a", "unless_p", + + "if_a_c_c", "if_a_a", "if_a_a_a", "if_s_a_a", "if_and2_s_a", "if_not_a_a", "if_not_a_a_a", + "if_b_a", "if_b_p", "if_b_r", "if_b_a_p", "if_b_p_a", "if_b_p_p", "if_b_n_n", + "if_a_a_p", "if_a_p_a", "if_s_p_a", "if_s_a_p", "if_s_p", "if_s_p_p", "if_s_r", "if_s_n", "if_s_n_n", + "if_opsq_p", "if_opsq_p_p", "if_opsq_r", "if_opsq_n", "if_opsq_n_n", + "if_is_type_s_p", "if_is_type_s_p_p", "if_is_type_s_r", "if_is_type_s_n", "if_is_type_s_n_n", "if_is_type_s_p_a", "if_is_type_s_a_a", "if_is_type_s_a_p", + "if_a_p", "if_a_p_p", "if_a_r", "if_a_n", "if_a_n_n", + "if_and2_p", "if_and2_p_p", "if_and2_r", "if_and2_n", "if_and2_n_n", + "if_and3_p", "if_and3_p_p", "if_and3_r", "if_and3_n", "if_and3_n_n", + "if_p_p", "if_p_p_p", "if_p_r", "if_p_n", "if_p_n_n", + "if_andp_p", "if_andp_p_p", "if_andp_r", "if_andp_n", "if_andp_n_n", + "if_orp_p", "if_orp_p_p", "if_orp_r", "if_orp_n", "if_orp_n_n", + "if_or2_p", "if_or2_p_p", "if_or2_r", "if_or2_n", "if_or2_n_n", + "if_pp", "if_ppp", "if_pn", "if_pr", "if_prr", "when_pp", "unless_pp", + + "cond_na_na", "cond_na_np", "cond_na_np_1", "cond_na_2e", "cond_na_3e", "cond_na_np_o", + "cond_feed", "cond_feed_1", + + "simple_do", "simple_do_step", "safe_dotimes", "safe_dotimes_step", "safe_dotimes_step_o", + "safe_do", "safe_do_step", "dox", "dox_step", "dox_step_o", "dox_no_body", "dox_pending_no_body", "dox_init", + "dotimes_p", "dotimes_step_o", + "do_no_vars", "do_no_vars_no_opt", "do_no_vars_no_opt_1", + "do_no_body_na_vars", "do_no_body_na_vars_step", "do_no_body_na_vars_step_1", + + "safe_c_p_1", "safe_c_pp_1", "safe_c_pp_3_mv", "safe_c_pp_5", "safe_c_pp_6_mv", + "safe_c_3p_1", "safe_c_3p_2", "safe_c_3p_3", "safe_c_3p_1_mv", "safe_c_3p_2_mv", "safe_c_3p_3_mv", + "safe_c_sp_1", "safe_c_sp_mv", "safe_cons_sp_1", "safe_add_sp_1", "safe_multiply_sp_1", + "safe_c_ps_1", "safe_c_pc_1", "safe_c_ps_mv", "safe_c_pc_mv", + "eval_macro_mv", "macroexpand_1", "apply_lambda", + "any_c_np_1", "any_c_np_mv", "safe_c_ssp_1", "safe_c_ssp_mv", + "c_p_1", "c_p_mv", "c_ap_1", "c_ap_mv", "any_c_np_2", "safe_c_pa_1", "safe_c_pa_mv", + "set_with_let_1", "set_with_let_2", + + "closure_ap_1", "closure_pa_1", "closure_pp_1", "closure_p_1", + "safe_closure_p_1", "safe_closure_p_a_1", "safe_closure_ap_1", "safe_closure_pa_1", "safe_closure_pp_1", + "any_closure_3p_1", "any_closure_3p_2", "any_closure_3p_3", "any_closure_np_1", "any_closure_np_mv", + "any_closure_4p_1", "any_closure_4p_2", "any_closure_4p_3", "any_closure_4p_4", "any_closure_np_2", + + "tc_and_a_or_a_la", "tc_or_a_and_a_la", "tc_and_a_or_a_laa", "tc_or_a_and_a_laa", "tc_and_a_or_a_l3a", "tc_or_a_and_a_l3a", + "tc_or_a_a_and_a_a_la", "tc_or_a_and_a_a_l3a", "tc_and_a_or_a_a_la", "tc_or_a_and_a_a_la", + "tc_when_la", "tc_when_laa", "tc_when_l3a", "tc_let_when_laa", "tc_let_unless_laa", + "tc_cond_a_z_a_z_laa", "tc_cond_a_z_a_laa_z", "tc_cond_a_z_a_laa_laa", "tc_let_cond", + "tc_if_a_z_la", "tc_if_a_z_laa", "tc_if_a_z_l3a", "tc_if_a_l3a_z", "tc_if_a_la_z", "tc_if_a_laa_z", + "tc_if_a_z_if_a_z_la", "tc_if_a_z_if_a_la_z", "tc_if_a_z_if_a_z_laa", "tc_if_a_z_if_a_laa_z", "tc_if_a_z_if_a_l3a_l3a", + "tc_cond_a_z_a_z_la", "tc_cond_a_z_a_la_z", "tc_cond_a_z_la", "tc_cond_a_la_z", "tc_cond_a_z_laa", "tc_cond_a_laa_z", + "tc_let_if_a_z_la", "tc_let_if_a_z_laa", "if_a_z_let_if_a_z_laa", + "tc_case_la", "tc_and_a_if_a_z_la", "tc_and_a_if_a_la_z", + + "recur_if_a_a_opa_laq", "recur_if_a_opa_laq_a", "recur_if_a_a_opla_aq", "recur_if_a_opla_aq_a", + "recur_if_a_a_opla_laq", "recur_if_a_opla_laq_a", + "recur_if_a_a_opa_la_laq", "recur_if_a_opa_la_laq_a", + "recur_if_a_a_opla_la_laq", "recur_if_a_a_if_a_a_opla_laq", "recur_if_a_a_if_a_a_oplaa_laaq", + "recur_if_a_a_opa_laaq", "recur_if_a_opa_laaq_a", "recur_if_a_a_opa_l3aq", + "recur_if_a_a_lopl3a_l3a_l3aq", "recur_if_a_a_and_a_laa_laa", + "recur_if_a_a_if_a_laa_opa_laaq", + "recur_cond_a_a_op_a_laq", "recur_cond_a_a_op_a_laaq", + "recur_cond_a_a_a_a_opla_laq", "recur_cond_a_a_a_a_oplaa_laaq", "recur_cond_a_a_a_a_opa_laaq", + "recur_cond_a_a_a_laa_lopa_laaq", "recur_cond_a_a_a_laa_opa_laaq", + "recur_and_a_or_a_laa_laa", +}; + +#define is_safe_c_op(op) ((op >= OP_SAFE_C_NC) && (op < OP_THUNK)) +#define is_unknown_op(op) ((op >= OP_UNKNOWN) && (op <= OP_UNKNOWN_NP)) +#define is_h_safe_c_nc(P) (optimize_op(P) == HOP_SAFE_C_NC) +#define is_safe_c_s(P) ((optimize_op(P) == OP_SAFE_C_S) || (optimize_op(P) == HOP_SAFE_C_S)) +#define is_h_safe_c_s(P) (optimize_op(P) == HOP_SAFE_C_S) +#define FIRST_UNHOPPABLE_OP OP_APPLY_SS + +static bool is_h_optimized(s7_pointer p) +{ + return((is_optimized(p)) && + (op_has_hop(p)) && + (optimize_op(p) < FIRST_UNHOPPABLE_OP) && /* was OP_S? */ + (optimize_op(p) > OP_GC_PROTECT)); +} + +/* if this changes, remember to change lint.scm */ +typedef enum {SL_NO_FIELD=0, SL_STACK_TOP, SL_STACK_SIZE, SL_STACKTRACE_DEFAULTS, SL_HEAP_SIZE, SL_FREE_HEAP_SIZE, + SL_GC_FREED, SL_GC_PROTECTED_OBJECTS, SL_GC_TOTAL_FREED, SL_GC_INFO, SL_FILE_NAMES, SL_FILENAMES, SL_ROOTLET_SIZE, SL_C_TYPES, + SL_SAFETY, SL_UNDEFINED_IDENTIFIER_WARNINGS, SL_UNDEFINED_CONSTANT_WARNINGS, SL_GC_STATS, SL_MAX_HEAP_SIZE, + SL_MAX_PORT_DATA_SIZE, SL_MAX_STACK_SIZE, SL_CPU_TIME, SL_CATCHES, SL_STACK, SL_MAJOR_VERSION, SL_MINOR_VERSION, + SL_MAX_STRING_LENGTH, SL_MAX_FORMAT_LENGTH, SL_MAX_LIST_LENGTH, SL_MAX_VECTOR_LENGTH, SL_MAX_VECTOR_DIMENSIONS, + SL_DEFAULT_HASH_TABLE_LENGTH, SL_INITIAL_STRING_PORT_LENGTH, SL_DEFAULT_RATIONALIZE_ERROR, + SL_DEFAULT_RANDOM_STATE, SL_EQUIVALENT_FLOAT_EPSILON, SL_HASH_TABLE_FLOAT_EPSILON, SL_PRINT_LENGTH, + SL_BIGNUM_PRECISION, SL_MEMORY_USAGE, SL_FLOAT_FORMAT_PRECISION, SL_HISTORY, SL_HISTORY_ENABLED, + SL_HISTORY_SIZE, SL_PROFILE, SL_PROFILE_INFO, SL_PROFILE_PREFIX, SL_AUTOLOADING, SL_ACCEPT_ALL_KEYWORD_ARGUMENTS, + SL_MUFFLE_WARNINGS, SL_MOST_POSITIVE_FIXNUM, SL_MOST_NEGATIVE_FIXNUM, SL_OUTPUT_PORT_DATA_SIZE, SL_DEBUG, SL_VERSION, + SL_GC_TEMPS_SIZE, SL_GC_RESIZE_HEAP_FRACTION, SL_GC_RESIZE_HEAP_BY_4_FRACTION, SL_OPENLETS, SL_EXPANSIONS, + SL_NUMBER_SEPARATOR, SL_NUM_FIELDS} s7_starlet_t; + +static const char *s7_starlet_names[SL_NUM_FIELDS] = + {"no-field", "stack-top", "stack-size", "stacktrace-defaults", "heap-size", "free-heap-size", + "gc-freed", "gc-protected-objects", "gc-total-freed", "gc-info", "file-names", "filenames", "rootlet-size", "c-types", + "safety", "undefined-identifier-warnings", "undefined-constant-warnings", "gc-stats", "max-heap-size", + "max-port-data-size", "max-stack-size", "cpu-time", "catches", "stack", "major-version", "minor-version", + "max-string-length", "max-format-length", "max-list-length", "max-vector-length", "max-vector-dimensions", + "default-hash-table-length", "initial-string-port-length", "default-rationalize-error", + "default-random-state", "equivalent-float-epsilon", "hash-table-float-epsilon", "print-length", + "bignum-precision", "memory-usage", "float-format-precision", "history", "history-enabled", + "history-size", "profile", "profile-info", "profile-prefix", "autoloading?", "accept-all-keyword-arguments", + "muffle-warnings?", "most-positive-fixnum", "most-negative-fixnum", "output-port-data-size", "debug", "version", + "gc-temps-size", "gc-resize-heap-fraction", "gc-resize-heap-by-4-fraction", "openlets", "expansions?", + "number-separator"}; + +static s7_pointer object_to_truncated_string(s7_scheme *sc, s7_pointer p, s7_int len); /* display_80 etc */ +static const char *type_name(s7_scheme *sc, s7_pointer arg, article_t article); +static s7_pointer cons_unchecked(s7_scheme *sc, s7_pointer a, s7_pointer b); +static s7_pointer unbound_variable(s7_scheme *sc, s7_pointer sym); + + +#define bold_text "\033[1m" +#define unbold_text "\033[22m" +#define red_text "\033[31m" +#define green_text "\033[32m" +#define blue_text "\033[34m" +#define uncolor_text "\033[0m" /* yellow=33 */ + + +/* -------------------------------- internal debugging apparatus -------------------------------- */ +static int64_t heap_location(s7_scheme *sc, s7_pointer p) +{ + for (heap_block_t *hp = sc->heap_blocks; hp; hp = hp->next) + if (((intptr_t)p >= hp->start) && ((intptr_t)p < hp->end)) + return(hp->offset + (((intptr_t)p - hp->start) / sizeof(s7_cell))); + return(((s7_big_pointer)p)->big_hloc); +} + +#if TRAP_SEGFAULT +#include +static Jmp_Buf senv; /* global here is not a problem -- it is used only to protect s7_is_valid */ +static volatile sig_atomic_t can_jump = 0; +static void segv(int32_t unused) {if (can_jump) LongJmp(senv, 1);} +#endif + +bool s7_is_valid(s7_scheme *sc, s7_pointer arg) +{ + bool result = false; + if (!arg) return(false); + { + s7_pointer heap0 = *(sc->heap); + const s7_pointer heap1 = (s7_pointer)(heap0 + sc->heap_size); + if ((arg >= heap0) && (arg < heap1)) return(true); + } +#if TRAP_SEGFAULT + if (SetJmp(senv, 1) == 0) + { + void (*old_segv)(int32_t sig); + can_jump = 1; + old_segv = signal(SIGSEGV, segv); +#endif + if ((unchecked_type(arg) > T_FREE) && + (unchecked_type(arg) < NUM_TYPES)) + { + if (!in_heap(arg)) + result = true; + else + { + int64_t loc = heap_location(sc, arg); + if ((loc >= 0) && (loc < sc->heap_size)) + result = (sc->heap[loc] == arg); + }} +#if TRAP_SEGFAULT + signal(SIGSEGV, old_segv); + } + else result = false; + can_jump = 0; +#endif + return(result); +} + +#define safe_print(Code) \ + do { \ + bool old_open = sc->has_openlets, old_stop = sc->stop_at_error; \ + sc->has_openlets = false; \ + sc->stop_at_error = false; \ + Code; \ + sc->stop_at_error = old_stop; \ + sc->has_openlets = old_open; \ + } while (0) + +void s7_show_history(s7_scheme *sc); +void s7_show_history(s7_scheme *sc) +{ +#if WITH_HISTORY + if (sc->cur_code == sc->history_sink) + fprintf(stderr, "history diabled\n"); + else + { + int32_t size = sc->history_size; + s7_pointer p = cdr(sc->cur_code); + fprintf(stderr, "history:\n"); + for (int32_t i = 0; i < size; i++, p = cdr(p)) /* stepper "i" is not redundant */ + safe_print(fprintf(stderr, "%d: %s\n", i, display_80(car(p)))); + fprintf(stderr, "\n"); + } +#else + fprintf(stderr, "%s\n", display(sc->cur_code)); +#endif +} + +#define stack_op(Stack, Loc) ((opcode_t)T_Op(stack_element(Stack, Loc))) +#define stack_args(Stack, Loc) stack_element(Stack, Loc - 1) +#define stack_let(Stack, Loc) stack_element(Stack, Loc - 2) +#define stack_code(Stack, Loc) stack_element(Stack, Loc - 3) +#define set_stack_op(Stack, Loc, Op) stack_element(Stack, Loc) = (s7_pointer)(Op) + +#define stack_top_op(Sc) ((opcode_t)T_Op(Sc->stack_end[-1])) +#define unchecked_stack_top_op(Sc) ((opcode_t)(Sc->stack_end[-1])) +#define stack_top_args(Sc) (Sc->stack_end[-2]) +#define stack_top_let(Sc) (Sc->stack_end[-3]) +#define stack_top_code(Sc) (Sc->stack_end[-4]) +#define set_stack_top_op(Sc, Op) Sc->stack_end[-1] = (s7_pointer)(Op) + +/* stack_top_code changes. If a function has a tail-call, the stack_top_code that form sees + * if stack_top_op==op-begin1 can change from call to call -- the begin actually refers + * to the caller, which is dependent on where the current function was called, so we can't hard-wire + * any optimizations based on that sequence. + */ + +void s7_show_stack(s7_scheme *sc); +void s7_show_stack(s7_scheme *sc) +{ + fprintf(stderr, "stack:\n"); + for (int64_t i = stack_top(sc) - 1; i >= 3; i -= 4) + fprintf(stderr, " %s\n", op_names[stack_op(sc->stack, i)]); +} + +#if S7_DEBUGGING +#define UNUSED_BITS 0xfc00000000c0 /* high 6 bits of optimizer code + high 2 bits of type */ + +static char *describe_type_bits(s7_scheme *sc, s7_pointer obj) +{ + uint64_t full_typ = full_type(obj); + uint8_t typ = unchecked_type(obj); + char *buf; + char str[900]; + + str[0] = '\0'; + catstrs(str, 900, /* if debugging, all of these bits are being watched, so we need to access them directly */ + /* bit 8 (the first 8 bits (after the 8 type bits) are easy...) */ + ((full_typ & T_MULTIFORM) != 0) ? ((is_any_closure(obj)) ? + (((full_typ & T_ONE_FORM) != 0) ? " closure-one-form-has-fx" : " closure-multiform") : + " ?0?") : "", + /* bit 9 */ + ((full_typ & T_SYNTACTIC) != 0) ? (((is_pair(obj)) || (is_syntax(obj)) || (is_normal_symbol(obj))) ? + " syntactic" : + " ?1?") : "", + /* bit 10 */ + ((full_typ & T_SIMPLE_ARG_DEFAULTS) != 0) ? ((is_pair(obj)) ? " simple-args|in-use" : + ((is_any_closure(obj)) ? " closure-one-form" : + " ?2?")) : "", + /* bit 11 */ + ((full_typ & T_OPTIMIZED) != 0) ? ((is_c_function(obj)) ? " scope-safe" : + ((is_pair(obj)) ? " optimized" : + " ?3?")) : "", + /* bit 12 */ + ((full_typ & T_SAFE_CLOSURE) != 0) ? (((has_closure_let(obj)) || (is_pair(obj))) ? " safe-closure" : " ?4?") : "", + /* bit 13 */ + ((full_typ & T_DONT_EVAL_ARGS) != 0) ? (((is_any_macro(obj)) || (is_syntax(obj))) ? " dont-eval-args" : " ?5?") : "", + /* bit 14 */ + ((full_typ & T_EXPANSION) != 0) ? (((is_normal_symbol(obj)) || (is_either_macro(obj))) ? " expansion" : " ?6?") : "", + /* bit 15 */ + ((full_typ & T_MULTIPLE_VALUE) != 0) ? ((is_symbol(obj)) ? " matched" : + ((is_pair(obj)) ? " values|matched" : + " ?7?")) : "", + /* bit 16 */ + ((full_typ & T_GLOBAL) != 0) ? ((is_pair(obj)) ? " unsafe-do" : + (((is_symbol(obj)) || (is_syntax(obj))) ? " global" : + ((is_let(obj)) ? " dox_slot1" : + " ?8?"))) : "", + /* bit 17 */ + ((full_typ & T_COLLECTED) != 0) ? " collected" : "", + /* bit 18 */ + ((full_typ & T_LOCATION) != 0) ? ((is_pair(obj)) ? " line-number" : + ((is_input_port(obj)) ? " loader-port" : + ((is_let(obj)) ? " with-let" : + ((is_any_procedure(obj)) ? " simple-defaults" : + ((is_slot(obj)) ? " has-setter" : + " ?10?"))))) : "", + /* bit 19 */ + ((full_typ & T_SHARED) != 0) ? ((is_sequence(obj)) ? " shared" : " ?11?") : "", + /* bit 20 */ + ((full_typ & T_LOCAL) != 0) ? ((is_normal_symbol(obj)) ? " local" : + ((is_pair(obj)) ? " high-c" : + " ?12?")) : "", + /* bit 21 */ + ((full_typ & T_SAFE_PROCEDURE) != 0) ? ((is_applicable(obj)) ? " safe-procedure" : " ?13?") : "", + /* bit 22 */ + ((full_typ & T_CHECKED) != 0) ? (((is_pair(obj)) || (is_slot(obj))) ? " checked" : + ((is_symbol(obj)) ? " all-integer" : + " ?14?")) : "", + /* bit 23 */ + ((full_typ & T_UNSAFE) != 0) ? ((is_symbol(obj)) ? " clean-symbol" : + ((is_slot(obj)) ? " has-stepper" : + ((is_pair(obj)) ? " unsafely-opt|no-float-opt" : + ((is_let(obj)) ? " dox-slot2" : + " ?15?")))) : "", + /* bit 24 */ + ((full_typ & T_IMMUTABLE) != 0) ? " immutable" : "", + /* bit 25 */ + ((full_typ & T_SETTER) != 0) ? ((is_normal_symbol(obj)) ? " setter" : + ((is_pair(obj)) ? " allow-other-keys|no-int-opt" : + ((is_slot(obj)) ? " has-expression" : + ((is_c_function_star(obj)) ? " allow-other-keys" : + " ?17?")))) : "", + /* bit 26 */ + ((full_typ & T_MUTABLE) != 0) ? ((is_number(obj)) ? " mutable" : + ((is_symbol(obj)) ? " has-keyword" : + ((is_let(obj)) ? " ref-fallback" : + ((is_iterator(obj)) ? " mark-sequence" : + ((is_slot(obj)) ? " step-end" : + ((is_pair(obj)) ? " no-opt" : + " ?18?")))))) : "", + /* bit 27 */ + ((full_typ & T_SAFE_STEPPER) != 0) ? ((is_let(obj)) ? " set-fallback" : + ((is_slot(obj)) ? " safe-stepper" : + ((is_c_function(obj)) ? " maybe-safe" : + ((is_number(obj)) ? " print-name" : + ((is_pair(obj)) ? " direct-opt" : + ((is_hash_table(obj)) ? " weak-hash" : + ((is_any_macro(obj)) ? " pair-macro-set" : + ((is_symbol(obj)) ? " all-float" : + " ?19?")))))))) : "", + /* bit 28, for c_function case see sc->apply */ + ((full_typ & T_COPY_ARGS) != 0) ? (((is_pair(obj)) || (is_any_macro(obj)) || (is_syntax(obj)) || + (is_any_closure(obj)) || (is_c_function(obj))) ? " copy-args" : + " ?20?") : "", + /* bit 29 */ + ((full_typ & T_GENSYM) != 0) ? ((is_let(obj)) ? " funclet" : + ((is_normal_symbol(obj)) ? " gensym" : + ((is_string(obj)) ? " documented-symbol" : + ((is_hash_table(obj)) ? " hash-chosen" : + ((is_pair(obj)) ? " fx-treed" : + ((is_any_vector(obj)) ? " subvector" : + ((is_slot(obj)) ? " has-pending-value" : + ((is_any_closure(obj)) ? " unknopt" : + " ?21?")))))))) : "", + /* bit 30 */ + ((full_typ & T_HAS_METHODS) != 0) ? (((is_let(obj)) || (is_c_object(obj)) || (is_any_closure(obj)) || + (is_any_macro(obj)) || (is_c_pointer(obj))) ? " has-methods" : " ?22?") : "", + /* bit 31 */ + ((full_typ & T_ITER_OK) != 0) ? ((is_iterator(obj)) ? " iter-ok" : + ((is_pair(obj)) ? " loop-end-possible" : + ((is_slot(obj)) ? " in-rootlet" : + ((is_c_function(obj)) ? " bool-function" : + ((is_symbol(obj)) ? " symbol-from-symbol" : + " ?23?"))))) : "", + /* bit 24+24 */ + ((full_typ & T_FULL_SYMCONS) != 0) ? ((is_symbol(obj)) ? " possibly-constant" : + ((is_any_procedure(obj)) ? " has-let-arg" : + ((is_hash_table(obj)) ? " has-value-type" : + ((is_pair(obj)) ? " int-optable" : + " ?24?")))) : "", + /* bit 25+24 */ + ((full_typ & T_FULL_HAS_LET_FILE) != 0) ? ((is_let(obj)) ? " has-let-file" : + ((is_any_vector(obj)) ? " typed-vector" : + ((is_hash_table(obj)) ? " typed-hash-table" : + ((is_c_function(obj)) ? " has-bool-setter" : + ((is_slot(obj)) ? " rest-slot" : + (((is_pair(obj)) || (is_closure_star(obj))) ? " no-defaults" : + " ?25?")))))) : "", + /* bit 26+24 */ + ((full_typ & T_FULL_DEFINER) != 0) ? ((is_normal_symbol(obj)) ? " definer" : + ((is_pair(obj)) ? " has-fx" : + ((is_slot(obj)) ? " slot-defaults" : + ((is_iterator(obj)) ? " weak-hash-iterator" : + ((is_hash_table(obj)) ? " has-key-type" : + ((is_let(obj)) ? " maclet" : + ((is_c_function(obj)) ? " func-definer" : + ((is_syntax(obj)) ? " syntax-definer" : + " ?26?")))))))) : "", + /* bit 27+24 */ + ((full_typ & T_FULL_BINDER) != 0) ? ((is_pair(obj)) ? " tree-collected" : + ((is_hash_table(obj)) ? " simple-values" : + ((is_normal_symbol(obj)) ? " binder" : + ((is_c_function(obj)) ? " safe-args" : + ((is_syntax(obj)) ? " syntax-binder" : + " ?27?"))))) : "", + /* bit 28+24 */ + ((full_typ & T_VERY_SAFE_CLOSURE) != 0) ? (((is_pair(obj)) || (is_any_closure(obj))) ? " very-safe-closure" : + ((is_let(obj)) ? " baffle-let" : + " ?28?")) : "", + /* bit 29+24 */ + ((full_typ & T_CYCLIC) != 0) ? (((is_simple_sequence(obj)) || (t_structure_p[type(obj)]) || + (is_any_closure(obj))) ? " cyclic" : " ?29?") : "", + /* bit 30+24 */ + ((full_typ & T_CYCLIC_SET) != 0) ? (((is_simple_sequence(obj)) || (t_structure_p[type(obj)]) || + (is_any_closure(obj))) ? " cyclic-set" : " ?30?") : "", + /* bit 31+24 */ + ((full_typ & T_KEYWORD) != 0) ? ((is_symbol(obj)) ? " keyword" : + ((is_pair(obj)) ? " fx-treeable" : + " ?31?")) : "", + /* bit 32+24 */ + ((full_typ & T_FULL_SIMPLE_ELEMENTS) != 0) ? ((is_normal_vector(obj)) ? " simple-elements" : + ((is_hash_table(obj)) ? " simple-keys" : + ((is_normal_symbol(obj)) ? " safe-setter" : + ((is_pair(obj)) ? " float-optable" : + ((typ >= T_C_MACRO) ? " function-simple-elements" : + " 32?"))))) : "", + /* bit 33+24 */ + ((full_typ & T_FULL_CASE_KEY) != 0) ? ((is_symbol(obj)) ? " case-key" : + ((is_pair(obj)) ? " opt1-func-listed" : + " ?33?")) : "", + /* bit 34+24 */ + ((full_typ & T_FULL_HAS_GX) != 0) ? ((is_pair(obj)) ? " has-gx" : " ?34?") : "", + /* bit 35+24 */ + ((full_typ & T_FULL_UNKNOPT) != 0) ? ((is_pair(obj)) ? " unknopt" : " ?35?") : "", + /* bit 36+24 */ + ((full_typ & T_FULL_SAFETY_CHECKED) != 0) ? ((is_pair(obj)) ? " safety-checked" : " ?36?") : "", + /* bit 37+24 */ + ((full_typ & T_FULL_HAS_FN) != 0) ? ((is_pair(obj)) ? " has-fn" : " ?37") : "", + /* bit 62 */ + ((full_typ & T_UNHEAP) != 0) ? " unheap" : "", + /* bit 63 */ + ((full_typ & T_GC_MARK) != 0) ? " gc-marked" : "", + + ((full_typ & UNUSED_BITS) != 0) ? " unused bits set?" : "", + ((is_symbol(obj)) && (((uint8_t)(symbol_type(obj) & 0xff) >= NUM_TYPES) || ((symbol_type(obj) & ~0xffff) != 0))) ? " bad-symbol-type" : "", + NULL); + + buf = (char *)Malloc(1024); + snprintf(buf, 1024, "type: %s? (%d), opt_op: %d %s, flags: #x%" PRIx64 "%s", + type_name(sc, obj, NO_ARTICLE), typ, optimize_op(obj), (optimize_op(obj) < NUM_OPS) ? op_names[optimize_op(obj)] : "", full_typ, str); + return(buf); +} + +/* snprintf returns the number of bytes that would have been written: (display (c-pointer 123123123 (symbol (make-string 130 #\a)))) */ + +static bool never_unheaped[NUM_TYPES]; +static void init_never_unheaped(void) +{ + for (int i = 0; i < NUM_TYPES; i++) never_unheaped[i] = false; + never_unheaped[T_BACRO] = true; + never_unheaped[T_BACRO_STAR] = true; + never_unheaped[T_CATCH] = true; + never_unheaped[T_CLOSURE] = true; + never_unheaped[T_CLOSURE_STAR] = true; + never_unheaped[T_CONTINUATION] = true; + never_unheaped[T_COUNTER] = true; + never_unheaped[T_C_OBJECT] = true; + never_unheaped[T_C_POINTER] = true; + never_unheaped[T_DYNAMIC_WIND] = true; + never_unheaped[T_FREE] = true; + never_unheaped[T_GOTO] = true; + never_unheaped[T_HASH_TABLE] = true; + never_unheaped[T_ITERATOR] = true; + never_unheaped[T_MACRO] = true; + never_unheaped[T_MACRO_STAR] = true; + never_unheaped[T_RANDOM_STATE] = true; + never_unheaped[T_SLOT] = true; + never_unheaped[T_STACK] = true; + never_unheaped[T_UNUSED] = true; + never_unheaped[T_VECTOR] = true; +} + +static bool has_odd_bits(s7_pointer obj) +{ + uint64_t full_typ = full_type(obj); + if ((full_typ & UNUSED_BITS) != 0) return(true); + if (((full_typ & T_MULTIFORM) != 0) && (!is_any_closure(obj))) return(true); + if (((full_typ & T_KEYWORD) != 0) && (!is_symbol(obj)) && (!is_pair(obj))) return(true); + if (((full_typ & T_SYNTACTIC) != 0) && (!is_syntax(obj)) && (!is_pair(obj)) && (!is_normal_symbol(obj))) return(true); + if (((full_typ & T_SIMPLE_ARG_DEFAULTS) != 0) && (!is_pair(obj)) && (!is_any_closure(obj))) return(true); + if (((full_typ & T_OPTIMIZED) != 0) && (!is_c_function(obj)) && (!is_pair(obj))) return(true); + if (((full_typ & T_SAFE_CLOSURE) != 0) && (!is_any_closure(obj)) && (!is_pair(obj))) return(true); + if (((full_typ & T_SAFE_PROCEDURE) != 0) && (!is_applicable(obj))) return(true); + if (((full_typ & T_EXPANSION) != 0) && (!is_normal_symbol(obj)) && (!is_either_macro(obj))) return(true); + if (((full_typ & T_MULTIPLE_VALUE) != 0) && (!is_symbol(obj)) && (!is_pair(obj))) return(true); + if (((full_typ & T_GLOBAL) != 0) && (!is_pair(obj)) && (!is_symbol(obj)) && (!is_let(obj)) && (!is_syntax(obj))) return(true); + if (((full_typ & T_ITER_OK) != 0) && (!is_iterator(obj)) && (!is_pair(obj)) && (!is_slot(obj)) && (!is_c_function(obj)) && (!is_symbol(obj))) return(true); + if (((full_typ & T_LOCAL) != 0) && (!is_normal_symbol(obj)) && (!is_pair(obj))) return(true); + if (((full_typ & T_UNSAFE) != 0) && (!is_symbol(obj)) && (!is_slot(obj)) && (!is_let(obj)) && (!is_pair(obj))) return(true); + if (((full_typ & T_VERY_SAFE_CLOSURE) != 0) && (!is_pair(obj)) && (!is_any_closure(obj)) && (!is_let(obj))) return(true); + if (((full_typ & T_FULL_CASE_KEY) != 0) && (!is_symbol(obj)) && (!is_pair(obj))) return(true); + if (((full_typ & T_FULL_UNKNOPT) != 0) && (!is_pair(obj))) return(true); + if (((full_typ & T_FULL_SAFETY_CHECKED) != 0) && (!is_pair(obj))) return(true); + if (((full_typ & T_FULL_HAS_GX) != 0) && (!is_pair(obj))) return(true); + if (((full_typ & T_DONT_EVAL_ARGS) != 0) && (!is_any_macro(obj)) && (!is_syntax(obj))) return(true); + if (((full_typ & T_CHECKED) != 0) && (!is_slot(obj)) && (!is_pair(obj)) && (!is_symbol(obj))) return(true); + if (((full_typ & T_SHARED) != 0) && (!t_sequence_p[type(obj)]) && (!t_structure_p[type(obj)]) && (!is_any_closure(obj))) return(true); + if (((full_typ & T_COPY_ARGS) != 0) && (!is_pair(obj)) && + (!is_any_macro(obj)) && (!is_any_closure(obj)) && (!is_c_function(obj)) && (!is_syntax(obj))) + return(true); + if (((full_typ & T_FULL_SYMCONS) != 0) && + (!is_symbol(obj)) && (!is_any_procedure(obj)) && (!is_hash_table(obj)) && (!is_pair(obj))) + return(true); + if (((full_typ & T_FULL_BINDER) != 0) && + ((!is_pair(obj)) && (!is_hash_table(obj)) && (!is_normal_symbol(obj)) && (!is_c_function(obj)) && (!is_syntax(obj)))) + return(true); + if (((full_typ & T_FULL_DEFINER) != 0) && + (!is_normal_symbol(obj)) && (!is_c_function(obj)) && (!is_pair(obj)) && (!is_slot(obj)) && (!is_iterator(obj)) && + (!is_hash_table(obj)) && (!is_let(obj)) && (!is_syntax(obj))) + return(true); + if (((full_typ & T_FULL_HAS_LET_FILE) != 0) && + (!is_let(obj)) && (!is_any_vector(obj)) && (!is_hash_table(obj)) && (!is_c_function(obj)) && + (!is_slot(obj)) && (!is_pair(obj)) && (!is_closure_star(obj))) + return(true); + if (((full_typ & T_SAFE_STEPPER) != 0) && + (!is_let(obj)) && (!is_slot(obj)) && (!is_c_function(obj)) && (!is_number(obj)) && + (!is_pair(obj)) && (!is_hash_table(obj)) && (!is_any_macro(obj)) && (!is_symbol(obj))) + return(true); + if (((full_typ & T_SETTER) != 0) && + (!is_slot(obj)) && (!is_normal_symbol(obj)) && (!is_pair(obj)) && (!is_let(obj)) && (!is_c_function_star(obj))) + return(true); + if (((full_typ & T_LOCATION) != 0) && + (!is_pair(obj)) && (!is_input_port(obj)) && (!is_let(obj)) && (!is_any_procedure(obj)) && (!is_slot(obj))) + return(true); + if (((full_typ & T_MUTABLE) != 0) && + (!is_number(obj)) && (!is_symbol(obj)) && (!is_let(obj)) && (!is_iterator(obj)) && (!is_slot(obj)) && (!is_let(obj)) && (!is_pair(obj))) + return(true); + if (((full_typ & T_GENSYM) != 0) && (!is_slot(obj)) && (!is_any_closure(obj)) && + (!is_let(obj)) && (!is_symbol(obj)) && (!is_string(obj)) && (!is_hash_table(obj)) && (!is_pair(obj)) && (!is_any_vector(obj))) + return(true); + if (((full_typ & T_FULL_SIMPLE_ELEMENTS) != 0) && + ((!is_normal_vector(obj)) && (!is_hash_table(obj)) && (!is_normal_symbol(obj)) && (!is_pair(obj)) && (unchecked_type(obj) < T_C_MACRO))) + return(true); + if (((full_typ & T_CYCLIC) != 0) && (!is_simple_sequence(obj)) && (!t_structure_p[type(obj)]) && (!is_any_closure(obj))) return(true); + if (((full_typ & T_CYCLIC_SET) != 0) && (!is_simple_sequence(obj)) && (!t_structure_p[type(obj)]) && (!is_any_closure(obj))) return(true); + if (((full_typ & T_FULL_HAS_FN) != 0) && (!is_pair(obj))) return(true); + if (is_symbol(obj)) + { + if ((uint8_t)(symbol_type(obj) & 0xff) >= NUM_TYPES) + return(true); + if ((symbol_type(obj) & ~0xffff) != 0) /* boolean function bool type and *s7*_let field id */ + return(true); + } + if ((signed_type(obj) == 0) && ((full_typ & T_GC_MARK) != 0)) return(true); + + if (!in_heap(obj)) + { + uint8_t typ = unchecked_type(obj); + if (never_unheaped[typ]) {fprintf(stderr, "unheap %s?\n", s7_type_names[typ]); return(true);} + } + /* all the hash_table bits seem to be compatible, symbols? (all_float/all_integer only apply to sc->divide_symbol et al at init time) */ + return(false); +} + +void s7_show_let(s7_scheme *sc); +void s7_show_let(s7_scheme *sc) /* debugging convenience */ +{ + for (s7_pointer olet = sc->curlet; is_let(T_Lid(olet)); olet = let_outlet(olet)) + { + if (olet == sc->owlet) + fprintf(stderr, "(owlet): "); + else + if (is_funclet(olet)) + fprintf(stderr, "(%s funclet): ", display(funclet_function(olet))); + else + if (olet == sc->shadow_rootlet) + fprintf(stderr, "(shadow rootlet): "); + fprintf(stderr, "%s\n", display(olet)); + } +} + +static const char *check_name(s7_scheme *sc, int32_t typ) +{ + if ((typ >= 0) && (typ < NUM_TYPES)) + { + s7_pointer p = sc->type_names[typ]; + if (is_string(p)) return(string_value(p)); + } + return("unknown type!"); +} + +#if REPORT_ROOTLET_REDEF +static void set_local_1(s7_scheme *sc, s7_pointer symbol, const char *func, int32_t line) +{ + if (is_global(symbol)) + fprintf(stderr, "%s[%d]: %s%s%s in %s\n", + func, line, + bold_text, display(symbol), unbold_text, + display_80(sc->cur_code)); + full_type(symbol) = (full_type(symbol) & ~(T_DONT_EVAL_ARGS | T_GLOBAL | T_SYNTACTIC)); +} +#endif + +static char *safe_object_to_string(s7_pointer p) +{ + char *buf = (char *)Malloc(128); + snprintf(buf, 128, "type: %d", unchecked_type(p)); + return(buf); +} + +static void complain(const char* complaint, s7_pointer p, const char *func, int32_t line, uint8_t typ) +{ + fprintf(stderr, complaint, bold_text, func, line, check_name(cur_sc, typ), safe_object_to_string(p), unbold_text); + if (cur_sc->stop_at_error) abort(); +} + +static char* show_debugger_bits(s7_pointer p) +{ + char *bits_str = (char *)Malloc(512); + int64_t bits = p->debugger_bits; + snprintf(bits_str, 512, " %s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", + ((bits & OPT1_SET) != 0) ? " opt1_set" : "", + ((bits & OPT1_FAST) != 0) ? " opt1_fast" : "", + ((bits & OPT1_CFUNC) != 0) ? " opt1_cfunc" : "", + ((bits & OPT1_CLAUSE) != 0) ? " opt1_clause" : "", + ((bits & OPT1_LAMBDA) != 0) ? " opt1_lambda" : "", + ((bits & OPT1_SYM) != 0) ? " opt1_sym" : "", + ((bits & OPT1_PAIR) != 0) ? " opt1_pair" : "", + ((bits & OPT1_CON) != 0) ? " opt1_con" : "", + ((bits & OPT1_ANY) != 0) ? " opt1_any" : "", + ((bits & OPT1_HASH) != 0) ? " opt1_hash" : "", + + ((bits & OPT2_SET) != 0) ? " opt2_set" : "", + ((bits & OPT2_KEY) != 0) ? " opt2_any" : "", + ((bits & OPT2_SLOW) != 0) ? " opt2_slow" : "", + ((bits & OPT2_SYM) != 0) ? " opt2_sym" : "", + ((bits & OPT2_PAIR) != 0) ? " opt2_pair" : "", + ((bits & OPT2_CON) != 0) ? " opt2_con" : "", + ((bits & OPT2_FX) != 0) ? " opt2_fx" : "", + ((bits & OPT2_FN) != 0) ? " opt2_fn" : "", + ((bits & OPT2_LAMBDA) != 0) ? " opt2_lambda" : "", + ((bits & OPT2_DIRECT) != 0) ? " opt2_direct" : "", + ((bits & OPT2_NAME) != 0) ? " opt2_raw_name" : "", + ((bits & OPT2_INT) != 0) ? " opt2_int" : "", + + ((bits & OPT3_SET) != 0) ? " opt3_set" : "", + ((bits & OPT3_ARGLEN) != 0) ? " opt3_arglen" : "", + ((bits & OPT3_SYM) != 0) ? " opt3_sym" : "", + ((bits & OPT3_CON) != 0) ? " opt3_con" : "", + ((bits & OPT3_AND) != 0) ? " opt3_pair " : "", + ((bits & OPT3_ANY) != 0) ? " opt3_any " : "", + ((bits & OPT3_LET) != 0) ? " opt3_let " : "", + ((bits & OPT3_BYTE) != 0) ? " opt3_byte " : "", + ((bits & OPT3_DIRECT) != 0) ? " opt3_direct" : "", + ((bits & OPT3_LOCATION) != 0) ? " opt3_location" : "", + ((bits & OPT3_LEN) != 0) ? " opt3_len" : "", + ((bits & OPT3_INT) != 0) ? " opt3_int" : "", + + ((bits & L_HIT) != 0) ? " let_set" : "", + ((bits & L_FUNC) != 0) ? " let_func" : "", + ((bits & L_DOX) != 0) ? " let_dox" : ""); + return(bits_str); +} + +static s7_pointer check_ref(s7_pointer p, uint8_t expected_type, const char *func, int32_t line, const char *func1, const char *func2) +{ + if (!p) + fprintf(stderr, "%s%s[%d]: null pointer passed to check_ref%s\n", bold_text, func, line, unbold_text); + else + { + uint8_t typ = unchecked_type(p); + if (typ != expected_type) + { + if ((!func1) || (typ != T_FREE)) + { + fprintf(stderr, "%s%s[%d]: not %s, but %s (%s)%s\n", + bold_text, + func, line, check_name(cur_sc, expected_type), check_name(cur_sc, typ), safe_object_to_string(p), + unbold_text); + if (cur_sc->stop_at_error) abort(); + } + else + if ((strcmp(func, func1) != 0) && + ((!func2) || (strcmp(func, func2) != 0))) + { + fprintf(stderr, "%s%s[%d]: free cell, not %s%s\n", bold_text, func, line, check_name(cur_sc, expected_type), unbold_text); + if (cur_sc->stop_at_error) abort(); + }}} + return(p); +} + +static s7_pointer check_let_ref(s7_pointer p, uint64_t role, const char *func, int32_t line) +{ + check_ref(p, T_LET, func, line, NULL, NULL); + if ((p->debugger_bits & L_HIT) == 0) fprintf(stderr, "%s[%d]: let not set\n", func, line); + if ((p->debugger_bits & L_MASK) != role) fprintf(stderr, "%s[%d]: let bad role\n", func, line); + return(p); +} + +static s7_pointer check_let_set(s7_pointer p, uint64_t role, const char *func, int32_t line) +{ + check_ref(p, T_LET, func, line, NULL, NULL); + p->debugger_bits &= (~L_MASK); + p->debugger_bits |= (L_HIT | role); + return(p); +} + +static s7_pointer check_ref2(s7_pointer p, uint8_t expected_type, int32_t other_type, const char *func, int32_t line, const char *func1, const char *func2) +{ + if (!p) + fprintf(stderr, "%s[%d]: null pointer passed to check_ref2\n", func, line); + else + { + uint8_t typ = unchecked_type(p); + if ((typ != expected_type) && (typ != other_type)) + return(check_ref(p, expected_type, func, line, func1, func2)); + } + return(p); +} + +static s7_pointer check_ref3(s7_pointer p, const char *func, int32_t line) +{ + uint8_t typ = unchecked_type(p); + if ((typ != T_INPUT_PORT) && (typ != T_OUTPUT_PORT) && (typ != T_FREE)) + complain("%s%s[%d]: not a port, but %s (%s)%s\n", p, func, line, typ); + return(p); +} + +static s7_pointer check_ref3i(s7_pointer p, const char *func, int32_t line) +{ + uint8_t typ = unchecked_type(p); + if ((typ != T_INPUT_PORT) && (p != cur_sc->F)) + complain("%s%s[%d]: not an input port, but %s (%s)%s\n", p, func, line, typ); + return(p); +} + +static s7_pointer check_ref3o(s7_pointer p, const char *func, int32_t line) +{ + uint8_t typ = unchecked_type(p); + if ((typ != T_OUTPUT_PORT) && (p != cur_sc->F)) + complain("%s%s[%d]: not an output port, but %s (%s)%s\n", p, func, line, typ); + return(p); +} + +static s7_pointer check_ref4(s7_pointer p, const char *func, int32_t line) +{ + if ((strcmp(func, "sweep") != 0) && + (strcmp(func, "process_multivector") != 0)) + { + uint8_t typ = unchecked_type(p); + if (!t_vector_p[typ]) complain("%s%s[%d]: not a vector, but %s (%s)%s\n", p, func, line, typ); + } + return(p); +} + +static s7_pointer check_ref5(s7_pointer p, const char *func, int32_t line) +{ + uint8_t typ = unchecked_type(p); + if (!t_has_closure_let[typ]) complain("%s%s[%d]: not a closure, but %s (%s)%s\n", p, func, line, typ); + return(p); +} + +static s7_pointer check_ref6(s7_pointer p, const char *func, int32_t line) +{ + uint8_t typ = unchecked_type(p); + if (typ < T_C_MACRO) complain("%s%s[%d]: not a c function or macro, but %s (%s)%s\n", p, func, line, typ); + return(p); +} + +static s7_pointer check_ref7(s7_pointer p, const char *func, int32_t line) +{ + uint8_t typ = unchecked_type(p); + if ((typ < T_INTEGER) || (typ > T_COMPLEX)) + complain("%s%s[%d]: not a number, but %s (%s)%s\n", p, func, line, typ); + return(p); +} + +static s7_pointer check_ref8(s7_pointer p, const char *func, int32_t line) +{ + uint8_t typ = unchecked_type(p); + if ((!t_sequence_p[typ]) && (!t_structure_p[typ]) && (!is_any_closure(p))) /* closure as iterator -- see s7test */ + complain("%s%s[%d]: not a sequence or structure, but %s (%s)%s\n", p, func, line, typ); + return(p); +} + +static s7_pointer check_ref9(s7_pointer p, const char *func, int32_t line) +{ + uint8_t typ = unchecked_type(p); + if ((typ != T_LET) && (typ != T_C_OBJECT) && (!is_any_closure(p)) && (!is_any_macro(p)) && (typ != T_C_POINTER)) + complain("%s%s[%d]: not a possible method holder, but %s (%s)%s\n", p, func, line, typ); + return(p); +} + +static s7_pointer check_ref10(s7_pointer p, const char *func, int32_t line) +{ + uint8_t typ = unchecked_type(p); + if ((typ != T_PAIR) && (typ != T_NIL) && (typ != T_SYMBOL)) + complain("%s%s[%d]: arglist is %s (%s)%s?\n", p, func, line, typ); + return(p); +} + +static s7_pointer check_ref11(s7_pointer p, const char *func, int32_t line) +{ + uint8_t typ = unchecked_type(p); + if ((!t_applicable_p[typ]) && (p != cur_sc->F)) + complain("%s%s[%d]: applicable object is %s (%s)%s?\n", p, func, line, typ); + return(p); +} + +static s7_pointer check_ref12(s7_pointer p, const char *func, int32_t line) +{ + uint8_t typ; + if (is_slot_end(p)) return(p); + typ = unchecked_type(p); + if ((typ != T_SLOT) && (typ != T_NIL)) /* unset slots are nil */ + complain("%s%s[%d]: slot is %s (%s)%s?\n", p, func, line, typ); + return(p); +} + +static s7_pointer check_ref13(s7_pointer p, const char *func, int32_t line) +{ + if (!is_any_vector(p)) complain("%s%s[%d]: subvector is %s (%s)%s?\n", p, func, line, unchecked_type(p)); + if (!is_subvector(p)) complain("%s%s[%d]: subvector is %s (%s), but not a subvector?%s\n", p, func, line, unchecked_type(p)); + return(p); +} + +static s7_pointer check_ref14(s7_pointer p, const char *func, int32_t line) +{ + if ((!is_any_procedure(p)) && (!is_boolean(p))) + complain("%s%s[%d]: setter is %s (%s)%s?\n", p, func, line, unchecked_type(p)); + return(p); +} + +static void print_gc_info(s7_scheme *sc, s7_pointer obj, int32_t line) +{ + if (!obj) + fprintf(stderr, "[%d]: obj is %p\n", line, obj); + else + if (unchecked_type(obj) != T_FREE) + fprintf(stderr, "[%d]: %p type is %d?\n", line, obj, unchecked_type(obj)); + else + { + s7_int free_type = full_type(obj); + char *bits; + char fline[128]; + full_type(obj) = obj->alloc_type; + sc->printing_gc_info = true; + bits = describe_type_bits(sc, obj); /* this func called in type macro */ + sc->printing_gc_info = false; + full_type(obj) = free_type; + if (obj->explicit_free_line > 0) + snprintf(fline, 128, ", freed at %d, ", obj->explicit_free_line); + fprintf(stderr, "%s%p is free (line %d, alloc type: %s %" ld64 " #x%" PRIx64 " (%s)), alloc: %s[%d], %sgc: %s[%d]%s", + bold_text, obj, line, s7_type_names[obj->alloc_type & 0xff], obj->alloc_type, obj->alloc_type, + bits, obj->alloc_func, obj->alloc_line, + (obj->explicit_free_line > 0) ? fline : "", obj->gc_func, obj->gc_line, unbold_text); + if (S7_DEBUGGING) fprintf(stderr, "%s, last gc line: %d%s", bold_text, sc->last_gc_line, unbold_text); + fprintf(stderr, "\n"); + free(bits); + } + if (sc->stop_at_error) abort(); +} + +static s7_pointer check_nref(s7_pointer p, const char *func, int32_t line) +{ + if (!p) + { + fprintf(stderr, "%s%s[%d]: null pointer!%s\n", bold_text, func, line, unbold_text); + if (cur_sc->stop_at_error) abort(); + } + else + if (unchecked_type(p) >= NUM_TYPES) + { + fprintf(stderr, "%s%s[%d]: attempt to use messed up cell (type: %d)%s\n", bold_text, func, line, unchecked_type(p), unbold_text); + if (cur_sc->stop_at_error) abort(); + } + if (unchecked_type(p) == T_FREE) + { + fprintf(stderr, "%s%s[%d]: attempt to use free cell%s\n", bold_text, func, line, unbold_text); + print_gc_info(cur_sc, p, line); + if (cur_sc->stop_at_error) abort(); + } + return(p); +} + +static s7_pointer check_ref15(s7_pointer p, const char *func, int32_t line) /* called in mark_let so s7_scheme* for cur_sc is difficult */ +{ + uint8_t typ = unchecked_type(p); + check_nref(p, func, line); + if ((is_multiple_value(p)) && + (!safe_strcmp(func, "mark_slot"))) /* match == multiple-values which causes false error messages */ + complain("%s%s[%d]: slot value is a multiple-value, %s (%s)%s?\n", p, func, line, typ); + if (has_odd_bits(p)) + {char *s; fprintf(stderr, "odd bits: %s\n", s = describe_type_bits(cur_sc, p)); free(s);} + return(p); +} + +static s7_pointer check_ref16(s7_pointer p, const char *func, int32_t line) +{ + uint8_t typ = unchecked_type(p); + check_nref(p, func, line); + if ((typ != T_LET) && (typ != T_NIL)) complain("%s%s[%d]: not a let or nil, but %s (%s)%s\n", p, func, line, typ); + return(p); +} + +static s7_pointer check_ref17(s7_pointer p, const char *func, int32_t line) +{ + if ((!is_any_macro(p)) || (is_c_macro(p))) complain("%s%s[%d]: macro is %s (%s)%s?\n", p, func, line, unchecked_type(p)); + return(p); +} + +static s7_pointer check_ref18(s7_pointer p, const char *func, int32_t line) +{ + if (!is_symbol_and_keyword(p)) complain("%s%s[%d]: not a keyword: %s (%s)%s?\n", p, func, line, unchecked_type(p)); + if (strcmp(func, "new_symbol") != 0) + { + if (global_value(p) != p) + { + fprintf(stderr, "%s%s[%d]: keyword %s value is not itself (type: %s)%s\n", + bold_text, func, line, display(p), s7_type_names[unchecked_type(global_value(p))], unbold_text); + if (cur_sc->stop_at_error) abort(); + } + if (in_heap(keyword_symbol_unchecked(p))) + fprintf(stderr, "%s%s[%d]: keyword %s symbol is in the heap%s\n", bold_text, func, line, display(p), unbold_text); + if (has_odd_bits(p)) + {char *s; fprintf(stderr, "odd bits: %s\n", s = describe_type_bits(cur_sc, p)); free(s);} + } + return(p); +} + +static s7_pointer check_ref19(s7_pointer p, const char *func, int32_t line) +{ + uint8_t typ = unchecked_type(p); + check_nref(p, func, line); + if (t_ext_p[typ]) + { + fprintf(stderr, "%s%s[%d]: attempt to use (internal) %s cell%s\n", bold_text, func, line, s7_type_names[typ], unbold_text); + if (cur_sc->stop_at_error) abort(); + } + return(p); +} + +static s7_pointer check_opcode(s7_pointer p, const char *func, int32_t line) +{ + s7_int op = (s7_int)(intptr_t)p; + if ((op < 0) || (op >= NUM_OPS)) + { + fprintf(stderr, "%s%s[%d]: opcode_t: %" ld64 " == %p?%s\n", bold_text, func, line, op, p, unbold_text); + if (cur_sc->stop_at_error) abort(); + } + return(p); +} + +static const char *opt1_role_name(uint64_t role) +{ + if (role == OPT1_FAST) return("opt1_fast"); + if (role == OPT1_CFUNC) return("opt1_cfunc"); + if (role == OPT1_LAMBDA) return("opt1_lambda"); + if (role == OPT1_CLAUSE) return("opt1_clause"); + if (role == OPT1_SYM) return("opt1_sym"); + if (role == OPT1_PAIR) return("opt1_pair"); + if (role == OPT1_CON) return("opt1_con"); + if (role == OPT1_ANY) return("opt1_any"); + return((role == OPT1_HASH) ? "opt1_hash" : "opt1_unknown"); +} + +static const char *opt2_role_name(uint64_t role) +{ + if (role == OPT2_FX) return("opt2_fx"); + if (role == OPT2_FN) return("opt2_fn"); + if (role == OPT2_KEY) return("opt2_any"); + if (role == OPT2_SLOW) return("opt2_slow"); + if (role == OPT2_SYM) return("opt2_sym"); + if (role == OPT2_PAIR) return("opt2_pair"); + if (role == OPT2_CON) return("opt2_con"); + if (role == OPT2_LAMBDA) return("opt2_lambda"); + if (role == OPT2_DIRECT) return("opt2_direct"); + if (role == OPT2_INT) return("opt2_int"); + return((role == OPT2_NAME) ? "opt2_raw_name" : "opt2_unknown"); +} + +static const char *opt3_role_name(uint64_t role) +{ + if (role == OPT3_ARGLEN) return("opt3_arglen"); + if (role == OPT3_SYM) return("opt3_sym"); + if (role == OPT3_CON) return("opt3_con"); + if (role == OPT3_AND) return("opt3_pair"); + if (role == OPT3_ANY) return("opt3_any"); + if (role == OPT3_LET) return("opt3_let"); + if (role == OPT3_BYTE) return("opt3_byte"); + if (role == OPT3_DIRECT) return("direct_opt3"); + if (role == OPT3_LEN) return("opt3_len"); + if (role == OPT3_INT) return("opt3_int"); + return((role == OPT3_LOCATION) ? "opt3_location" : "opt3_unknown"); +} + +static void show_opt1_bits(s7_pointer p, const char *func, int32_t line, uint64_t role) +{ + char *bits = show_debugger_bits(p); + fprintf(stderr, "%s%s[%d]%s: opt1: %p->%p wants %s, debugger bits are %" PRIx64 "%s but expects %" ld64, + bold_text, func, line, unbold_text, + p, p->object.cons.opt1, opt1_role_name(role), p->debugger_bits, bits, (s7_int)role); + free(bits); +} + +static s7_pointer opt1_1(s7_scheme *sc, s7_pointer p, uint64_t role, const char *func, int32_t line) +{ + if ((!opt1_is_set(p)) || + ((!opt1_role_matches(p, role)) && + (role != OPT1_ANY))) + { + show_opt1_bits(p, func, line, role); + if (sc->stop_at_error) abort(); + } + return(p->object.cons.opt1); +} + +static void base_opt1(s7_pointer p, uint64_t role) +{ + set_opt1_role(p, role); + set_opt1_is_set(p); +} + +static s7_pointer set_opt1_1(s7_pointer p, s7_pointer x, uint64_t role, const char *func, int32_t line) +{ + if (((p->debugger_bits & OPT1_MASK) != role) && + ((p->debugger_bits & OPT1_MASK) == OPT1_LAMBDA) && + (role != OPT1_CFUNC)) + fprintf(stderr, "%s[%d]: opt1_lambda -> %s, op: %s, x: %s,\n %s\n", + func, line, opt1_role_name(role), + (is_optimized(x)) ? op_names[optimize_op(x)] : "unopt", + display(x), display(p)); + p->object.cons.opt1 = x; + base_opt1(p, role); + return(x); +} + +static uint64_t opt1_hash_1(s7_scheme *sc, s7_pointer p, const char *func, int32_t line) +{ + if ((!opt1_is_set(p)) || (!opt1_role_matches(p, OPT1_HASH))) + { + show_opt1_bits(p, func, line, (uint64_t)OPT1_HASH); + if (sc->stop_at_error) abort(); + } + return(p->object.sym_cons.hash); +} + +static void set_opt1_hash_1(s7_pointer p, uint64_t x) +{ + p->object.sym_cons.hash = x; + base_opt1(p, OPT1_HASH); +} + +static void show_opt2_bits(s7_pointer p, const char *func, int32_t line, uint64_t role) +{ + char *bits = show_debugger_bits(p); + fprintf(stderr, "%s%s[%d]%s: opt2: %p->%p wants %s, debugger bits are %" PRIx64 "%s but expects %" ld64 " %s", + bold_text, func, line, unbold_text, + p, p->object.cons.o2.opt2, opt2_role_name(role), p->debugger_bits, bits, (s7_int)role, opt2_role_name(role)); + free(bits); +} + +static bool f_call_func_mismatch(const char *func) +{ + return((!safe_strcmp(func, "check_and")) && /* these reflect set_fx|unchecked where the destination checks for null fx_proc */ + (!safe_strcmp(func, "check_or")) && + (!safe_strcmp(func, "eval")) && + (!safe_strcmp(func, "set_any_c_np")) && + (!safe_strcmp(func, "set_any_closure_np")) && + (!safe_strcmp(func, "optimize_func_two_args")) && + (!safe_strcmp(func, "optimize_func_many_args")) && + (!safe_strcmp(func, "optimize_func_three_args")) && + (!safe_strcmp(func, "fx_c_ff")) && + (!safe_strcmp(func, "op_map_for_each_fa")) && + (!safe_strcmp(func, "op_map_for_each_faa"))); +} + +static void check_opt2_bits(s7_scheme *sc, s7_pointer p, uint64_t role, const char *func, int32_t line) +{ + if (!p) + { + fprintf(stderr, "%s%s[%d]: opt2 null!\n%s", bold_text, func, line, unbold_text); + if (sc->stop_at_error) abort(); + } + if ((!opt2_is_set(p)) || + (!opt2_role_matches(p, role))) + { + show_opt2_bits(p, func, line, role); + if (sc->stop_at_error) abort(); + } +} + +static s7_pointer opt2_1(s7_scheme *sc, s7_pointer p, uint64_t role, const char *func, int32_t line) +{ + check_opt2_bits(sc, p, role, func, line); + return(p->object.cons.o2.opt2); +} + +static s7_int opt2_n_1(s7_scheme *sc, s7_pointer p, uint64_t role, const char *func, int32_t line) +{ + check_opt2_bits(sc, p, role, func, line); + return(p->object.cons.o2.n); +} + +static void base_opt2(s7_pointer p, uint64_t role) +{ + set_opt2_role(p, role); + set_opt2_is_set(p); +} + +static void set_opt2_1(s7_scheme *sc, s7_pointer p, s7_pointer x, uint64_t role, const char *func, int32_t line) +{ + if ((role == OPT2_FX) && + (x == NULL) && + (f_call_func_mismatch(func))) + fprintf(stderr, "%s[%d]: set fx_proc for %s to null (%s%s%s)\n", func, line, + string_value(object_to_truncated_string(sc, p, 80)), + ((is_h_optimized(car(p))) && (is_safe_c_op(optimize_op(car(p))))) ? bold_text : "", + op_names[optimize_op(car(p))], + ((is_h_optimized(car(p))) && (is_safe_c_op(optimize_op(car(p))))) ? unbold_text : ""); + if ((role != OPT2_FX) && (role != OPT2_DIRECT) && (has_fx(p))) /* sometimes opt2_direct just specializes fx */ + { + fprintf(stderr, "%s[%d]: overwrite has_fx: %s %s\n", func, line, opt2_role_name(role), display_80(p)); + if (sc->stop_at_error) abort(); + } + p->object.cons.o2.opt2 = x; + base_opt2(p, role); +} + +static void set_opt2_n_1(s7_scheme *unused_sc, s7_pointer p, s7_int x, uint64_t role, const char *unused_func, int32_t unused_line) +{ + p->object.cons.o2.n = x; + base_opt2(p, role); +} + +static const char *opt2_name_1(s7_scheme *sc, s7_pointer p, const char *func, int32_t line) +{ + if ((!opt2_is_set(p)) || + (!opt2_role_matches(p, OPT2_NAME))) + { + show_opt2_bits(p, func, line, (uint64_t)OPT2_NAME); + if (sc->stop_at_error) abort(); + } + return(p->object.sym_cons.fstr); +} + +static void set_opt2_name_1(s7_pointer p, const char *str) +{ + p->object.sym_cons.fstr = str; + base_opt2(p, OPT2_NAME); +} + +static void show_opt3_bits(s7_pointer p, const char *func, int32_t line, uint64_t role) +{ + char *bits = show_debugger_bits(p); + fprintf(stderr, "%s%s[%d]%s: opt3: %s %" PRIx64 "%s", bold_text, func, line, unbold_text, opt3_role_name(role), p->debugger_bits, bits); + free(bits); +} + +static void check_opt3_bits(s7_scheme *sc, s7_pointer p, uint64_t role, const char *func, int32_t line) +{ + if (!p) + { + fprintf(stderr, "%s%s[%d]: opt3 null!\n%s", bold_text, func, line, unbold_text); + if (sc->stop_at_error) abort(); + } + if ((!opt3_is_set(p)) || + (!opt3_role_matches(p, role))) + { + show_opt3_bits(p, func, line, role); + if (sc->stop_at_error) abort(); + } +} + +static s7_pointer opt3_1(s7_scheme *sc, s7_pointer p, uint64_t role, const char *func, int32_t line) +{ + check_opt3_bits(sc, p, role, func, line); + return(p->object.cons.o3.opt3); +} + +static s7_int opt3_n_1(s7_scheme *sc, s7_pointer p, uint64_t role, const char *func, int32_t line) +{ + check_opt3_bits(sc, p, role, func, line); + return(p->object.cons.o3.n); +} + +static void base_opt3(s7_pointer p, uint64_t role) +{ + set_opt3_role(p, role); + set_opt3_is_set(p); +} + +static void set_opt3_1(s7_pointer p, s7_pointer x, uint64_t role) +{ + clear_type_bit(p, T_LOCATION); + p->object.cons.o3.opt3 = x; + base_opt3(p, role); +} + +static void set_opt3_n_1(s7_pointer p, s7_int x, uint64_t role) +{ + clear_type_bit(p, T_LOCATION); + p->object.cons.o3.n = x; + base_opt3(p, role); +} + +static uint8_t opt3_byte_1(s7_scheme *sc, s7_pointer p, uint64_t role, const char *func, int32_t line) +{ + check_opt3_bits(sc, p, role, func, line); + return(p->object.cons.o3.opt_type); +} + +static void set_opt3_byte_1(s7_pointer p, uint8_t x, uint64_t role, const char *unused_func, int32_t unused_line) +{ + clear_type_bit(p, T_LOCATION); + p->object.cons.o3.opt_type = x; + base_opt3(p, role); +} + +static uint64_t opt3_location_1(s7_scheme *sc, s7_pointer p, const char *func, int32_t line) +{ + if ((!opt3_is_set(p)) || + ((p->debugger_bits & OPT3_LOCATION) == 0) || + (!has_location(p))) + { + show_opt3_bits(p, func, line, (uint64_t)OPT3_LOCATION); + if (sc->stop_at_error) abort(); + } + return(p->object.sym_cons.location); /* don't use pair_location macro here or below (infinite recursion if S7_DEBUGGING via opt3_location_1) */ +} + +static void set_opt3_location_1(s7_pointer p, uint64_t x) +{ + p->object.sym_cons.location = x; + (p)->debugger_bits = (OPT3_LOCATION | (p->debugger_bits & ~OPT3_LEN)); /* turn on line, cancel len */ + set_opt3_is_set(p); +} + +static uint64_t opt3_len_1(s7_scheme *sc, s7_pointer p, const char *func, int32_t line) +{ + if ((!opt3_is_set(p)) || + ((p->debugger_bits & OPT3_LEN) == 0) || + (has_location(p))) + { + show_opt3_bits(p, func, line, (uint64_t)OPT3_LEN); + if (sc->stop_at_error) abort(); + } + return(p->object.sym_cons.location); +} + +static void set_opt3_len_1(s7_pointer p, uint64_t x) +{ + clear_type_bit(p, T_LOCATION); + p->object.sym_cons.location = x; + (p)->debugger_bits = (OPT3_LEN | (p->debugger_bits & ~(OPT3_LOCATION))); + set_opt3_is_set(p); +} + +static inline void check_opt_bits(s7_pointer p, int bits) /* "inline" to disable "defined but not used" warning from gcc */ +{ + if (((bits & 1) != 0) && (opt1_is_set(p))) fprintf(stderr, "opt1 set "); + if (((bits & 2) != 0) && (opt2_is_set(p))) fprintf(stderr, "opt2 set "); + if (((bits & 4) != 0) && (opt3_is_set(p)) && (!has_type_bit(p, T_LOCATION))) fprintf(stderr, "opt3 set "); +} + +static void print_debugging_state(s7_scheme *sc, s7_pointer obj, s7_pointer port) +{ + /* show current state, current allocated state */ + char *allocated_bits, *str; + int64_t save_full_type = full_type(obj); + s7_int len, nlen; + const char *excl_name = (is_free(obj)) ? "free cell!" : "unknown object!"; + block_t *b; + char *current_bits = describe_type_bits(sc, obj); + + full_type(obj) = obj->alloc_type; + allocated_bits = describe_type_bits(sc, obj); + full_type(obj) = save_full_type; + + len = safe_strlen(excl_name) + safe_strlen(current_bits) + safe_strlen(allocated_bits) + safe_strlen(obj->alloc_func) + 512; + b = mallocate(sc, len); + str = (char *)block_data(b); + nlen = snprintf(str, len, + "\n<%s %s,\n alloc: %s[%d] %s, %d uses>", excl_name, current_bits, + obj->alloc_func, obj->alloc_line, allocated_bits, obj->uses); + free(current_bits); + free(allocated_bits); + if (is_null(port)) + fprintf(stderr, "%p: %s\n", obj, str); + else port_write_string(port)(sc, str, clamp_length(nlen, len), port); + liberate(sc, b); +} + +static s7_pointer symbol_to_local_slot(s7_scheme *sc, s7_pointer symbol, s7_pointer e); +static s7_pointer check_null_sym(s7_scheme *sc, s7_pointer p, s7_pointer sym, int32_t line, const char *func) +{ + if (!p) + { + s7_pointer slot = symbol_to_local_slot(sc, sym, sc->curlet); + char *s = describe_type_bits(sc, sym); + fprintf(stderr, "%s%s[%d]: %s unbound%s\n", bold_text, func, line, symbol_name(sym), unbold_text); + fprintf(stderr, " symbol_id: %" ld64 ", let_id: %" ld64 ", bits: %s", symbol_id(sym), let_id(sc->curlet), s); + free(s); + if (is_slot(slot)) fprintf(stderr, ", slot: %s", display(slot)); + fprintf(stderr, "\n"); + if (sc->stop_at_error) abort(); + } + return(p); +} +#endif /* S7_DEBUGGING */ +/* -------------------------------- end internal debugging apparatus -------------------------------- */ + + +static s7_pointer set_elist_1(s7_scheme *sc, s7_pointer x1) +{ + set_car(sc->elist_1, x1); + return(sc->elist_1); +} + +static s7_pointer set_elist_2(s7_scheme *sc, s7_pointer x1, s7_pointer x2) +{ + set_car(sc->elist_2, x1); + set_cadr(sc->elist_2, x2); + return(sc->elist_2); +} + +static s7_pointer set_elist_3(s7_scheme *sc, s7_pointer x1, s7_pointer x2, s7_pointer x3) +{ + s7_pointer p = sc->elist_3; + set_car(p, x1); p = cdr(p); + set_car(p, x2); p = cdr(p); + set_car(p, x3); + return(sc->elist_3); +} + +static s7_pointer set_elist_4(s7_scheme *sc, s7_pointer x1, s7_pointer x2, s7_pointer x3, s7_pointer x4) +{ + s7_pointer p = sc->elist_4; + set_car(p, x1); p = cdr(p); + set_car(p, x2); p = cdr(p); + set_car(p, x3); p = cdr(p); + set_car(p, x4); + return(sc->elist_4); +} + +static s7_pointer set_elist_5(s7_scheme *sc, s7_pointer x1, s7_pointer x2, s7_pointer x3, s7_pointer x4, s7_pointer x5) +{ + set_car(sc->elist_5, x1); + set_elist_4(sc, x2, x3, x4, x5); + return(sc->elist_5); +} + +static s7_pointer set_elist_6(s7_scheme *sc, s7_pointer x1, s7_pointer x2, s7_pointer x3, s7_pointer x4, s7_pointer x5, s7_pointer x6) +{ + set_car(sc->elist_6, x1); + set_elist_5(sc, x2, x3, x4, x5, x6); + return(sc->elist_6); +} + +static s7_pointer set_elist_7(s7_scheme *sc, s7_pointer x1, s7_pointer x2, s7_pointer x3, s7_pointer x4, s7_pointer x5, s7_pointer x6, s7_pointer x7) +{ + set_car(sc->elist_7, x1); + set_elist_6(sc, x2, x3, x4, x5, x6, x7); + return(sc->elist_7); +} + +static s7_pointer set_wlist_3(s7_pointer lst, s7_pointer x1, s7_pointer x2, s7_pointer x3) +{ + s7_pointer p = lst; + set_car(p, x1); p = cdr(p); + set_car(p, x2); p = cdr(p); + set_car(p, x3); + return(lst); +} + +static s7_pointer set_wlist_4(s7_pointer lst, s7_pointer x1, s7_pointer x2, s7_pointer x3, s7_pointer x4) +{ + s7_pointer p = lst; + set_car(p, x1); p = cdr(p); + set_car(p, x2); p = cdr(p); + set_car(p, x3); p = cdr(p); + set_car(p, x4); + return(lst); +} + +static s7_pointer set_mlist_1(s7_scheme *sc, s7_pointer x1) +{ + set_car(sc->mlist_1, x1); + return(sc->mlist_1); +} + +static s7_pointer set_mlist_2(s7_scheme *sc, s7_pointer x1, s7_pointer x2) /* mlist_3 saves 3 in tmock -- see ~/old/s7-mlist_3.c */ +{ + set_car(sc->mlist_2, x1); + set_cadr(sc->mlist_2, x2); + return(sc->mlist_2); +} + +static s7_pointer set_plist_1(s7_scheme *sc, s7_pointer x1) +{ + set_car(sc->plist_1, x1); + return(sc->plist_1); +} + +static s7_pointer set_plist_2(s7_scheme *sc, s7_pointer x1, s7_pointer x2) +{ + set_car(sc->plist_2, x1); + set_car(sc->plist_2_2, x2); + return(sc->plist_2); +} + +static s7_pointer set_plist_3(s7_scheme *sc, s7_pointer x1, s7_pointer x2, s7_pointer x3) +{ + return(set_wlist_3(sc->plist_3, x1, x2, x3)); +} + +static s7_pointer set_qlist_2(s7_scheme *sc, s7_pointer x1, s7_pointer x2) /* let_ref_fallback */ +{ + set_car(sc->qlist_2, x1); + set_cadr(sc->qlist_2, x2); + return(sc->qlist_2); +} + +static s7_pointer set_qlist_3(s7_scheme *sc, s7_pointer x1, s7_pointer x2, s7_pointer x3) /* let_set_fallback */ +{ + return(set_wlist_3(sc->qlist_3, x1, x2, x3)); +} + +static s7_pointer set_clist_1(s7_scheme *sc, s7_pointer x1) /* for c_object length method etc, a "weak" list */ +{ + set_car(sc->clist_1, x1); + return(sc->clist_1); +} + +static s7_pointer set_clist_2(s7_scheme *sc, s7_pointer x1, s7_pointer x2) /* for c_object equal method etc, a "weak" list */ +{ + set_car(sc->clist_2, x1); + set_cadr(sc->clist_2, x2); + return(sc->clist_2); +} + +static s7_pointer set_dlist_1(s7_scheme *sc, s7_pointer x1) /* another like clist: temp usage, "weak" (not gc_marked), but semipermanent list */ +{ + set_car(sc->dlist_1, x1); + return(sc->dlist_1); +} + +static s7_pointer set_ulist_1(s7_scheme *sc, s7_pointer x1, s7_pointer x2) +{ + set_car(sc->u1_1, x1); + set_cdr(sc->u1_1, x2); + return(sc->u1_1); +} + + +/* ---------------- error handlers ---------------- */ +static const char *make_type_name(s7_scheme *sc, const char *name, article_t article) +{ + s7_int i, slen = safe_strlen(name); + s7_int len = slen + 8; + if (len > sc->typnam_len) + { + if (sc->typnam) free(sc->typnam); + sc->typnam = (char *)Malloc(len); + sc->typnam_len = len; + } + if (article == INDEFINITE_ARTICLE) + { + i = 1; + sc->typnam[0] = 'a'; + if ((name[0] == 'a') || (name[0] == 'e') || (name[0] == 'i') || (name[0] == 'o') || (name[0] == 'u')) + sc->typnam[i++] = 'n'; + sc->typnam[i++] = ' '; + } + else i = 0; + memcpy((void *)(sc->typnam + i), (const void *)name, slen); + sc->typnam[i + slen] = '\0'; + return(sc->typnam); +} + +static const char *type_name_from_type(int32_t typ, article_t article) +{ + /* if the type enum never changed, this could just be an array lookup, but it doesn't matter -- this function isn't called much */ + switch (typ) + { + case T_FREE: return((article == NO_ARTICLE) ? "free-cell" : "a free cell"); + case T_NIL: return("nil"); + case T_UNUSED: return((article == NO_ARTICLE) ? "#" : "the unused object"); + case T_EOF: return((article == NO_ARTICLE) ? "#" : "the end-of-file object"); + case T_UNSPECIFIED: return((article == NO_ARTICLE) ? "#" : "the unspecified object"); + case T_UNDEFINED: return((article == NO_ARTICLE) ? "undefined" : "an undefined object"); + case T_BOOLEAN: return("boolean"); + case T_STRING: return((article == NO_ARTICLE) ? "string" : "a string"); + case T_BYTE_VECTOR: return((article == NO_ARTICLE) ? "byte-vector" : "a byte-vector"); + case T_SYMBOL: return((article == NO_ARTICLE) ? "symbol" : "a symbol"); + case T_SYNTAX: return((article == NO_ARTICLE) ? "syntax" : "syntactic"); + case T_PAIR: return((article == NO_ARTICLE) ? "pair" : "a pair"); + case T_GOTO: return((article == NO_ARTICLE) ? "goto" : "a goto (from call-with-exit)"); + case T_CONTINUATION: return((article == NO_ARTICLE) ? "continuation" : "a continuation"); + case T_C_RST_NO_REQ_FUNCTION: + case T_C_FUNCTION: return((article == NO_ARTICLE) ? "c-function" : "a c-function"); + case T_C_FUNCTION_STAR: return((article == NO_ARTICLE) ? "c-function*" : "a c-function*"); + case T_CLOSURE: return((article == NO_ARTICLE) ? "function" : "a function"); + case T_CLOSURE_STAR: return((article == NO_ARTICLE) ? "function*" : "a function*"); + case T_C_MACRO: return((article == NO_ARTICLE) ? "c-macro" : "a c-macro"); + case T_C_POINTER: return((article == NO_ARTICLE) ? "c-pointer" : "a c-pointer"); + case T_CHARACTER: return((article == NO_ARTICLE) ? "character" : "a character"); + case T_VECTOR: return((article == NO_ARTICLE) ? "vector" : "a vector"); + case T_INT_VECTOR: return((article == NO_ARTICLE) ? "int-vector" : "an int-vector"); + case T_FLOAT_VECTOR: return((article == NO_ARTICLE) ? "float-vector" : "a float-vector"); + case T_MACRO_STAR: return((article == NO_ARTICLE) ? "macro*" : "a macro*"); + case T_MACRO: return((article == NO_ARTICLE) ? "macro" : "a macro"); + case T_BACRO_STAR: return((article == NO_ARTICLE) ? "bacro*" : "a bacro*"); + case T_BACRO: return((article == NO_ARTICLE) ? "bacro" : "a bacro"); + case T_CATCH: return((article == NO_ARTICLE) ? "catch" : "a catch"); + case T_STACK: return((article == NO_ARTICLE) ? "stack" : "a stack"); + case T_DYNAMIC_WIND: return((article == NO_ARTICLE) ? "dynamic-wind" : "a dynamic-wind"); + case T_HASH_TABLE: return((article == NO_ARTICLE) ? "hash-table" : "a hash-table"); + case T_ITERATOR: return((article == NO_ARTICLE) ? "iterator" : "an iterator"); + case T_LET: return((article == NO_ARTICLE) ? "let" : "a let"); + case T_COUNTER: return((article == NO_ARTICLE) ? "internal-counter" : "an internal counter"); + case T_RANDOM_STATE: return((article == NO_ARTICLE) ? "random-state" : "a random-state"); + case T_SLOT: return((article == NO_ARTICLE) ? "slot" : "a slot (variable binding)"); + case T_INTEGER: return((article == NO_ARTICLE) ? "integer" : "an integer"); + case T_RATIO: return((article == NO_ARTICLE) ? "ratio" : "a ratio"); + case T_REAL: return((article == NO_ARTICLE) ? "real" : "a real"); + case T_COMPLEX: return((article == NO_ARTICLE) ? "complex-number" : "a complex number"); + case T_BIG_INTEGER: return((article == NO_ARTICLE) ? "big-integer" : "a big integer"); + case T_BIG_RATIO: return((article == NO_ARTICLE) ? "big-ratio" : "a big ratio"); + case T_BIG_REAL: return((article == NO_ARTICLE) ? "big-real" : "a big real"); + case T_BIG_COMPLEX: return((article == NO_ARTICLE) ? "big-complex-number": "a big complex number"); + case T_INPUT_PORT: return((article == NO_ARTICLE) ? "input-port" : "an input port"); + case T_OUTPUT_PORT: return((article == NO_ARTICLE) ? "output-port" : "an output port"); + case T_C_OBJECT: return((article == NO_ARTICLE) ? "c-object" : "a c_object"); + } + return(NULL); +} + +static s7_pointer find_let(s7_scheme *sc, s7_pointer obj) +{ + if (is_let(obj)) return(obj); + switch (type(obj)) + { + case T_MACRO: case T_MACRO_STAR: + case T_BACRO: case T_BACRO_STAR: + case T_CLOSURE: case T_CLOSURE_STAR: + return(closure_let(obj)); + case T_C_OBJECT: + return(c_object_let(obj)); + case T_C_POINTER: + if ((is_let(c_pointer_info(obj))) && + (c_pointer_info(obj) != sc->rootlet)) + return(c_pointer_info(obj)); + } + return(sc->nil); +} + +static inline s7_pointer lookup_slot_from(s7_pointer symbol, s7_pointer e); + +static s7_pointer find_method(s7_scheme *sc, s7_pointer let, s7_pointer symbol) +{ + s7_pointer slot; + if (symbol_id(symbol) == 0) /* this means the symbol has never been used locally, so how can it be a method? */ + return(sc->undefined); + slot = lookup_slot_from(symbol, let); + if (slot != global_slot(symbol)) + return(slot_value(slot)); + return(sc->undefined); +} + +static s7_pointer find_method_with_let(s7_scheme *sc, s7_pointer let, s7_pointer symbol) +{ + return(find_method(sc, find_let(sc, let), symbol)); +} + +static const char *type_name(s7_scheme *sc, s7_pointer arg, article_t article) +{ + switch (unchecked_type(arg)) + { + case T_C_OBJECT: return(make_type_name(sc, string_value(c_object_scheme_name(sc, arg)), article)); + case T_INPUT_PORT: return(make_type_name(sc, (is_file_port(arg)) ? "input file port" : ((is_string_port(arg)) ? "input string port" : "input port"), article)); + case T_OUTPUT_PORT: return(make_type_name(sc, (is_file_port(arg)) ? "output file port" : ((is_string_port(arg)) ? "output string port" : "output port"), article)); + case T_LET: + if (has_active_methods(sc, arg)) + { + s7_pointer class_name = find_method(sc, arg, sc->class_name_symbol); + if (is_symbol(class_name)) + return(make_type_name(sc, symbol_name(class_name), article)); + } + default: + { + const char *str = type_name_from_type(unchecked_type(arg), article); + if (str) return(str); + }} + return("messed up object"); +} + +static s7_pointer object_type_name(s7_scheme *sc, s7_pointer x) +{ + uint8_t typ; + if (has_active_methods(sc, x)) + { + s7_pointer p = find_method_with_let(sc, x, sc->class_name_symbol); + if (is_symbol(p)) return(symbol_name_cell(p)); + } + typ = type(x); + if (typ < NUM_TYPES) + { + if (typ == T_C_OBJECT) return(c_object_scheme_name(sc, x)); + return(sc->type_names[typ]); + } + return(wrap_string(sc, "unknown type!", 13)); +} + +static s7_pointer type_name_string(s7_scheme *sc, s7_pointer arg) +{ + if (type(arg) < NUM_TYPES) + { + s7_pointer p = sc->type_names[type(arg)]; /* these use INDEFINITE_ARTICLE */ + if (is_string(p)) return(p); + } + return(s7_make_string_wrapper(sc, type_name(sc, arg, INDEFINITE_ARTICLE))); +} + + +static noreturn void sole_arg_wrong_type_error_nr(s7_scheme *sc, s7_pointer caller, s7_pointer arg, s7_pointer typ) +{ + set_wlist_4(cdr(sc->sole_arg_wrong_type_info), caller, arg, object_type_name(sc, arg), typ); + error_nr(sc, sc->wrong_type_arg_symbol, sc->sole_arg_wrong_type_info); +} + +static /* Inline */ noreturn void wrong_type_error_nr(s7_scheme *sc, s7_pointer caller, s7_int arg_num, s7_pointer arg, s7_pointer typ) +{ + s7_pointer p = cdr(sc->wrong_type_arg_info); /* info list is '(format_string caller arg_n arg type_name descr) */ + set_car(p, caller); p = cdr(p); + set_car(p, (is_small_int(arg_num)) ? small_int(arg_num) : wrap_integer(sc, arg_num)); p = cdr(p); + set_car(p, arg); p = cdr(p); + set_car(p, object_type_name(sc, arg)); p = cdr(p); + set_car(p, typ); + error_nr(sc, sc->wrong_type_arg_symbol, sc->wrong_type_arg_info); +} + +s7_pointer s7_wrong_type_arg_error(s7_scheme *sc, const char *caller, s7_int arg_n, s7_pointer arg, const char *descr) +{ + if (arg_n > 0) + wrong_type_error_nr(sc, wrap_string(sc, caller, safe_strlen(caller)), arg_n, arg, wrap_string(sc, descr, safe_strlen(descr))); + sole_arg_wrong_type_error_nr(sc, wrap_string(sc, caller, safe_strlen(caller)), arg, wrap_string(sc, descr, safe_strlen(descr))); + return(sc->wrong_type_arg_symbol); +} + +s7_pointer s7_wrong_type_error(s7_scheme *sc, s7_pointer caller, s7_int arg_n, s7_pointer arg, s7_pointer descr) +{ + if (arg_n > 0) wrong_type_error_nr(sc, caller, arg_n, arg, descr); + sole_arg_wrong_type_error_nr(sc, caller, arg, descr); + return(sc->wrong_type_arg_symbol); /* never happens */ +} + +static noreturn void sole_arg_out_of_range_error_nr(s7_scheme *sc, s7_pointer caller, s7_pointer arg, s7_pointer descr) +{ + set_wlist_3(cdr(sc->sole_arg_out_of_range_info), caller, arg, descr); + error_nr(sc, sc->out_of_range_symbol, sc->sole_arg_out_of_range_info); +} + +static noreturn void out_of_range_error_nr(s7_scheme *sc, s7_pointer caller, s7_pointer arg_n, s7_pointer arg, s7_pointer descr) +{ + set_wlist_4(cdr(sc->out_of_range_info), caller, arg_n, arg, descr); + error_nr(sc, sc->out_of_range_symbol, sc->out_of_range_info); +} + +s7_pointer s7_out_of_range_error(s7_scheme *sc, const char *caller, s7_int arg_n, s7_pointer arg, const char *descr) +{ + if (arg_n > 0) + { + set_wlist_4(cdr(sc->out_of_range_info), wrap_string(sc, caller, safe_strlen(caller)), + wrap_integer(sc, arg_n), arg, wrap_string(sc, descr, safe_strlen(descr))); + error_nr(sc, sc->out_of_range_symbol, sc->out_of_range_info); + } + set_wlist_3(cdr(sc->sole_arg_out_of_range_info), wrap_string(sc, caller, safe_strlen(caller)), + arg, wrap_string(sc, descr, safe_strlen(descr))); + error_nr(sc, sc->out_of_range_symbol, sc->sole_arg_out_of_range_info); + return(sc->out_of_range_symbol); +} + +static noreturn void wrong_number_of_arguments_error_nr(s7_scheme *sc, const char *errmsg, s7_int len, s7_pointer args) +{ + error_nr(sc, sc->wrong_number_of_args_symbol, set_elist_2(sc, wrap_string(sc, errmsg, len), args)); +} + +s7_pointer s7_wrong_number_of_args_error(s7_scheme *sc, const char *caller, s7_pointer args) +{ + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_2(sc, s7_make_string_wrapper(sc, caller), args)); /* "caller" includes the format directives */ + return(sc->wrong_number_of_args_symbol); +} + + +static noreturn void syntax_error_nr(s7_scheme *sc, const char *errmsg, s7_int len, s7_pointer obj) +{ + error_nr(sc, sc->syntax_error_symbol, set_elist_2(sc, wrap_string(sc, errmsg, len), obj)); +} + +static noreturn void syntax_error_with_caller_nr(s7_scheme *sc, const char *errmsg, s7_int len, s7_pointer caller, s7_pointer obj) +{ + error_nr(sc, sc->syntax_error_symbol, set_elist_3(sc, wrap_string(sc, errmsg, len), caller, obj)); +} + +static noreturn void syntax_error_with_caller2_nr(s7_scheme *sc, const char *errmsg, s7_int len, s7_pointer caller, s7_pointer name, s7_pointer obj) +{ + error_nr(sc, sc->syntax_error_symbol, set_elist_4(sc, wrap_string(sc, errmsg, len), caller, name, obj)); +} + +static noreturn void missing_method_error_nr(s7_scheme *sc, s7_pointer method, s7_pointer obj) +{ + error_nr(sc, sc->missing_method_symbol, + set_elist_3(sc, wrap_string(sc, "missing ~S method in ~A", 23), method, + (is_c_object(obj)) ? c_object_scheme_name(sc, obj) : obj)); +} + +static noreturn void immutable_object_error_nr(s7_scheme *sc, s7_pointer info) {error_nr(sc, sc->immutable_error_symbol, info);} + + +/* -------- method handlers -------- */ +s7_pointer s7_method(s7_scheme *sc, s7_pointer obj, s7_pointer method) +{ + if (has_active_methods(sc, obj)) return(find_method_with_let(sc, obj, method)); + return(sc->undefined); +} + +/* if a method is shadowing a built-in like abs, it should expect the same args as abs and behave the same -- no multiple values etc */ +#define check_method(Sc, Obj, Method, Args) \ + { \ + s7_pointer func; \ + if ((has_active_methods(Sc, Obj)) && \ + ((func = find_method_with_let(Sc, Obj, Method)) != Sc->undefined)) \ + return(s7_apply_function(Sc, func, Args)); \ + } + +static s7_pointer apply_boolean_method(s7_scheme *sc, s7_pointer obj, s7_pointer method) +{ + s7_pointer func = find_method_with_let(sc, obj, method); + if (func == sc->undefined) return(sc->F); + return(s7_apply_function(sc, func, set_mlist_1(sc, obj))); /* plist here and below will probably not work (_pp case known bad) */ +} + +/* this is a macro mainly to simplify the Checker handling */ +#define check_boolean_method(Sc, Checker, Method, Args) \ + { \ + s7_pointer p = car(Args); \ + if (Checker(p)) return(Sc->T); \ + if (!has_active_methods(Sc, p)) return(Sc->F); \ + return(apply_boolean_method(Sc, p, Method)); \ + } + +static s7_pointer apply_method_closure(s7_scheme *sc, s7_pointer func, s7_pointer args); + +static s7_pointer find_and_apply_method(s7_scheme *sc, s7_pointer obj, s7_pointer sym, s7_pointer args) /* slower if inline */ +{ + s7_pointer func = find_method_with_let(sc, obj, sym); + if (is_closure(func)) return(apply_method_closure(sc, func, args)); + if (func == sc->undefined) missing_method_error_nr(sc, sym, obj); + return(s7_apply_function(sc, func, args)); +} + +static s7_pointer method_or_bust(s7_scheme *sc, s7_pointer obj, s7_pointer method, s7_pointer args, s7_pointer typ, int32_t num) +{ + if (!has_active_methods(sc, obj)) wrong_type_error_nr(sc, method, num, obj, typ); + return(find_and_apply_method(sc, obj, method, args)); +} + +static s7_pointer mutable_method_or_bust(s7_scheme *sc, s7_pointer obj, s7_pointer method, s7_pointer args, s7_pointer typ, int32_t num) +{ + if (has_active_methods(sc, obj)) return(find_and_apply_method(sc, obj, method, args)); + if (sc->type_names[type(obj)] != typ) wrong_type_error_nr(sc, method, num, obj, typ); + if (!is_immutable(obj)) wrong_type_error_nr(sc, method, num, obj, typ); + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, method, obj)); + return(NULL); +} + +static s7_pointer mutable_method_or_bust_ppp(s7_scheme *sc, s7_pointer obj, s7_pointer method, + s7_pointer x1, s7_pointer x2, s7_pointer x3, s7_pointer typ, int32_t num) +{ + return(mutable_method_or_bust(sc, obj, method, set_qlist_3(sc, x1, x2, x3), typ, num)); /* was list_3, plist_3 not safe */ +} + +static s7_pointer method_or_bust_p(s7_scheme *sc, s7_pointer obj, s7_pointer method, s7_pointer typ) +{ + if (!has_active_methods(sc, obj)) sole_arg_wrong_type_error_nr(sc, method, obj, typ); + return(find_and_apply_method(sc, obj, method, set_mlist_1(sc, obj))); +} + +static s7_pointer method_or_bust_pp(s7_scheme *sc, s7_pointer obj, s7_pointer method, s7_pointer x1, s7_pointer x2, s7_pointer typ, int32_t num) +{ + if (!has_active_methods(sc, obj)) wrong_type_error_nr(sc, method, num, obj, typ); + return(find_and_apply_method(sc, obj, method, set_mlist_2(sc, x1, x2))); +} + +static s7_pointer method_or_bust_ppp(s7_scheme *sc, s7_pointer obj, s7_pointer method, + s7_pointer x1, s7_pointer x2, s7_pointer x3, s7_pointer typ, int32_t num) +{ + if (!has_active_methods(sc, obj)) wrong_type_error_nr(sc, method, num, obj, typ); + return(find_and_apply_method(sc, obj, method, set_qlist_3(sc, x1, x2, x3))); /* was list_3, plist not safe */ +} + +static s7_pointer method_or_bust_with_type_and_loc_pp(s7_scheme *sc, s7_pointer obj, s7_pointer method, + s7_pointer x1, s7_pointer x2, s7_pointer typ, int32_t num) +{ + int32_t loc = sc->error_argnum + num; + sc->error_argnum = 0; + if (!has_active_methods(sc, obj)) wrong_type_error_nr(sc, method, loc, obj, typ); + return(find_and_apply_method(sc, obj, method, set_mlist_2(sc, x1, x2))); +} + +static s7_pointer method_or_bust_with_type_pi(s7_scheme *sc, s7_pointer obj, s7_pointer method, + s7_pointer x1, s7_int x2, s7_pointer typ, int32_t num) +{ + if (!has_active_methods(sc, obj)) wrong_type_error_nr(sc, method, num, obj, typ); + return(find_and_apply_method(sc, obj, method, set_mlist_2(sc, x1, make_integer(sc, x2)))); +} + +static s7_pointer method_or_bust_with_type_pf(s7_scheme *sc, s7_pointer obj, s7_pointer method, + s7_pointer x1, s7_double x2, s7_pointer typ, int32_t num) +{ + if (!has_active_methods(sc, obj)) wrong_type_error_nr(sc, method, num, obj, typ); + return(find_and_apply_method(sc, obj, method, set_mlist_2(sc, x1, make_real(sc, x2)))); +} + +static s7_pointer sole_arg_method_or_bust(s7_scheme *sc, s7_pointer obj, s7_pointer method, s7_pointer args, s7_pointer typ) +{ + if (!has_active_methods(sc, obj)) sole_arg_wrong_type_error_nr(sc, method, obj, typ); + return(find_and_apply_method(sc, obj, method, args)); +} + + +/* -------------------------------- constants -------------------------------- */ +/* #f and #t */ +s7_pointer s7_f(s7_scheme *sc) {return(sc->F);} +s7_pointer s7_t(s7_scheme *sc) {return(sc->T);} + + +/* () */ +s7_pointer s7_nil(s7_scheme *sc) {return(sc->nil);} /* should this be "s7_null" ? */ +bool s7_is_null(s7_scheme *sc, s7_pointer p) {return(is_null(p));} +static bool is_null_b_p(s7_pointer p) {return(type(p) == T_NIL);} /* faster than b_7p because opt_b_p is faster */ + +static s7_pointer g_is_null(s7_scheme *sc, s7_pointer args) +{ + #define H_is_null "(null? obj) returns #t if obj is the empty list" + #define Q_is_null sc->pl_bt + check_boolean_method(sc, is_null, sc->is_null_symbol, args); +} + + +/* # and # */ +s7_pointer s7_undefined(s7_scheme *sc) {return(sc->undefined);} +s7_pointer s7_unspecified(s7_scheme *sc) {return(sc->unspecified);} + +bool s7_is_unspecified(s7_scheme *sc, s7_pointer val) {return(is_unspecified(val));} + +static s7_pointer g_is_undefined(s7_scheme *sc, s7_pointer args) +{ + #define H_is_undefined "(undefined? val) returns #t if val is # or its reader equivalent" + #define Q_is_undefined sc->pl_bt + check_boolean_method(sc, is_undefined, sc->is_undefined_symbol, args); +} + +static s7_pointer g_is_unspecified(s7_scheme *sc, s7_pointer args) +{ + #define H_is_unspecified "(unspecified? val) returns #t if val is #" + #define Q_is_unspecified sc->pl_bt + check_boolean_method(sc, is_unspecified, sc->is_unspecified_symbol, args); +} + + +/* -------------------------------- eof-object? -------------------------------- */ +s7_pointer eof_object = NULL; /* # is an entry in the chars array, so it's not a part of sc */ + +s7_pointer s7_eof_object(s7_scheme *sc) {return(eof_object);} + +static s7_pointer g_is_eof_object(s7_scheme *sc, s7_pointer args) +{ + #define H_is_eof_object "(eof-object? val) returns #t if val is the end-of-file object" + #define Q_is_eof_object sc->pl_bt + check_boolean_method(sc, is_eof, sc->is_eof_object_symbol, args); +} + +static bool is_eof_object_b_p(s7_pointer p) {return(p == eof_object);} + + +/* -------------------------------- not -------------------------------- */ +static bool not_b_7p(s7_scheme *sc, s7_pointer p) {return(p == sc->F);} + +static s7_pointer g_not(s7_scheme *sc, s7_pointer args) +{ + #define H_not "(not obj) returns #t if obj is #f, otherwise #t: (not ()) -> #f" + #define Q_not sc->pl_bt + return((car(args) == sc->F) ? sc->T : sc->F); +} + + +/* -------------------------------- boolean? -------------------------------- */ +bool s7_boolean(s7_scheme *sc, s7_pointer x) {return(x != sc->F);} +s7_pointer s7_make_boolean(s7_scheme *sc, bool x) {return(make_boolean(sc, x));} + +bool s7_is_boolean(s7_pointer x) {return(type(x) == T_BOOLEAN);} + +static s7_pointer g_is_boolean(s7_scheme *sc, s7_pointer args) +{ + #define H_is_boolean "(boolean? obj) returns #t if obj is #f or #t: (boolean? ()) -> #f" + #define Q_is_boolean sc->pl_bt + check_boolean_method(sc, is_boolean, sc->is_boolean_symbol, args); +} + + +/* -------------------------------- constant? -------------------------------- */ +static inline bool is_constant_symbol(s7_scheme *sc, s7_pointer sym) /* inline: 7 in cb, 5 in tgen */ +{ + if (is_immutable_symbol(sym)) /* for keywords */ + return(true); + if (is_possibly_constant(sym)) + { + s7_pointer slot = s7_slot(sc, sym); + return((is_slot(slot)) && (is_immutable_slot(slot))); + } + return(false); +} + +#define is_constant(sc, p) ((type(p) != T_SYMBOL) || (is_constant_symbol(sc, p))) + +static s7_pointer g_is_constant(s7_scheme *sc, s7_pointer args) +{ + #define H_is_constant "(constant? obj) returns #t if obj either evaluates to itself, or is a symbol whose binding is constant" + #define Q_is_constant sc->pl_bt + return(make_boolean(sc, is_constant(sc, car(args)))); +} + +static bool is_constant_b_7p(s7_scheme *sc, s7_pointer p) {return(is_constant(sc, p));} +static s7_pointer is_constant_p_p(s7_scheme *sc, s7_pointer p) {return(make_boolean(sc, is_constant(sc, p)));} + + +/* -------------------------------- immutable? -------------------------------- */ +bool s7_is_immutable(s7_pointer p) {return(is_immutable(p));} + +static s7_pointer g_is_immutable(s7_scheme *sc, s7_pointer args) +{ + #define H_is_immutable "(immutable? sequence) returns #t if the sequence is immutable" + #define Q_is_immutable sc->pl_bt + s7_pointer p = car(args); + if (is_number(p)) return(sc->T); /* should these be marked immutable? should we use (type != SYMBOL) as above? */ + return(make_boolean(sc, is_immutable(p))); +} + + +/* -------------------------------- immutable! -------------------------------- */ +s7_pointer s7_immutable(s7_pointer p) +{ + set_immutable(p); + return(p); +} + +static s7_pointer g_immutable(s7_scheme *sc, s7_pointer args) +{ + #define H_immutable "(immutable! x) declares that the x can't be changed. x is returned." + #define Q_immutable s7_make_signature(sc, 2, sc->T, sc->T) + s7_pointer p = car(args); + if (is_symbol(p)) + { + s7_pointer slot = s7_slot(sc, p); + if (is_slot(slot)) + { + set_immutable_slot(slot); + return(p); /* symbol is not set immutable ? */ + }} + set_immutable(p); /* could set_immutable save the current file/line? Then the immutable error checks for define-constant and this setting */ + return(p); +} + +/* there's no way to make a slot setter (as setter) immutable (t_multiform as bit) */ + + +/* -------------------------------- GC -------------------------------- */ +/* in most code, pairs, lets, and slots dominate the heap -- each about 25% to 40% of the + * total cell allocations. In snd-test, reals are 50%. slots need not be in the heap, + * but moving them out to their own free list was actually slower because we need (in that + * case) to manage them in the sweep process by tracking lets. + */ + +#if S7_DEBUGGING +static s7_int gc_protect_2(s7_scheme *sc, s7_pointer x, int32_t line) +{ + s7_int loc = s7_gc_protect(sc, x); + if (loc > 8192) + { + fprintf(stderr, "infinite loop or memory leak at line %d %s?\n", line, string_value(s7_object_to_string(sc, current_code(sc), false))); + abort(); + } + return(loc); +} +#define gc_protect_1(Sc, X) gc_protect_2(Sc, X, __LINE__) +#else +#define gc_protect_1(Sc, X) s7_gc_protect(Sc, X) +#endif + +static void resize_gc_protect(s7_scheme *sc) +{ + s7_int size = sc->protected_objects_size; + block_t *ob = vector_block(sc->protected_objects); + s7_int new_size = 2 * size; + block_t *nb = reallocate(sc, ob, new_size * sizeof(s7_pointer)); + block_info(nb) = NULL; + vector_block(sc->protected_objects) = nb; + vector_elements(sc->protected_objects) = (s7_pointer *)block_data(nb); + vector_length(sc->protected_objects) = new_size; + sc->protected_objects_size = new_size; + sc->protected_objects_free_list = (s7_int *)Realloc(sc->protected_objects_free_list, new_size * sizeof(s7_int)); + for (s7_int i = size; i < new_size; i++) + { + vector_element(sc->protected_objects, i) = sc->unused; + sc->protected_objects_free_list[++sc->protected_objects_free_list_loc] = i; + } +} + +s7_int s7_gc_protect(s7_scheme *sc, s7_pointer x) +{ + s7_int loc; + if (sc->protected_objects_free_list_loc < 0) + resize_gc_protect(sc); + loc = sc->protected_objects_free_list[sc->protected_objects_free_list_loc--]; + vector_element(sc->protected_objects, loc) = x; + return(loc); +} + +void s7_gc_unprotect_at(s7_scheme *sc, s7_int loc) +{ + if (loc < sc->protected_objects_size) + { + if (vector_element(sc->protected_objects, loc) != sc->unused) + sc->protected_objects_free_list[++sc->protected_objects_free_list_loc] = loc; + else if (S7_DEBUGGING) fprintf(stderr, "redundant gc_unprotect_at location %" ld64 "\n", loc); + vector_element(sc->protected_objects, loc) = sc->unused; + } +} + +s7_pointer s7_gc_protected_at(s7_scheme *sc, s7_int loc) +{ + s7_pointer obj = sc->unspecified; + if (loc < sc->protected_objects_size) + obj = vector_element(sc->protected_objects, loc); + if (obj == sc->unused) + return(sc->unspecified); + return(obj); +} + +#define gc_protected_at(Sc, Loc) vector_element(Sc->protected_objects, Loc) + +s7_pointer s7_gc_protect_via_location(s7_scheme *sc, s7_pointer x, s7_int loc) +{ + vector_element(sc->protected_objects, loc) = x; + return(x); +} + +s7_pointer s7_gc_unprotect_via_location(s7_scheme *sc, s7_int loc) +{ + vector_element(sc->protected_objects, loc) = sc->F; + return(sc->F); +} + + +/* these 3 are needed by sweep */ +static void (*mark_function[NUM_TYPES])(s7_pointer p); +void s7_mark(s7_pointer p) {if (!is_marked(p)) (*mark_function[unchecked_type(p)])(p);} +static void mark_noop(s7_pointer unused_p) {} + +static void process_iterator(s7_scheme *unused_sc, s7_pointer s1) +{ + if (is_weak_hash_iterator(s1)) + { + s7_pointer h = iterator_sequence(s1); + clear_weak_hash_iterator(s1); + if (unchecked_type(h) == T_HASH_TABLE) + weak_hash_iters(h)--; + } +} + +static void process_multivector(s7_scheme *sc, s7_pointer s1) +{ + vdims_t *info = vector_dimension_info(s1); /* a multidimensional empty vector can have dimension info, wrapped vectors always have dimension info */ + if ((info) && + (info != sc->wrap_only)) + { + if (vector_elements_should_be_freed(info)) /* a kludge for foreign code convenience */ + { + free(any_vector_elements(s1)); + vector_elements_should_be_freed(info) = false; + } + liberate(sc, info); + vector_set_dimension_info(s1, NULL); + } + liberate(sc, vector_block(s1)); +} + +static void process_input_string_port(s7_scheme *sc, s7_pointer s1) +{ +#if S7_DEBUGGING + /* this set of ports is a subset of the ports that respond true to is_string_port -- + * the latter include file ports fully read into local memory; see read_file which uses add_input_port, not add_input_string_port + */ + if (port_filename(s1)) + fprintf(stderr, "string input port has a filename: %s\n", port_filename(s1)); + if (port_needs_free(s1)) + fprintf(stderr, "string input port needs data release\n"); +#endif + liberate(sc, port_block(s1)); +} + +static void free_port_data(s7_scheme *sc, s7_pointer s1) +{ + if (port_data(s1)) + { + liberate(sc, port_data_block(s1)); + port_data_block(s1) = NULL; + port_data(s1) = NULL; + port_data_size(s1) = 0; + } + port_needs_free(s1) = false; +} + +static void close_input_function(s7_scheme *sc, s7_pointer p); + +static void process_input_port(s7_scheme *sc, s7_pointer s1) +{ + if (!port_is_closed(s1)) + { + if (is_file_port(s1)) + { + if (port_file(s1)) + { + fclose(port_file(s1)); + port_file(s1) = NULL; + }} + else + if (is_function_port(s1)) + close_input_function(sc, s1); + } + if (port_needs_free(s1)) + free_port_data(sc, s1); + + if (port_filename(s1)) + { + liberate(sc, port_filename_block(s1)); + port_filename(s1) = NULL; + } + liberate(sc, port_block(s1)); +} + +static void close_output_port(s7_scheme *sc, s7_pointer p); + +static void process_output_port(s7_scheme *sc, s7_pointer s1) +{ + close_output_port(sc, s1); /* needed for free filename, etc */ + liberate(sc, port_block(s1)); + if (port_needs_free(s1)) + { + port_needs_free(s1) = false; + if (port_data_block(s1)) + { + liberate(sc, port_data_block(s1)); + port_data_block(s1) = NULL; + }} +} + +static void process_continuation(s7_scheme *sc, s7_pointer s1) +{ + continuation_op_stack(s1) = NULL; + liberate_block(sc, continuation_block(s1)); +} + + +#if WITH_GMP +#if ((__GNU_MP_VERSION < 6) || ((__GNU_MP_VERSION == 6) && (__GNU_MP_VERSION_MINOR == 0))) +static int32_t mpq_cmp_z(const mpq_t op1, const mpz_t op2) +{ + mpq_t z1; + int32_t result; + mpq_init(z1); + mpq_set_z(z1, op2); + result = mpq_cmp(op1, z1); + mpq_clear(z1); + return(result); +} +#endif + +static s7_int big_integer_to_s7_int(s7_scheme *sc, mpz_t n); + +static s7_int s7_integer_clamped_if_gmp(s7_scheme *sc, s7_pointer p) +{ + if (is_t_integer(p)) + return(integer(p)); + if (is_t_big_integer(p)) + return(big_integer_to_s7_int(sc, big_integer(p))); + return(0); +} + +static void free_big_integer(s7_scheme *sc, s7_pointer p) +{ + big_integer_nxt(p) = sc->bigints; + sc->bigints = big_integer_bgi(p); + big_integer_bgi(p) = NULL; +} + +static void free_big_ratio(s7_scheme *sc, s7_pointer p) +{ + big_ratio_nxt(p) = sc->bigrats; + sc->bigrats = big_ratio_bgr(p); + big_ratio_bgr(p) = NULL; +} + +static void free_big_real(s7_scheme *sc, s7_pointer p) +{ + big_real_nxt(p) = sc->bigflts; + sc->bigflts = big_real_bgf(p); + big_real_bgf(p) = NULL; +} + +static void free_big_complex(s7_scheme *sc, s7_pointer p) +{ + big_complex_nxt(p) = sc->bigcmps; + sc->bigcmps = big_complex_bgc(p); + big_complex_bgc(p) = NULL; +} +#else +#define s7_integer_clamped_if_gmp(Sc, P) integer(P) +#endif + + +static void free_hash_table(s7_scheme *sc, s7_pointer table); +static void remove_gensym_from_symbol_table(s7_scheme *sc, s7_pointer sym); +static void cull_weak_hash_table(s7_scheme *sc, s7_pointer table); + +static void sweep(s7_scheme *sc) +{ + s7_int i, j; + gc_list_t *gp; + + #define process_gc_list(Code) \ + if (gp->loc > 0) \ + { \ + for (i = 0, j = 0; i < gp->loc; i++) \ + { \ + s7_pointer s1 = gp->list[i]; \ + if (is_free_and_clear(s1)) \ + { \ + Code; \ + } \ + else gp->list[j++] = s1; \ + } \ + gp->loc = j; \ + } \ + + gp = sc->strings; + process_gc_list(liberate(sc, string_block(s1))) + + gp = sc->gensyms; + process_gc_list(remove_gensym_from_symbol_table(sc, s1); liberate(sc, gensym_block(s1))) + if (gp->loc == 0) mark_function[T_SYMBOL] = mark_noop; + + gp = sc->undefineds; + process_gc_list(free(undefined_name(s1))) + + gp = sc->c_objects; + process_gc_list((c_object_gc_free(sc, s1)) ? (void)(*(c_object_gc_free(sc, s1)))(sc, s1) : (void)(*(c_object_free(sc, s1)))(c_object_value(s1))) + + gp = sc->vectors; + process_gc_list(liberate(sc, vector_block(s1))) + + gp = sc->multivectors; + process_gc_list(process_multivector(sc, s1)); + + gp = sc->hash_tables; + if (gp->loc > 0) + { + for (i = 0, j = 0; i < gp->loc; i++) + { + s7_pointer s1 = gp->list[i]; + if (is_free_and_clear(s1)) + free_hash_table(sc, s1); + else + { + if ((is_weak_hash_table(s1)) && + (weak_hash_iters(s1) == 0) && + (hash_table_entries(s1) > 0)) + cull_weak_hash_table(sc, s1); + gp->list[j++] = s1; + }} + gp->loc = j; + } + + gp = sc->weak_hash_iterators; + process_gc_list(process_iterator(sc, s1)); + + gp = sc->opt1_funcs; + if (gp->loc > 0) + { + for (i = 0, j = 0; i < gp->loc; i++) + { + s7_pointer s1 = gp->list[i]; + if (!is_free_and_clear(s1)) + gp->list[j++] = s1; + } + gp->loc = j; + } + + gp = sc->input_ports; + process_gc_list(process_input_port(sc, s1)); + + gp = sc->input_string_ports; + process_gc_list(process_input_string_port(sc, s1)); + + gp = sc->output_ports; + process_gc_list(process_output_port(sc, s1)); + + gp = sc->continuations; + process_gc_list(process_continuation(sc, s1)); + + gp = sc->weak_refs; + if (gp->loc > 0) + { + for (i = 0, j = 0; i < gp->loc; i++) + { + s7_pointer s1 = gp->list[i]; + if (!is_free_and_clear(s1)) + { + if (is_free_and_clear(c_pointer_weak1(s1))) + c_pointer_weak1(s1) = sc->F; + if (is_free_and_clear(c_pointer_weak2(s1))) + c_pointer_weak2(s1) = sc->F; + if ((c_pointer_weak1(s1) != sc->F) || + (c_pointer_weak2(s1) != sc->F)) + gp->list[j++] = s1; + }} + gp->loc = j; + } + +#if WITH_GMP + gp = sc->big_integers; + process_gc_list(free_big_integer(sc, s1)) + + gp = sc->big_ratios; + process_gc_list(free_big_ratio(sc ,s1)) + + gp = sc->big_reals; + process_gc_list(free_big_real(sc, s1)) + + gp = sc->big_complexes; + process_gc_list(free_big_complex(sc, s1)) + + gp = sc->big_random_states; + process_gc_list(gmp_randclear(random_gmp_state(s1))) +#endif +} + +static /* inline */ void add_to_gc_list(gc_list_t *gp, s7_pointer p) /* why inline here? (not tgc) */ +{ + if (gp->loc == gp->size) + { + gp->size *= 2; + gp->list = (s7_pointer *)Realloc(gp->list, gp->size * sizeof(s7_pointer)); + } + gp->list[gp->loc++] = p; +} + +static gc_list_t *make_gc_list(void) +{ + gc_list_t *gp = (gc_list_t *)Malloc(sizeof(gc_list_t)); + #define INIT_GC_CACHE_SIZE 4 + gp->size = INIT_GC_CACHE_SIZE; + gp->loc = 0; + gp->list = (s7_pointer *)Malloc(gp->size * sizeof(s7_pointer)); + return(gp); +} + +static void just_mark(s7_pointer p) {set_mark(p);} + +static void add_gensym(s7_scheme *sc, s7_pointer p) +{ + add_to_gc_list(sc->gensyms, p); + mark_function[T_SYMBOL] = just_mark; +} + +#define add_c_object(sc, p) add_to_gc_list(sc->c_objects, p) +#define add_hash_table(sc, p) add_to_gc_list(sc->hash_tables, p) +#define add_string(sc, p) add_to_gc_list(sc->strings, p) +#define add_input_port(sc, p) add_to_gc_list(sc->input_ports, p) +#define add_input_string_port(sc, p) add_to_gc_list(sc->input_string_ports, p) +#define add_output_port(sc, p) add_to_gc_list(sc->output_ports, p) +#define add_continuation(sc, p) add_to_gc_list(sc->continuations, p) +#define add_undefined(sc, p) add_to_gc_list(sc->undefineds, p) +#define add_vector(sc, p) add_to_gc_list(sc->vectors, p) +#define add_multivector(sc, p) add_to_gc_list(sc->multivectors, p) +#define add_weak_ref(sc, p) add_to_gc_list(sc->weak_refs, p) +#define add_weak_hash_iterator(sc, p) add_to_gc_list(sc->weak_hash_iterators, p) +#define add_opt1_func(sc, p) do {if (!opt1_func_listed(p)) add_to_gc_list(sc->opt1_funcs, p); set_opt1_func_listed(p);} while (0) + +#if WITH_GMP +#define add_big_integer(sc, p) add_to_gc_list(sc->big_integers, p) +#define add_big_ratio(sc, p) add_to_gc_list(sc->big_ratios, p) +#define add_big_real(sc, p) add_to_gc_list(sc->big_reals, p) +#define add_big_complex(sc, p) add_to_gc_list(sc->big_complexes, p) +#define add_big_random_state(sc, p) add_to_gc_list(sc->big_random_states, p) +#endif + +static void init_gc_caches(s7_scheme *sc) +{ + sc->strings = make_gc_list(); + sc->gensyms = make_gc_list(); + sc->undefineds = make_gc_list(); + sc->vectors = make_gc_list(); + sc->multivectors = make_gc_list(); + sc->hash_tables = make_gc_list(); + sc->input_ports = make_gc_list(); + sc->input_string_ports = make_gc_list(); + sc->output_ports = make_gc_list(); + sc->continuations = make_gc_list(); + sc->c_objects = make_gc_list(); + sc->weak_refs = make_gc_list(); + sc->weak_hash_iterators = make_gc_list(); + sc->opt1_funcs = make_gc_list(); +#if WITH_GMP + sc->big_integers = make_gc_list(); + sc->big_ratios = make_gc_list(); + sc->big_reals = make_gc_list(); + sc->big_complexes = make_gc_list(); + sc->big_random_states = make_gc_list(); + sc->ratloc = NULL; +#endif + /* slightly unrelated... */ + sc->setters_size = 4; + sc->setters_loc = 0; + sc->setters = (s7_pointer *)Malloc(sc->setters_size * sizeof(s7_pointer)); +} + +static s7_pointer semipermanent_cons(s7_scheme *sc, s7_pointer a, s7_pointer b, uint64_t type); + +static void add_setter(s7_scheme *sc, s7_pointer p, s7_pointer setter) +{ + /* setters GC-protected. The c_function_setter field can't be used because the built-in functions + * are often removed from the heap and never thereafter marked. Only closures and macros are protected here. + */ + for (s7_int i = 0; i < sc->setters_loc; i++) + { + s7_pointer x = sc->setters[i]; + if (car(x) == p) + { + set_cdr(x, setter); + return; + }} + if (sc->setters_loc == sc->setters_size) + { + sc->setters_size *= 2; + sc->setters = (s7_pointer *)Realloc(sc->setters, sc->setters_size * sizeof(s7_pointer)); + } + sc->setters[sc->setters_loc++] = semipermanent_cons(sc, p, setter, T_PAIR | T_IMMUTABLE); +} + + +static inline void gc_mark(s7_pointer p) {if (!is_marked(p)) (*mark_function[unchecked_type(p)])(p);} + +static void mark_symbol_vector(s7_pointer p, s7_int len) +{ + set_mark(p); + if (mark_function[T_SYMBOL] != mark_noop) /* else no gensyms */ + { + s7_pointer *e = vector_elements(p); + for (s7_int i = 0; i < len; i++) + if ((is_symbol(e[i])) && (is_gensym(e[i]))) /* need is_symbol: make-vector + set! vector-typer symbol? where init is not a symbol */ + set_mark(e[i]); + } +} + +static void mark_simple_vector(s7_pointer p, s7_int len) +{ + s7_pointer *e = vector_elements(p); + set_mark(p); + for (s7_int i = 0; i < len; i++) + set_mark(e[i]); +} + +static void just_mark_vector(s7_pointer p, s7_int unused_len) {set_mark(p);} + +static void mark_vector_1(s7_pointer p, s7_int top) +{ + s7_pointer *tp = (s7_pointer *)(vector_elements(p)); + s7_pointer *tend, *tend4; + set_mark(p); + if (!tp) return; + tend = (s7_pointer *)(tp + top); + tend4 = (s7_pointer *)(tend - 8); + while (tp <= tend4) + LOOP_8(gc_mark(*tp++)); + while (tp < tend) + gc_mark(*tp++); +} + +static void mark_typed_vector_1(s7_pointer p, s7_int top) /* for typed vectors with closure setters */ +{ + gc_mark(typed_vector_typer(p)); + mark_vector_1(p, top); +} + +static inline void mark_slot(s7_pointer p) +{ + set_mark(T_Slt(p)); + gc_mark(slot_value(p)); + if (slot_has_setter_or_pending_value(p)) + gc_mark(slot_pending_value_unchecked(p)); /* setter field == pending_value */ + set_mark(slot_symbol(p)); +} + +static void mark_let(s7_pointer let) +{ + for (s7_pointer x = let; is_let(x) && (!is_marked(x)); x = let_outlet(x)) /* let can be sc->nil, e.g. closure_let */ + { + set_mark(x); + if (has_dox_slot1(x)) mark_slot(let_dox_slot1(x)); + if ((has_dox_slot2(x)) && (is_slot(let_dox_slot2(x)))) mark_slot(let_dox_slot2(x)); + /* it can happen (call/cc related) that let_dox_slot2 is a slot but invalid, but in that case has_dox_slot2 will not be set(?) */ + for (s7_pointer y = let_slots(x); tis_slot(y); y = next_slot(y)) + if (!is_marked(y)) /* slot value might be the enclosing let */ + mark_slot(y); + } +} + +#if WITH_HISTORY +static void gc_owlet_mark(s7_pointer tp) +{ + /* gc_mark but if tp is a pair ignore the marked bit on unheaped entries */ + if (is_pair(tp)) + { + s7_pointer p = tp; + do { + set_mark(p); + gc_mark(car(p)); /* does this need to be gc_owlet_mark? I can't find a case */ + p = cdr(p); + } while ((is_pair(p)) && (p != tp) && ((!in_heap(p)) || (!is_marked(p)))); /* ((full_type(p) & (TYPE_MASK | T_GC_MARK)) == T_PAIR) is much slower */ + gc_mark(p); + } + else + if (!is_marked(tp)) + (*mark_function[unchecked_type(tp)])(tp); +} +#endif + +static void mark_owlet(s7_scheme *sc) +{ +#if WITH_HISTORY + { + for (s7_pointer p1 = sc->eval_history1, p2 = sc->eval_history2, p3 = sc->history_pairs; ; p2 = cdr(p2), p3 = cdr(p3)) + { + gc_owlet_mark(car(p1)); + gc_owlet_mark(car(p2)); + gc_owlet_mark(car(p3)); + p1 = cdr(p1); + if (p1 == sc->eval_history1) break; /* these are circular lists */ + }} +#endif + /* sc->error_type and friends are slots in owlet */ + mark_slot(sc->error_type); + slot_set_value(sc->error_data, sc->F); /* or maybe mark_tree(slot_value(sc->error_data)) ? */ + mark_slot(sc->error_data); + mark_slot(sc->error_code); + mark_slot(sc->error_line); + mark_slot(sc->error_file); + mark_slot(sc->error_position); +#if WITH_HISTORY + mark_slot(sc->error_history); +#endif + set_mark(sc->owlet); + mark_let(let_outlet(sc->owlet)); +} + +static void mark_c_pointer(s7_pointer p) +{ + set_mark(p); + gc_mark(c_pointer_type(p)); + gc_mark(c_pointer_info(p)); +} + +static void mark_c_proc_star(s7_pointer p) +{ + set_mark(p); + if ((!c_func_has_simple_defaults(p)) && + (c_function_call_args(p))) /* NULL if not a safe function */ + for (s7_pointer arg = c_function_call_args(p); is_pair(arg); arg = cdr(arg)) + gc_mark(car(arg)); +} + +static void mark_pair(s7_pointer p) +{ + do { + set_mark(p); + gc_mark(car(p)); /* expanding this to avoid recursion is slower */ + p = cdr(p); + } while ((is_pair(p)) && (!is_marked(p))); /* ((full_type(p) & (TYPE_MASK | T_GC_MARK)) == T_PAIR) is much slower */ + gc_mark(p); +} + +static void mark_counter(s7_pointer p) +{ + set_mark(p); + gc_mark(counter_result(p)); + gc_mark(counter_list(p)); + gc_mark(counter_let(p)); +} + +static void mark_closure(s7_pointer p) +{ + set_mark(p); + gc_mark(closure_args(p)); + gc_mark(closure_body(p)); + mark_let(closure_let(p)); + gc_mark(closure_setter_or_map_list(p)); +} + +static void mark_stack_1(s7_pointer p, s7_int top) +{ + s7_pointer *tp = (s7_pointer *)(stack_elements(p)), *tend; + set_mark(p); + if (!tp) return; + tend = (s7_pointer *)(tp + top); + while (tp < tend) + { + gc_mark(*tp++); + gc_mark(*tp++); + gc_mark(*tp++); + tp++; + } +} + +static void mark_stack(s7_pointer p) +{ + /* we can have a bare stack waiting for a continuation to hold it if the new_cell for the continuation triggers the GC! But we need a top-of-stack?? */ + mark_stack_1(p, temp_stack_top(p)); +} + +static void mark_continuation(s7_pointer p) +{ + set_mark(p); + if (!is_marked(continuation_stack(p))) /* can these be cyclic? */ + mark_stack_1(continuation_stack(p), continuation_stack_top(p)); + gc_mark(continuation_op_stack(p)); +} + +static void mark_vector(s7_pointer p) +{ + if (is_typed_vector(p)) + typed_vector_gc_mark(p)(p, vector_length(p)); + else mark_vector_1(p, vector_length(p)); +} + +static void mark_vector_possibly_shared(s7_pointer p) +{ + /* If a subvector (an inner dimension) of a vector is the only remaining reference + * to the main vector, we want to make sure the main vector is not GC'd until + * the subvector is also GC-able. The subvector field either points to the + * parent vector, or it is sc->F, so we need to check for a vector parent if + * the current is multidimensional (this will include 1-dim slices). We need + * to keep the parent case separate (i.e. sc->F means the current is the original) + * so that we only free once (or remove_from_heap once). + * + * If we have a subvector of a subvector, and the middle and original are not otherwise + * in use, we mark the middle one, but (since it itself is not in use anywhere else) + * we don't mark the original! So we need to follow the share-vector chain marking every one. + * + * To remove a cell from the heap, we need its current heap location so that we can replace it. + * The heap is allocated as needed in monolithic blocks of (say) 1/2M s7_cells. When a cell + * is replaced, the new cell (at heap[x] say) is no longer from the original block. Since the + * GC clears all type bits when it frees a cell, we can't use a type bit to distinguish the + * replacements from the originals, but we need that info because in the base case, we use + * the distance of the cell from the base cell to get "x", its location. In the replacement + * case, we add the location at the end of the s7_cell (s7_big_cell). We track the current + * heap blocks via the sc->heap_blocks list. To get the location of "p" above, we run through + * that list looking for a block it fits in. If none is found, we assume it is an s7_big_cell + * and use the saved location. + */ + if (is_subvector(p)) + mark_vector_possibly_shared(subvector_vector(p)); + + /* mark_vector_1 does not check the marked bit, so if subvector below is in a cycle involving + * the calling vector, we get infinite recursion unless we check the mark bit here. + */ + if (!is_marked(p)) + mark_vector_1(p, vector_length(p)); +} + +static void mark_int_or_float_vector(s7_pointer p) {set_mark(p);} + +static void mark_int_or_float_vector_possibly_shared(s7_pointer p) +{ + if (is_subvector(p)) + mark_int_or_float_vector_possibly_shared(subvector_vector(p)); + set_mark(p); +} + +static void mark_c_object(s7_pointer p) +{ + set_mark(p); + if (c_object_gc_mark(c_object_s7(p), p)) + (*(c_object_gc_mark(c_object_s7(p), p)))(c_object_s7(p), p); + else (*(c_object_mark(c_object_s7(p), p)))(c_object_value(p)); +} + +static void mark_catch(s7_pointer p) +{ + set_mark(p); + gc_mark(catch_tag(p)); + gc_mark(catch_handler(p)); +} + +static void mark_dynamic_wind(s7_pointer p) +{ + set_mark(p); + gc_mark(dynamic_wind_in(p)); + gc_mark(dynamic_wind_out(p)); + gc_mark(dynamic_wind_body(p)); +} + +static void mark_hash_table(s7_pointer p) +{ + set_mark(p); + gc_mark(hash_table_procedures(p)); + if (is_pair(hash_table_procedures(p))) + { + gc_mark(hash_table_key_typer_unchecked(p)); /* unchecked to avoid s7-debugger's reference to sc */ + gc_mark(hash_table_value_typer_unchecked(p)); + } + if (hash_table_entries(p) > 0) + { + s7_int len = hash_table_mask(p) + 1; + hash_entry_t **entries = hash_table_elements(p); + hash_entry_t **last = (hash_entry_t **)(entries + len); + + if ((is_weak_hash_table(p)) && + (weak_hash_iters(p) == 0)) + while (entries < last) + { + hash_entry_t *xp; + for (xp = *entries++; xp; xp = hash_entry_next(xp)) + gc_mark(hash_entry_value(xp)); + for (xp = *entries++; xp; xp = hash_entry_next(xp)) + gc_mark(hash_entry_value(xp)); + } + else + while (entries < last) /* counting entries here was slightly faster */ + { + hash_entry_t *xp; + for (xp = *entries++; xp; xp = hash_entry_next(xp)) + { + gc_mark(hash_entry_key(xp)); + gc_mark(hash_entry_value(xp)); + } + for (xp = *entries++; xp; xp = hash_entry_next(xp)) + { + gc_mark(hash_entry_key(xp)); + gc_mark(hash_entry_value(xp)); + }}} +} + +static void mark_iterator(s7_pointer p) +{ + set_mark(p); + gc_mark(iterator_sequence(p)); + if (is_mark_seq(p)) + gc_mark(iterator_current(p)); +} + +static void mark_input_port(s7_pointer p) +{ + set_mark(p); + gc_mark(port_string_or_function(p)); +} + +static void mark_output_port(s7_pointer p) +{ + set_mark(p); + if (is_function_port(p)) + gc_mark(port_string_or_function(p)); +} + +#define clear_type(p) full_type(p) = T_FREE + +static void init_mark_functions(void) +{ + mark_function[T_FREE] = mark_noop; + mark_function[T_UNDEFINED] = just_mark; + mark_function[T_EOF] = mark_noop; + mark_function[T_UNSPECIFIED] = mark_noop; + mark_function[T_NIL] = mark_noop; + mark_function[T_UNUSED] = mark_noop; + mark_function[T_BOOLEAN] = mark_noop; + mark_function[T_SYNTAX] = mark_noop; + mark_function[T_CHARACTER] = mark_noop; + mark_function[T_SYMBOL] = mark_noop; /* this changes to just_mark when gensyms are in the heap */ + mark_function[T_STRING] = just_mark; + mark_function[T_INTEGER] = just_mark; + mark_function[T_RATIO] = just_mark; + mark_function[T_REAL] = just_mark; + mark_function[T_COMPLEX] = just_mark; + mark_function[T_BIG_INTEGER] = just_mark; + mark_function[T_BIG_RATIO] = just_mark; + mark_function[T_BIG_REAL] = just_mark; + mark_function[T_BIG_COMPLEX] = just_mark; + mark_function[T_RANDOM_STATE] = just_mark; + mark_function[T_GOTO] = just_mark; + mark_function[T_OUTPUT_PORT] = just_mark; /* changed to mark_output_port if output function ports are active */ + mark_function[T_C_MACRO] = just_mark; + mark_function[T_C_POINTER] = mark_c_pointer; + mark_function[T_C_FUNCTION] = just_mark; + mark_function[T_C_FUNCTION_STAR] = just_mark; /* changes to mark_c_proc_star if defaults involve an expression */ + mark_function[T_C_RST_NO_REQ_FUNCTION] = just_mark; + mark_function[T_PAIR] = mark_pair; + mark_function[T_CLOSURE] = mark_closure; + mark_function[T_CLOSURE_STAR] = mark_closure; + mark_function[T_CONTINUATION] = mark_continuation; + mark_function[T_INPUT_PORT] = mark_input_port; + mark_function[T_VECTOR] = mark_vector; /* this changes if subvector created (similarly below) */ + mark_function[T_INT_VECTOR] = mark_int_or_float_vector; + mark_function[T_FLOAT_VECTOR] = mark_int_or_float_vector; + mark_function[T_BYTE_VECTOR] = just_mark; + mark_function[T_MACRO] = mark_closure; + mark_function[T_BACRO] = mark_closure; + mark_function[T_MACRO_STAR] = mark_closure; + mark_function[T_BACRO_STAR] = mark_closure; + mark_function[T_C_OBJECT] = mark_c_object; + mark_function[T_CATCH] = mark_catch; + mark_function[T_DYNAMIC_WIND] = mark_dynamic_wind; + mark_function[T_HASH_TABLE] = mark_hash_table; + mark_function[T_ITERATOR] = mark_iterator; + mark_function[T_LET] = mark_let; + mark_function[T_STACK] = mark_stack; + mark_function[T_COUNTER] = mark_counter; + mark_function[T_SLOT] = mark_slot; +} + +static void mark_op_stack(s7_scheme *sc) +{ + s7_pointer *p = sc->op_stack; + s7_pointer *tp = sc->op_stack_now; + while (p < tp) + gc_mark(*p++); +} + +static void mark_input_port_stack(s7_scheme *sc) +{ + s7_pointer *tp = (s7_pointer *)(sc->input_port_stack + sc->input_port_stack_loc); + for (s7_pointer *p = sc->input_port_stack; p < tp; p++) + gc_mark(*p); +} + +static void mark_rootlet(s7_scheme *sc) +{ + s7_pointer ge = sc->rootlet; + s7_pointer *tmp = rootlet_elements(ge); + s7_pointer *top = (s7_pointer *)(tmp + sc->rootlet_entries); + set_mark(ge); + while (tmp < top) + gc_mark(slot_value(*tmp++)); + /* slot_setter is handled below with an explicit list -- more code than its worth probably */ + /* we're not marking slot_symbol above which makes me worry that a top-level gensym won't be protected + * (apply define (gensym) '(32)), then try to get the GC to clobber {gensym}-0, + * but I can't get it to break, so they must be protected somehow; apparently they are + * removed from the heap! At least: (define-macro (defit) (let ((n (gensym))) `(define (,n) (format #t "fun")))) (defit) + * removes the function from the heap (protecting the gensym). + */ +} + +/* mark_closure calls mark_let on closure_let(func) which marks slot values. + * if we move rootlet to end, unmarked closures at that point could mark let/slot but not slot value? + * or save safe-closure lets to handle all at end? or a gc_list of safe closure lets and only mark let if not safe? + */ + +static void mark_semipermanent_objects(s7_scheme *sc) +{ + for (gc_obj_t *g = sc->semipermanent_objects; g; g = (gc_obj_t *)(g->nxt)) + gc_mark(g->p); + /* semipermanent_objects also has lets (removed from heap) -- should they be handled like semipermanent_lets? + * if unmarked should either be removed from the list and perhaps placed on a free list? + * if outlet is free can the let potentially be in use? + * there are many more semipermanent_lets(slots) than semipermanent objects + */ +} +/* do we mark funclet slot values from the function as root? Maybe treat them like semipermanent_lets here? */ + +static void unmark_semipermanent_objects(s7_scheme *sc) +{ + gc_obj_t *g; + for (g = sc->semipermanent_objects; g; g = (gc_obj_t *)(g->nxt)) + clear_mark(g->p); + for (g = sc->semipermanent_lets; g; g = (gc_obj_t *)(g->nxt)) /* there are lets and slots in this list */ + clear_mark(g->p); +} + +#if (!MS_WINDOWS) + #include + #include +#endif + +#if WITH_GCC +static __attribute__ ((format (printf, 3, 4))) void s7_warn(s7_scheme *sc, s7_int len, const char *ctrl, ...); +#else +static void s7_warn(s7_scheme *sc, s7_int len, const char *ctrl, ...); +#endif + +#if S7_DEBUGGING +static int64_t gc(s7_scheme *sc, const char *func, int32_t line) +#else +static int64_t gc(s7_scheme *sc) +#endif +{ + s7_cell **old_free_heap_top; + s7_int i; + + if (sc->gc_in_progress) + error_nr(sc, sc->error_symbol, set_elist_1(sc, wrap_string(sc, "GC called recursively", 21))); + sc->gc_in_progress = true; + sc->gc_start = my_clock(); + sc->gc_calls++; +#if S7_DEBUGGING + sc->last_gc_line = line; +#endif + sc->continuation_counter = 0; + + mark_rootlet(sc); + mark_owlet(sc); + + gc_mark(sc->code); + if (sc->args) gc_mark(sc->args); + gc_mark(sc->curlet); /* not mark_let because op_any_closure_3p uses sc->curlet as a temp!! */ + mark_current_code(sc); /* probably redundant if with_history */ + gc_mark(sc->value); + + mark_stack_1(sc->stack, stack_top(sc)); + set_mark(current_input_port(sc)); + mark_input_port_stack(sc); + set_mark(current_output_port(sc)); + set_mark(current_error_port(sc)); + gc_mark(sc->stacktrace_defaults); + gc_mark(sc->autoload_table); + gc_mark(sc->default_random_state); + if (sc->let_temp_hook) gc_mark(sc->let_temp_hook); + + gc_mark(sc->w); + gc_mark(sc->x); + gc_mark(sc->y); + gc_mark(sc->z); + gc_mark(sc->temp1); + gc_mark(sc->temp2); + gc_mark(sc->temp3); + gc_mark(sc->temp4); + gc_mark(sc->temp5); + gc_mark(sc->temp7); + gc_mark(sc->temp8); + gc_mark(sc->temp9); + gc_mark(sc->temp10); + + gc_mark(car(sc->t1_1)); + gc_mark(car(sc->t2_1)); gc_mark(car(sc->t2_2)); + gc_mark(car(sc->t3_1)); gc_mark(car(sc->t3_2)); gc_mark(car(sc->t3_3)); + gc_mark(car(sc->t4_1)); + gc_mark(car(sc->mlist_1)); + gc_mark(car(sc->mlist_2)); gc_mark(cadr(sc->mlist_2)); + gc_mark(car(sc->plist_1)); + gc_mark(car(sc->plist_2)); gc_mark(cadr(sc->plist_2)); + gc_mark(car(sc->plist_3)); gc_mark(cadr(sc->plist_3)); gc_mark(caddr(sc->plist_3)); + gc_mark(car(sc->qlist_2)); gc_mark(cadr(sc->qlist_2)); + gc_mark(car(sc->qlist_3)); + gc_mark(car(sc->u1_1)); + + gc_mark(sc->rec_p1); + gc_mark(sc->rec_p2); + + /* these probably don't need to be marked */ + for (s7_pointer p = sc->wrong_type_arg_info; is_pair(p); p = cdr(p)) gc_mark(car(p)); + for (s7_pointer p = sc->sole_arg_wrong_type_info; is_pair(p); p = cdr(p)) gc_mark(car(p)); + for (s7_pointer p = sc->out_of_range_info; is_pair(p); p = cdr(p)) gc_mark(car(p)); + for (s7_pointer p = sc->sole_arg_out_of_range_info; is_pair(p); p = cdr(p)) gc_mark(car(p)); + + gc_mark(car(sc->elist_1)); + gc_mark(car(sc->elist_2)); gc_mark(cadr(sc->elist_2)); + gc_mark(car(sc->elist_3)); gc_mark(cadr(sc->elist_3)); gc_mark(caddr(sc->elist_3)); + gc_mark(car(sc->elist_4)); + gc_mark(car(sc->elist_5)); + gc_mark(car(sc->elist_6)); + gc_mark(car(sc->elist_7)); + + for (i = 1; i < NUM_SAFE_LISTS; i++) + if ((is_pair(sc->safe_lists[i])) && + (list_is_in_use(sc->safe_lists[i]))) + for (s7_pointer p = sc->safe_lists[i]; is_pair(p); p = cdr(p)) + gc_mark(car(p)); + + for (i = 0; i < sc->setters_loc; i++) + gc_mark(cdr(sc->setters[i])); + + for (i = 0; i <= sc->format_depth; i++) /* sc->num_fdats is size of array */ + if (sc->fdats[i]) + gc_mark(sc->fdats[i]->curly_arg); + + if (sc->rec_stack) + { + just_mark(sc->rec_stack); + for (i = 0; i < sc->rec_loc; i++) + gc_mark(sc->rec_els[i]); + } + mark_vector(sc->protected_objects); + mark_vector(sc->protected_setters); + set_mark(sc->protected_setter_symbols); + if ((is_symbol(sc->profile_prefix)) && (is_gensym(sc->profile_prefix))) set_mark(sc->profile_prefix); + + /* protect recent allocations using the free_heap cells above the current free_heap_top (if any). + * cells above sc->free_heap_top might be malloc'd garbage (after heap reallocation), so we keep track of + * where the last actually freed cells were after the previous GC call. We're trying to + * GC protect the previous GC_TEMPS_SIZE allocated pointers so that the caller doesn't have + * to gc-protect every temporary cell. + */ + { + s7_pointer *tmps = sc->free_heap_top; + s7_pointer *tmps_top = tmps + sc->gc_temps_size; + if (tmps_top > sc->previous_free_heap_top) + tmps_top = sc->previous_free_heap_top; + while (tmps < tmps_top) + gc_mark(*tmps++); + } + mark_op_stack(sc); + mark_semipermanent_objects(sc); + + if (sc->profiling_gensyms) + { + profile_data_t *pd = sc->profile_data; + for (i = 0; i < pd->top; i++) + if ((pd->funcs[i]) && (is_gensym(pd->funcs[i]))) + set_mark(pd->funcs[i]); + } + + { + gc_list_t *gp = sc->opt1_funcs; + for (i = 0; i < gp->loc; i++) + { + s7_pointer s1 = T_Pair(gp->list[i]); + if ((is_marked(s1)) && (!is_marked(opt1_any(s1)))) /* opt1_lambda, but op_unknown* can change to opt1_cfunc etc */ + gc_mark(opt1_any(s1)); /* not set_mark -- need to protect let/body/args as well */ + }} + + /* free up all unmarked objects */ + old_free_heap_top = sc->free_heap_top; + { + s7_pointer *fp = sc->free_heap_top; + s7_pointer *tp = sc->heap; + s7_pointer *heap_top = (s7_pointer *)(sc->heap + sc->heap_size); + +#if S7_DEBUGGING + #define gc_object(Tp) \ + p = (*Tp++); \ + if (signed_type(p) > 0) \ + { \ + p->debugger_bits = 0; p->gc_func = func; p->gc_line = line; \ + if (has_odd_bits(p)) {char *s; fprintf(stderr, "odd bits: %s\n", s = describe_type_bits(sc, p)); free(s);} \ + signed_type(p) = 0; \ + (*fp++) = p; \ + } \ + else if (signed_type(p) < 0) clear_mark(p); +#else + #define gc_object(Tp) p = (*Tp++); if (signed_type(p) > 0) {signed_type(p) = 0; (*fp++) = p;} else if (signed_type(p) < 0) clear_mark(p); + /* this appears to be about 10% faster than the previous form + * if the sign bit is on, but no other bits, this version will take no action (it thinks the cell is on the free list), but + * it means we've marked a free cell as in-use: since types are set as soon as removed from the free list, this has to be a bug + * (this case is caught by has_odd_bits). If ignored, the type will be set, and later the bit cleared, so no problem? + * An alternate form that simply calls clear_mark (no check for < 0) appears to be the same speed even in cases with lots + * of long-lived objects. + */ +#endif + while (tp < heap_top) /* != here or ^ makes no difference, and going to 64 (from 32) doesn't matter */ + { + s7_pointer p; + LOOP_8(gc_object(tp)); + LOOP_8(gc_object(tp)); + LOOP_8(gc_object(tp)); + LOOP_8(gc_object(tp)); + } + /* I tried using pthreads here, since there is no need for a lock in this loop, but the *fp++ part needs to + * be local to each thread, then merged at the end. In my timing tests, the current version was faster. + * If NUM_THREADS=2, and all thread variables are local, surely there's no "false sharing"? + */ + sc->free_heap_top = fp; + sweep(sc); + } + + unmark_semipermanent_objects(sc); + sc->gc_freed = (int64_t)(sc->free_heap_top - old_free_heap_top); + sc->gc_total_freed += sc->gc_freed; + sc->gc_end = my_clock(); + sc->gc_total_time += (sc->gc_end - sc->gc_start); + + if (show_gc_stats(sc)) + { +#if (!MS_WINDOWS) +#if S7_DEBUGGING + s7_warn(sc, 512, "%s[%d]: gc freed %" ld64 "/%" ld64 " (free: %" p64 "), time: %f\n", func, line, + sc->gc_freed, sc->heap_size, (intptr_t)(sc->free_heap_top - sc->free_heap), (double)(sc->gc_end - sc->gc_start) / ticks_per_second()); +#else + s7_warn(sc, 256, "gc freed %" ld64 "/%" ld64 " (free: %" p64 "), time: %f\n", + sc->gc_freed, sc->heap_size, (intptr_t)(sc->free_heap_top - sc->free_heap), (double)(sc->gc_end - sc->gc_start) / ticks_per_second()); +#endif +#else + s7_warn(sc, 256, "gc freed %" ld64 "/%" ld64 "\n", sc->gc_freed, sc->heap_size); +#endif + } + if (show_protected_objects_stats(sc)) + { + s7_int num, len = vector_length(sc->protected_objects); /* allocated at startup */ + for (i = 0, num = 0; i < len; i++) + if (vector_element(sc->protected_objects, i) != sc->unused) + num++; + s7_warn(sc, 256, "gc-protected-objects: %" ld64 " in use of %" ld64 "\n", num, len); + } + sc->previous_free_heap_top = sc->free_heap_top; + sc->gc_in_progress = false; + return(sc->gc_freed); +} + + +#ifndef GC_RESIZE_HEAP_FRACTION + #define GC_RESIZE_HEAP_FRACTION 0.8 +/* 1/2 is ok, 3/4 speeds up some GC benchmarks, 7/8 is a bit faster, 95/100 comes to a halt (giant heap) + * in my tests, only tvect.scm ends up larger if 3/4 used + */ +#endif + +#define GC_RESIZE_HEAP_BY_4_FRACTION 0.67 +/* .5+.1: test -3?, dup +86, tmap +45, tsort -3, thash +305. .85+.7: dup -5 */ + +static s7_pointer make_symbol(s7_scheme *sc, const char *name, s7_int len); /* calls new_symbol */ +#define make_symbol_with_strlen(Sc, Name) make_symbol(Sc, Name, safe_strlen(Name)) + +#if S7_DEBUGGING +#define resize_heap_to(Sc, Size) resize_heap_to_1(Sc, Size, __func__, __LINE__) +static void resize_heap_to_1(s7_scheme *sc, int64_t size, const char *func, int line) +#else +static void resize_heap_to(s7_scheme *sc, int64_t size) +#endif +{ + int64_t old_size = sc->heap_size; + int64_t old_free = sc->free_heap_top - sc->free_heap; + s7_cell *cells; + s7_cell **cp; + heap_block_t *hp; + +#if (S7_DEBUGGING) && (!MS_WINDOWS) + if (show_gc_stats(sc)) + s7_warn(sc, 512, "%s from %s[%d]: old: %" ld64 " / %" ld64 ", new: %" ld64 ", fraction: %.3f -> %" ld64 "\n", + __func__, func, line, old_free, old_size, size, sc->gc_resize_heap_fraction, (int64_t)(floor(sc->heap_size * sc->gc_resize_heap_fraction))); +#endif + + if (size == 0) + { + if (old_free < old_size * sc->gc_resize_heap_by_4_fraction) + sc->heap_size *= 4; /* *8 if < 1M (or whatever) doesn't make much difference */ + else sc->heap_size *= 2; + if (sc->gc_resize_heap_fraction > .4) + sc->gc_resize_heap_fraction *= .95; + } + else + if (size > sc->heap_size) + while (sc->heap_size < size) sc->heap_size *= 2; + else return; + /* do not call new_cell here! */ +#if POINTER_32 + if (((2 * sc->heap_size * sizeof(s7_cell *)) + ((sc->heap_size - old_size) * sizeof(s7_cell))) >= SIZE_MAX) + { /* can this happen in 64-bit land? SIZE_MAX is unsigned int in 32-bit, unsigned long in 64 bit = UINTPTR_MAX = 18446744073709551615UL */ + s7_warn(sc, 256, "heap size requested, %" ld64 " => %" ld64 " bytes, is greater than size_t: %u\n", + sc->heap_size, + (2 * sc->heap_size * sizeof(s7_cell *)) + ((sc->heap_size - old_size) * sizeof(s7_cell)), + SIZE_MAX); + sc->heap_size = old_size + 64000; + } +#endif + cp = (s7_cell **)Realloc(sc->heap, sc->heap_size * sizeof(s7_cell *)); + if (cp) + sc->heap = cp; + else + { + s7_warn(sc, 256, "heap reallocation failed! tried to get %" ld64 " bytes (will retry with a smaller amount)\n", + (int64_t)(sc->heap_size * sizeof(s7_cell *))); + sc->heap_size = old_size + 64000; + sc->heap = (s7_cell **)Realloc(sc->heap, sc->heap_size * sizeof(s7_cell *)); + } + sc->free_heap = (s7_cell **)Realloc(sc->free_heap, sc->heap_size * sizeof(s7_cell *)); + sc->free_heap_trigger = (s7_cell **)(sc->free_heap + GC_TRIGGER_SIZE); + sc->free_heap_top = sc->free_heap + old_free; /* incremented below, added old_free 21-Aug-12?!? */ + + cells = (s7_cell *)Calloc(sc->heap_size - old_size, sizeof(s7_cell)); /* Malloc + clear_type below is much slower?! */ + add_saved_pointer(sc, (void *)cells); + { + s7_pointer p = cells; + for (int64_t k = old_size; k < sc->heap_size;) + { + LOOP_8(sc->heap[k++] = p; (*sc->free_heap_top++) = p++); + LOOP_8(sc->heap[k++] = p; (*sc->free_heap_top++) = p++); + LOOP_8(sc->heap[k++] = p; (*sc->free_heap_top++) = p++); + LOOP_8(sc->heap[k++] = p; (*sc->free_heap_top++) = p++); + }} + hp = (heap_block_t *)Malloc(sizeof(heap_block_t)); + hp->start = (intptr_t)cells; + hp->end = (intptr_t)cells + ((sc->heap_size - old_size) * sizeof(s7_cell)); + hp->offset = old_size; + hp->next = sc->heap_blocks; + sc->heap_blocks = hp; + sc->previous_free_heap_top = sc->free_heap_top; + + if (show_heap_stats(sc)) + { + const char *str = string_value(object_to_truncated_string(sc, current_code(sc), 80)); + if (size != 0) + s7_warn(sc, 512, "heap grows to %" ld64 " (old free/size: %" ld64 "/%" ld64 ", requested %" ld64 ") from %s\n", + sc->heap_size, old_free, old_size, size, str); + else s7_warn(sc, 512, "heap grows to %" ld64 " (old free/size: %" ld64 "/%" ld64 ") from %s\n", + sc->heap_size, old_free, old_size, str); + } + if (sc->heap_size >= sc->max_heap_size) + error_nr(sc, make_symbol(sc, "heap-too-big", 12), + set_elist_3(sc, wrap_string(sc, "heap has grown past (*s7* 'max-heap-size): ~S > ~S", 50), + wrap_integer(sc, sc->max_heap_size), + wrap_integer(sc, sc->heap_size))); +} + + +#define resize_heap(Sc) resize_heap_to(Sc, 0) + +#if S7_DEBUGGING +#define call_gc(Sc) gc(Sc, __func__, __LINE__) +static void try_to_call_gc_1(s7_scheme *sc, const char *func, int32_t line) +#else +#define call_gc(Sc) gc(Sc) +static void try_to_call_gc(s7_scheme *sc) +#endif +{ + /* called only from new_cell */ + if (sc->gc_off) /* we can't just return here! Someone needs a new cell, and once the heap free list is exhausted, segfault */ + resize_heap(sc); + else + { + if ((sc->gc_resize_heap_fraction > 0.5) && (sc->heap_size >= 4194304)) + sc->gc_resize_heap_fraction = 0.5; +#if S7_DEBUGGING + gc(sc, func, line); /* not call_gc! */ +#else + gc(sc); +#endif + if ((int64_t)(sc->free_heap_top - sc->free_heap) < (sc->heap_size * sc->gc_resize_heap_fraction)) /* changed 21-Jul-22 */ + resize_heap(sc); + } +} + /* originally I tried to mark each temporary value until I was done with it, but that way madness lies... By delaying + * GC of _every_ %$^#%@ pointer, I can dispense with hundreds of individual protections. So the free_heap's last + * GC_TEMPS_SIZE allocated pointers are protected during the mark sweep. + */ + +static s7_pointer g_gc(s7_scheme *sc, s7_pointer args) +{ + #define H_gc "(gc (on #t)) runs the garbage collector. If 'on' is supplied, it turns the GC on or off. \ +Evaluation produces a surprising amount of garbage, so don't leave the GC off for very long!" + #define Q_gc s7_make_signature(sc, 2, sc->T, sc->is_boolean_symbol) + + /* g_gc can't be called in a situation where these lists matter (I think...) */ + set_mlist_1(sc, sc->unused); + set_mlist_2(sc, sc->unused, sc->unused); + set_plist_1(sc, sc->unused); + set_plist_2(sc, sc->unused, sc->unused); + set_plist_3(sc, sc->unused, sc->unused, sc->unused); + set_qlist_2(sc, sc->unused, sc->unused); + set_car(sc->qlist_3, sc->unused); + set_elist_1(sc, sc->unused); + set_elist_2(sc, sc->unused, sc->unused); + set_elist_3(sc, sc->unused, sc->unused, sc->unused); + set_car(sc->elist_4, sc->unused); + set_car(sc->elist_5, sc->unused); + set_car(sc->elist_6, sc->unused); + set_car(sc->elist_7, sc->unused); /* clist and dlist are weak references */ + set_ulist_1(sc, sc->unused, sc->unused); + if (is_not_null(args)) + { + if (!is_boolean(car(args))) + return(sole_arg_method_or_bust(sc, car(args), sc->gc_symbol, args, sc->type_names[T_BOOLEAN])); + sc->gc_off = (car(args) == sc->F); + if (sc->gc_off) + return(sc->F); + } + call_gc(sc); + return(sc->unspecified); +} + +s7_pointer s7_gc_on(s7_scheme *sc, bool on) +{ + sc->gc_off = !on; + return(s7_make_boolean(sc, on)); +} + +#if S7_DEBUGGING +static void check_free_heap_size_1(s7_scheme *sc, s7_int size, const char *func, int32_t line) +#define check_free_heap_size(Sc, Size) check_free_heap_size_1(Sc, Size, __func__, __LINE__) +#else +static void check_free_heap_size(s7_scheme *sc, s7_int size) +#endif +{ + s7_int free_cells = sc->free_heap_top - sc->free_heap; + if (free_cells < size) + { +#if S7_DEBUGGING + gc(sc, func, line); +#else + gc(sc); +#endif + while ((sc->free_heap_top - sc->free_heap) < (s7_int)(size * 1.5)) + resize_heap(sc); + } +} + +#define ALLOC_POINTER_SIZE 256 +static s7_cell *alloc_pointer(s7_scheme *sc) +{ + if (sc->alloc_pointer_k == ALLOC_POINTER_SIZE) /* if either no current block or the block is used up, make a new block */ + { + sc->semipermanent_cells += ALLOC_POINTER_SIZE; + sc->alloc_pointer_cells = (s7_cell *)Calloc(ALLOC_POINTER_SIZE, sizeof(s7_cell)); /* not Malloc here or below (maybe set full type to 0 if Malloc) */ + add_saved_pointer(sc, sc->alloc_pointer_cells); + sc->alloc_pointer_k = 0; + } + return(&(sc->alloc_pointer_cells[sc->alloc_pointer_k++])); +} + +#define ALLOC_BIG_POINTER_SIZE 256 +static s7_big_cell *alloc_big_pointer(s7_scheme *sc, int64_t loc) +{ + s7_big_pointer p; + if (sc->alloc_big_pointer_k == ALLOC_BIG_POINTER_SIZE) + { + sc->semipermanent_cells += ALLOC_BIG_POINTER_SIZE; + sc->alloc_big_pointer_cells = (s7_big_cell *)Calloc(ALLOC_BIG_POINTER_SIZE, sizeof(s7_big_cell)); + add_saved_pointer(sc, sc->alloc_big_pointer_cells); + sc->alloc_big_pointer_k = 0; + } + p = (&(sc->alloc_big_pointer_cells[sc->alloc_big_pointer_k++])); + p->big_hloc = loc; + /* needed if this new pointer is itself petrified later -- it's not from one of the heap blocks, + * but it's in the heap, and we'll need to know where it is in the heap to replace it + */ + return(p); +} + +static void add_semipermanent_object(s7_scheme *sc, s7_pointer obj) /* called by remove_from_heap */ +{ + gc_obj_t *g = (gc_obj_t *)Malloc(sizeof(gc_obj_t)); + g->p = obj; + g->nxt = sc->semipermanent_objects; + sc->semipermanent_objects = g; +} + +static void add_semipermanent_let_or_slot(s7_scheme *sc, s7_pointer obj) +{ + gc_obj_t *g = (gc_obj_t *)Malloc(sizeof(gc_obj_t)); + g->p = obj; + g->nxt = sc->semipermanent_lets; + sc->semipermanent_lets = g; +} + +#if S7_DEBUGGING +static const char *type_name_from_type(int32_t typ, article_t article); + +#define free_cell(Sc, P) free_cell_1(Sc, P, __LINE__) +static void free_cell_1(s7_scheme *sc, s7_pointer p, int32_t line) +#else +static void free_cell(s7_scheme *sc, s7_pointer p) +#endif +{ +#if S7_DEBUGGING + /* anything that needs gc_list attention should not be freed here */ + uint8_t typ = unchecked_type(p); + gc_list_t *gp = sc->opt1_funcs; + + if ((t_freeze_p[typ]) || ((typ == T_SYMBOL) && (is_gensym(p)))) + fprintf(stderr, "free_cell of %s?\n", type_name_from_type(typ, NO_ARTICLE)); + if ((t_any_closure_p[typ]) && (gp->loc > 0)) + for (s7_int i = 0; i < gp->loc; i++) + if (gp->list[i] == p) + fprintf(stderr, "opt1_funcs free_cell of %s?\n", type_name_from_type(typ, NO_ARTICLE)); + gp = sc->weak_refs; + if (gp->loc > 0) + for (s7_int i = 0; i < gp->loc; i++) + if (gp->list[i] == p) + fprintf(stderr, "weak refs free_cell of %s?\n", type_name_from_type(typ, NO_ARTICLE)); + + p->debugger_bits = 0; + p->explicit_free_line = line; +#endif + clear_type(p); + (*(sc->free_heap_top++)) = p; +} + +static inline s7_pointer petrify(s7_scheme *sc, s7_pointer x) +{ + int64_t loc = heap_location(sc, x); + s7_pointer p = (s7_pointer)alloc_big_pointer(sc, loc); + sc->heap[loc] = p; + free_cell(sc, p); + unheap(sc, x); /* set_immutable(x); */ /* if there are GC troubles, this might catch them? */ + return(x); +} + +#if S7_DEBUGGING +#define remove_gensym_from_heap(Sc, Gensym) remove_gensym_from_heap_1(Sc, Gensym, __func__, __LINE__) +static void remove_gensym_from_heap_1(s7_scheme *sc, s7_pointer x, const char *func, int line) +#else +static void remove_gensym_from_heap(s7_scheme *sc, s7_pointer x) /* x known to be a symbol and in the heap */ +#endif +{ + int64_t loc = heap_location(sc, x); + sc->heap[loc] = (s7_pointer)alloc_big_pointer(sc, loc); + free_cell(sc, sc->heap[loc]); +#if S7_DEBUGGING + x->gc_func = func; + x->gc_line = line; +#endif + unheap(sc, x); /* set UNHEAP bit in type(x) */ + { + gc_list_t *gp = sc->gensyms; + for (s7_int i = 0; i < gp->loc; i++) /* sc->gensyms reaches size 512 during s7test, but this search is called 3 times and costs nothing */ + if (gp->list[i] == x) + { + for (s7_int j = i + 1; i < gp->loc - 1; i++, j++) + gp->list[i] = gp->list[j]; + gp->list[i] = NULL; + gp->loc--; + if (gp->loc == 0) mark_function[T_SYMBOL] = mark_noop; + break; + }} +} + +static inline void remove_from_heap(s7_scheme *sc, s7_pointer x) +{ + /* global functions are very rarely redefined, so we can remove the function body from the heap when it is defined */ + if (!in_heap(x)) return; + if (is_pair(x)) /* all the compute time is here, might be faster to go down a level explicitly */ + { + s7_pointer p = x; + do { + petrify(sc, p); + remove_from_heap(sc, car(p)); + p = cdr(p); + } while (is_pair(p) && (in_heap(p))); + if (in_heap(p)) petrify(sc, p); + return; + } + switch (type(x)) + { + case T_LET: /* very rare */ + if (is_funclet(x)) set_immutable_let(x); + case T_HASH_TABLE: + case T_VECTOR: + /* not int|float_vector or string because none of their elements are GC-able (so unheap below is ok) + * but hash-table and let seem like they need protection? And let does happen via define-class. + */ + add_semipermanent_object(sc, x); + return; + case T_SYMBOL: + if (is_gensym(x)) + remove_gensym_from_heap(sc, x); + return; + case T_CLOSURE: case T_CLOSURE_STAR: + case T_MACRO: case T_MACRO_STAR: + case T_BACRO: case T_BACRO_STAR: + /* these need to be GC-protected! */ + add_semipermanent_object(sc, x); + return; + default: break; + } + petrify(sc, x); +} + + +/* -------------------------------- stacks -------------------------------- */ +#define OP_STACK_INITIAL_SIZE 64 + +#if S7_DEBUGGING +static void push_op_stack(s7_scheme *sc, s7_pointer op) +{ + (*sc->op_stack_now++) = T_Ext(op); /* not T_App etc -- args can be pushed */ + if (sc->op_stack_now > (sc->op_stack + sc->op_stack_size)) + { + fprintf(stderr, "%sop_stack overflow%s\n", bold_text, unbold_text); + if (sc->stop_at_error) abort(); + } +} + +static s7_pointer pop_op_stack(s7_scheme *sc) +{ + s7_pointer op = T_Ext(*(--(sc->op_stack_now))); + if (sc->op_stack_now < sc->op_stack) + { + fprintf(stderr, "%sop_stack underflow%s\n", bold_text, unbold_text); + if (sc->stop_at_error) abort(); + } + return(T_Ext(op)); +} +#else +#define push_op_stack(Sc, Op) (*Sc->op_stack_now++) = Op +#define pop_op_stack(Sc) (*(--(Sc->op_stack_now))) +#endif + +static void initialize_op_stack(s7_scheme *sc) +{ + sc->op_stack = (s7_pointer *)Malloc(OP_STACK_INITIAL_SIZE * sizeof(s7_pointer)); + sc->op_stack_size = OP_STACK_INITIAL_SIZE; + sc->op_stack_now = sc->op_stack; + sc->op_stack_end = (s7_pointer *)(sc->op_stack + sc->op_stack_size); + for (int32_t i = 0; i < OP_STACK_INITIAL_SIZE; i++) sc->op_stack[i] = sc->unused; +} + +static void resize_op_stack(s7_scheme *sc) +{ + int32_t new_size = sc->op_stack_size * 2; + int32_t loc = (int32_t)(sc->op_stack_now - sc->op_stack); + sc->op_stack = (s7_pointer *)Realloc((void *)(sc->op_stack), new_size * sizeof(s7_pointer)); + for (int32_t i = sc->op_stack_size; i < new_size; i++) sc->op_stack[i] = sc->unused; + sc->op_stack_size = (uint32_t)new_size; + sc->op_stack_now = (s7_pointer *)(sc->op_stack + loc); + sc->op_stack_end = (s7_pointer *)(sc->op_stack + sc->op_stack_size); +} + + +#define stack_end_code(Sc) Sc->stack_end[0] +#define stack_end_let(Sc) Sc->stack_end[1] +#define stack_end_args(Sc) Sc->stack_end[2] +#define stack_end_op(Sc) Sc->stack_end[3] + +#if S7_DEBUGGING +#define pop_stack(Sc) pop_stack_1(Sc, __func__, __LINE__) +static void pop_stack_1(s7_scheme *sc, const char *func, int32_t line) +{ + sc->stack_end -= 4; + if (sc->stack_end < sc->stack_start) + { + fprintf(stderr, "%s%s[%d]: stack underflow%s\n", bold_text, func, line, unbold_text); + if (sc->stop_at_error) abort(); + } + /* here and in push_stack, both code and args might be non-free only because they've been retyped + * inline (as in named let) -- they actually don't make sense in these cases, but are ignored, + * and are carried around as GC protection in other cases. + */ + sc->code = T_Pos(stack_end_code(sc)); + sc->curlet = stack_end_let(sc); /* not T_Lid|Pos, see op_any_closure_3p_end et al (stack used to pass args, not curlet) */ + sc->args = stack_end_args(sc); + sc->cur_op = (opcode_t)T_Op(stack_end_op(sc)); + if (sc->cur_op >= NUM_OPS) + { + fprintf(stderr, "%s%s[%d]: pop_stack invalid opcode: %" p64 " %s\n", bold_text, func, line, sc->cur_op, unbold_text); + if (sc->stop_at_error) abort(); + } + if ((sc->cur_op != OP_GC_PROTECT) && + (!is_let(stack_end_let(sc))) && (!is_null(stack_end_let(sc))) && + (sc->cur_op != OP_ANY_CLOSURE_3P_3)) /* used as third GC protection field */ + fprintf(stderr, "%s[%d]: curlet not a let: %s\n", func, line, op_names[sc->cur_op]); +} + +#define pop_stack_no_op(Sc) pop_stack_no_op_1(Sc, __func__, __LINE__) +static void pop_stack_no_op_1(s7_scheme *sc, const char *func, int32_t line) +{ + sc->stack_end -= 4; + if (sc->stack_end < sc->stack_start) + { + fprintf(stderr, "%s%s[%d]: stack underflow%s\n", bold_text, func, line, unbold_text); + if (sc->stop_at_error) abort(); + } + sc->code = T_Pos(stack_end_code(sc)); + if ((sc->cur_op != OP_GC_PROTECT) && (!is_let(stack_end_let(sc))) && (!is_null(stack_end_let(sc)))) + fprintf(stderr, "%s[%d]: curlet not a let\n", func, line); + sc->curlet = stack_end_let(sc); /* not T_Lid|Pos: gc_protect can set this directly (not through push_stack) to anything */ + sc->args = stack_end_args(sc); +} + +#define push_stack(Sc, Op, Args, Code) \ + do {s7_pointer *_end_; _end_ = Sc->stack_end; push_stack_1(Sc, Op, Args, Code, _end_, __func__, __LINE__);} while (0) + +static void push_stack_1(s7_scheme *sc, opcode_t op, s7_pointer args, s7_pointer code, s7_pointer *end, const char *func, int32_t line) +{ + if ((SHOW_EVAL_OPS) && (op == OP_EVAL_DONE)) fprintf(stderr, " %s[%d]: push eval_done\n", func, line); + if (sc->stack_end >= sc->stack_start + sc->stack_size) + { + fprintf(stderr, "%s%s[%d]: stack overflow, %" ld64 " > %u, trigger: %" ld64 " %s\n", + bold_text, func, line, + (s7_int)((intptr_t)(sc->stack_end - sc->stack_start)), sc->stack_size, + (s7_int)((intptr_t)(sc->stack_resize_trigger - sc->stack_start)), + unbold_text); + s7_show_stack(sc); + /* make room for debugging */ + + abort(); + if (sc->stop_at_error) abort(); + } + if (sc->stack_end >= sc->stack_resize_trigger) + { + fprintf(stderr, "%s%s[%d]: stack resize skipped%s\n", bold_text, func, line, unbold_text); + if (sc->stop_at_error) + { + /* this is pointless if we can't look around in gdb, so give us some room */ + sc->stack_resize_trigger = (s7_pointer *)(sc->stack_start + (sc->stack_size - ((STACK_RESIZE_TRIGGER) / 2))); + abort(); + }} + if (sc->stack_end != end) + fprintf(stderr, "%s[%d]: stack changed in push_stack\n", func, line); + if (op >= NUM_OPS) + { + fprintf(stderr, "%s%s[%d]: push_stack invalid opcode: %" p64 " %s\n", bold_text, func, line, sc->cur_op, unbold_text); + if (sc->stop_at_error) abort(); + } + if (code) stack_end_code(sc) = T_Pos(code); + stack_end_let(sc) = T_Lid(sc->curlet); + if ((args) && (unchecked_type(args) != T_FREE)) stack_end_args(sc) = T_Pos(args); + stack_end_op(sc) = (s7_pointer)op; + sc->stack_end += 4; +} + +#define push_stack_no_code(Sc, Op, Args) push_stack(Sc, Op, Args, Sc->unused) +#define push_stack_no_let_no_code(Sc, Op, Args) push_stack(Sc, Op, Args, Sc->unused) +#define push_stack_no_args(Sc, Op, Code) push_stack(Sc, Op, Sc->unused, Code) +#define push_stack_no_let(Sc, Op, Args, Code) push_stack(Sc, Op, Args, Code) +#define push_stack_op(Sc, Op) push_stack(Sc, Op, Sc->unused, Sc->unused) +#define push_stack_op_let(Sc, Op) push_stack(Sc, Op, Sc->unused, Sc->unused) +#define push_stack_direct(Sc, Op) push_stack(Sc, Op, Sc->args, Sc->code) +#define push_stack_no_args_direct(Sc, Op) push_stack(Sc, Op, Sc->unused, Sc->code) +/* in the non-debugging case, the sc->unused's here are not set, so we can (later) pop free cells */ + +#else + +#define pop_stack(Sc) do {Sc->stack_end -= 4; memcpy((void *)Sc, (void *)(Sc->stack_end), 4 * sizeof(s7_pointer));} while (0) +#define pop_stack_no_op(Sc) {Sc->stack_end -= 4; memcpy((void *)Sc, (void *)(Sc->stack_end), 3 * sizeof(s7_pointer));} while (0) + +#define push_stack(Sc, Op, Args, Code) \ + do { \ + stack_end_code(sc) = Code; \ + stack_end_let(sc) = Sc->curlet; \ + stack_end_args(sc) = Args; \ + stack_end_op(sc) = (s7_pointer)(Op); \ + Sc->stack_end += 4; \ + } while (0) + +#define push_stack_direct(Sc, Op) \ + do { \ + Sc->cur_op = Op; \ + memcpy((void *)(Sc->stack_end), (void *)Sc, 4 * sizeof(s7_pointer)); \ + /* stack_end_op(sc) = (s7_pointer)(Op); */ \ + Sc->stack_end += 4; \ + } while (0) +/* is this faster with cur_op because of the cast to s7_pointer, or is callgrind messing up memcpy stats? + * time's output is all over the map. I think the cur_op form should be slower, but callgrind disagrees. + */ + +#define push_stack_no_code(Sc, Op, Args) \ + do { \ + stack_end_let(sc) = Sc->curlet; \ + stack_end_args(sc) = Args; \ + stack_end_op(sc) = (s7_pointer)(Op); \ + Sc->stack_end += 4; \ + } while (0) + +#define push_stack_no_let_no_code(Sc, Op, Args) \ + do { \ + stack_end_args(sc) = Args; \ + stack_end_op(sc) = (s7_pointer)(Op); \ + Sc->stack_end += 4; \ + } while (0) + +#define push_stack_no_args(Sc, Op, Code) \ + do { \ + stack_end_code(sc) = Code; \ + stack_end_let(sc) = Sc->curlet; \ + stack_end_op(sc) = (s7_pointer)(Op); \ + Sc->stack_end += 4; \ + } while (0) + +#define push_stack_no_args_direct(Sc, Op) \ + do { \ + memcpy((void *)(Sc->stack_end), (void *)Sc, 2 * sizeof(s7_pointer)); \ + stack_end_op(sc) = (s7_pointer)(Op); \ + Sc->stack_end += 4; \ + } while (0) + +#define push_stack_no_let(Sc, Op, Args, Code) \ + do { \ + stack_end_code(sc) = Code; \ + stack_end_args(sc) = Args; \ + stack_end_op(sc) = (s7_pointer)(Op); \ + Sc->stack_end += 4; \ + } while (0) + +#define push_stack_op(Sc, Op) \ + do { \ + stack_end_op(sc) = (s7_pointer)(Op); \ + Sc->stack_end += 4; \ + } while (0) + +#define push_stack_op_let(Sc, Op) \ + do { \ + stack_end_let(sc) = Sc->curlet; \ + stack_end_op(sc) = (s7_pointer)(Op); \ + Sc->stack_end += 4; \ + } while (0) +#endif +/* since we don't GC mark the stack past the stack_top, push_stack_no_args and friends can cause pop_stack to set + * sc->code and sc->args to currently free objects. + */ + +#if S7_DEBUGGING +#define unstack(Sc) unstack_1(Sc, __func__, __LINE__) +static void unstack_1(s7_scheme *sc, const char *func, int32_t line) +{ + sc->stack_end -= 4; + if ((opcode_t)T_Op(stack_end_op(sc)) != OP_GC_PROTECT) + { + fprintf(stderr, "%s%s[%d]: popped %s, (expected OP_GC_PROTECT)?%s\n", bold_text, func, line, op_names[(opcode_t)T_Op(stack_end_op(sc))], unbold_text); + /* "popped apply" means we called something that went to eval+apply when we thought it was a safe function */ + fprintf(stderr, " code: %s, args: %s\n", display(sc->code), display(sc->args)); + fprintf(stderr, " cur_code: %s, estr: %s\n", display(current_code(sc)), display(s7_name_to_value(sc, "estr"))); + if (sc->stop_at_error) abort(); + } +} +#define unstack_with(Sc, Op) unstack_2(Sc, Op, __func__, __LINE__) +static void unstack_2(s7_scheme *sc, opcode_t op, const char *func, int32_t line) +{ + sc->stack_end -= 4; + if ((opcode_t)T_Op(stack_end_op(sc)) != op) + { + fprintf(stderr, "%s%s[%d]: popped %s? (expected %s)%s\n", bold_text, func, line, op_names[(opcode_t)T_Op(stack_end_op(sc))], op_names[op], unbold_text); + fprintf(stderr, " code: %s, args: %s\n", display(sc->code), display(sc->args)); + fprintf(stderr, " cur_code: %s, estr: %s\n", display(current_code(sc)), display(s7_name_to_value(sc, "estr"))); + if (sc->stop_at_error) abort(); + } +} +#else +#define unstack(sc) sc->stack_end -= 4 +#define unstack_with(sc, op) sc->stack_end -= 4 +#endif + +static void stack_reset(s7_scheme *sc) +{ + sc->stack_end = sc->stack_start; + push_stack_op(sc, OP_EVAL_DONE); +} + +static uint32_t resize_stack_unchecked(s7_scheme *sc) +{ + uint64_t loc = stack_top(sc); + uint32_t new_size = sc->stack_size * 2; + block_t *ob = stack_block(sc->stack); + block_t *nb = reallocate(sc, ob, new_size * sizeof(s7_pointer)); + block_info(nb) = NULL; + stack_block(sc->stack) = nb; + stack_elements(sc->stack) = (s7_pointer *)block_data(nb); + { + s7_pointer *orig = stack_elements(sc->stack); + s7_int i = sc->stack_size; + s7_int left = new_size - i - 8; + while (i <= left) + LOOP_8(orig[i++] = sc->unused); + for (; i < new_size; i++) + orig[i] = sc->unused; + } + vector_length(sc->stack) = new_size; + sc->stack_size = new_size; + sc->stack_start = stack_elements(sc->stack); + sc->stack_end = (s7_pointer *)(sc->stack_start + loc); + sc->stack_resize_trigger = (s7_pointer *)(sc->stack_start + (new_size - STACK_RESIZE_TRIGGER)); + return(new_size); +} + +#if S7_DEBUGGING +#define resize_stack(Sc) resize_stack_1(Sc, __func__, __LINE__) +static void resize_stack_1(s7_scheme *sc, const char *func, int line) +{ + if ((sc->stack_size * 2) > sc->max_stack_size) + { + fprintf(stderr, "%s%s[%d]: stack too big, %" ld64 " > %u, trigger: %" ld64 " %s\n", + bold_text, func, line, + (s7_int)((intptr_t)(sc->stack_end - sc->stack_start)), sc->stack_size, + (s7_int)((intptr_t)(sc->stack_resize_trigger - sc->stack_start)), + unbold_text); + /* s7_show_stack(sc); */ /* prints so much the error message is inaccessible */ + fprintf(stderr, "stack:\n"); + for (int64_t i = stack_top(sc) - 1; i >= stack_top(sc) - 100; i -= 4) + fprintf(stderr, " %s\n", op_names[stack_op(sc->stack, i)]); + resize_stack_unchecked(sc); /* give us some room while debugging! */ + abort(); + if (sc->stop_at_error) abort(); + } + resize_stack_unchecked(sc); +} +#else +static void resize_stack(s7_scheme *sc) +{ + uint32_t new_size = resize_stack_unchecked(sc); + if (show_stack_stats(sc)) + s7_warn(sc, 128, "stack grows to %u\n", new_size); + if (new_size > sc->max_stack_size) + error_nr(sc, make_symbol(sc, "stack-too-big", 13), + set_elist_1(sc, wrap_string(sc, "stack has grown past (*s7* 'max-stack-size)", 43))); + /* error needs to follow realloc, else error -> catchers in error_nr -> let_temp* -> eval_done -> stack_resize -> infinite loop */ +} +#endif + +#define check_stack_size(Sc) do {if (Sc->stack_end >= Sc->stack_resize_trigger) resize_stack(Sc);} while (0) + +s7_pointer s7_gc_protect_via_stack(s7_scheme *sc, s7_pointer x) +{ + push_stack_no_let_no_code(sc, OP_GC_PROTECT, x); + return(x); +} + +s7_pointer s7_gc_unprotect_via_stack(s7_scheme *sc, s7_pointer x) +{ + unstack(sc); + return(x); +} + +#define stack_protected1(Sc) Sc->stack_end[-2] /* args */ /* stack_top_args(Sc) etc, but it's easier to remember these aliases */ +#define stack_protected2(Sc) Sc->stack_end[-4] /* code */ +#define stack_protected3(Sc) Sc->stack_end[-3] /* curlet */ + +#if S7_DEBUGGING + #define set_stack_protected1(Sc, Val) do {if (stack_top_op(Sc) != OP_GC_PROTECT) fprintf(stderr, "%s[%d]: stack_protected %s\n", __func__, __LINE__, op_names[stack_top_op(Sc)]); Sc->stack_end[-2] = Val;} while (0) + #define set_stack_protected2(Sc, Val) do {if (stack_top_op(Sc) != OP_GC_PROTECT) fprintf(stderr, "%s[%d]: stack_protected %s\n", __func__, __LINE__, op_names[stack_top_op(Sc)]); Sc->stack_end[-4] = Val;} while (0) + #define set_stack_protected3(Sc, Val) do {if (stack_top_op(Sc) != OP_GC_PROTECT) fprintf(stderr, "%s[%d]: stack_protected %s\n", __func__, __LINE__, op_names[stack_top_op(Sc)]); Sc->stack_end[-3] = Val;} while (0) + + #define set_stack_protected1_with(Sc, Val, Op) do {if (stack_top_op(Sc) != Op) fprintf(stderr, "%s[%d]: stack_protected %s\n", __func__, __LINE__, op_names[stack_top_op(Sc)]); Sc->stack_end[-2] = Val;} while (0) + #define set_stack_protected2_with(Sc, Val, Op) do {if (stack_top_op(Sc) != Op) fprintf(stderr, "%s[%d]: stack_protected %s\n", __func__, __LINE__, op_names[stack_top_op(Sc)]); Sc->stack_end[-4] = Val;} while (0) + #define set_stack_protected3_with(Sc, Val, Op) do {if (stack_top_op(Sc) != Op) fprintf(stderr, "%s[%d]: stack_protected %s\n", __func__, __LINE__, op_names[stack_top_op(Sc)]); Sc->stack_end[-3] = Val;} while (0) +#else + #define set_stack_protected1(Sc, Val) Sc->stack_end[-2] = Val + #define set_stack_protected2(Sc, Val) Sc->stack_end[-4] = Val + #define set_stack_protected3(Sc, Val) Sc->stack_end[-3] = Val + + #define set_stack_protected1_with(Sc, Val, Op) Sc->stack_end[-2] = Val + #define set_stack_protected2_with(Sc, Val, Op) Sc->stack_end[-4] = Val + #define set_stack_protected3_with(Sc, Val, Op) Sc->stack_end[-3] = Val +#endif + +#define gc_protect_via_stack(Sc, Obj) push_stack_no_let_no_code(Sc, OP_GC_PROTECT, Obj) +#define gc_protect_2_via_stack(Sc, X, Y) do {push_stack_no_let_no_code(Sc, OP_GC_PROTECT, X); stack_protected2(Sc) = Y;} while (0) /* often X and Y are fx_calls, so push X, then set Y */ +/* #define gc_protect_3_via_stack(Sc, X, Y, Z) do {push_stack_no_let_no_code(Sc, OP_GC_PROTECT, X); stack_protected2(Sc) = Y; stack_protected3(sc) = Z;} while (0) */ + + +/* -------------------------------- symbols -------------------------------- */ +static inline uint64_t raw_string_hash(const uint8_t *key, s7_int len) +{ + if (len <= 8) + { + uint64_t xs[1] = {0}; + memcpy((void *)xs, (const void *)key, len); + return(xs[0]); + } + else + { + uint64_t xs[2] = {0, 0}; + memcpy((void *)xs, (const void *)key, (len > 16) ? 16 : len); /* compiler complaint here is bogus */ + return(xs[0] + xs[1]); + } +} + +static uint8_t *alloc_symbol(s7_scheme *sc) +{ + #define SYMBOL_SIZE (3 * sizeof(s7_cell) + sizeof(block_t)) + #define ALLOC_SYMBOL_SIZE (64 * SYMBOL_SIZE) + uint8_t *result; + if (sc->alloc_symbol_k == ALLOC_SYMBOL_SIZE) + { + sc->alloc_symbol_cells = (uint8_t *)Malloc(ALLOC_SYMBOL_SIZE); + add_saved_pointer(sc, sc->alloc_symbol_cells); + sc->alloc_symbol_k = 0; + } + result = &(sc->alloc_symbol_cells[sc->alloc_symbol_k]); + sc->alloc_symbol_k += SYMBOL_SIZE; + return(result); +} + +static s7_pointer make_semipermanent_slot(s7_scheme *sc, s7_pointer symbol, s7_pointer value) +{ + s7_pointer slot = alloc_pointer(sc); + set_full_type(slot, T_SLOT | T_UNHEAP); + slot_set_symbol_and_value(slot, symbol, value); + return(slot); +} + +static /* inline */ s7_pointer new_symbol(s7_scheme *sc, const char *name, s7_int len, uint64_t hash, uint32_t location) /* inline useless here 20-Oct-22 */ +{ + /* name might not be null-terminated, these are semipermanent symbols even in s7_gensym; g_gensym handles everything separately */ + uint8_t *base = alloc_symbol(sc); + s7_pointer x = (s7_pointer)base; + s7_pointer str = (s7_pointer)(base + sizeof(s7_cell)); + s7_pointer p = (s7_pointer)(base + 2 * sizeof(s7_cell)); + uint8_t *val = (uint8_t *)permalloc(sc, len + 1); + memcpy((void *)val, (const void *)name, len); + val[len] = '\0'; + + full_type(str) = T_STRING | T_IMMUTABLE | T_UNHEAP; /* avoid debugging confusion involving set_type (also below) */ + string_length(str) = len; + string_value(str) = (char *)val; + string_hash(str) = hash; + + full_type(x) = T_SYMBOL | T_UNHEAP; + symbol_set_name_cell(x, str); + set_global_slot(x, sc->undefined); /* was sc->nil */ + symbol_info(x) = (block_t *)(base + 3 * sizeof(s7_cell)); + set_initial_slot(x, sc->undefined); + symbol_set_local_slot_unchecked_and_unincremented(x, 0LL, sc->nil); + symbol_set_tag(x, 0); + symbol_set_tag2(x, 0); + symbol_clear_ctr(x); /* alloc_symbol uses malloc */ + symbol_clear_type(x); + + if ((len > 1) && /* not 0, otherwise : is a keyword */ + ((name[0] == ':') || (name[len - 1] == ':'))) /* see s7test under keyword? for troubles if both colons are present */ + { + s7_pointer slot, ksym; + set_type_bit(x, T_IMMUTABLE | T_KEYWORD | T_GLOBAL); + set_optimize_op(str, OP_CONSTANT); + ksym = make_symbol(sc, (name[0] == ':') ? (const char *)(name + 1) : name, len - 1); + keyword_set_symbol(x, ksym); + set_has_keyword(ksym); + /* the keyword symbol needs to be semipermanent (not a gensym) else we have to laboriously gc-protect it */ + if ((is_gensym(ksym)) && + (in_heap(ksym))) + remove_gensym_from_heap(sc, ksym); + slot = make_semipermanent_slot(sc, x, x); + set_global_slot(x, slot); + set_local_slot(x, slot); + if (S7_DEBUGGING) set_immutable_slot(slot); + } + full_type(p) = T_PAIR | T_IMMUTABLE | T_UNHEAP; /* add x to the symbol table */ + set_car(p, x); + set_cdr(p, vector_element(sc->symbol_table, location)); + vector_element(sc->symbol_table, location) = p; + pair_set_raw_hash(p, hash); + pair_set_raw_len(p, (uint64_t)len); /* symbol name length, so it ought to fit! */ + pair_set_raw_name(p, string_value(str)); + return(x); +} + +static Inline s7_pointer inline_make_symbol(s7_scheme *sc, const char *name, s7_int len) /* inline out: ca 40=2% in tload */ +{ /* name here might not be null-terminated */ + uint64_t hash = raw_string_hash((const uint8_t *)name, len); + uint32_t location = hash % SYMBOL_TABLE_SIZE; + + if (len <= 8) + { + for (s7_pointer x = vector_element(sc->symbol_table, location); is_pair(x); x = cdr(x)) + if ((hash == pair_raw_hash(x)) && + ((uint64_t)len == pair_raw_len(x))) + return(car(x)); + } + else /* checking name[len=='\0' and using strcmp if so was not a big win */ + for (s7_pointer x = vector_element(sc->symbol_table, location); is_pair(x); x = cdr(x)) + if ((hash == pair_raw_hash(x)) && + ((uint64_t)len == pair_raw_len(x)) && + (strings_are_equal_with_length(name, pair_raw_name(x), len))) /* length here because name might not be null-terminated */ + return(car(x)); + return(new_symbol(sc, name, len, hash, location)); +} + +static s7_pointer make_symbol(s7_scheme *sc, const char *name, s7_int len) {return(inline_make_symbol(sc, name, len));} + +s7_pointer s7_make_symbol(s7_scheme *sc, const char *name) {return(inline_make_symbol(sc, name, safe_strlen(name)));} + +static s7_pointer symbol_table_find_by_name(s7_scheme *sc, const char *name, uint64_t hash, uint32_t location, s7_int len) +{ + for (s7_pointer x = vector_element(sc->symbol_table, location); is_not_null(x); x = cdr(x)) + if ((hash == pair_raw_hash(x)) && + (strings_are_equal_with_length(name, pair_raw_name(x), len))) + return(car(x)); + return(sc->nil); +} + +s7_pointer s7_symbol_table_find_name(s7_scheme *sc, const char *name) +{ + s7_int len = safe_strlen(name); + uint64_t hash = raw_string_hash((const uint8_t *)name, len); + s7_pointer result = symbol_table_find_by_name(sc, name, hash, hash % SYMBOL_TABLE_SIZE, len); + return((is_null(result)) ? NULL : result); +} + + +/* -------------------------------- symbol-table -------------------------------- */ +static inline s7_pointer make_simple_vector(s7_scheme *sc, s7_int len); + +static s7_pointer g_symbol_table(s7_scheme *sc, s7_pointer unused_args) +{ + #define H_symbol_table "(symbol-table) returns a vector containing the current symbol-table symbols" + #define Q_symbol_table s7_make_signature(sc, 1, sc->is_vector_symbol) + + s7_pointer *els, *entries = vector_elements(sc->symbol_table); + int32_t syms = 0; + s7_pointer lst; + /* this can't be optimized by returning the actual symbol-table (a vector of lists), because + * gensyms can cause the table's lists and symbols to change at any time. This wreaks havoc + * on traversals like for-each. So, symbol-table returns a snap-shot of the table contents + * at the time it is called. + * (define (for-each-symbol func num) (for-each (lambda (sym) (if (> num 0) (for-each-symbol func (- num 1)) (func sym))) (symbol-table))) + * (for-each-symbol (lambda (sym) (gensym) 1)) + */ + for (int32_t i = 0; i < SYMBOL_TABLE_SIZE; i++) + for (s7_pointer x = entries[i]; is_not_null(x); x = cdr(x)) + syms++; + sc->w = make_simple_vector(sc, syms); + els = vector_elements(sc->w); + for (int32_t i = 0, j = 0; i < SYMBOL_TABLE_SIZE; i++) + for (s7_pointer x = entries[i]; is_not_null(x); x = cdr(x)) + els[j++] = car(x); + lst = sc->w; + sc->w = sc->unused; + return(lst); +} + +bool s7_for_each_symbol_name(s7_scheme *sc, bool (*symbol_func)(const char *symbol_name, void *data), void *data) +{ + /* this includes the special constants # and so on for simplicity -- are there any others? */ + for (int32_t i = 0; i < SYMBOL_TABLE_SIZE; i++) + for (s7_pointer x = vector_element(sc->symbol_table, i); is_not_null(x); x = cdr(x)) + if (symbol_func(symbol_name(car(x)), data)) + return(true); + return((symbol_func("#t", data)) || (symbol_func("#f", data)) || + (symbol_func("#", data)) || (symbol_func("#", data)) || + (symbol_func("#", data)) || + (symbol_func("#true", data)) || (symbol_func("#false", data))); +} + +bool s7_for_each_symbol(s7_scheme *sc, bool (*symbol_func)(const char *symbol_name, void *data), void *data) +{ + for (int32_t i = 0; i < SYMBOL_TABLE_SIZE; i++) + for (s7_pointer x = vector_element(sc->symbol_table, i); is_not_null(x); x = cdr(x)) + if (symbol_func(symbol_name(car(x)), data)) + return(true); + return(false); +} + + +/* -------------------------------- gensym -------------------------------- */ +static void remove_gensym_from_symbol_table(s7_scheme *sc, s7_pointer sym) +{ + /* sym is a free cell at this point (we're called after the GC), but the name_cell is still intact */ + s7_pointer name = symbol_name_cell(sym); + uint32_t location = string_hash(name) % SYMBOL_TABLE_SIZE; + s7_pointer x = vector_element(sc->symbol_table, location); + if (car(x) == sym) + vector_element(sc->symbol_table, location) = cdr(x); + else + for (s7_pointer y = x, z = cdr(x); is_pair(z); y = z, z = cdr(z)) + if (car(z) == sym) + { + set_cdr(y, cdr(z)); + return; + } +} + +s7_pointer s7_gensym(s7_scheme *sc, const char *prefix) +{ + s7_int len = safe_strlen(prefix) + 32; + block_t *b = mallocate(sc, len); + char *name = (char *)block_data(b); + /* there's no point in heroic efforts here to avoid name collisions -- the user can screw up no matter what we do */ + name[0] = '\0'; + { + s7_int slen = catstrs(name, len, "{", (prefix) ? prefix : "", "}-", pos_int_to_str_direct(sc, sc->gensym_counter++), (char *)NULL); + uint64_t hash = raw_string_hash((const uint8_t *)name, slen); + int32_t location = hash % SYMBOL_TABLE_SIZE; + s7_pointer x = new_symbol(sc, name, slen, hash, location); /* not T_GENSYM -- might be called from outside */ + liberate(sc, b); + return(x); + } +} + +static bool is_gensym_b_p(s7_pointer g) {return((is_symbol(g)) && (is_gensym(g)));} + +static s7_pointer g_is_gensym(s7_scheme *sc, s7_pointer args) +{ + #define H_is_gensym "(gensym? sym) returns #t if sym is a gensym" + #define Q_is_gensym sc->pl_bt + check_boolean_method(sc, is_gensym_b_p, sc->is_gensym_symbol, args); +} + +static s7_pointer g_gensym(s7_scheme *sc, s7_pointer args) +{ + #define H_gensym "(gensym (prefix \"gensym\")) returns a new, unused symbol" + #define Q_gensym s7_make_signature(sc, 2, sc->is_gensym_symbol, sc->is_string_symbol) + + const char *prefix; + char *name, *p, *base; + s7_int len, plen, nlen; + uint32_t location; + uint64_t hash; + s7_pointer x, str, stc; + block_t *b, *ib; + + /* get symbol name */ + if (is_not_null(args)) + { + s7_pointer gname = car(args); + if (!is_string(gname)) + return(sole_arg_method_or_bust(sc, gname, sc->gensym_symbol, args, sc->type_names[T_STRING])); + prefix = string_value(gname); + plen = string_length(gname); /* was safe_strlen(prefix): were we stopping at #\null deliberately? */ + } + else + { + prefix = "gensym"; + plen = 6; + } + len = plen + 32; /* why 32 -- we need room for the gensym_counter integer, but (length "9223372036854775807") = 19 */ + + b = mallocate(sc, len + sizeof(block_t) + 2 * sizeof(s7_cell)); + base = (char *)block_data(b); + str = (s7_cell *)base; + stc = (s7_cell *)(base + sizeof(s7_cell)); + ib = (block_t *)(base + 2 * sizeof(s7_cell)); + name = (char *)(base + sizeof(block_t) + 2 * sizeof(s7_cell)); + + name[0] = '{'; + memcpy((void *)(name + 1), prefix, plen); /* memcpy is ok with plen==0, I think */ + name[plen + 1] = '}'; + name[plen + 2] = '-'; /* {gensym}-nnn */ + + p = pos_int_to_str(sc, sc->gensym_counter++, &len, '\0'); + memcpy((void *)(name + plen + 3), (void *)p, len); + nlen = len + plen + 2; + hash = raw_string_hash((const uint8_t *)name, nlen); + location = hash % SYMBOL_TABLE_SIZE; + + if ((WITH_WARNINGS) && (!is_null(symbol_table_find_by_name(sc, name, hash, location, nlen)))) + s7_warn(sc, nlen + 32, "%s is already in use!", name); + + /* make-string for symbol name */ + if (S7_DEBUGGING) full_type(str) = 0; /* here and below, this is needed to avoid set_type check errors (mallocate above) */ + set_full_type(str, T_STRING | T_IMMUTABLE | T_UNHEAP); + string_length(str) = nlen; + string_value(str) = name; + string_hash(str) = hash; + + /* allocate the symbol in the heap so GC'd when inaccessible */ + new_cell(sc, x, T_SYMBOL | T_GENSYM); + symbol_set_name_cell(x, str); + symbol_info(x) = ib; + set_global_slot(x, sc->undefined); /* set_initial_slot(x, sc->undefined); */ + symbol_set_local_slot_unchecked(x, 0LL, sc->nil); + symbol_clear_ctr(x); + symbol_set_tag(x, 0); + symbol_set_tag2(x, 0); + symbol_clear_type(x); + gensym_block(x) = b; + + /* place new symbol in symbol-table */ + if (S7_DEBUGGING) full_type(stc) = 0; + set_full_type(stc, T_PAIR | T_IMMUTABLE | T_UNHEAP); + set_car(stc, x); + set_cdr(stc, vector_element(sc->symbol_table, location)); + vector_element(sc->symbol_table, location) = stc; + pair_set_raw_hash(stc, hash); + pair_set_raw_len(stc, (uint64_t)string_length(str)); + pair_set_raw_name(stc, string_value(str)); + + add_gensym(sc, x); + return(x); +} + + +/* -------------------------------- syntax? -------------------------------- */ +bool s7_is_syntax(s7_pointer p) {return(is_syntax(p));} + +static s7_pointer g_is_syntax(s7_scheme *sc, s7_pointer args) +{ + #define H_is_syntax "(syntax? obj) returns #t if obj is a syntactic value (e.g. lambda)" + #define Q_is_syntax sc->pl_bt + check_boolean_method(sc, is_syntax, sc->is_syntax_symbol, args); +} + + +/* -------------------------------- symbol? -------------------------------- */ +bool s7_is_symbol(s7_pointer p) {return(is_symbol(p));} + +static s7_pointer g_is_symbol(s7_scheme *sc, s7_pointer args) +{ + #define H_is_symbol "(symbol? obj) returns #t if obj is a symbol" + #define Q_is_symbol sc->pl_bt + check_boolean_method(sc, is_symbol, sc->is_symbol_symbol, args); +} + +const char *s7_symbol_name(s7_pointer p) {return(symbol_name(p));} + +s7_pointer s7_name_to_value(s7_scheme *sc, const char *name) {return(s7_symbol_value(sc, make_symbol_with_strlen(sc, name)));} +/* should this also handle non-symbols such as "+nan.0"? */ + + +/* -------------------------------- symbol->string -------------------------------- */ +static Inline s7_pointer inline_make_string_with_length(s7_scheme *sc, const char *str, s7_int len) +{ + s7_pointer x; + new_cell(sc, x, T_STRING | T_SAFE_PROCEDURE); + string_block(x) = inline_mallocate(sc, len + 1); + string_value(x) = (char *)block_data(string_block(x)); + memcpy((void *)string_value(x), (const void *)str, len); + string_value(x)[len] = 0; + string_length(x) = len; + string_hash(x) = 0; + add_string(sc, x); + return(x); +} + +static s7_pointer make_string_with_length(s7_scheme *sc, const char *str, s7_int len) +{ + return(inline_make_string_with_length(sc, str, len)); /* packaged to avoid inlining everywhere */ +} + +static s7_pointer g_symbol_to_string(s7_scheme *sc, s7_pointer args) +{ + #define H_symbol_to_string "(symbol->string sym) returns the symbol sym converted to a string" + #define Q_symbol_to_string s7_make_signature(sc, 2, sc->is_string_symbol, sc->is_symbol_symbol) + + s7_pointer sym = car(args); + if (!is_symbol(sym)) + return(sole_arg_method_or_bust(sc, sym, sc->symbol_to_string_symbol, args, sc->type_names[T_SYMBOL])); + /* s7_make_string uses strlen which stops at an embedded null */ + return(inline_make_string_with_length(sc, symbol_name(sym), symbol_name_length(sym))); /* return a copy */ +} + +static s7_pointer g_symbol_to_string_uncopied(s7_scheme *sc, s7_pointer args) +{ + s7_pointer sym = car(args); + if (!is_symbol(sym)) + return(sole_arg_method_or_bust(sc, sym, sc->symbol_to_string_symbol, args, sc->type_names[T_SYMBOL])); + if (is_gensym(sym)) + return(make_string_with_length(sc, symbol_name(sym), symbol_name_length(sym))); /* return a copy of gensym name (which will be freed) */ + return(symbol_name_cell(sym)); +} + +static s7_pointer symbol_to_string_p_p(s7_scheme *sc, s7_pointer sym) +{ + if (!is_symbol(sym)) + return(sole_arg_method_or_bust(sc, sym, sc->symbol_to_string_symbol, set_plist_1(sc, sym), sc->type_names[T_SYMBOL])); + return(inline_make_string_with_length(sc, symbol_name(sym), symbol_name_length(sym))); +} + +static s7_pointer symbol_to_string_uncopied_p(s7_scheme *sc, s7_pointer sym) +{ + if (!is_symbol(sym)) + return(sole_arg_method_or_bust(sc, sym, sc->symbol_to_string_symbol, set_plist_1(sc, sym), sc->type_names[T_SYMBOL])); + if (is_gensym(sym)) + return(make_string_with_length(sc, symbol_name(sym), symbol_name_length(sym))); + return(symbol_name_cell(sym)); +} + + +/* -------------------------------- string->symbol -------------------------------- */ +static inline s7_pointer g_string_to_symbol_1(s7_scheme *sc, s7_pointer str, s7_pointer caller) +{ + if (!is_string(str)) + return(method_or_bust_p(sc, str, caller, sc->type_names[T_STRING])); + if (string_length(str) <= 0) + sole_arg_wrong_type_error_nr(sc, caller, str, wrap_string(sc, "a non-null string", 17)); + return(make_symbol(sc, string_value(str), string_length(str))); +} + +static s7_pointer g_string_to_symbol(s7_scheme *sc, s7_pointer args) +{ + #define H_string_to_symbol "(string->symbol str) returns the string str converted to a symbol" + #define Q_string_to_symbol s7_make_signature(sc, 2, sc->is_symbol_symbol, sc->is_string_symbol) + return(g_string_to_symbol_1(sc, car(args), sc->string_to_symbol_symbol)); +} + +static s7_pointer string_to_symbol_p_p(s7_scheme *sc, s7_pointer p) {return(g_string_to_symbol_1(sc, p, sc->string_to_symbol_symbol));} + + +/* -------------------------------- symbol -------------------------------- */ +static s7_pointer g_string_append_1(s7_scheme *sc, s7_pointer args, s7_pointer caller); + +static s7_pointer mark_as_symbol_from_symbol(s7_pointer sym) +{ + set_is_symbol_from_symbol(sym); + return(sym); +} + +static s7_pointer g_symbol(s7_scheme *sc, s7_pointer args) +{ + #define H_symbol "(symbol str ...) returns its string arguments concatenated and converted to a symbol" + #define Q_symbol s7_make_circular_signature(sc, 1, 2, sc->is_symbol_symbol, sc->is_string_symbol) + + /* (let ((x 0)) (set! (symbol "x") 12)) ;symbol (a c-function) does not have a setter: (set! (symbol "x") 12) + * (let (((symbol "x") 3)) x) ; bad variable ((symbol "x") + * (let ((x 2)) (+ (symbol "x") 1)) ;+ first argument, x, is a symbol but should be a number + * maybe document this: (symbol...) just returns the symbol + * (let ((x 3)) (+ (symbol->value (symbol "x")) 1)) -> 4, (let ((x 0)) (apply set! (symbol "x") (list 32)) x) -> 32 + */ + + s7_int len = 0, cur_len; + s7_pointer p, sym; + block_t *b; + char *name; + + for (p = args; is_pair(p); p = cdr(p)) + if (is_string(car(p))) + len += string_length(car(p)); + else break; + if (is_pair(p)) + { + if (is_null(cdr(args))) + return(mark_as_symbol_from_symbol(g_string_to_symbol_1(sc, car(args), sc->symbol_symbol))); + return(mark_as_symbol_from_symbol(g_string_to_symbol_1(sc, g_string_append_1(sc, args, sc->symbol_symbol), sc->symbol_symbol))); + } + if (len == 0) + sole_arg_wrong_type_error_nr(sc, sc->symbol_symbol, car(args), wrap_string(sc, "a non-null string", 17)); + + b = mallocate(sc, len + 1); + name = (char *)block_data(b); + /* can't use catstrs_direct here because it stops at embedded null */ + for (cur_len = 0, p = args; is_pair(p); p = cdr(p)) + { + s7_pointer str = car(p); + if (string_length(str) > 0) + { + memcpy((void *)(name + cur_len), (void *)string_value(str), string_length(str)); + cur_len += string_length(str); + }} + name[len] = '\0'; + sym = mark_as_symbol_from_symbol(inline_make_symbol(sc, name, len)); + liberate(sc, b); + return(sym); +} + +static s7_pointer symbol_p_pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + char buf[256]; + s7_int len; + if ((!is_string(p1)) || (!is_string(p2))) return(g_symbol(sc, set_plist_2(sc, p1, p2))); + len = string_length(p1) + string_length(p2); + if ((len == 0) || (len >= 256)) return(g_symbol(sc, set_plist_2(sc, p1, p2))); + memcpy((void *)buf, (void *)string_value(p1), string_length(p1)); + memcpy((void *)(buf + string_length(p1)), (void *)string_value(p2), string_length(p2)); + return(mark_as_symbol_from_symbol(inline_make_symbol(sc, buf, len))); +} + + +/* -------- symbol sets -------- */ +static inline s7_pointer add_symbol_to_list(s7_scheme *sc, s7_pointer sym) +{ + symbol_set_tag(sym, sc->syms_tag); + symbol_set_tag2(sym, sc->syms_tag2); + return(sym); +} + +static inline void clear_symbol_list(s7_scheme *sc) +{ + sc->syms_tag++; + if (sc->syms_tag == 0) + { + sc->syms_tag = 1; /* we're assuming (in let_equal) that this tag is not 0 */ + sc->syms_tag2++; + } +} + +#define symbol_is_in_list(Sc, Sym) ((symbol_tag(Sym) == Sc->syms_tag) && (symbol_tag2(Sym) == Sc->syms_tag2)) + + +/* -------------------------------- lets/slots -------------------------------- */ +static Inline s7_pointer inline_make_let(s7_scheme *sc, s7_pointer old_let) +{ + s7_pointer x; + new_cell(sc, x, T_LET | T_SAFE_PROCEDURE); + let_set_id(x, ++sc->let_number); + let_set_slots(x, slot_end); + let_set_outlet(x, old_let); + return(x); +} + +static inline s7_pointer make_let(s7_scheme *sc, s7_pointer old_let) {return(inline_make_let(sc, old_let));} + +static Inline s7_pointer inline_make_let_with_slot(s7_scheme *sc, s7_pointer old_let, s7_pointer symbol, s7_pointer value) +{ + s7_pointer new_let, slot; + sc->value = value; + new_cell(sc, new_let, T_LET | T_SAFE_PROCEDURE); + let_set_id(new_let, ++sc->let_number); + let_set_outlet(new_let, old_let); + new_cell_no_check(sc, slot, T_SLOT); + slot_set_symbol_and_value(slot, symbol, value); + symbol_set_local_slot(symbol, sc->let_number, slot); + slot_set_next(slot, slot_end); + let_set_slots(new_let, slot); + return(new_let); +} + +static s7_pointer make_let_with_slot(s7_scheme *sc, s7_pointer old_let, s7_pointer symbol, s7_pointer value) +{ + return(inline_make_let_with_slot(sc, old_let, symbol, value)); +} + +static Inline s7_pointer inline_make_let_with_two_slots(s7_scheme *sc, s7_pointer old_let, + s7_pointer symbol1, s7_pointer value1, s7_pointer symbol2, s7_pointer value2) +{ + /* we leave value1/value2 computation order to the C compiler here -- in the old macro, it was explicitly value1 then value2 + * this means any let in old scheme code that actually depends on the order may break -- it should be let*. + */ + s7_pointer new_let, slot1, slot2; + new_cell(sc, new_let, T_LET | T_SAFE_PROCEDURE); + let_set_id(new_let, ++sc->let_number); + let_set_outlet(new_let, old_let); + + new_cell_no_check(sc, slot1, T_SLOT); + slot_set_symbol_and_value(slot1, symbol1, value1); + symbol_set_local_slot(symbol1, sc->let_number, slot1); + let_set_slots(new_let, slot1); + + new_cell_no_check(sc, slot2, T_SLOT); + slot_set_symbol_and_value(slot2, symbol2, value2); + symbol_set_local_slot(symbol2, sc->let_number, slot2); + slot_set_next(slot2, slot_end); + slot_set_next(slot1, slot2); + return(new_let); +} + +static s7_pointer make_let_with_two_slots(s7_scheme *sc, s7_pointer old_let, s7_pointer symbol1, s7_pointer value1, s7_pointer symbol2, s7_pointer value2) +{ + return(inline_make_let_with_two_slots(sc, old_let, symbol1, value1, symbol2, value2)); +} + +/* in all these functions, symbol_set_local_slot should follow slot_set_value so that we can evaluate the slot's value in its old state */ +static inline void add_slot_unchecked(s7_scheme *sc, s7_pointer let, s7_pointer symbol, s7_pointer value, uint64_t id) +{ + s7_pointer slot; + new_cell_no_check(sc, slot, T_SLOT); + slot_set_symbol_and_value(slot, symbol, value); + slot_set_next(slot, let_slots(let)); + let_set_slots(let, slot); + set_local(symbol); + symbol_set_local_slot(symbol, id, slot); +} + +static void add_slot_unchecked_no_local(s7_scheme *sc, s7_pointer let, s7_pointer symbol, s7_pointer value) +{ + s7_pointer slot; + new_cell_no_check(sc, slot, T_SLOT); + slot_set_symbol_and_value(slot, symbol, value); + slot_set_next(slot, let_slots(let)); + let_set_slots(let, slot); + set_local(symbol); +} + +#define add_slot(Sc, Let, Symbol, Value) add_slot_unchecked(Sc, Let, Symbol, Value, let_id(Let)) + +static inline s7_pointer add_slot_checked(s7_scheme *sc, s7_pointer let, s7_pointer symbol, s7_pointer value) +{ + s7_pointer slot; + new_cell(sc, slot, T_SLOT); + slot_set_symbol_and_value(slot, symbol, value); + symbol_set_local_slot(symbol, let_id(let), slot); + slot_set_next(slot, let_slots(let)); + let_set_slots(let, slot); + return(slot); +} + +static inline s7_pointer add_slot_checked_with_id(s7_scheme *sc, s7_pointer let, s7_pointer symbol, s7_pointer value) +{ + s7_pointer slot; + new_cell(sc, slot, T_SLOT); + slot_set_symbol_and_value(slot, symbol, value); + set_local(symbol); + if (let_id(let) >= symbol_id(symbol)) + symbol_set_local_slot(symbol, let_id(let), slot); + slot_set_next(slot, let_slots(let)); + let_set_slots(let, slot); + return(slot); +} + +static s7_pointer add_slot_unchecked_with_id(s7_scheme *sc, s7_pointer let, s7_pointer symbol, s7_pointer value) +{ + s7_pointer slot; + new_cell_no_check(sc, slot, T_SLOT); + slot_set_symbol_and_value(slot, symbol, value); + set_local(symbol); + if (let_id(let) >= symbol_id(symbol)) + symbol_set_local_slot(symbol, let_id(let), slot); + slot_set_next(slot, let_slots(let)); + let_set_slots(let, slot); + return(slot); +} + +static Inline s7_pointer inline_add_slot_at_end(s7_scheme *sc, uint64_t id, s7_pointer last_slot, s7_pointer symbol, s7_pointer value) +{ + s7_pointer slot; + new_cell_no_check(sc, slot, T_SLOT); + slot_set_symbol_and_value(slot, symbol, value); + slot_set_next(slot, slot_end); + symbol_set_local_slot(symbol, id, slot); + slot_set_next(last_slot, slot); + return(slot); +} + +static s7_pointer add_slot_at_end(s7_scheme *sc, uint64_t id, s7_pointer last_slot, s7_pointer symbol, s7_pointer value) +{ + return(inline_add_slot_at_end(sc, id, last_slot, symbol, value)); +} + +static s7_pointer add_slot_at_end_no_local(s7_scheme *sc, s7_pointer last_slot, s7_pointer symbol, s7_pointer value) +{ + s7_pointer slot; + new_cell_no_check(sc, slot, T_SLOT); + slot_set_symbol_and_value(slot, symbol, value); + slot_set_next(slot, slot_end); + slot_set_next(last_slot, slot); + return(slot); +} + +static inline void make_let_with_three_slots(s7_scheme *sc, s7_pointer func, s7_pointer val1, s7_pointer val2, s7_pointer val3) +{ + s7_pointer last_slot, cargs = closure_args(func); + sc->curlet = inline_make_let_with_two_slots(sc, closure_let(func), car(cargs), val1, cadr(cargs), val2); + last_slot = next_slot(let_slots(sc->curlet)); + inline_add_slot_at_end(sc, let_id(sc->curlet), last_slot, caddr(cargs), val3); +} + +static inline void make_let_with_four_slots(s7_scheme *sc, s7_pointer func, s7_pointer val1, s7_pointer val2, s7_pointer val3, s7_pointer val4) +{ + s7_pointer last_slot, cargs = closure_args(func); + sc->curlet = inline_make_let_with_two_slots(sc, closure_let(func), car(cargs), val1, cadr(cargs), val2); + cargs = cddr(cargs); + last_slot = next_slot(let_slots(sc->curlet)); + last_slot = inline_add_slot_at_end(sc, let_id(sc->curlet), last_slot, car(cargs), val3); + inline_add_slot_at_end(sc, let_id(sc->curlet), last_slot, cadr(cargs), val4); +} + +static inline void make_let_with_five_slots(s7_scheme *sc, s7_pointer func, s7_pointer val1, s7_pointer val2, s7_pointer val3, s7_pointer val4, s7_pointer val5) +{ + s7_pointer last_slot, cargs = closure_args(func); + sc->curlet = inline_make_let_with_two_slots(sc, closure_let(func), car(cargs), val1, cadr(cargs), val2); + cargs = cddr(cargs); + last_slot = next_slot(let_slots(sc->curlet)); + last_slot = inline_add_slot_at_end(sc, let_id(sc->curlet), last_slot, car(cargs), val3); + cargs = cdr(cargs); + last_slot = inline_add_slot_at_end(sc, let_id(sc->curlet), last_slot, car(cargs), val4); + inline_add_slot_at_end(sc, let_id(sc->curlet), last_slot, cadr(cargs), val5); +} + +static s7_pointer reuse_as_let(s7_scheme *sc, s7_pointer let, s7_pointer next_let) +{ + /* we're reusing let here as a let -- it was probably a pair */ +#if S7_DEBUGGING + let->debugger_bits = 0; + if (!in_heap(let)) {fprintf(stderr, "reusing an unheaped %s as a let?\n", s7_type_names[type(let)]); abort();} +#endif + set_full_type(T_Pair(let), T_LET | T_SAFE_PROCEDURE); + let_set_slots(let, slot_end); + let_set_outlet(let, next_let); + let_set_id(let, ++sc->let_number); + return(let); +} + +static s7_pointer reuse_as_slot(s7_pointer slot, s7_pointer symbol, s7_pointer value) +{ + set_full_type(T_Pair(slot), T_SLOT); + slot_set_symbol_and_value(slot, symbol, value); + return(slot); +} + +#define update_slot(Slot, Val, Id) do {s7_pointer sym; slot_set_value(Slot, Val); sym = slot_symbol(Slot); symbol_set_local_slot_unincremented(sym, Id, Slot);} while (0) + +static s7_pointer update_let_with_slot(s7_scheme *sc, s7_pointer let, s7_pointer val) +{ + s7_pointer slot = let_slots(let); + uint64_t id = ++sc->let_number; + let_set_id(let, id); + update_slot(slot, val, id); + return(let); +} + +static s7_pointer update_let_with_two_slots(s7_scheme *sc, s7_pointer let, s7_pointer val1, s7_pointer val2) +{ + s7_pointer slot = let_slots(let); + uint64_t id = ++sc->let_number; + let_set_id(let, id); + update_slot(slot, val1, id); slot = next_slot(slot); + update_slot(slot, val2, id); + return(let); +} + +static s7_pointer update_let_with_three_slots(s7_scheme *sc, s7_pointer let, s7_pointer val1, s7_pointer val2, s7_pointer val3) +{ + s7_pointer slot = let_slots(let); + uint64_t id = ++sc->let_number; + let_set_id(let, id); + update_slot(slot, val1, id); slot = next_slot(slot); + update_slot(slot, val2, id); slot = next_slot(slot); + update_slot(slot, val3, id); + return(let); +} + +static s7_pointer update_let_with_four_slots(s7_scheme *sc, s7_pointer let, s7_pointer val1, s7_pointer val2, s7_pointer val3, s7_pointer val4) +{ + s7_pointer slot = let_slots(let); + uint64_t id = ++sc->let_number; + let_set_id(let, id); + update_slot(slot, val1, id); slot = next_slot(slot); + update_slot(slot, val2, id); slot = next_slot(slot); + update_slot(slot, val3, id); slot = next_slot(slot); + update_slot(slot, val4, id); + return(let); +} + +static s7_pointer make_semipermanent_let(s7_scheme *sc, s7_pointer vars) +{ + s7_pointer slot, let = alloc_pointer(sc); + set_full_type(let, T_LET | T_SAFE_PROCEDURE | T_UNHEAP); + let_set_id(let, ++sc->let_number); + let_set_outlet(let, sc->curlet); + slot = make_semipermanent_slot(sc, caar(vars), sc->F); + add_semipermanent_let_or_slot(sc, slot); + symbol_set_local_slot(caar(vars), sc->let_number, slot); + let_set_slots(let, slot); + for (s7_pointer var = cdr(vars); is_pair(var); var = cdr(var)) + { + s7_pointer last_slot = slot; + slot = make_semipermanent_slot(sc, caar(var), sc->F); + add_semipermanent_let_or_slot(sc, slot); + symbol_set_local_slot(caar(var), sc->let_number, slot); + slot_set_next(last_slot, slot); + } + slot_set_next(slot, slot_end); + add_semipermanent_let_or_slot(sc, let); /* need to mark outlet and maybe slot values */ + return(let); +} + +static s7_pointer call_setter(s7_scheme *sc, s7_pointer slot, s7_pointer old_value); + +static inline s7_pointer checked_slot_set_value(s7_scheme *sc, s7_pointer y, s7_pointer value) +{ + if (slot_has_setter(y)) + slot_set_value(y, call_setter(sc, y, value)); + else + { + if (is_immutable_slot(y)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->let_set_symbol, slot_symbol(y))); + slot_set_value(y, value); + } + return(slot_value(y)); +} + +static s7_pointer let_fill(s7_scheme *sc, s7_pointer args) +{ + s7_pointer e = car(args), val; + if (e == sc->rootlet) + out_of_range_error_nr(sc, sc->fill_symbol, int_one, e, wrap_string(sc, "can't fill! rootlet", 19)); + if (e == sc->s7_starlet) + out_of_range_error_nr(sc, sc->fill_symbol, int_one, e, wrap_string(sc, "can't fill! *s7*", 16)); + if (e == sc->owlet) /* (owlet) copies sc->owlet, so this probably can't happen */ + out_of_range_error_nr(sc, sc->fill_symbol, int_one, e, wrap_string(sc, "can't fill! owlet", 17)); + if (is_funclet(e)) + out_of_range_error_nr(sc, sc->fill_symbol, int_one, e, wrap_string(sc, "can't fill! a funclet", 21)); + val = cadr(args); + for (s7_pointer p = let_slots(e); tis_slot(p); p = next_slot(p)) + checked_slot_set_value(sc, p, val); + return(val); +} + +static s7_int s7_starlet_length(void); + +static s7_int let_length(s7_scheme *sc, s7_pointer e) +{ + /* used by length, applicable_length, copy, and some length optimizations */ + s7_int i; + s7_pointer p; + + if (e == sc->rootlet) + return(sc->rootlet_entries); + if (e == sc->s7_starlet) + return(s7_starlet_length()); + if (has_active_methods(sc, e)) + { + s7_pointer length_func = find_method(sc, e, sc->length_symbol); + if (length_func != sc->undefined) + { + p = s7_apply_function(sc, length_func, set_plist_1(sc, e)); + return((s7_is_integer(p)) ? s7_integer(p) : -1); /* ?? */ + }} + for (i = 0, p = let_slots(e); tis_slot(p); i++, p = next_slot(p)); + return(i); +} + +static void slot_set_setter(s7_pointer p, s7_pointer val) +{ + if ((type(val) == T_C_FUNCTION) && + (c_function_has_bool_setter(val))) + slot_set_setter_1(p, c_function_bool_setter(val)); + else slot_set_setter_1(p, val); +} + +static void slot_set_value_with_hook_1(s7_scheme *sc, s7_pointer slot, s7_pointer value) +{ + /* (set! (hook-functions *rootlet-redefinition-hook*) (list (lambda (hook) (format *stderr* "~A ~A~%" (hook 'name) (hook 'value))))) */ + s7_pointer symbol = slot_symbol(slot); + if ((global_slot(symbol) == slot) && + (value != slot_value(slot))) + s7_call(sc, sc->rootlet_redefinition_hook, set_plist_2(sc, symbol, value)); + slot_set_value(slot, value); +} + +static void remove_function_from_heap(s7_scheme *sc, s7_pointer value); /* calls remove_let_from_heap */ + +static void remove_let_from_heap(s7_scheme *sc, s7_pointer lt) +{ + for (s7_pointer p = let_slots(lt); tis_slot(p); p = next_slot(p)) + { + s7_pointer val = slot_value(p); + if ((has_closure_let(val)) && + (in_heap(closure_args(val)))) + remove_function_from_heap(sc, val); + } + let_set_removed(lt); +} + +static void add_slot_to_rootlet(s7_scheme *sc, s7_pointer slot) +{ + s7_pointer ge = sc->rootlet; + rootlet_element(ge, sc->rootlet_entries++) = slot; + set_in_rootlet(slot); + if (sc->rootlet_entries >= vector_length(ge)) + { + s7_int len; + block_t *ob, *nb; + vector_length(ge) *= 2; + len = vector_length(ge); + ob = rootlet_block(ge); + nb = reallocate(sc, ob, len * sizeof(s7_pointer)); + block_info(nb) = NULL; + rootlet_block(ge) = nb; + rootlet_elements(ge) = (s7_pointer *)block_data(nb); + for (s7_int i = sc->rootlet_entries; i < len; i++) rootlet_element(ge, i) = sc->nil; + } +} + +static void remove_function_from_heap(s7_scheme *sc, s7_pointer value) +{ + s7_pointer lt; + remove_from_heap(sc, closure_args(value)); + remove_from_heap(sc, closure_body(value)); + /* remove closure if it's local to current func (meaning (define f (let ...) (lambda ...)) removes the enclosing let) */ + lt = closure_let(value); /* closure_let and all its outlets can't be rootlet */ + if ((is_let(lt)) && (!let_removed(lt)) && (lt != sc->shadow_rootlet)) + { + lt = let_outlet(lt); + if ((is_let(lt)) && (!let_removed(lt)) && (lt != sc->shadow_rootlet)) + { + remove_let_from_heap(sc, lt); + lt = let_outlet(lt); + if ((is_let(lt)) && (!let_removed(lt)) && (lt != sc->shadow_rootlet)) + remove_let_from_heap(sc, lt); + }} +} + +s7_pointer s7_make_slot(s7_scheme *sc, s7_pointer let, s7_pointer symbol, s7_pointer value) +{ + if ((!is_let(let)) || + (let == sc->rootlet)) + { + s7_pointer slot; + if (is_immutable(sc->rootlet)) + immutable_object_error_nr(sc, set_elist_2(sc, wrap_string(sc, "can't define '~S; rootlet is immutable", 38), symbol)); + if ((sc->safety <= NO_SAFETY) && + (has_closure_let(value))) + remove_function_from_heap(sc, value); /* optimization of access pointers happens later so presumably this is safe */ + + /* first look for existing slot -- this is not always checked before calling s7_make_slot */ + if (is_slot(global_slot(symbol))) + { + slot = global_slot(symbol); + symbol_increment_ctr(symbol); + slot_set_value_with_hook(slot, value); + return(slot); + } + + slot = make_semipermanent_slot(sc, symbol, value); + add_slot_to_rootlet(sc, slot); + set_global_slot(symbol, slot); + if (symbol_id(symbol) == 0) /* never defined locally? */ + { + if ((!is_gensym(symbol)) && + (initial_slot(symbol) == sc->undefined) && + (!in_heap(value)) && /* else initial_slot value can be GC'd if symbol set! (initial != global, initial unprotected) */ + ((!sc->unlet) || /* init_unlet creates sc->unlet (includes syntax), after that initial_slot is for c_functions?? */ + (is_c_function(value)))) /* || (is_syntax(value)) -- we need 'else as a special case? */ + set_initial_slot(symbol, make_semipermanent_slot(sc, symbol, value)); + set_local_slot(symbol, slot); + set_global(symbol); + } + symbol_increment_ctr(symbol); + if (is_gensym(symbol)) + remove_gensym_from_heap(sc, symbol); + return(slot); + } + return(add_slot_checked_with_id(sc, let, symbol, value)); + /* there are about as many lets as local variables -- this strikes me as surprising, but it holds up across a lot of code */ +} + +static s7_pointer make_slot(s7_scheme *sc, s7_pointer variable, s7_pointer value) +{ + s7_pointer y; + new_cell(sc, y, T_SLOT); + slot_set_symbol_and_value(y, variable, value); + return(y); +} + + +/* -------------------------------- let? -------------------------------- */ +bool s7_is_let(s7_pointer e) {return(is_let(e));} + +static s7_pointer g_is_let(s7_scheme *sc, s7_pointer args) +{ + #define H_is_let "(let? obj) returns #t if obj is a let." + #define Q_is_let sc->pl_bt + check_boolean_method(sc, is_let, sc->is_let_symbol, args); +} + + +/* -------------------------------- funclet? -------------------------------- */ +static s7_pointer g_is_funclet(s7_scheme *sc, s7_pointer args) +{ + #define H_is_funclet "(funclet? obj) returns #t if obj is a funclet (a function's environment)." + #define Q_is_funclet sc->pl_bt + + s7_pointer lt = car(args); + if ((is_let(lt)) && ((is_funclet(lt)) || (is_maclet(lt)))) + return(sc->T); + if (!has_active_methods(sc, lt)) + return(sc->F); + return(apply_boolean_method(sc, lt, sc->is_funclet_symbol)); +} + + +/* -------------------------------- unlet -------------------------------- */ +static s7_pointer normal_vector_setter(s7_scheme *sc, s7_pointer vec, s7_int loc, s7_pointer val); +static s7_pointer normal_vector_getter(s7_scheme *sc, s7_pointer vec, s7_int loc); + +#define UNLET_ENTRIES 512 /* 397 if not --disable-deprecated etc */ + +static void init_unlet(s7_scheme *sc) +{ + int32_t k = 0; + s7_pointer *inits; + s7_pointer *els = vector_elements(sc->symbol_table); + block_t *block = mallocate(sc, UNLET_ENTRIES * sizeof(s7_pointer)); + sc->unlet = (s7_pointer)Calloc(1, sizeof(s7_cell)); /* freed explicitly in s7_free */ + set_full_type(sc->unlet, T_VECTOR | T_UNHEAP); + vector_length(sc->unlet) = UNLET_ENTRIES; + vector_block(sc->unlet) = block; + vector_elements(sc->unlet) = (s7_pointer *)block_data(block); + vector_set_dimension_info(sc->unlet, NULL); + vector_getter(sc->unlet) = normal_vector_getter; + vector_setter(sc->unlet) = normal_vector_setter; + inits = vector_elements(sc->unlet); + s7_vector_fill(sc, sc->unlet, sc->nil); + + inits[k++] = initial_slot(sc->else_symbol); + for (int32_t i = 0; i < SYMBOL_TABLE_SIZE; i++) + for (s7_pointer x = els[i]; is_pair(x); x = cdr(x)) + { + s7_pointer sym = car(x); + if ((!is_gensym(sym)) && (is_slot(initial_slot(sym)))) + { + s7_pointer val = initial_value(sym); + if ((is_c_function(val)) || (is_syntax(val))) /* we assume the initial_slot value needs no GC protection */ + inits[k++] = initial_slot(sym); + /* non-c_functions that are not set! (and therefore initial_slot GC) protected by default: make-hook hook-functions + * if these initial_slot values are added to unlet, they need explicit GC protection. + */ + if ((S7_DEBUGGING) && (k >= UNLET_ENTRIES)) fprintf(stderr, "unlet overflow\n"); + }} +} + +static s7_pointer g_unlet(s7_scheme *sc, s7_pointer unused_args) +{ + /* add sc->unlet bindings to the current environment */ + #define H_unlet "(unlet) returns a let that establishes the original bindings of all the predefined functions" + #define Q_unlet s7_make_signature(sc, 1, sc->is_let_symbol) + + s7_pointer *inits = vector_elements(sc->unlet); + s7_pointer res; + + sc->w = make_let(sc, sc->curlet); + for (int32_t i = 0; (i < UNLET_ENTRIES) && (is_slot(inits[i])); i++) + { + s7_pointer sym = slot_symbol(inits[i]); + s7_pointer x = slot_value(inits[i]); + if ((x != global_value(sym)) || /* it has been changed globally */ + ((!is_global(sym)) && /* it might be shadowed locally */ + (s7_symbol_local_value(sc, sym, sc->curlet) != global_value(sym)))) + add_slot_checked_with_id(sc, sc->w, sym, x); + } + /* if (set! + -) then + needs to be overridden, but the local bit isn't set, so we have to check the actual values in the non-local case. + * (define (f x) (with-let (unlet) (+ x 1))) + */ + res = sc->w; + sc->w = sc->unused; + return(res); +} + + +/* -------------------------------- openlet? -------------------------------- */ +bool s7_is_openlet(s7_pointer e) {return(has_methods(e));} + +static s7_pointer g_is_openlet(s7_scheme *sc, s7_pointer args) +{ + #define H_is_openlet "(openlet? obj) returns #t is 'obj' has methods." + #define Q_is_openlet sc->pl_bt + + s7_pointer e = car(args); /* if e is not a let, should this raise an error? -- no, easier to use this way in cond */ + check_method(sc, e, sc->is_openlet_symbol, args); + return(make_boolean(sc, has_methods(e))); +} + + +/* -------------------------------- openlet -------------------------------- */ +s7_pointer s7_openlet(s7_scheme *sc, s7_pointer e) +{ + set_has_methods(e); + return(e); +} + +static s7_pointer g_openlet(s7_scheme *sc, s7_pointer args) +{ + #define H_openlet "(openlet e) tells the built-in generic functions that the let 'e might have an over-riding method." + #define Q_openlet sc->pcl_e + + s7_pointer e = car(args), elet, func; + if ((e == sc->rootlet) || (e == sc->nil)) + error_nr(sc, sc->out_of_range_symbol, set_elist_1(sc, wrap_string(sc, "can't openlet rootlet", 21))); + elet = find_let(sc, e); /* returns nil if no let found, so has to follow error check above */ + if (!is_let(elet)) + sole_arg_wrong_type_error_nr(sc, sc->openlet_symbol, e, a_let_string); + if ((has_active_methods(sc, e)) && + ((func = find_method(sc, elet, sc->openlet_symbol)) != sc->undefined)) + return(s7_apply_function(sc, func, args)); + set_has_methods(e); + return(e); +} + +/* -------------------------------- coverlet -------------------------------- */ +static s7_pointer g_coverlet(s7_scheme *sc, s7_pointer args) +{ + #define H_coverlet "(coverlet e) undoes an earlier openlet." + #define Q_coverlet sc->pcl_e + + s7_pointer e = car(args); + check_method(sc, e, sc->coverlet_symbol, set_plist_1(sc, e)); + if ((e == sc->rootlet) || (e == sc->s7_starlet)) + error_nr(sc, sc->out_of_range_symbol, set_elist_2(sc, wrap_string(sc, "can't coverlet ~S", 17), e)); + + if ((is_let(e)) || (has_closure_let(e)) || + ((is_c_object(e)) && (c_object_let(e) != sc->nil)) || + ((is_c_pointer(e)) && (is_let(c_pointer_info(e))))) + { + clear_has_methods(e); + return(e); + } + sole_arg_wrong_type_error_nr(sc, sc->coverlet_symbol, e, a_let_string); + return(NULL); +} + + +/* -------------------------------- varlet -------------------------------- */ +static void check_let_fallback(s7_scheme *sc, const s7_pointer symbol, s7_pointer let) +{ + if (symbol == sc->let_ref_fallback_symbol) + set_has_let_ref_fallback(let); + else + if (symbol == sc->let_set_fallback_symbol) + set_has_let_set_fallback(let); +} + +static void append_let(s7_scheme *sc, s7_pointer new_e, s7_pointer old_e) +{ + if ((old_e == sc->rootlet) || (new_e == sc->s7_starlet)) + return; + if (new_e == sc->rootlet) + for (s7_pointer x = let_slots(old_e); tis_slot(x); x = next_slot(x)) + { + s7_pointer sym = slot_symbol(x), val = slot_value(x); + if (is_slot(global_slot(sym))) + slot_set_value(global_slot(sym), val); + else s7_make_slot(sc, new_e, sym, val); + } + else + if (old_e == sc->s7_starlet) + { + s7_pointer iter = s7_make_iterator(sc, sc->s7_starlet); + s7_int gc_loc = s7_gc_protect(sc, iter); + iterator_current(iter) = cons_unchecked(sc, sc->F, sc->F); + set_mark_seq(iter); /* so carrier is GC protected by mark_iterator */ + while (true) + { + s7_pointer y = s7_iterate(sc, iter); + if (iterator_is_at_end(iter)) break; + add_slot_checked_with_id(sc, new_e, car(y), cdr(y)); + } + s7_gc_unprotect_at(sc, gc_loc); + } + else + for (s7_pointer x = let_slots(old_e); tis_slot(x); x = next_slot(x)) + add_slot_checked_with_id(sc, new_e, slot_symbol(x), slot_value(x)); /* not add_slot here because we might run off the free heap end */ +} + +static s7_pointer check_c_object_let(s7_scheme *sc, s7_pointer old_e, s7_pointer caller) +{ + if (is_c_object(old_e)) + old_e = c_object_let(old_e); + if (!is_let(old_e)) + sole_arg_wrong_type_error_nr(sc, caller, old_e, a_let_string); + return(old_e); +} + +s7_pointer s7_varlet(s7_scheme *sc, s7_pointer let, s7_pointer symbol, s7_pointer value) +{ + if (!is_let(let)) + wrong_type_error_nr(sc, sc->varlet_symbol, 1, let, a_let_string); + if (!is_symbol(symbol)) + wrong_type_error_nr(sc, sc->varlet_symbol, 2, symbol, a_symbol_string); + + if ((is_slot(global_slot(symbol))) && + (is_syntax(global_value(symbol)))) + wrong_type_error_nr(sc, sc->varlet_symbol, 2, symbol, wrap_string(sc, "a non-syntactic symbol", 22)); + + if (let == sc->rootlet) + { + if (is_slot(global_slot(symbol))) + slot_set_value(global_slot(symbol), value); + else s7_make_slot(sc, let, symbol, value); + } + else + { + add_slot_checked_with_id(sc, let, symbol, value); + check_let_fallback(sc, symbol, let); + } + return(value); +} + +static int32_t position_of(const s7_pointer p, s7_pointer args) +{ + int32_t i; + for (i = 1; p != args; i++, args = cdr(args)); + return(i); +} + +static s7_pointer g_varlet(s7_scheme *sc, s7_pointer args) /* varlet = with-let + define */ +{ + #define H_varlet "(varlet target-let ...) adds its arguments (a let, a cons: symbol . value, or two arguments, the symbol and its value) \ +to the let target-let, and returns target-let. (varlet (curlet) 'a 1) adds 'a to the current environment with the value 1." + #define Q_varlet s7_make_circular_signature(sc, 2, 4, sc->is_let_symbol, \ + s7_make_signature(sc, 2, sc->is_let_symbol, sc->is_null_symbol), \ + s7_make_signature(sc, 3, sc->is_pair_symbol, sc->is_symbol_symbol, sc->is_let_symbol), \ + sc->T) + + s7_pointer e = car(args); + if (is_null(e)) + e = sc->rootlet; + else + { + check_method(sc, e, sc->varlet_symbol, args); + if (!is_let(e)) + wrong_type_error_nr(sc, sc->varlet_symbol, 1, e, a_let_string); + if ((is_immutable_let(e)) || (e == sc->s7_starlet)) + error_nr(sc, sc->immutable_error_symbol, + set_elist_3(sc, wrap_string(sc, "can't (varlet ~{~S~^ ~}), ~S is immutable", 41), args, e)); + } + for (s7_pointer x = cdr(args); is_pair(x); x = cdr(x)) + { + s7_pointer sym, val, p = car(x); + switch (type(p)) + { + case T_SYMBOL: + sym = (is_keyword(p)) ? keyword_symbol(p) : p; + if (!is_pair(cdr(x))) + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "varlet: symbol ~S, but no value: ~S", 35), p, args)); + if (is_constant_symbol(sc, sym)) + wrong_type_error_nr(sc, sc->varlet_symbol, position_of(x, args), sym, a_non_constant_symbol_string); + x = cdr(x); + val = car(x); + break; + + case T_PAIR: + sym = car(p); + if (!is_symbol(sym)) + wrong_type_error_nr(sc, sc->varlet_symbol, position_of(x, args), p, a_symbol_string); + if (is_constant_symbol(sc, sym)) + wrong_type_error_nr(sc, sc->varlet_symbol, position_of(x, args), sym, a_non_constant_symbol_string); + val = cdr(p); + break; + + case T_LET: + append_let(sc, e, check_c_object_let(sc, p, sc->varlet_symbol)); + if (has_let_set_fallback(p)) set_has_let_set_fallback(e); + if (has_let_ref_fallback(p)) set_has_let_ref_fallback(e); + continue; + + default: + wrong_type_error_nr(sc, sc->varlet_symbol, position_of(x, args), p, a_symbol_string); + } + if (e == sc->rootlet) + { + if (is_slot(global_slot(sym))) + { + if (is_syntax(global_value(sym))) + wrong_type_error_nr(sc, sc->varlet_symbol, position_of(x, args), p, wrap_string(sc, "a non-syntactic symbol", 22)); + /* without this check we can end up turning our code into gibberish: + * (set! quote 1) -> ;can't set! quote + * (varlet (rootlet) '(quote . 1)), :quote -> 1 + * or worse set quote to a function of one arg that tries to quote something -- infinite loop + */ + slot_set_value_with_hook(global_slot(sym), val); + } + else s7_make_slot(sc, e, sym, val); + } + else + { + check_let_fallback(sc, sym, e); + add_slot_checked_with_id(sc, e, sym, val); + }} + /* this used to check for sym already defined, and set its value, but that greatly slows down + * the most common use (adding a slot), and makes it hard to shadow explicitly. Don't use + * varlet as a substitute for set!/let-set!. + */ + return(e); +} + + +/* -------------------------------- cutlet -------------------------------- */ +static s7_pointer g_cutlet(s7_scheme *sc, s7_pointer args) +{ + #define H_cutlet "(cutlet e symbol ...) removes symbols from the let e." + #define Q_cutlet s7_make_circular_signature(sc, 2, 3, sc->is_let_symbol, sc->is_let_symbol, sc->is_symbol_symbol) + + s7_pointer e = car(args); + s7_int the_un_id; + if (is_null(e)) + e = sc->rootlet; + else + { + check_method(sc, e, sc->cutlet_symbol, args); + if (!is_let(e)) + wrong_type_error_nr(sc, sc->cutlet_symbol, 1, e, a_let_string); + if ((is_immutable_let(e)) || (e == sc->s7_starlet)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->cutlet_symbol, e)); + } + /* besides removing the slot we have to make sure the symbol_id does not match else + * let-ref and others will use the old slot! What's the un-id? Perhaps the next one? + * (let ((b 1)) (let ((b 2)) (cutlet (curlet) 'b)) b) + */ + the_un_id = ++sc->let_number; + + for (s7_pointer syms = cdr(args); is_pair(syms); syms = cdr(syms)) + { + s7_pointer sym = car(syms); + + if (!is_symbol(sym)) + wrong_type_error_nr(sc, sc->cutlet_symbol, position_of(syms, args), sym, a_symbol_string); + if (is_keyword(sym)) + sym = keyword_symbol(sym); + + if (e == sc->rootlet) + { + if (!is_slot(global_slot(sym))) + error_nr(sc, sc->out_of_range_symbol, set_elist_2(sc, wrap_string(sc, "cutlet can't remove ~S", 22), sym)); + if (is_immutable(global_slot(sym))) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->cutlet_symbol, sym)); + symbol_set_id(sym, the_un_id); + slot_set_value(global_slot(sym), sc->undefined); + /* here we need to at least clear bits: syntactic binder clean-symbol(?) etc, maybe also locally */ + } + else + { + s7_pointer slot; + if ((has_let_fallback(e)) && + ((sym == sc->let_ref_fallback_symbol) || (sym == sc->let_set_fallback_symbol))) + error_nr(sc, sc->out_of_range_symbol, set_elist_2(sc, wrap_string(sc, "cutlet can't remove ~S", 22), sym)); + + slot = let_slots(e); + if (tis_slot(slot)) + { + if (slot_symbol(slot) == sym) + { + if (is_immutable_slot(slot)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->cutlet_symbol, sym)); + let_set_slots(e, next_slot(let_slots(e))); + symbol_set_id(sym, the_un_id); + } + else + { + s7_pointer last_slot = slot; + for (slot = next_slot(let_slots(e)); tis_slot(slot); last_slot = slot, slot = next_slot(slot)) + if (slot_symbol(slot) == sym) + { + if (is_immutable_slot(slot)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->cutlet_symbol, sym)); + symbol_set_id(sym, the_un_id); + slot_set_next(last_slot, next_slot(slot)); + break; + }}}}} + return(e); +} + + +/* -------------------------------- sublet -------------------------------- */ +static s7_pointer sublet_1(s7_scheme *sc, s7_pointer e, s7_pointer bindings, s7_pointer caller) +{ + s7_pointer new_e = make_let(sc, (e == sc->rootlet) ? sc->nil : e); + set_all_methods(new_e, e); + + if (!is_null(bindings)) + { + s7_pointer sp = NULL; + sc->temp3 = new_e; + for (s7_pointer x = bindings; is_pair(x); x = cdr(x)) + { + s7_pointer p = car(x), sym, val; + + switch (type(p)) + { + case T_SYMBOL: + sym = (is_keyword(p)) ? keyword_symbol(p) : p; + if (!is_pair(cdr(x))) + error_nr(sc, sc->syntax_error_symbol, + set_elist_4(sc, wrap_string(sc, "~A: entry ~S, but no value: ~S", 30), caller, p, bindings)); + x = cdr(x); + val = car(x); + break; + + case T_PAIR: + sym = car(p); + if (!is_symbol(sym)) + wrong_type_error_nr(sc, caller, 1 + position_of(x, bindings), p, a_symbol_string); + if (is_keyword(sym)) + sym = keyword_symbol(sym); + val = cdr(p); + break; + + case T_LET: + append_let(sc, new_e, check_c_object_let(sc, p, caller)); + if (tis_slot(let_slots(new_e))) /* make sure the end slot (sp) is correct */ + for (sp = let_slots(new_e); tis_slot(next_slot(sp)); sp = next_slot(sp)); + continue; + + default: + wrong_type_error_nr(sc, caller, 1 + position_of(x, bindings), p, a_symbol_string); + } + + if (is_constant_symbol(sc, sym)) + wrong_type_error_nr(sc, caller, 1 + position_of(x, bindings), sym, a_non_constant_symbol_string); + if ((is_slot(global_slot(sym))) && + (is_syntax_or_qq(global_value(sym)))) + wrong_type_error_nr(sc, caller, 2, sym, wrap_string(sc, "a non-syntactic symbol", 22)); + + /* here we know new_e is a let and is not rootlet */ + if (!sp) + sp = add_slot_checked_with_id(sc, new_e, sym, val); + else + { + if (sc->free_heap_top <= sc->free_heap_trigger) try_to_call_gc(sc); /* or maybe add add_slot_at_end_checked? */ + sp = inline_add_slot_at_end(sc, let_id(new_e), sp, sym, val); + set_local(sym); /* ? */ + } + check_let_fallback(sc, sym, new_e); + } + sc->temp3 = sc->unused; + } + return(new_e); +} + +s7_pointer s7_sublet(s7_scheme *sc, s7_pointer e, s7_pointer bindings) {return(sublet_1(sc, e, bindings, sc->sublet_symbol));} + +static s7_pointer g_sublet(s7_scheme *sc, s7_pointer args) +{ + #define H_sublet "(sublet let ...) makes a new let within the environment 'let', initializing it with the bindings" + #define Q_sublet Q_varlet + + s7_pointer e = car(args); + if (is_null(e)) + e = sc->rootlet; + else + { + check_method(sc, e, sc->sublet_symbol, args); + if (!is_let(e)) + wrong_type_error_nr(sc, sc->sublet_symbol, 1, e, a_let_string); + } + return(sublet_1(sc, e, cdr(args), sc->sublet_symbol)); +} + +static s7_pointer g_sublet_curlet(s7_scheme *sc, s7_pointer args) +{ + s7_pointer sym = cadr(args), new_e; + check_method(sc, sc->curlet, sc->sublet_symbol, args); + new_e = inline_make_let_with_slot(sc, sc->curlet, sym, caddr(args)); + set_all_methods(new_e, sc->curlet); + check_let_fallback(sc, sym, new_e); + return(new_e); +} + +static s7_pointer sublet_chooser(s7_scheme *sc, s7_pointer f, int32_t num_args, s7_pointer expr, bool ops) +{ + if (num_args == 3) + { + s7_pointer args = cdr(expr); + if ((is_pair(car(args))) && (caar(args) == sc->curlet_symbol) && (is_null(cdar(args))) && + (is_pair(cadr(args))) && (caadr(args) == sc->quote_symbol) && (is_symbol(cadadr(args)))) + return(sc->sublet_curlet); + } + return(f); +} + + +/* -------------------------------- inlet -------------------------------- */ +s7_pointer s7_inlet(s7_scheme *sc, s7_pointer args) +{ + #define H_inlet "(inlet ...) adds its arguments, each a let, a cons: '(symbol . value), or a keyword/value pair, \ +to a new let, and returns the new let. (inlet :a 1 :b 2) or (inlet '(a . 1) '(b . 2))" + #define Q_inlet s7_make_circular_signature(sc, 1, 2, sc->is_let_symbol, sc->T) + return(sublet_1(sc, sc->rootlet, args, sc->inlet_symbol)); +} + +#define g_inlet s7_inlet + +static s7_pointer g_simple_inlet(s7_scheme *sc, s7_pointer args) +{ + /* here all args are paired with normal symbol/value, no fallbacks, no immutable symbols, no syntax, etc */ + s7_pointer new_e = make_let(sc, sc->nil); + int64_t id = let_id(new_e); + s7_pointer sp = NULL; + + sc->temp3 = new_e; + for (s7_pointer x = args; is_pair(x); x = cddr(x)) + { + s7_pointer symbol = car(x); + if (is_keyword(symbol)) /* (inlet ':allow-other-keys 3) */ + symbol = keyword_symbol(symbol); + if (is_constant_symbol(sc, symbol)) /* (inlet 'pi 1) */ + wrong_type_error_nr(sc, sc->inlet_symbol, 1, symbol, a_non_constant_symbol_string); + if (!sp) + { + add_slot_unchecked(sc, new_e, symbol, cadr(x), id); + sp = let_slots(new_e); + } + else sp = inline_add_slot_at_end(sc, id, sp, symbol, cadr(x)); + } + sc->temp3 = sc->unused; + return(new_e); +} + +static s7_pointer inlet_p_pp(s7_scheme *sc, s7_pointer symbol, s7_pointer value) +{ + s7_pointer x; + + if (!is_symbol(symbol)) + return(sublet_1(sc, sc->nil, set_plist_2(sc, symbol, value), sc->inlet_symbol)); + if (is_keyword(symbol)) + symbol = keyword_symbol(symbol); + if (is_constant_symbol(sc, symbol)) + wrong_type_error_nr(sc, sc->inlet_symbol, 1, symbol, a_non_constant_symbol_string); + if ((is_global(symbol)) && + (is_syntax_or_qq(global_value(symbol)))) + wrong_type_error_nr(sc, sc->inlet_symbol, 1, symbol, wrap_string(sc, "a non-syntactic symbol", 22)); + + new_cell(sc, x, T_LET | T_SAFE_PROCEDURE); + sc->temp3 = x; + let_set_id(x, ++sc->let_number); + let_set_outlet(x, sc->nil); + let_set_slots(x, slot_end); + add_slot_unchecked(sc, x, symbol, value, let_id(x)); + sc->temp3 = sc->unused; + return(x); +} + +static s7_pointer internal_inlet(s7_scheme *sc, s7_int num_args, ...) +{ + va_list ap; + s7_pointer new_e = make_let(sc, sc->nil); + int64_t id = let_id(new_e); + s7_pointer sp = NULL; + + sc->temp3 = new_e; + va_start(ap, num_args); + for (s7_int i = 0; i < num_args; i += 2) + { + s7_pointer symbol = va_arg(ap, s7_pointer); + s7_pointer value = va_arg(ap, s7_pointer); + if ((S7_DEBUGGING) && (is_keyword(symbol))) fprintf(stderr, "internal_inlet key: %s??\n", display(symbol)); + if (!sp) + { + add_slot_unchecked(sc, new_e, symbol, value, id); + sp = let_slots(new_e); + } + else sp = inline_add_slot_at_end(sc, id, sp, symbol, value); + } + va_end(ap); + sc->temp3 = sc->unused; + return(new_e); +} + +static bool is_proper_quote(s7_scheme *sc, s7_pointer p) +{ + return((is_quoted_pair(p)) && + (is_pair(cdr(p))) && + (is_null(cddr(p))) && + (is_global(sc->quote_symbol))); +} + +static s7_pointer inlet_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool ops) +{ + if (!ops) return(f); + if ((args > 0) && + ((args % 2) == 0)) + { + for (s7_pointer p = cdr(expr); is_pair(p); p = cddr(p)) + { + s7_pointer sym; + if (is_symbol_and_keyword(car(p))) /* (inlet :if ...) */ + sym = keyword_symbol(car(p)); + else + { + if (!is_proper_quote(sc, car(p))) return(f); /* (inlet abs ...) */ + sym = cadar(p); /* looking for (inlet 'a ...) */ + if (!is_symbol(sym)) return(f); /* (inlet '(a . 3) ...) */ + if (is_keyword(sym)) sym = keyword_symbol(sym); /* (inlet ':abs ...) */ + } + if ((is_possibly_constant(sym)) || /* (inlet 'define-constant ...) or (inlet 'pi ...) */ + (is_syntactic_symbol(sym)) || /* (inlet 'if 3) */ + ((is_slot(global_slot(sym))) && + (is_syntax_or_qq(global_value(sym)))) || /* (inlet 'quasiquote 1) */ + (sym == sc->let_ref_fallback_symbol) || + (sym == sc->let_set_fallback_symbol)) + return(f); + } + return(sc->simple_inlet); + } + return(f); +} + + +/* -------------------------------- let->list -------------------------------- */ +static s7_pointer proper_list_reverse_in_place(s7_scheme *sc, s7_pointer list); + +s7_pointer s7_let_to_list(s7_scheme *sc, s7_pointer let) +{ + s7_pointer x; + sc->temp3 = sc->w; + sc->w = sc->nil; + if (let == sc->rootlet) + { + s7_int i, lim2 = sc->rootlet_entries; + s7_pointer *entries = rootlet_elements(let); + + if (lim2 & 1) lim2--; + for (i = 0; i < lim2; ) + { + sc->w = cons_unchecked(sc, cons(sc, slot_symbol(entries[i]), slot_value(entries[i])), sc->w); i++; + sc->w = cons_unchecked(sc, cons_unchecked(sc, slot_symbol(entries[i]), slot_value(entries[i])), sc->w); i++; + } + if (lim2 < sc->rootlet_entries) + sc->w = cons_unchecked(sc, cons(sc, slot_symbol(entries[i]), slot_value(entries[i])), sc->w); + } + else + { + s7_pointer iter, func; + s7_int gc_loc = -1; + /* need to check make-iterator method before dropping into let->list */ + + if ((has_active_methods(sc, let)) && + ((func = find_method(sc, let, sc->make_iterator_symbol)) != sc->undefined)) + iter = s7_apply_function(sc, func, set_plist_1(sc, let)); + else + if (let == sc->s7_starlet) /* (let->list *s7*) via s7_starlet_make_iterator */ + { + iter = s7_make_iterator(sc, let); + gc_loc = s7_gc_protect(sc, iter); + } + else iter = sc->nil; + + if (is_null(iter)) + for (x = let_slots(let); tis_slot(x); x = next_slot(x)) + sc->w = cons_unchecked(sc, cons(sc, slot_symbol(x), slot_value(x)), sc->w); + else + /* (begin (load "mockery.scm") (let ((lt ((*mock-pair* 'mock-pair) 1 2 3))) (format *stderr* "~{~A ~}" lt))) */ + while (true) + { + x = s7_iterate(sc, iter); + if (iterator_is_at_end(iter)) break; + sc->w = cons(sc, x, sc->w); + } + sc->w = proper_list_reverse_in_place(sc, sc->w); + if (gc_loc != -1) + s7_gc_unprotect_at(sc, gc_loc); + } + x = sc->w; + sc->w = sc->temp3; + sc->temp3 = sc->unused; + return(x); +} + +#if (!WITH_PURE_S7) +static s7_pointer g_let_to_list(s7_scheme *sc, s7_pointer args) +{ + #define H_let_to_list "(let->list let) returns let's bindings as a list of cons's: '(symbol . value)." + #define Q_let_to_list s7_make_signature(sc, 2, sc->is_pair_symbol, sc->is_let_symbol) + + s7_pointer let = car(args); + check_method(sc, let, sc->let_to_list_symbol, args); + if (!is_let(let)) + { + if (is_c_object(let)) + let = c_object_let(let); + else + if (is_c_pointer(let)) + let = c_pointer_info(let); + if (!is_let(let)) + sole_arg_wrong_type_error_nr(sc, sc->let_to_list_symbol, let, a_let_string); + } + return(s7_let_to_list(sc, let)); +} +#endif + + +/* -------------------------------- let-ref -------------------------------- */ +static s7_pointer call_let_ref_fallback(s7_scheme *sc, s7_pointer let, s7_pointer symbol) +{ + s7_pointer p; + push_stack_no_let(sc, OP_GC_PROTECT, sc->value, sc->code); + /* (let ((x #f)) (let begin ((x 1234)) (begin 1) 2)) -> stack overflow eventually, but should we try to catch it? */ + p = s7_apply_function(sc, find_method(sc, let, sc->let_ref_fallback_symbol), set_qlist_2(sc, let, symbol)); + unstack(sc); + sc->code = T_Pos(stack_end_code(sc)); /* can be # */ + sc->value = T_Ext(stack_end_args(sc)); + return(p); +} + +static s7_pointer call_let_set_fallback(s7_scheme *sc, s7_pointer let, s7_pointer symbol, s7_pointer value) +{ + s7_pointer p; + push_stack_no_let(sc, OP_GC_PROTECT, sc->value, sc->code); + p = s7_apply_function(sc, find_method(sc, let, sc->let_set_fallback_symbol), set_qlist_3(sc, let, symbol, value)); + unstack(sc); + sc->code = T_Pos(stack_end_code(sc)); + sc->value = T_Ext(stack_end_args(sc)); + return(p); +} + +static /* inline */ s7_pointer let_ref(s7_scheme *sc, s7_pointer let, s7_pointer symbol) +{ + /* (let ((a 1)) ((curlet) 'a)) or ((rootlet) 'abs) */ + if (!is_let(let)) + { + let = find_let(sc, let); + if (!is_let(let)) + wrong_type_error_nr(sc, sc->let_ref_symbol, 1, let, a_let_string); + } + if (!is_symbol(symbol)) + { + if (has_let_ref_fallback(let)) /* let-ref|set-fallback refer to (explicit) let-ref in various forms, not the method lookup process */ + return(call_let_ref_fallback(sc, let, symbol)); + wrong_type_error_nr(sc, sc->let_ref_symbol, 2, symbol, a_symbol_string); + } +#if 0 + /* let-ref is currently immutable */ + if (!is_global(sc->let_ref_symbol)) + check_method(sc, let, sc->let_ref_symbol, set_plist_2(sc, let, symbol)); + /* a let-ref method is almost impossible to write without creating an infinite loop: + * any reference to the let will probably call let-ref somewhere, calling us again, and looping. + * This is not a problem in c-objects and funclets because c-object-ref and funclet-ref don't exist. + * After much wasted debugging, I decided to make let-ref and let-set! immutable. + */ +#endif + if (is_keyword(symbol)) + symbol = keyword_symbol(symbol); + + if (let == sc->rootlet) + return((is_slot(global_slot(symbol))) ? global_value(symbol) : sc->undefined); + + if (let_id(let) == symbol_id(symbol)) + return(local_value(symbol)); /* this has to follow the rootlet check(?) */ + + for (s7_pointer x = let; is_let(x); x = let_outlet(x)) + for (s7_pointer y = let_slots(x); tis_slot(y); y = next_slot(y)) + if (slot_symbol(y) == symbol) + return(slot_value(y)); + + if (is_openlet(let)) + { + /* If a let is a mock-hash-table (for example), implicit indexing of the hash-table collides with the same thing for the let (field names + * versus keys), and we can't just try again here because that makes it too easy to get into infinite recursion. So, 'let-ref-fallback... + */ + if (has_let_ref_fallback(let)) + return(call_let_ref_fallback(sc, let, symbol)); + } + return((is_slot(global_slot(symbol))) ? global_value(symbol) : sc->undefined); /* (let () ((curlet) 'pi)) */ +} + +s7_pointer s7_let_ref(s7_scheme *sc, s7_pointer let, s7_pointer symbol) {return(let_ref(sc, let, symbol));} + +static s7_pointer g_let_ref(s7_scheme *sc, s7_pointer args) +{ + #define H_let_ref "(let-ref let sym) returns the value of the symbol sym in the let" + #define Q_let_ref s7_make_signature(sc, 3, sc->T, sc->is_let_symbol, sc->is_symbol_symbol) + return(let_ref(sc, car(args), cadr(args))); +} + +static s7_pointer slot_in_let(s7_scheme *sc, s7_pointer e, const s7_pointer sym) +{ + for (s7_pointer y = let_slots(e); tis_slot(y); y = next_slot(y)) + if (slot_symbol(y) == sym) + return(y); + return(sc->undefined); +} + +static s7_pointer lint_let_ref_p_pp(s7_scheme *sc, s7_pointer lt, s7_pointer sym) +{ + for (s7_pointer x = lt; is_let(x); x = let_outlet(x)) + for (s7_pointer y = let_slots(x); tis_slot(y); y = next_slot(y)) + if (slot_symbol(y) == sym) + return(slot_value(y)); + + if (has_let_ref_fallback(lt)) + return(call_let_ref_fallback(sc, lt, sym)); + + return((is_slot(global_slot(sym))) ? global_value(sym) : sc->undefined); +} + +static inline s7_pointer g_lint_let_ref(s7_scheme *sc, s7_pointer args) +{ + s7_pointer lt = car(args), sym = cadr(args); + if (!is_let(lt)) + wrong_type_error_nr(sc, sc->let_ref_symbol, 1, lt, a_let_string); + for (s7_pointer y = let_slots(lt); tis_slot(y); y = next_slot(y)) + if (slot_symbol(y) == sym) + return(slot_value(y)); + return(lint_let_ref_p_pp(sc, let_outlet(lt), sym)); +} + +static s7_pointer let_ref_chooser(s7_scheme *sc, s7_pointer f, int32_t unused_args, s7_pointer expr, bool ops) +{ + if (!ops) /* || (!is_global(sc->let_ref_symbol))) */ return(f); + if (optimize_op(expr) == HOP_SAFE_C_opSq_C) + { + s7_pointer arg1 = cadr(expr), arg2 = caddr(expr); + if ((car(arg1) == sc->cdr_symbol) && + (is_quoted_symbol(arg2)) && + (!is_possibly_constant(cadr(arg2)))) + { + set_opt3_sym(cdr(expr), cadr(arg2)); + return(sc->lint_let_ref); + }} + return(f); +} + +static bool op_implicit_let_ref_c(s7_scheme *sc) +{ + s7_pointer s = lookup_checked(sc, car(sc->code)); + if (!is_let(s)) {sc->last_function = s; return(false);} + sc->value = let_ref(sc, T_Ext(s), opt3_con(sc->code)); + return(true); +} + +static bool op_implicit_let_ref_a(s7_scheme *sc) +{ + s7_pointer s = lookup_checked(sc, car(sc->code)); + if (!is_let(s)) {sc->last_function = s; return(false);} + sc->value = let_ref(sc, s, fx_call(sc, cdr(sc->code))); + return(true); +} + + +/* -------------------------------- let-set! -------------------------------- */ +static s7_pointer let_set_1(s7_scheme *sc, s7_pointer let, s7_pointer symbol, s7_pointer value) +{ + if (is_keyword(symbol)) + symbol = keyword_symbol(symbol); + symbol_increment_ctr(symbol); + + if (let == sc->rootlet) + { + s7_pointer slot; + if (is_constant_symbol(sc, symbol)) /* (let-set! (rootlet) 'pi #f) */ + wrong_type_error_nr(sc, sc->let_set_symbol, 2, symbol, a_non_constant_symbol_string); + /* it would be nice if safety>0 to add an error check for bad arity if a built-in method is set (set! (lt 'write) hash-table-set!), + * built_in being is_slot(initial_slot(sym)), but this function is called a ton, and this error can't easily be + * checked by the optimizer (we see the names, but not the values, so bad arity check requires assumptions about those values). + */ + slot = global_slot(symbol); + if (!is_slot(slot)) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_3(sc, wrap_string(sc, "let-set!: ~A is not defined in ~A", 33), symbol, let)); + if (is_syntax(slot_value(slot))) + wrong_type_error_nr(sc, sc->let_set_symbol, 2, symbol, wrap_string(sc, "a non-syntactic symbol", 22)); + slot_set_value(slot, (slot_has_setter(slot)) ? call_setter(sc, slot, value) : value); + return(slot_value(slot)); + } + + if (let_id(let) == symbol_id(symbol)) + { + s7_pointer slot = local_slot(symbol); + if (is_slot(slot)) + return(checked_slot_set_value(sc, slot, value)); + } + for (s7_pointer x = let; is_let(x); x = let_outlet(x)) + for (s7_pointer y = let_slots(x); tis_slot(y); y = next_slot(y)) + if (slot_symbol(y) == symbol) + return(checked_slot_set_value(sc, y, value)); + + if (!has_let_set_fallback(let)) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_3(sc, wrap_string(sc, "let-set!: ~A is not defined in ~A", 33), symbol, let)); + /* not sure about this -- what's the most useful choice? */ + return(call_let_set_fallback(sc, let, symbol, value)); +} + +static s7_pointer let_set(s7_scheme *sc, s7_pointer let, s7_pointer symbol, s7_pointer value) +{ + if (!is_let(let)) + wrong_type_error_nr(sc, sc->let_set_symbol, 1, let, a_let_string); + if (!is_symbol(symbol)) + { + if (has_let_set_fallback(let)) + return(call_let_set_fallback(sc, let, symbol, value)); + wrong_type_error_nr(sc, sc->let_set_symbol, 2, symbol, a_symbol_string); + } +#if 0 + /* currently let-set! is immutable */ + if (!is_global(sc->let_set_symbol)) + check_method(sc, let, sc->let_set_symbol, set_plist_3(sc, let, symbol, value)); +#endif + return(let_set_1(sc, let, symbol, value)); +} + +s7_pointer s7_let_set(s7_scheme *sc, s7_pointer let, s7_pointer symbol, s7_pointer value) {return(let_set(sc, let, symbol, value));} + +static s7_pointer g_let_set(s7_scheme *sc, s7_pointer args) +{ + /* (let ((a 1)) (set! ((curlet) 'a) 32) a) */ + #define H_let_set "(let-set! let sym val) sets the symbol sym's value in the let to val" + #define Q_let_set s7_make_signature(sc, 4, sc->T, sc->is_let_symbol, sc->is_symbol_symbol, sc->T) + + if (!is_pair(cdr(args))) /* (let ((a 123.0)) (define (f) (set! (let-ref) a)) (catch #t f (lambda args #f)) (f)) */ + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_3(sc, wrap_string(sc, "~S: not enough arguments: ~S", 28), sc->let_set_symbol, sc->code)); + + return(let_set(sc, car(args), cadr(args), caddr(args))); +} + +static s7_pointer let_set_p_ppp_2(s7_scheme *sc, s7_pointer p1, s7_pointer p2, s7_pointer p3) +{ + if (!is_symbol(p2)) + wrong_type_error_nr(sc, sc->let_set_symbol, 2, p2, a_symbol_string); + return(let_set_1(sc, p1, p2, p3)); +} + +static s7_pointer g_lint_let_set(s7_scheme *sc, s7_pointer args) +{ + s7_pointer y, lt = car(args), sym = cadr(args), val = caddr(args); + + if (!is_let(lt)) + wrong_type_error_nr(sc, sc->let_set_symbol, 1, lt, a_let_string); + if (lt != sc->rootlet) + { + for (s7_pointer x = lt; is_let(x); x = let_outlet(x)) + for (y = let_slots(x); tis_slot(y); y = next_slot(y)) + if (slot_symbol(y) == sym) + { + slot_set_value(y, (slot_has_setter(y)) ? call_setter(sc, y, val) : val); + return(slot_value(y)); + } + if (has_let_set_fallback(lt)) + return(call_let_set_fallback(sc, lt, sym, val)); + } + y = global_slot(sym); + if (!is_slot(y)) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_3(sc, wrap_string(sc, "let-set!: ~A is not defined in ~A", 33), sym, lt)); + slot_set_value(y, (slot_has_setter(y)) ? call_setter(sc, y, val) : val); + return(slot_value(y)); +} + +static s7_pointer let_set_chooser(s7_scheme *sc, s7_pointer f, int32_t unused_args, s7_pointer expr, bool ops) +{ + if (!ops) /* || (!is_global(sc->let_set_symbol))) */ return(f); + if (optimize_op(expr) == HOP_SAFE_C_opSq_CS) + { + s7_pointer arg1 = cadr(expr), arg2 = caddr(expr), arg3 = cadddr(expr); + if ((car(arg1) == sc->cdr_symbol) && + (car(arg2) == sc->quote_symbol) && + (is_symbol(cadr(arg2))) && + (!is_possibly_constant(cadr(arg2))) && + (!is_possibly_constant(arg3))) + return(sc->lint_let_set); + } + return(f); +} + + +static s7_pointer reverse_slots(s7_pointer list) +{ + s7_pointer p = list, result = slot_end; + while (tis_slot(p)) + { + s7_pointer q = next_slot(p); + slot_set_next(p, result); + result = p; + p = q; + } + return(result); +} + +static s7_pointer let_copy(s7_scheme *sc, s7_pointer let) +{ + s7_pointer new_e; + + if (T_Let(let) == sc->rootlet) /* (copy (rootlet)) or (copy (funclet abs)) etc */ + return(sc->rootlet); + /* we can't make copy handle lets-as-objects specially because the make-object function in define-class uses copy to make a new object! + * So if it is present, we get it here, and then there's almost surely trouble. + */ + new_e = make_let(sc, let_outlet(let)); + set_all_methods(new_e, let); + sc->temp3 = new_e; + if (tis_slot(let_slots(let))) + { + s7_int id = let_id(new_e); + s7_pointer y = NULL; + for (s7_pointer x = let_slots(let); tis_slot(x); x = next_slot(x)) + { + s7_pointer z; + new_cell(sc, z, T_SLOT); + slot_set_symbol_and_value(z, slot_symbol(x), slot_value(x)); + if (symbol_id(slot_symbol(z)) != id) /* keep shadowing intact */ + symbol_set_local_slot(slot_symbol(x), id, z); + if (slot_has_setter(x)) + { + slot_set_setter(z, slot_setter(x)); + slot_set_has_setter(z); + } + if (y) + slot_set_next(y, z); + else let_set_slots(new_e, z); + slot_set_next(z, slot_end); /* in case GC runs during this loop */ + y = z; + }} + /* We can't do a (normal) loop here then reverse the slots later because the symbol's local_slot has to + * match the unshadowed slot, not the last in the list: + * (let ((e1 (inlet 'a 1 'a 2))) (let ((e2 (copy e1))) (list (equal? e1 e2) (equal? (e1 'a) (e2 'a))))) + */ + sc->temp3 = sc->unused; + return(new_e); +} + + +/* -------------------------------- rootlet -------------------------------- */ +static s7_pointer g_rootlet(s7_scheme *sc, s7_pointer unused) +{ + #define H_rootlet "(rootlet) returns the current top-level definitions (symbol bindings)." + #define Q_rootlet s7_make_signature(sc, 1, sc->is_let_symbol) + return(sc->rootlet); +} +/* as with the symbol-table, this function can lead to disaster -- user could + * clobber the let etc. But we want it to be editable and augmentable, + * so I guess I'll leave it alone. (See curlet|funclet as well). + */ + +s7_pointer s7_rootlet(s7_scheme *sc) {return(sc->rootlet);} + +/* shadow_rootlet is a convenience for foreign function writers -- the C code can act as if it were loading everything into rootlet, + * but when actually loaded, everything can be shunted into a separate namespace (*motif* for example). + */ +s7_pointer s7_shadow_rootlet(s7_scheme *sc) {return(sc->shadow_rootlet);} + +s7_pointer s7_set_shadow_rootlet(s7_scheme *sc, s7_pointer let) +{ + s7_pointer old_let = sc->shadow_rootlet; + sc->shadow_rootlet = let; + return(old_let); /* like s7_set_curlet below */ +} + + +/* -------------------------------- curlet -------------------------------- */ +static s7_pointer g_curlet(s7_scheme *sc, s7_pointer unused_args) +{ + #define H_curlet "(curlet) returns the current definitions (symbol bindings)" + #define Q_curlet s7_make_signature(sc, 1, sc->is_let_symbol) + + sc->capture_let_counter++; + return((is_let(sc->curlet)) ? sc->curlet : sc->rootlet); +} + +s7_pointer s7_curlet(s7_scheme *sc) +{ + sc->capture_let_counter++; + return(sc->curlet); +} + +static void update_symbol_ids(s7_scheme *sc, s7_pointer e) +{ + for (s7_pointer p = let_slots(e); tis_slot(p); p = next_slot(p)) + { + s7_pointer sym = slot_symbol(p); + if (symbol_id(sym) != sc->let_number) + symbol_set_local_slot_unincremented(sym, sc->let_number, p); + } +} + +s7_pointer s7_set_curlet(s7_scheme *sc, s7_pointer e) +{ + s7_pointer old_e = sc->curlet; + set_curlet(sc, e); + if ((is_let(e)) && (let_id(e) > 0)) /* might be () [id=-1] or rootlet [id=0?] etc */ + { + let_set_id(e, ++sc->let_number); + update_symbol_ids(sc, e); + } + return(old_e); +} + + +/* -------------------------------- outlet -------------------------------- */ +s7_pointer s7_outlet(s7_scheme *sc, s7_pointer let) +{ + if ((let == sc->rootlet) || (is_null(let_outlet(let)))) + return(sc->rootlet); + return(let_outlet(let)); +} + +static s7_pointer outlet_p_p(s7_scheme *sc, s7_pointer let) +{ + if (!is_let(let)) + sole_arg_wrong_type_error_nr(sc, sc->outlet_symbol, let, a_let_string); /* not a method call here! */ + if ((let == sc->rootlet) || (is_null(let_outlet(let)))) + return(sc->rootlet); + return(let_outlet(let)); +} + +static s7_pointer g_outlet(s7_scheme *sc, s7_pointer args) +{ + #define H_outlet "(outlet let) is the environment that contains let." + #define Q_outlet s7_make_signature(sc, 2, sc->is_let_symbol, sc->is_let_symbol) + return(outlet_p_p(sc, car(args))); +} + + +static s7_pointer g_set_outlet(s7_scheme *sc, s7_pointer args) +{ + /* (let ((a 1)) (let ((b 2)) (set! (outlet (curlet)) (rootlet)) ((curlet) 'a))) */ + s7_pointer let = car(args), new_outer; + + if (!is_let(let)) + wrong_type_error_nr(sc, wrap_string(sc, "set! outlet", 11), 1, let, sc->type_names[T_LET]); + if (let == sc->s7_starlet) + error_nr(sc, sc->out_of_range_symbol, set_elist_1(sc, wrap_string(sc, "can't set! (outlet *s7*)", 24))); + if (is_immutable_let(let)) + immutable_object_error_nr(sc, set_elist_4(sc, wrap_string(sc, "can't (set! (outlet ~S) ~S), ~S is immutable", 44), let, cadr(args), let)); + new_outer = cadr(args); + if (!is_let(new_outer)) + wrong_type_error_nr(sc, wrap_string(sc, "set! outlet", 11), 2, new_outer, sc->type_names[T_LET]); + if (let != sc->rootlet) + { + /* here it's possible to get cyclic let chains; maybe do this check only if safety>0 */ + for (s7_pointer lt = new_outer; (is_let(lt)) && (lt != sc->rootlet); lt = let_outlet(lt)) + if (let == lt) + error_nr(sc, make_symbol(sc, "cyclic-let", 10), + set_elist_2(sc, wrap_string(sc, "set! (outlet ~A) creates a cyclic let chain", 43), let)); + let_set_outlet(let, (new_outer == sc->rootlet) ? sc->nil : new_outer); /* outlet rootlet->() so that slot search can use is_let(outlet) I think */ + } + return(new_outer); +} + +/* -------------------------------- symbol lookup -------------------------------- */ +static Inline s7_pointer inline_lookup_from(s7_scheme *sc, const s7_pointer symbol, s7_pointer e) +{ /* splitting out the no-sc WITH_GCC case made no difference in speed */ + if (let_id(e) == symbol_id(symbol)) + return(local_value(symbol)); + if (symbol_id(symbol) < let_id(e)) + { + do {e = let_outlet(e);} while (symbol_id(symbol) < let_id(e)); + if (let_id(e) == symbol_id(symbol)) + return(local_value(symbol)); + } + for (; is_let(e); e = let_outlet(e)) + for (s7_pointer y = let_slots(e); tis_slot(y); y = next_slot(y)) + if (slot_symbol(y) == symbol) + return(slot_value(y)); + + if (is_slot(global_slot(symbol))) + return(global_value(symbol)); +#if WITH_GCC + return(NULL); /* much faster than various alternatives */ +#else + return(unbound_variable(sc, symbol)); /* only use of sc */ +#endif +} + +#if WITH_GCC && S7_DEBUGGING +static s7_pointer lookup_1(s7_scheme *sc, const s7_pointer symbol) +#else +static inline s7_pointer lookup(s7_scheme *sc, const s7_pointer symbol) /* lookup_checked includes the unbound_variable call */ +#endif +{ + return(inline_lookup_from(sc, symbol, sc->curlet)); +} + +static inline s7_pointer lookup_slot_from(s7_pointer symbol, s7_pointer e) +{ + if (let_id(e) == symbol_id(symbol)) + return(local_slot(symbol)); + if (symbol_id(symbol) < let_id(e)) + { + do {e = let_outlet(e);} while (symbol_id(symbol) < let_id(e)); + if (let_id(e) == symbol_id(symbol)) + return(local_slot(symbol)); + } + for (; is_let(e); e = let_outlet(e)) + for (s7_pointer y = let_slots(e); tis_slot(y); y = next_slot(y)) + if (slot_symbol(y) == symbol) + return(y); + return(global_slot(symbol)); +} + +s7_pointer s7_slot(s7_scheme *sc, s7_pointer symbol) {return(lookup_slot_from(symbol, sc->curlet));} +static s7_pointer lookup_slot_with_let(s7_pointer symbol, s7_pointer let) {return(lookup_slot_from(symbol, let));} + +s7_pointer s7_slot_value(s7_pointer slot) {return(slot_value(slot));} + +s7_pointer s7_slot_set_value(s7_scheme *sc, s7_pointer slot, s7_pointer value) {slot_set_value(slot, value); return(value);} + +void s7_slot_set_real_value(s7_scheme *sc, s7_pointer slot, s7_double value) {set_real(slot_value(slot), value);} + +static s7_pointer symbol_to_local_slot(s7_scheme *sc, s7_pointer symbol, s7_pointer e) +{ + if (!is_let(e)) + return(global_slot(symbol)); + if (symbol_id(symbol) != 0) + for (s7_pointer y = let_slots(e); tis_slot(y); y = next_slot(y)) + if (slot_symbol(y) == symbol) + return(y); + return(sc->undefined); +} + +s7_pointer s7_symbol_value(s7_scheme *sc, s7_pointer sym) +{ + s7_pointer x = s7_slot(sc, sym); + return((is_slot(x)) ? slot_value(x) : sc->undefined); +} + +s7_pointer s7_symbol_local_value(s7_scheme *sc, s7_pointer sym, s7_pointer let) +{ + /* restrict the search to local let outward */ + if ((let == sc->rootlet) || (is_global(sym))) + return((is_slot(global_slot(sym))) ? global_value(sym) : sc->undefined); + + if (!is_let(let)) + return(s7_symbol_value(sc, sym)); + + if (let_id(let) == symbol_id(sym)) + return(local_value(sym)); + if (symbol_id(sym) < let_id(let)) + { + do {let = let_outlet(let);} while (symbol_id(sym) < let_id(let)); + if (let_id(let) == symbol_id(sym)) + return(local_value(sym)); + } + for (; is_let(let); let = let_outlet(let)) + for (s7_pointer y = let_slots(let); tis_slot(y); y = next_slot(y)) + if (slot_symbol(y) == sym) + return(slot_value(y)); + + /* need to check rootlet before giving up */ + if (is_slot(global_slot(sym))) + return(global_value(sym)); + + /* (let ((e (curlet))) (let ((a 1)) (symbol->value 'a e))) -> # not 1 */ + return(sc->undefined); /* 29-Nov-17 */ +} + + +/* -------------------------------- symbol->value -------------------------------- */ +#define lookup_global(Sc, Sym) ((is_global(Sym)) ? global_value(Sym) : lookup_checked(Sc, Sym)) + +static s7_pointer s7_starlet(s7_scheme *sc, s7_int choice); + +static s7_pointer g_symbol_to_value(s7_scheme *sc, s7_pointer args) +{ + #define H_symbol_to_value "(symbol->value sym (let (curlet))) returns the binding of (the value associated with) the \ +symbol sym in the given let: (let ((x 32)) (symbol->value 'x)) -> 32" + #define Q_symbol_to_value s7_make_signature(sc, 3, sc->T, sc->is_symbol_symbol, sc->is_let_symbol) + /* (symbol->value 'x e) => (e 'x)? */ + + s7_pointer sym = car(args); + if (!is_symbol(sym)) + return(method_or_bust(sc, sym, sc->symbol_to_value_symbol, args, sc->type_names[T_SYMBOL], 1)); + + if (is_not_null(cdr(args))) + { + s7_pointer local_let = cadr(args); + if (local_let == sc->unlet_symbol) + return((is_slot(initial_slot(sym))) ? initial_value(sym) : sc->undefined); + + if (!is_let(local_let)) + { + local_let = find_let(sc, local_let); + if (!is_let(local_let)) + return(method_or_bust(sc, cadr(args), sc->symbol_to_value_symbol, args, a_let_string, 2)); /* not local_let */ + } + if (local_let == sc->s7_starlet) + return(s7_starlet(sc, s7_starlet_symbol(sym))); + + return(s7_symbol_local_value(sc, sym, local_let)); + } + if (is_global(sym)) + return(global_value(sym)); + return(s7_symbol_value(sc, sym)); +} + +s7_pointer s7_symbol_set_value(s7_scheme *sc, s7_pointer sym, s7_pointer val) +{ + s7_pointer x = s7_slot(sc, sym); /* if immutable should this return an error? */ + if (is_slot(x)) + slot_set_value(x, val); /* with_hook? */ + return(val); +} + + +/* -------------------------------- symbol->dynamic-value -------------------------------- */ +static s7_pointer find_dynamic_value(s7_scheme *sc, s7_pointer x, s7_pointer sym, int64_t *id) +{ + for (; symbol_id(sym) < let_id(x); x = let_outlet(x)); + if (let_id(x) == symbol_id(sym)) + { + (*id) = let_id(x); + return(local_value(sym)); + } + for (; (is_let(x)) && (let_id(x) > (*id)); x = let_outlet(x)) + for (s7_pointer y = let_slots(x); tis_slot(y); y = next_slot(y)) + if (slot_symbol(y) == sym) + { + (*id) = let_id(x); + return(slot_value(y)); + } + return(sc->unused); +} + +static s7_pointer g_symbol_to_dynamic_value(s7_scheme *sc, s7_pointer args) +{ + #define H_symbol_to_dynamic_value "(symbol->dynamic-value sym) returns the dynamic binding of the symbol sym" + #define Q_symbol_to_dynamic_value s7_make_signature(sc, 2, sc->T, sc->is_symbol_symbol) + + s7_pointer sym = car(args), val; + int64_t top_id = -1; + + if (!is_symbol(sym)) + return(method_or_bust(sc, sym, sc->symbol_to_dynamic_value_symbol, args, sc->type_names[T_SYMBOL], 1)); + + if (is_global(sym)) + return(global_value(sym)); + + if (let_id(sc->curlet) == symbol_id(sym)) + return(local_value(sym)); + + val = find_dynamic_value(sc, sc->curlet, sym, &top_id); + if (top_id == symbol_id(sym)) + return(val); + + for (int64_t i = stack_top(sc) - 1; i > 0; i -= 4) + if (is_let_unchecked(stack_let(sc->stack, i))) /* OP_GC_PROTECT let slot can be anything (even free) */ + { + s7_pointer cur_val = find_dynamic_value(sc, stack_let(sc->stack, i), sym, &top_id); + if (cur_val != sc->unused) + val = cur_val; + if (top_id == symbol_id(sym)) + return(val); + } + return((val == sc->unused) ? s7_symbol_value(sc, sym) : val); +} + +static bool direct_memq(const s7_pointer symbol, s7_pointer symbols) +{ + for (s7_pointer x = symbols; is_pair(x); x = cdr(x)) + if (car(x) == symbol) + return(true); + return(false); +} + +static bool direct_assq(const s7_pointer symbol, s7_pointer symbols) +{ /* used only below in do_symbol_is_safe */ + for (s7_pointer x = symbols; is_pair(x); x = cdr(x)) + if (caar(x) == symbol) + return(true); + return(false); +} + +static bool do_symbol_is_safe(s7_scheme *sc, s7_pointer sym, s7_pointer e) +{ + return((is_slot(global_slot(sym))) || + (direct_assq(sym, e)) || + (is_slot(s7_slot(sc, sym)))); +} + +static bool let_symbol_is_safe(s7_scheme *sc, s7_pointer sym, s7_pointer e) +{ + if (is_slot(global_slot(sym))) + return(true); + if (is_null(e)) + e = sc->rootlet; + return((!is_with_let_let(e)) && + (is_slot(s7_slot(sc, sym)))); +} + +static bool let_symbol_is_safe_or_listed(s7_scheme *sc, s7_pointer sym, s7_pointer e) +{ + return((symbol_is_in_list(sc, sym)) || + (let_symbol_is_safe(sc, sym, e))); +} + +static bool let_star_symbol_is_safe(s7_scheme *sc, s7_pointer sym, s7_pointer e) +{ + return((symbol_is_in_list(sc, sym)) || + (is_slot(global_slot(sym))) || + ((is_let(e)) && (!is_with_let_let(e)) && (is_slot(s7_slot(sc, sym))))); +} + +static bool pair_symbol_is_safe(s7_scheme *unused_sc, s7_pointer sym, s7_pointer e) +{ + return((is_slot(global_slot(sym))) || + (direct_memq(sym, e))); +} + +static s7_pointer collect_variables(s7_scheme *sc, s7_pointer lst, s7_pointer e) +{ + /* collect local variable names from let/do (pre-error-check), 20 overhead in tgen -> 14 if cons_unchecked below */ + sc->w = e; + for (s7_pointer p = lst; is_pair(p); p = cdr(p)) + sc->w = cons(sc, add_symbol_to_list(sc, caar(p)), sc->w); + return(sc->w); +} + +static s7_pointer collect_parameters(s7_scheme *sc, s7_pointer lst, s7_pointer e) +{ + /* collect local variable names from lambda arglists (pre-error-check) */ + s7_pointer p; + s7_int the_un_id = ++sc->let_number; + if (is_symbol(lst)) + { + symbol_set_id(lst, the_un_id); + return(cons(sc, add_symbol_to_list(sc, lst), e)); + } + sc->w = e; + for (p = lst; is_pair(p); p = cdr(p)) + { + s7_pointer car_p = car(p); + if (is_pair(car_p)) + car_p = car(car_p); + if (is_normal_symbol(car_p)) + { + symbol_set_id(car_p, the_un_id); + sc->w = cons(sc, add_symbol_to_list(sc, car_p), sc->w); + }} + if (is_symbol(p)) /* rest arg */ + { + symbol_set_id(p, the_un_id); + sc->w = cons(sc, add_symbol_to_list(sc, p), sc->w); + } + return(sc->w); +} + +typedef enum {OPT_F, OPT_T, OPT_OOPS} opt_t; +static opt_t optimize(s7_scheme *sc, s7_pointer code, int32_t hop, s7_pointer e); + +static void clear_all_optimizations(s7_scheme *sc, s7_pointer p) +{ + /* I believe that we would not have been optimized to begin with if the tree were circular, + * and this tree is supposed to be a function call + args -- a circular list here is a bug. + */ + if (is_pair(p)) + { + if ((is_optimized(p)) && + (((optimize_op(p) >= FIRST_UNHOPPABLE_OP) || /* avoid clearing hop ops, fx_function and op_unknown* need to be cleared */ + (!op_has_hop(p))))) + { + clear_optimized(p); /* includes T_SYNTACTIC */ + clear_optimize_op(p); + } + clear_all_optimizations(sc, cdr(p)); + clear_all_optimizations(sc, car(p)); + } +} + +static s7_pointer add_trace(s7_scheme *sc, s7_pointer code) +{ + if ((is_pair(car(code))) && (caar(code) == sc->trace_in_symbol)) + return(code); + return(cons_unchecked(sc, list_2(sc, sc->trace_in_symbol, list_1(sc, sc->curlet_symbol)), code)); +} + +static s7_pointer add_profile(s7_scheme *sc, s7_pointer code) +{ + s7_pointer p; + if ((is_pair(car(code))) && (caar(code) == sc->profile_in_symbol)) + return(code); + p = cons_unchecked(sc, list_3(sc, sc->profile_in_symbol, make_integer_unchecked(sc, sc->profile_position), list_1(sc, sc->curlet_symbol)), code); + sc->profile_position++; + set_unsafe_optimize_op(car(p), OP_PROFILE_IN); + return(p); +} + +static bool tree_has_definers(s7_scheme *sc, s7_pointer tree) +{ + for (s7_pointer p = tree; is_pair(p); p = cdr(p)) + if (tree_has_definers(sc, car(p))) + return(true); + return((is_symbol(tree)) && (is_definer(tree))); +} + +static s7_pointer make_macro(s7_scheme *sc, opcode_t op, bool named) +{ + s7_pointer mac, body, mac_name = NULL; + uint64_t typ; + if (SHOW_EVAL_OPS) fprintf(stderr, "%s[%d]: %s\n", __func__, __LINE__, display_80(sc->code)); + switch (op) + { + case OP_DEFINE_MACRO: case OP_MACRO: typ = T_MACRO; break; + case OP_DEFINE_MACRO_STAR: case OP_MACRO_STAR: typ = T_MACRO_STAR; break; + case OP_DEFINE_BACRO: case OP_BACRO: typ = T_BACRO; break; + case OP_DEFINE_BACRO_STAR: case OP_BACRO_STAR: typ = T_BACRO_STAR; break; + case OP_DEFINE_EXPANSION: typ = T_MACRO | ((is_let(sc->curlet)) ? 0 : T_EXPANSION); break; /* local expansions are just normal macros */ + case OP_DEFINE_EXPANSION_STAR: typ = T_MACRO_STAR | ((is_let(sc->curlet)) ? 0 : T_EXPANSION); break; + default: + if (S7_DEBUGGING) fprintf(stderr, "%s[%d]: got %s\n", __func__, __LINE__, op_names[op]); + typ = T_MACRO; + break; + } + new_cell(sc, mac, typ | T_DONT_EVAL_ARGS); + closure_set_args(mac, (named) ? cdar(sc->code) : car(sc->code)); + body = cdr(sc->code); + closure_set_body(mac, body); + closure_set_setter(mac, sc->F); + closure_set_let(mac, sc->curlet); + closure_set_arity(mac, CLOSURE_ARITY_NOT_SET); + sc->capture_let_counter++; + gc_protect_via_stack(sc, mac); + + if (named) + { + s7_pointer mac_slot; + mac_name = caar(sc->code); + + if (((op == OP_DEFINE_EXPANSION) || (op == OP_DEFINE_EXPANSION_STAR)) && + (!is_let(sc->curlet))) + set_full_type(mac_name, T_EXPANSION | T_SYMBOL | (full_type(mac_name) & T_UNHEAP)); + + /* symbol? macro name has already been checked, find name in let, and define it */ + mac_slot = symbol_to_local_slot(sc, mac_name, sc->curlet); /* returns global_slot(symbol) if sc->curlet == nil */ + if (is_slot(mac_slot)) + { + if ((sc->curlet == sc->nil) && (!in_rootlet(mac_slot))) + add_slot_to_rootlet(sc, mac_slot); + slot_set_value_with_hook(mac_slot, mac); + } + else s7_make_slot(sc, sc->curlet, mac_name, mac); /* was current but we've checked immutable already */ + if (tree_has_definers(sc, body)) + set_is_definer(mac_name); /* (list-values 'define ...) aux-13 */ + } + + if ((!is_either_bacro(mac)) && + (optimize(sc, body, 1, collect_parameters(sc, closure_args(mac), sc->nil)) == OPT_OOPS)) + clear_all_optimizations(sc, body); + + if (sc->debug > 1) /* no profile here */ + closure_set_body(mac, add_trace(sc, body)); + + unstack(sc); + if (named) + { + set_pair_macro(closure_body(mac), mac_name); + set_has_pair_macro(mac); + if (has_location(car(sc->code))) + { + pair_set_location(closure_body(mac), pair_location(car(sc->code))); + set_has_location(closure_body(mac)); + }} + /* passed to maclet in apply_macro et al, copied in copy_closure */ + return(mac); +} + +static s7_pointer make_closure_unchecked(s7_scheme *sc, s7_pointer args, s7_pointer code, uint64_t type, int32_t arity) +{ + s7_pointer x; + new_cell_no_check(sc, x, (type | closure_bits(code))); + closure_set_args(x, args); + closure_set_let(x, sc->curlet); + closure_set_setter(x, sc->F); + closure_set_arity(x, arity); + closure_set_body(x, code); + if (is_pair(cdr(code))) set_closure_has_multiform(x); else set_closure_has_one_form(x); + sc->capture_let_counter++; + return(x); +} + +static inline s7_pointer make_closure_gc_checked(s7_scheme *sc, s7_pointer args, s7_pointer code, uint64_t type, int32_t arity) /* inline 100=1% tgc, 35=2% texit */ +{ + s7_pointer x; + new_cell(sc, x, (type | closure_bits(code))); + closure_set_args(x, args); + closure_set_let(x, sc->curlet); + closure_set_setter(x, sc->F); + closure_set_arity(x, arity); + closure_set_body(x, code); + if (is_pair(cdr(code))) set_closure_has_multiform(x); else set_closure_has_one_form(x); + sc->capture_let_counter++; + return(x); +} + +static s7_pointer make_closure(s7_scheme *sc, s7_pointer args, s7_pointer code, uint64_t type, int32_t arity) +{ + /* this is called (almost?) every time a lambda form is evaluated, or during letrec, etc */ + s7_pointer x; + new_cell(sc, x, (type | closure_bits(code))); + closure_set_args(x, args); + closure_set_let(x, sc->curlet); + closure_set_setter(x, sc->F); + closure_set_arity(x, arity); + closure_set_body(x, code); /* in case add_trace triggers GC, new func (x) needs some legit body for mark_closure */ + if (sc->debug_or_profile) + { + gc_protect_via_stack(sc, x); /* GC protect func during add_trace */ + closure_set_body(x, (sc->debug > 1) ? add_trace(sc, code) : add_profile(sc, code)); + set_closure_has_multiform(x); + unstack(sc); + } + else + if (is_pair(cdr(code))) + set_closure_has_multiform(x); + else set_closure_has_one_form(x); + sc->capture_let_counter++; + return(x); +} + +static int32_t closure_length(s7_scheme *sc, s7_pointer e) +{ + /* we can't use let_length(sc, closure_let(e)) because the closure_let(closure) + * changes. So the open bit is not always on. Besides, the fallbacks need to be for closures, not lets. + */ + s7_pointer length_func = find_method(sc, closure_let(e), sc->length_symbol); + if (length_func != sc->undefined) + return((int32_t)s7_integer(s7_apply_function(sc, length_func, set_plist_1(sc, e)))); + /* there are cases where this should raise a wrong-type-arg error, but for now... */ + return(-1); +} + +static s7_pointer cons_unchecked_with_type(s7_scheme *sc, s7_pointer p, s7_pointer a, s7_pointer b) /* (used only in copy_tree_with_type) */ +{ + s7_pointer x; + new_cell_no_check(sc, x, full_type(p) & (TYPE_MASK | T_IMMUTABLE | T_SAFE_PROCEDURE)); + set_car(x, a); + set_cdr(x, b); + return(x); +} + +static s7_pointer copy_tree_with_type(s7_scheme *sc, s7_pointer tree) +{ + /* if sc->safety > NO_SAFETY, '(1 2) is set immutable by the reader, but eval (in that safety case) calls + * copy_body on the incoming tree, so we have to preserve T_IMMUTABLE in that case. + * if tree is something like (+ 1 (car '#1=(2 . #1#))), we have to see the quoted list and not copy it. + * Before getting here, we have checked that there is room for the entire tree (in copy_body), or 8192 cells (in list_values) in the free heap. + */ +#if WITH_GCC + #define COPY_TREE_WITH_TYPE(P) ({s7_pointer _p; _p = P; \ + cons_unchecked_with_type(sc, _p, (is_unquoted_pair(car(_p))) ? copy_tree_with_type(sc, car(_p)) : car(_p), \ + (is_unquoted_pair(cdr(_p))) ? copy_tree_with_type(sc, cdr(_p)) : cdr(_p));}) +#else + #define COPY_TREE_WITH_TYPE(P) copy_tree_with_type(sc, P) +#endif + return(cons_unchecked_with_type(sc, tree, + (is_unquoted_pair(car(tree))) ? COPY_TREE_WITH_TYPE(car(tree)) : car(tree), + (is_unquoted_pair(cdr(tree))) ? COPY_TREE_WITH_TYPE(cdr(tree)) : cdr(tree))); +} + +static inline s7_pointer copy_tree(s7_scheme *sc, s7_pointer tree) +{ +#if WITH_GCC + #define COPY_TREE(P) ({s7_pointer _p; _p = P; \ + cons_unchecked(sc, (is_unquoted_pair(car(_p))) ? copy_tree(sc, car(_p)) : car(_p), \ + (is_pair(cdr(_p))) ? copy_tree(sc, cdr(_p)) : cdr(_p));}) +#else + #define COPY_TREE(P) copy_tree(sc, P) +#endif + return(cons_unchecked(sc, + (is_unquoted_pair(car(tree))) ? COPY_TREE(car(tree)) : car(tree), + (is_pair(cdr(tree))) ? COPY_TREE(cdr(tree)) : cdr(tree))); +} + + +/* -------------------------------- tree-cyclic? -------------------------------- */ +#define TREE_NOT_CYCLIC 0 +#define TREE_CYCLIC 1 +#define TREE_HAS_PAIRS 2 + +static int32_t tree_is_cyclic_or_has_pairs(s7_scheme *sc, s7_pointer tree) +{ + s7_pointer fast = tree, slow = tree; /* we assume tree is a pair */ + bool has_pairs = false; + while (true) + { + if (tree_is_collected(fast)) return(TREE_CYCLIC); + if ((!has_pairs) && (is_unquoted_pair(car(fast)))) + has_pairs = true; + fast = cdr(fast); + if (!is_pair(fast)) + { + if (!has_pairs) return(TREE_NOT_CYCLIC); + break; + } + if (tree_is_collected(fast)) return(TREE_CYCLIC); + if ((!has_pairs) && (is_unquoted_pair(car(fast)))) + has_pairs = true; + fast = cdr(fast); + if (!is_pair(fast)) + { + if (!has_pairs) return(TREE_NOT_CYCLIC); + break; + } + slow = cdr(slow); + if (fast == slow) return(TREE_CYCLIC); + } + return(TREE_HAS_PAIRS); +} + +/* we can't use shared_info here because tree_is_cyclic may be called in the midst of output that depends on sc->circle_info */ + +static bool tree_is_cyclic_1(s7_scheme *sc, s7_pointer tree) +{ + for (s7_pointer p = tree; is_pair(p); p = cdr(p)) + { + tree_set_collected(p); + if (sc->tree_pointers_top == sc->tree_pointers_size) + { + if (sc->tree_pointers_size == 0) + { + sc->tree_pointers_size = 8; + sc->tree_pointers = (s7_pointer *)Malloc(sc->tree_pointers_size * sizeof(s7_pointer)); + } + else + { + sc->tree_pointers_size *= 2; + sc->tree_pointers = (s7_pointer *)Realloc(sc->tree_pointers, sc->tree_pointers_size * sizeof(s7_pointer)); + }} + sc->tree_pointers[sc->tree_pointers_top++] = p; + if (is_unquoted_pair(car(p))) + { + int32_t old_top = sc->tree_pointers_top, result; + result = tree_is_cyclic_or_has_pairs(sc, car(p)); + if ((result == TREE_CYCLIC) || + (tree_is_cyclic_1(sc, car(p)))) + return(true); + for (int32_t i = old_top; i < sc->tree_pointers_top; i++) + tree_clear_collected(sc->tree_pointers[i]); + sc->tree_pointers_top = old_top; + }} + return(false); +} + +static bool tree_is_cyclic(s7_scheme *sc, s7_pointer tree) +{ + int32_t result; + if (!is_pair(tree)) return(false); + result = tree_is_cyclic_or_has_pairs(sc, tree); + if (result == TREE_NOT_CYCLIC) return(false); + if (result == TREE_CYCLIC) return(true); + result = tree_is_cyclic_1(sc, tree); + for (int32_t i = 0; i < sc->tree_pointers_top; i++) + tree_clear_collected(sc->tree_pointers[i]); + sc->tree_pointers_top = 0; + return(result); +} + +static s7_pointer g_tree_is_cyclic(s7_scheme *sc, s7_pointer args) +{ + #define H_tree_is_cyclic "(tree-cyclic? tree) returns #t if the tree has a cycle." + #define Q_tree_is_cyclic sc->pl_bt + return(make_boolean(sc, tree_is_cyclic(sc, car(args)))); +} + +static inline s7_int tree_len(s7_scheme *sc, s7_pointer p); + +static s7_pointer copy_body(s7_scheme *sc, s7_pointer p) +{ + sc->w = p; + if (tree_is_cyclic(sc, p)) /* don't wrap this in is_safety_checked */ + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "copy: tree is cyclic: ~S", 24), p)); + check_free_heap_size(sc, tree_len(sc, p) * 2); + return((sc->safety > NO_SAFETY) ? copy_tree_with_type(sc, p) : copy_tree(sc, p)); +} + +static s7_pointer copy_closure(s7_scheme *sc, s7_pointer fnc) +{ + /* copy the source tree annotating (for eventual optimization), return a thing of the same type as fnc */ + s7_pointer x, body = copy_body(sc, closure_body(fnc)); + if ((is_any_macro(fnc)) && (has_pair_macro(fnc))) + { + set_pair_macro(body, pair_macro(closure_body(fnc))); + set_has_pair_macro(fnc); + } + new_cell(sc, x, full_type(fnc) & (~T_COLLECTED)); /* I'm paranoid about that is_collected bit */ + closure_set_args(x, closure_args(fnc)); + closure_set_body(x, body); + closure_set_setter(x, closure_setter(fnc)); + closure_set_arity(x, closure_arity(fnc)); + closure_set_let(x, closure_let(fnc)); + return(x); +} + + +/* -------------------------------- defined? -------------------------------- */ +static s7_pointer g_is_defined(s7_scheme *sc, s7_pointer args) +{ + #define H_is_defined "(defined? symbol (let (curlet)) ignore-globals) returns #t if symbol has a binding (a value) in the let. \ +Only the let is searched if ignore-globals is not #f." + #define Q_is_defined s7_make_signature(sc, 4, sc->is_boolean_symbol, sc->is_symbol_symbol, \ + s7_make_signature(sc, 5, sc->is_let_symbol, sc->is_procedure_symbol, sc->is_macro_symbol, \ + sc->is_c_object_symbol, sc->is_c_pointer_symbol), sc->is_boolean_symbol) + /* if the symbol has a global slot and e is unset or rootlet, this returns #t */ + s7_pointer sym = car(args); + if (!is_symbol(sym)) + return(method_or_bust(sc, sym, sc->is_defined_symbol, args, sc->type_names[T_SYMBOL], 1)); + + if (is_pair(cdr(args))) + { + s7_pointer e = cadr(args), b, x; + if (!is_let(e)) + { + bool nil_is_rootlet = is_any_procedure(e); /* (defined? 'abs (lambda () 1)) -- unsure about this */ + e = find_let(sc, e); + if ((is_null(e)) && (nil_is_rootlet)) + e = sc->rootlet; + else + if (!is_let(e)) + wrong_type_error_nr(sc, sc->is_defined_symbol, 2, cadr(args), a_let_string); /* not e */ + } + if (is_keyword(sym)) /* if no "e", is global -> #t */ + sym = keyword_symbol(sym); /* (defined? :print-length *s7*) */ + if (e == sc->s7_starlet) + return(make_boolean(sc, s7_starlet_symbol(sym) != SL_NO_FIELD)); + if (is_pair(cddr(args))) + { + b = caddr(args); + if (!is_boolean(b)) + return(method_or_bust(sc, b, sc->is_defined_symbol, args, a_boolean_string, 3)); + } + else b = sc->F; + if (e == sc->rootlet) /* we checked (let? e) above */ + { + if (b == sc->F) + return(make_boolean(sc, is_slot(global_slot(sym)))); /* new_symbol and gensym initialize global_slot to # */ + return(sc->F); + } + x = symbol_to_local_slot(sc, sym, e); + if (is_slot(x)) + return(sc->T); + return((b == sc->T) ? sc->F : make_boolean(sc, is_slot(global_slot(sym)))); + } + return((is_global(sym)) ? sc->T : make_boolean(sc, is_slot(s7_slot(sc, sym)))); +} + +static s7_pointer g_is_defined_in_rootlet(s7_scheme *sc, s7_pointer args) +{ + /* here we know arg2=(rootlet), and no arg3, arg1 is a symbol (see chooser below) */ + s7_pointer sym = lookup(sc, car(args)); /* args are unevalled because the chooser calls us through op_safe_c_nc?? */ + if (!is_symbol(sym)) /* if sym is openlet with defined? perhaps it makes sense to call it, but we need to include the rootlet arg */ + return(method_or_bust_pp(sc, sym, sc->is_defined_symbol, sym, sc->rootlet, sc->type_names[T_SYMBOL], 1)); + return(make_boolean(sc, is_slot(global_slot(sym)))); +} + +static s7_pointer is_defined_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool ops) +{ + if (!ops) return(f); + if ((args == 2) && (is_symbol(cadr(expr)))) + { + s7_pointer e = caddr(expr); + if ((is_pair(e)) && (is_null(cdr(e))) && (car(e) == sc->rootlet_symbol)) + { + set_safe_optimize_op(expr, HOP_SAFE_C_NC); + return(sc->is_defined_in_rootlet); + }} + return(f); +} + +bool s7_is_defined(s7_scheme *sc, const char *name) +{ + s7_pointer x = s7_symbol_table_find_name(sc, name); + if (!x) return(false); + return(is_slot(s7_slot(sc, x))); +} + +static bool is_defined_b_7p(s7_scheme *sc, s7_pointer p) +{ + if (!is_symbol(p)) return(method_or_bust(sc, p, sc->is_defined_symbol, set_plist_1(sc, p), sc->type_names[T_SYMBOL], 1) != sc->F); + return(is_slot(s7_slot(sc, p))); +} + +static bool is_defined_b_7pp(s7_scheme *sc, s7_pointer p, s7_pointer e) {return(g_is_defined(sc, set_plist_2(sc, p, e)) != sc->F);} + + +void s7_define(s7_scheme *sc, s7_pointer let, s7_pointer symbol, s7_pointer value) +{ + s7_pointer x; + if ((let == sc->nil) || (let == sc->rootlet)) + let = sc->shadow_rootlet; + x = symbol_to_local_slot(sc, symbol, let); + if (is_slot(x)) + slot_set_value_with_hook(x, value); + else + { + s7_make_slot(sc, let, symbol, value); /* I think this means C code can override "constant" defs */ + /* if let is sc->nil or rootlet, s7_make_slot makes a semipermanent_slot */ + if ((let == sc->shadow_rootlet) && + (!is_slot(global_slot(symbol)))) + { + set_global(symbol); /* is_global => global_slot is usable -- is this a good idea? */ + set_global_slot(symbol, local_slot(symbol)); + }} +} + +s7_pointer s7_define_variable(s7_scheme *sc, const char *name, s7_pointer value) +{ + s7_pointer sym = make_symbol_with_strlen(sc, name); + s7_define(sc, sc->nil, sym, value); + return(sym); +} + +s7_pointer s7_define_variable_with_documentation(s7_scheme *sc, const char *name, s7_pointer value, const char *help) +{ + s7_pointer sym = s7_define_variable(sc, name, value); + symbol_set_has_help(sym); + symbol_set_help(sym, copy_string(help)); + add_saved_pointer(sc, symbol_help(sym)); + return(sym); +} + +s7_pointer s7_define_constant_with_environment(s7_scheme *sc, s7_pointer envir, const char *name, s7_pointer value) +{ + s7_pointer sym = make_symbol_with_strlen(sc, name); + s7_define(sc, envir, sym, value); + set_immutable(sym); + set_possibly_constant(sym); + set_immutable(global_slot(sym)); + set_immutable(local_slot(sym)); + return(sym); +} + +s7_pointer s7_define_constant(s7_scheme *sc, const char *name, s7_pointer value) +{ + return(s7_define_constant_with_environment(sc, sc->nil, name, value)); +} + +/* (define (func a) (let ((cvar (+ a 1))) cvar)) (define-constant cvar 23) (func 1) -> ;can't bind an immutable object: cvar + * (let ((aaa 1)) (define-constant aaa 32) (set! aaa 3)) -> set!: can't alter immutable object: aaa + */ + +s7_pointer s7_define_constant_with_documentation(s7_scheme *sc, const char *name, s7_pointer value, const char *help) +{ + s7_pointer sym = s7_define_constant(sc, name, value); + symbol_set_has_help(sym); + symbol_set_help(sym, copy_string(help)); + add_saved_pointer(sc, symbol_help(sym)); + return(value); /* inconsistent with variable above, but consistent with define_function? */ +} + + +/* -------------------------------- keyword? -------------------------------- */ +bool s7_is_keyword(s7_pointer obj) {return(is_symbol_and_keyword(obj));} + +static s7_pointer g_is_keyword(s7_scheme *sc, s7_pointer args) +{ + #define H_is_keyword "(keyword? obj) returns #t if obj is a keyword, (keyword? :rest) -> #t" + #define Q_is_keyword sc->pl_bt + check_boolean_method(sc, is_symbol_and_keyword, sc->is_keyword_symbol, args); +} + + +/* -------------------------------- string->keyword -------------------------------- */ +s7_pointer s7_make_keyword(s7_scheme *sc, const char *key) +{ + s7_pointer sym; + size_t slen = (size_t)safe_strlen(key); + block_t *b = inline_mallocate(sc, slen + 2); + char *name = (char *)block_data(b); + name[0] = ':'; + memcpy((void *)(name + 1), (const void *)key, slen); + name[slen + 1] = '\0'; + sym = inline_make_symbol(sc, name, slen + 1); /* keyword slot etc taken care of here (in new_symbol actually) */ + liberate(sc, b); + return(sym); +} + +static s7_pointer g_string_to_keyword(s7_scheme *sc, s7_pointer args) +{ + #define H_string_to_keyword "(string->keyword str) prepends ':' to str and defines that as a keyword" + #define Q_string_to_keyword s7_make_signature(sc, 2, sc->is_keyword_symbol, sc->is_string_symbol) + + s7_pointer str = car(args); + if (!is_string(str)) + return(sole_arg_method_or_bust(sc, str, sc->string_to_keyword_symbol, args, sc->type_names[T_STRING])); + if ((string_length(str) == 0) || + (string_value(str)[0] == '\0')) + error_nr(sc, sc->out_of_range_symbol, set_elist_2(sc, wrap_string(sc, "string->keyword wants a non-null string: ~S", 43), str)); + return(s7_make_keyword(sc, string_value(str))); +} + + +/* -------------------------------- keyword->symbol -------------------------------- */ +static s7_pointer g_keyword_to_symbol(s7_scheme *sc, s7_pointer args) +{ + #define H_keyword_to_symbol "(keyword->symbol key) returns a symbol with the same name as key but no prepended colon" + #define Q_keyword_to_symbol s7_make_signature(sc, 2, sc->is_symbol_symbol, sc->is_keyword_symbol) + + s7_pointer sym = car(args); + if (!is_symbol_and_keyword(sym)) + return(method_or_bust_p(sc, sym, sc->keyword_to_symbol_symbol, wrap_string(sc, "a keyword", 9))); + return(keyword_symbol(sym)); +} + +s7_pointer s7_keyword_to_symbol(s7_scheme *sc, s7_pointer key) {return(keyword_symbol(key));} + + +/* -------------------------------- symbol->keyword -------------------------------- */ +#define symbol_to_keyword(Sc, Sym) s7_make_keyword(Sc, symbol_name(Sym)) + +static s7_pointer g_symbol_to_keyword(s7_scheme *sc, s7_pointer args) +{ + #define H_symbol_to_keyword "(symbol->keyword sym) returns a keyword with the same name as sym, but with a colon prepended" + #define Q_symbol_to_keyword s7_make_signature(sc, 2, sc->is_keyword_symbol, sc->is_symbol_symbol) + + if (!is_symbol(car(args))) + return(sole_arg_method_or_bust(sc, car(args), sc->symbol_to_keyword_symbol, args, sc->type_names[T_SYMBOL])); + return(symbol_to_keyword(sc, car(args))); +} + + +/* -------------------------------- c-pointer? -------------------------------- */ +bool s7_is_c_pointer(s7_pointer arg) {return(is_c_pointer(arg));} + +bool s7_is_c_pointer_of_type(s7_pointer arg, s7_pointer type) {return((is_c_pointer(arg)) && (c_pointer_type(arg) == type));} + +static s7_pointer g_is_c_pointer(s7_scheme *sc, s7_pointer args) +{ + #define H_is_c_pointer "(c-pointer? obj type) returns #t if obj is a C pointer being held in s7. \ +If type is given, the c_pointer's type is also checked." + #define Q_is_c_pointer s7_make_signature(sc, 3, sc->is_boolean_symbol, sc->T, sc->T) + + s7_pointer p = car(args); + if (is_c_pointer(p)) + return((is_pair(cdr(args))) ? make_boolean(sc, c_pointer_type(p) == cadr(args)) : sc->T); + if (!has_active_methods(sc, p)) return(sc->F); + return(apply_boolean_method(sc, p, sc->is_c_pointer_symbol)); +} + + +/* -------------------------------- c-pointer -------------------------------- */ +void *s7_c_pointer(s7_pointer p) {return(c_pointer(p));} + +void *s7_c_pointer_with_type(s7_scheme *sc, s7_pointer p, s7_pointer expected_type, const char *caller, s7_int argnum) +{ + if (!is_c_pointer(p)) + wrong_type_error_nr(sc, wrap_string(sc, caller, safe_strlen(caller)), argnum, p, sc->type_names[T_C_POINTER]); + if ((c_pointer(p) != NULL) && + (c_pointer_type(p) != expected_type)) + error_nr(sc, sc->wrong_type_arg_symbol, + (argnum == 0) ? + set_elist_4(sc, wrap_string(sc, "~S argument is a pointer of type ~S, but expected ~S", 52), + wrap_string(sc, caller, safe_strlen(caller)), c_pointer_type(p), expected_type) : + set_elist_5(sc, wrap_string(sc, "~S ~:D argument got a pointer of type ~S, but expected ~S", 57), + wrap_string(sc, caller, safe_strlen(caller)), + wrap_integer(sc, argnum), c_pointer_type(p), expected_type)); + return(c_pointer(p)); +} + +s7_pointer s7_make_c_pointer_with_type(s7_scheme *sc, void *ptr, s7_pointer type, s7_pointer info) +{ + s7_pointer x; + new_cell(sc, x, T_C_POINTER); + c_pointer(x) = ptr; + c_pointer_type(x) = type; + c_pointer_info(x) = info; + c_pointer_weak1(x) = sc->F; + c_pointer_weak2(x) = sc->F; + return(x); +} + +s7_pointer s7_make_c_pointer(s7_scheme *sc, void *ptr) {return(s7_make_c_pointer_with_type(sc, ptr, sc->F, sc->F));} + +static s7_pointer g_c_pointer(s7_scheme *sc, s7_pointer args) +{ + #define H_c_pointer "(c-pointer int type info weak1 weak2) returns a c-pointer object. The type and info args are optional, defaulting to #f." + #define Q_c_pointer s7_make_circular_signature(sc, 2, 3, sc->is_c_pointer_symbol, sc->is_integer_symbol, sc->T) + + s7_pointer arg = car(args), type = sc->F, info = sc->F, weak1 = sc->F, weak2 = sc->F, cp; + intptr_t p; + + if (!s7_is_integer(arg)) + return(method_or_bust(sc, arg, sc->c_pointer_symbol, args, sc->type_names[T_INTEGER], 1)); + p = (intptr_t)s7_integer_clamped_if_gmp(sc, arg); /* (c-pointer (bignum "1234")) */ + args = cdr(args); + if (is_pair(args)) + { + type = car(args); + args = cdr(args); + if (is_pair(args)) + { + info = car(args); + args = cdr(args); + if (is_pair(args)) + { + weak1 = car(args); + args = cdr(args); + if (is_pair(args)) + weak2 = car(args); + }}} + cp = s7_make_c_pointer_with_type(sc, (void *)p, type, info); + c_pointer_set_weak1(cp, weak1); + c_pointer_set_weak2(cp, weak2); + if ((weak1 != sc->F) || (weak2 != sc->F)) + add_weak_ref(sc, cp); + return(cp); +} + + +/* -------------------------------- c-pointer-info -------------------------------- */ +static s7_pointer c_pointer_info_p_p(s7_scheme *sc, s7_pointer p) +{ + if (!is_c_pointer(p)) + return(method_or_bust_p(sc, p, sc->c_pointer_info_symbol, sc->type_names[T_C_POINTER])); + return(c_pointer_info(p)); +} + +static s7_pointer g_c_pointer_info(s7_scheme *sc, s7_pointer args) +{ + #define H_c_pointer_info "(c-pointer-info obj) returns the c-pointer info field" + #define Q_c_pointer_info s7_make_signature(sc, 2, sc->T, sc->is_c_pointer_symbol) + return(c_pointer_info_p_p(sc, car(args))); +} + + +/* -------------------------------- c-pointer-type -------------------------------- */ +static s7_pointer method_or_bust_lp(s7_scheme *sc, s7_pointer obj, s7_pointer method, uint8_t typ) +{ /* weird -- overhead goes berserk in callgrind if using the simpler method_or_bust_p! */ + if (!has_active_methods(sc, obj)) + wrong_type_error_nr(sc, method, 1, obj, sc->type_names[typ]); + return(find_and_apply_method(sc, obj, method, set_plist_1(sc, obj))); +} + +s7_pointer s7_c_pointer_type(s7_pointer p) {return((is_c_pointer(p)) ? c_pointer_type(p) : NULL);} + +static s7_pointer c_pointer_type_p_p(s7_scheme *sc, s7_pointer p) +{ + return((is_c_pointer(p)) ? c_pointer_type(p) : method_or_bust_lp(sc, p, sc->c_pointer_type_symbol, T_C_POINTER)); +} + +static s7_pointer g_c_pointer_type(s7_scheme *sc, s7_pointer args) +{ + #define H_c_pointer_type "(c-pointer-type obj) returns the c-pointer type field" + #define Q_c_pointer_type s7_make_signature(sc, 2, sc->T, sc->is_c_pointer_symbol) + return(c_pointer_type_p_p(sc, car(args))); +} + + +/* -------------------------------- c-pointer-weak1/2 -------------------------------- */ +static s7_pointer c_pointer_weak1_p_p(s7_scheme *sc, s7_pointer p) +{ + return((is_c_pointer(p)) ? c_pointer_weak1(p) : method_or_bust_lp(sc, p, sc->c_pointer_weak1_symbol, T_C_POINTER)); +} + +static s7_pointer g_c_pointer_weak1(s7_scheme *sc, s7_pointer args) +{ + #define H_c_pointer_weak1 "(c-pointer-weak1 obj) returns the c-pointer weak1 field" + #define Q_c_pointer_weak1 s7_make_signature(sc, 2, sc->T, sc->is_c_pointer_symbol) + return(c_pointer_weak1_p_p(sc, car(args))); +} + +static s7_pointer c_pointer_weak2_p_p(s7_scheme *sc, s7_pointer p) +{ + return((is_c_pointer(p)) ? c_pointer_weak2(p) : method_or_bust_lp(sc, p, sc->c_pointer_weak2_symbol, T_C_POINTER)); +} + +static s7_pointer g_c_pointer_weak2(s7_scheme *sc, s7_pointer args) +{ + #define H_c_pointer_weak2 "(c-pointer-weak2 obj) returns the c-pointer weak2 field" + #define Q_c_pointer_weak2 s7_make_signature(sc, 2, sc->T, sc->is_c_pointer_symbol) + return(c_pointer_weak2_p_p(sc, car(args))); +} + + +/* -------------------------------- c-pointer->list -------------------------------- */ +static s7_pointer g_c_pointer_to_list(s7_scheme *sc, s7_pointer args) +{ + #define H_c_pointer_to_list "(c-pointer->list obj) returns the c-pointer data as (list pointer-as-int type info)" + #define Q_c_pointer_to_list s7_make_signature(sc, 2, sc->is_pair_symbol, sc->is_c_pointer_symbol) + + s7_pointer p = car(args); + if (!is_c_pointer(p)) + return(method_or_bust(sc, p, sc->c_pointer_to_list_symbol, args, sc->type_names[T_C_POINTER], 1)); + return(list_3(sc, make_integer(sc, (s7_int)((intptr_t)c_pointer(p))), c_pointer_type(p), c_pointer_info(p))); +} + + +/* -------------------------------- continuations and gotos -------------------------------- */ +enum {NO_JUMP, CALL_WITH_EXIT_JUMP, THROW_JUMP, CATCH_JUMP, ERROR_JUMP, ERROR_QUIT_JUMP}; +enum {NO_SET_JUMP, READ_SET_JUMP, LOAD_SET_JUMP, DYNAMIC_WIND_SET_JUMP, S7_CALL_SET_JUMP, EVAL_SET_JUMP}; + + +/* ----------------------- continuation? -------------------------------- */ +static s7_pointer g_is_continuation(s7_scheme *sc, s7_pointer args) +{ + #define H_is_continuation "(continuation? obj) returns #t if obj is a continuation" + #define Q_is_continuation sc->pl_bt + check_boolean_method(sc, is_continuation, sc->is_continuation_symbol, args); + /* is this the right thing? It returns #f for call-with-exit ("goto") because + * that form of continuation can't continue (via a jump back to its context). + */ +} + +static bool is_continuation_b_p(s7_pointer p) {return(is_continuation(p));} + +#if S7_DEBUGGING +static s7_pointer check_wrap_return(s7_pointer lst) +{ + for (s7_pointer fast = lst, slow = lst; is_pair(fast); slow = cdr(slow), fast = cdr(fast)) + { + if (is_matched_pair(fast)) fprintf(stderr, "matched_pair not cleared\n"); + fast = cdr(fast); + if (!is_pair(fast)) return(lst); + if (fast == slow) return(lst); + if (is_matched_pair(fast)) fprintf(stderr, "matched_pair not cleared\n"); + } + return(lst); +} +#endif + +static s7_pointer copy_any_list(s7_scheme *sc, s7_pointer a) +{ + s7_pointer slow = cdr(a); + s7_pointer fast = slow; + s7_pointer p; +#if S7_DEBUGGING + #define wrap_return(W) do {fast = W; W = sc->unused; sc->y = sc->unused; return(check_wrap_return(fast));} while (0) +#else + #define wrap_return(W) do {fast = W; W = sc->unused; sc->y = sc->unused; return(fast);} while (0) +#endif + init_temp(sc->y, a); /* gc_protect_via_stack doesn't work here because we're called in copy_stack, I think (trouble is in call/cc stuff) */ + sc->w = list_1(sc, car(a)); + p = sc->w; + while (true) + { + if (!is_pair(fast)) + { + if (is_null(fast)) + wrap_return(sc->w); + set_cdr(p, fast); + wrap_return(sc->w); + } + + set_cdr(p, list_1(sc, car(fast))); + p = cdr(p); + fast = cdr(fast); + if (!is_pair(fast)) + { + if (is_null(fast)) + wrap_return(sc->w); + set_cdr(p, fast); + wrap_return(sc->w); + } + /* if unrolled further, it's a lot slower? */ + set_cdr(p, list_1_unchecked(sc, car(fast))); + p = cdr(p); + fast = cdr(fast); + slow = cdr(slow); + if (fast == slow) + { + /* try to preserve the original cyclic structure */ + s7_pointer p1, f1, p2, f2; + set_match_pair(a); + for (p1 = sc->w, f1 = a; !(is_matched_pair(cdr(f1))); f1 = cdr(f1), p1 = cdr(p1)) + set_match_pair(f1); + for (p2 = sc->w, f2 = a; cdr(f1) != f2; f2 = cdr(f2), p2 = cdr(p2)) + clear_match_pair(f2); + for (f1 = f2; is_pair(f1); f1 = cdr(f1), f2 = cdr(f2)) + { + clear_match_pair(f1); + f1 = cdr(f1); + clear_match_pair(f1); + if (f1 == f2) break; + } + clear_match_pair(a); + if (is_null(p1)) + set_cdr(p2, p2); + else set_cdr(p1, p2); + wrap_return(sc->w); + }} + wrap_return(sc->w); +} + +static s7_pointer copy_counter(s7_scheme *sc, s7_pointer obj) +{ + s7_pointer nobj; + new_cell(sc, nobj, T_COUNTER); + counter_set_result(nobj, counter_result(obj)); + counter_set_list(nobj, counter_list(obj)); + counter_set_capture(nobj, counter_capture(obj)); + counter_set_let(nobj, counter_let(obj)); + counter_set_slots(nobj, counter_slots(obj)); + return(nobj); +} + +static void copy_stack_list_set_immutable(s7_pointer pold, s7_pointer pnew) +{ + for (s7_pointer p1 = pold, p2 = pnew, slow = pold; is_pair(p2); p1 = cdr(p1), p2 = cdr(p2)) + { + if (is_immutable(p1)) set_immutable(p2); + if (is_pair(cdr(p1))) + { + p1 = cdr(p1); + p2 = cdr(p2); + if (is_immutable(p1)) set_immutable(p2); + if (p1 == slow) break; + slow = cdr(slow); + }} +} + +static s7_pointer copy_stack(s7_scheme *sc, s7_pointer new_v, s7_pointer old_v, int64_t top) +{ + bool has_pairs = false; + s7_pointer *nv = stack_elements(new_v); + s7_pointer *ov = stack_elements(old_v); + memcpy((void *)nv, (void *)ov, top * sizeof(s7_pointer)); + stack_clear_flags(new_v); + + s7_gc_on(sc, false); + if (stack_has_counters(old_v)) + { + for (int64_t i = 2; i < top; i += 4) + { + s7_pointer p = ov[i]; /* args */ + /* if op_gc_protect, any ov[i] (except op) can be a list, but it isn't the arglist, so it seems to be safe */ + if (is_pair(p)) /* args need not be a list (it can be a port or #f, etc) */ + { + has_pairs = true; + if (is_null(cdr(p))) + nv[i] = cons_unchecked(sc, car(p), sc->nil); /* GC is off -- could uncheck list_2 et al also */ + else + if ((is_pair(cdr(p))) && (is_null(cddr(p)))) + nv[i] = list_2_unchecked(sc, car(p), cadr(p)); + else nv[i] = copy_any_list(sc, p); /* args (copy is needed -- see s7test.scm) */ + /* if op=eval_args4 for example, this has to be a proper list, and in many cases it doesn't need to be copied */ + copy_stack_list_set_immutable(p, nv[i]); + } + /* lst can be dotted or circular here. The circular list only happens in a case like: + * (dynamic-wind (lambda () (eq? (let ((lst (cons 1 2))) (set-cdr! lst lst) lst) (call/cc (lambda (k) k)))) (lambda () #f) (lambda () #f)) + * proper_list_reverse_in_place(sc->args) is one reason we need to copy, another reuse_as_let + */ + else + if (is_counter(p)) /* these can only occur in this context (not in a list etc) */ + { + stack_set_has_counters(new_v); + nv[i] = copy_counter(sc, p); + }}} + else + for (int64_t i = 2; i < top; i += 4) + if (is_pair(ov[i])) + { + s7_pointer p = ov[i]; + has_pairs = true; + if (is_null(cdr(p))) + nv[i] = cons_unchecked(sc, car(p), sc->nil); + else + if ((is_pair(cdr(p))) && (is_null(cddr(p)))) + nv[i] = list_2_unchecked(sc, car(p), cadr(p)); + else nv[i] = copy_any_list(sc, p); /* args (copy is needed -- see s7test.scm) */ + copy_stack_list_set_immutable(p, nv[i]); + } + if (has_pairs) stack_set_has_pairs(new_v); + s7_gc_on(sc, true); + return(new_v); +} + +static s7_pointer copy_op_stack(s7_scheme *sc) +{ + int32_t len = (int32_t)(sc->op_stack_now - sc->op_stack); + s7_pointer nv = make_simple_vector(sc, len); /* not sc->op_stack_size */ + if (len > 0) + { + s7_pointer *src = sc->op_stack; + s7_pointer *dst = (s7_pointer *)vector_elements(nv); + for (int32_t i = len; i > 0; i--) *dst++ = *src++; + } + return(nv); +} + +/* -------------------------------- with-baffle -------------------------------- */ +/* (with-baffle . body) calls body guaranteeing that there can be no jumps into the + * middle of it from outside -- no outer evaluation of a continuation can jump across this + * barrier: The flip-side of call-with-exit. + */ + +static bool find_baffle(s7_scheme *sc, s7_int key) +{ + /* search backwards through sc->curlet for baffle_let with (continuation_)key as its baffle_key value */ + if (sc->baffle_ctr > 0) + for (s7_pointer x = sc->curlet; is_let(x); x = let_outlet(x)) + if ((is_baffle_let(x)) && + (let_baffle_key(x) == key)) + return(true); + return(false); +} + +#define NOT_BAFFLED -1 + +static s7_int find_any_baffle(s7_scheme *sc) +{ + /* search backwards through sc->curlet for any sc->baffle_symbol -- called by s7_make_continuation to set continuation_key */ + if (sc->baffle_ctr > 0) + for (s7_pointer x = sc->curlet; is_let(x); x = let_outlet(x)) + if (is_baffle_let(x)) + return(let_baffle_key(x)); + return(NOT_BAFFLED); +} + +static void check_with_baffle(s7_scheme *sc) +{ + if (!s7_is_proper_list(sc, sc->code)) + syntax_error_nr(sc, "with-baffle: unexpected dot? ~A", 31, sc->code); + pair_set_syntax_op(sc->code, OP_WITH_BAFFLE_UNCHECKED); +} + +static bool op_with_baffle_unchecked(s7_scheme *sc) +{ + sc->code = cdr(sc->code); + if (is_null(sc->code)) + { + sc->value = sc->nil; + return(true); + } + sc->curlet = make_let(sc, sc->curlet); + set_baffle_let(sc->curlet); + set_let_baffle_key(sc->curlet, sc->baffle_ctr++); + return(false); +} + + +/* -------------------------------- call/cc -------------------------------- */ +static void make_room_for_cc_stack(s7_scheme *sc) +{ + if ((int64_t)(sc->free_heap_top - sc->free_heap) < (int64_t)(sc->heap_size / 8)) /* we probably never need this much space */ + { + call_gc(sc); + if ((int64_t)(sc->free_heap_top - sc->free_heap) < (int64_t)(sc->heap_size / 8)) + resize_heap(sc); + } +} + +s7_pointer s7_make_continuation(s7_scheme *sc) +{ + s7_pointer x, stack; + int64_t loc; + block_t *block; + + sc->continuation_counter++; + make_room_for_cc_stack(sc); + if (sc->continuation_counter > 2000) call_gc(sc); /* call_gc zeros cc counter, gc time up, but run time down -- try big cache */ + + loc = stack_top(sc); + stack = make_simple_vector(sc, loc); + set_full_type(stack, T_STACK); + temp_stack_top(stack) = loc; + sc->temp7 = stack; + copy_stack(sc, stack, sc->stack, loc); + + new_cell(sc, x, T_CONTINUATION); + block = mallocate_block(sc); + continuation_block(x) = block; + continuation_set_stack(x, stack); + continuation_stack_size(x) = vector_length(continuation_stack(x)); + continuation_stack_start(x) = stack_elements(continuation_stack(x)); + continuation_stack_end(x) = (s7_pointer *)(continuation_stack_start(x) + loc); + continuation_op_stack(x) = copy_op_stack(sc); + continuation_op_loc(x) = (int32_t)(sc->op_stack_now - sc->op_stack); + continuation_op_size(x) = sc->op_stack_size; + continuation_key(x) = find_any_baffle(sc); + continuation_name(x) = sc->F; + sc->temp7 = sc->unused; + + add_continuation(sc, x); + return(x); +} + +static void let_temp_done(s7_scheme *sc, s7_pointer args, s7_pointer let); +static void let_temp_unwind(s7_scheme *sc, s7_pointer slot, s7_pointer new_value); +static s7_pointer s7_starlet_set_1(s7_scheme *sc, s7_pointer sym, s7_pointer val); + +static bool check_for_dynamic_winds(s7_scheme *sc, s7_pointer c) +{ + /* called only from call_with_current_continuation. + * if call/cc jumps into a dynamic-wind, the init/finish funcs are wrapped in with-baffle + * so they'll complain. Otherwise we're supposed to re-run the init func before diving + * into the body. Similarly for let-temporarily. If a call/cc jumps out of a dynamic-wind + * body-func, we're supposed to call the finish-func. The continuation is called at + * stack_top(sc); the continuation form is at continuation_stack_top(c). + * + * check sc->stack for dynamic-winds we're jumping out of + * we need to check from the current stack top down to where the continuation stack matches the current stack?? + * this was (i > 0), but that goes too far back; perhaps s7 should save the position of the call/cc invocation. + * also the two stacks can be different sizes (either can be larger) + */ + int64_t top1 = stack_top(sc), top2 = continuation_stack_top(c); + for (int64_t i = top1 - 1; (i > 0) && ((i >= top2) || (stack_code(sc->stack, i) != stack_code(continuation_stack(c), i))); i -= 4) + { + opcode_t op = stack_op(sc->stack, i); + switch (op) + { + case OP_DYNAMIC_WIND: + case OP_LET_TEMP_DONE: + { + s7_pointer x = stack_code(sc->stack, i); + int64_t s_base = 0; + for (int64_t j = 3; j < top2; j += 4) + if (((stack_op(continuation_stack(c), j) == OP_DYNAMIC_WIND) || + (stack_op(continuation_stack(c), j) == OP_LET_TEMP_DONE)) && + (x == stack_code(continuation_stack(c), j))) + { + s_base = i; + break; + } + if (s_base == 0) + { + if (op == OP_DYNAMIC_WIND) + { + if (dynamic_wind_state(x) == DWIND_BODY) + { + dynamic_wind_state(x) = DWIND_FINISH; + if (dynamic_wind_out(x) != sc->F) + sc->value = s7_call(sc, dynamic_wind_out(x), sc->nil); + }} + else let_temp_done(sc, stack_args(sc->stack, i), stack_let(sc->stack, i)); + }} + break; + + case OP_DYNAMIC_UNWIND: + case OP_DYNAMIC_UNWIND_PROFILE: + set_stack_op(sc->stack, i, OP_GC_PROTECT); + break; + + case OP_LET_TEMP_UNWIND: + let_temp_unwind(sc, stack_code(sc->stack, i), stack_args(sc->stack, i)); + break; + + case OP_LET_TEMP_S7_UNWIND: + s7_starlet_set_1(sc, T_Sym(stack_code(sc->stack, i)), stack_args(sc->stack, i)); + break; + + case OP_LET_TEMP_S7_DIRECT_UNWIND: + sc->has_openlets = (stack_args(sc->stack, i) != sc->F); + break; + + case OP_BARRIER: + if (i > top2) /* otherwise it's some unproblematic outer eval-string? */ + return(false); /* but what if we've already evaluated a dynamic-wind closer? */ + break; + + case OP_DEACTIVATE_GOTO: /* here we're jumping out of an unrelated call-with-exit block */ + if (i > top2) + call_exit_active(stack_args(sc->stack, i)) = false; + break; + + case OP_UNWIND_INPUT: + if (stack_args(sc->stack, i) != sc->unused) + set_current_input_port(sc, stack_args(sc->stack, i)); /* "args" = port that we shadowed */ + break; + + case OP_UNWIND_OUTPUT: + if (stack_args(sc->stack, i) != sc->unused) + set_current_output_port(sc, stack_args(sc->stack, i)); /* "args" = port that we shadowed */ + break; + + default: + if ((S7_DEBUGGING) && (op == OP_MAP_UNWIND)) fprintf(stderr, "%s[%d]: unwind %" ld64 "\n", __func__, __LINE__, sc->map_call_ctr); + break; + }} + + /* check continuation-stack for dynamic-winds we're jumping into */ + for (int64_t i = stack_top(sc) - 1; i < top2; i += 4) + { + opcode_t op = stack_op(continuation_stack(c), i); + if (op == OP_DYNAMIC_WIND) + { + s7_pointer x = T_Dyn(stack_code(continuation_stack(c), i)); + if (dynamic_wind_in(x) != sc->F) + sc->value = s7_call(sc, dynamic_wind_in(x), sc->nil); + dynamic_wind_state(x) = DWIND_BODY; + } + else + if (op == OP_DEACTIVATE_GOTO) + call_exit_active(stack_args(continuation_stack(c), i)) = true; + /* not let_temp_done here! */ + /* if op == OP_LET_TEMP_DONE, we're jumping back into a let-temporarily. MIT and Chez scheme say they remember the + * let-temp vars (fluid-let or parameters in their terminology) at the point of the call/cc, and restore them + * on re-entry; that strikes me as incoherently complex -- they've wrapped a hidden dynamic-wind around the + * call/cc to restore all let-temp vars! I think let-temp here should be the same as let -- if you jump back + * in, nothing hidden happens. So, + * (let ((x #f) (cc #f)) + * (let-temporarily ((x 1)) + * (set! x 2) (call/cc (lambda (r) (set! cc r))) (display x) (unless (= x 2) (newline) (exit)) (set! x 3) (cc))) + * behaves the same (in this regard) if let-temp is replaced with let. + */ + } + return(true); +} + +static s7_pointer splice_in_values(s7_scheme *sc, s7_pointer args); + +static void call_with_current_continuation(s7_scheme *sc) +{ + s7_pointer c = sc->code; /* sc->args are the returned values */ + + /* check for (baffle ...) blocking the current attempt to continue */ + if ((continuation_key(c) != NOT_BAFFLED) && + (!(find_baffle(sc, continuation_key(c))))) + error_nr(sc, sc->baffled_symbol, + (is_symbol(continuation_name(sc->code))) ? + set_elist_2(sc, wrap_string(sc, "continuation ~S can't jump into with-baffle", 43), continuation_name(sc->code)) : + set_elist_1(sc, wrap_string(sc, "continuation can't jump into with-baffle", 40))); + + if (check_for_dynamic_winds(sc, c)) + { + /* make_room_for_cc_stack(sc); */ /* 28-May-21 */ + /* we push_stack sc->code before calling an embedded eval above, so sc->code should still be c here, etc */ + if ((stack_has_pairs(continuation_stack(c))) || + (stack_has_counters(continuation_stack(c)))) + { + make_room_for_cc_stack(sc); + copy_stack(sc, sc->stack, continuation_stack(c), continuation_stack_top(c)); + } + else + { + s7_pointer *nv = stack_elements(sc->stack); + s7_pointer *ov = stack_elements(continuation_stack(c)); + memcpy((void *)nv, (void *)ov, continuation_stack_top(c) * sizeof(s7_pointer)); + } + /* copy_stack(sc, sc->stack, continuation_stack(c), continuation_stack_top(c)); */ + sc->stack_end = (s7_pointer *)(sc->stack_start + continuation_stack_top(c)); + + { + int32_t top = continuation_op_loc(c); + s7_pointer *src, *dst; + sc->op_stack_now = (s7_pointer *)(sc->op_stack + top); + sc->op_stack_size = continuation_op_size(c); + sc->op_stack_end = (s7_pointer *)(sc->op_stack + sc->op_stack_size); + src = (s7_pointer *)vector_elements(continuation_op_stack(c)); + dst = sc->op_stack; + for (int32_t i = 0; i < top; i++) dst[i] = src[i]; + } + sc->value = (is_null(sc->args)) ? sc->nil : ((is_null(cdr(sc->args))) ? car(sc->args) : splice_in_values(sc, sc->args)); + } +} + +static s7_pointer g_call_cc(s7_scheme *sc, s7_pointer args) +{ + #define H_call_cc "(call-with-current-continuation (lambda (continuer)...)) is always a mistake!" + #define Q_call_cc s7_make_signature(sc, 2, sc->T, sc->is_procedure_symbol) + + s7_pointer p = car(args); /* this is the procedure passed to call/cc */ + if (!is_t_procedure(p)) /* this includes continuations */ + { + check_method(sc, p, sc->call_cc_symbol, args); + check_method(sc, p, sc->call_with_current_continuation_symbol, args); + sole_arg_wrong_type_error_nr(sc, sc->call_cc_symbol, p, a_procedure_string); + } + if (((!is_closure(p)) || + (closure_arity(p) != 1)) && + (!s7_is_aritable(sc, p, 1))) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_2(sc, wrap_string(sc, "call/cc procedure, ~A, should take one argument", 47), p)); + + sc->w = s7_make_continuation(sc); + if ((is_any_closure(p)) && (is_pair(closure_args(p))) && (is_symbol(car(closure_args(p))))) + continuation_name(sc->w) = car(closure_args(p)); + push_stack(sc, OP_APPLY, list_1_unchecked(sc, sc->w), p); /* apply function p to continuation sc->w */ + sc->w = sc->unused; + return(sc->nil); +} + +/* we can't naively optimize call/cc to call-with-exit if the continuation is only + * used as a function in the call/cc body because it might (for example) be wrapped + * in a lambda form that is being exported. See b-func in s7test for an example. + */ + +static void op_call_cc(s7_scheme *sc) +{ + sc->w = s7_make_continuation(sc); + continuation_name(sc->w) = caar(opt2_pair(sc->code)); /* caadadr(sc->code) */ + sc->curlet = inline_make_let_with_slot(sc, sc->curlet, continuation_name(sc->w), sc->w); + sc->w = sc->unused; + sc->code = cdr(opt2_pair(sc->code)); /* cddadr(sc->code) */ +} + +static bool op_implicit_continuation_a(s7_scheme *sc) +{ + s7_pointer code = sc->code; /* dumb-looking code, but it's faster than the pretty version, according to callgrind */ + s7_pointer s = lookup_checked(sc, car(code)); + if (!is_continuation(s)) {sc->last_function = s; return(false);} + sc->code = s; + sc->args = set_plist_1(sc, fx_call(sc, cdr(code))); + call_with_current_continuation(sc); + return(true); +} + + +/* -------------------------------- call-with-exit -------------------------------- */ +static void pop_input_port(s7_scheme *sc); +static s7_pointer dynamic_unwind(s7_scheme *sc, s7_pointer func, s7_pointer e); + +static void call_with_exit(s7_scheme *sc) +{ + int64_t i, new_stack_top, quit = 0; + + if (!call_exit_active(sc->code)) + error_nr(sc, sc->invalid_escape_function_symbol, + set_elist_1(sc, wrap_string(sc, "call-with-exit escape procedure called outside its block", 56))); + + call_exit_active(sc->code) = false; + new_stack_top = call_exit_goto_loc(sc->code); + sc->op_stack_now = (s7_pointer *)(sc->op_stack + call_exit_op_loc(sc->code)); + + /* look for dynamic-wind in the stack section that we are jumping out of */ + i = stack_top(sc) - 1; + do { + switch (stack_op(sc->stack, i)) /* avoidable if we group these ops at the end and use op< */ + { + case OP_DYNAMIC_WIND: + { + s7_pointer lx = T_Dyn(stack_code(sc->stack, i)); + if (dynamic_wind_state(lx) == DWIND_BODY) + { + dynamic_wind_state(lx) = DWIND_FINISH; + if (dynamic_wind_out(lx) != sc->F) + { + s7_pointer arg = (sc->args == sc->plist_1) ? car(sc->plist_1) : sc->unused; /* might also need GC protection here */ + /* protect the sc->args value across this call if it is sc->plist_1 -- I can't find a broken case */ + sc->value = s7_call(sc, dynamic_wind_out(lx), sc->nil); + if (arg != sc->unused) set_plist_1(sc, arg); + }}} + break; + + case OP_DYNAMIC_UNWIND: + case OP_DYNAMIC_UNWIND_PROFILE: + set_stack_op(sc->stack, i, OP_GC_PROTECT); + dynamic_unwind(sc, stack_code(sc->stack, i), stack_args(sc->stack, i)); + break; + + case OP_EVAL_STRING: + s7_close_input_port(sc, current_input_port(sc)); + pop_input_port(sc); + break; + + case OP_BARRIER: /* oops -- we almost certainly went too far */ + goto SET_VALUE; + + case OP_DEACTIVATE_GOTO: /* here we're jumping into an unrelated call-with-exit block */ + call_exit_active(stack_args(sc->stack, i)) = false; + break; + + case OP_LET_TEMP_DONE: + { + s7_pointer old_args = sc->args; + let_temp_done(sc, stack_args(sc->stack, i), stack_let(sc->stack, i)); + sc->args = old_args; + } + break; + + case OP_LET_TEMP_UNWIND: + let_temp_unwind(sc, stack_code(sc->stack, i), stack_args(sc->stack, i)); + break; + + case OP_LET_TEMP_S7_UNWIND: + s7_starlet_set_1(sc, T_Sym(stack_code(sc->stack, i)), stack_args(sc->stack, i)); + break; + + case OP_LET_TEMP_S7_DIRECT_UNWIND: + sc->has_openlets = (stack_args(sc->stack, i) != sc->F); + break; + + /* call/cc does not close files, but I think call-with-exit should */ + case OP_GET_OUTPUT_STRING: + case OP_UNWIND_OUTPUT: + { + s7_pointer x = T_Pro(stack_code(sc->stack, i)); /* "code" = port that we opened */ + s7_close_output_port(sc, x); + x = stack_args(sc->stack, i); /* "args" = port that we shadowed, if not # */ + if (x != sc->unused) + set_current_output_port(sc, x); + } + break; + + case OP_UNWIND_INPUT: + s7_close_input_port(sc, T_Pri(stack_code(sc->stack, i))); /* "code" = port that we opened */ + if (stack_args(sc->stack, i) != sc->unused) + set_current_input_port(sc, stack_args(sc->stack, i)); /* "args" = port that we shadowed */ + break; + + case OP_EVAL_DONE: /* goto called in a method -- put off the inner eval return(s) until we clean up the stack */ + quit++; + break; + + default: + if ((S7_DEBUGGING) && (stack_op(sc->stack, i) == OP_MAP_UNWIND)) fprintf(stderr, "%s[%d]: unwind %" ld64 "\n", __func__, __LINE__, sc->map_call_ctr); + break; + } + i -= 4; + } while (i > new_stack_top); + + SET_VALUE: + sc->stack_end = (s7_pointer *)(sc->stack_start + new_stack_top); + + /* the return value should have an implicit values call, just as in call/cc */ + sc->value = (is_null(sc->args)) ? sc->nil : ((is_null(cdr(sc->args))) ? car(sc->args) : splice_in_values(sc, sc->args)); + if (quit > 0) + { + if (sc->longjmp_ok) + { + pop_stack(sc); + LongJmp(*(sc->goto_start), CALL_WITH_EXIT_JUMP); + } + for (i = 0; i < quit; i++) + push_stack_op_let(sc, OP_EVAL_DONE); + } +} + +static s7_pointer g_is_goto(s7_scheme *sc, s7_pointer args) +{ + #define H_is_goto "(goto? obj) returns #t if obj is a call-with-exit exit function" + #define Q_is_goto sc->pl_bt + return(make_boolean(sc, is_goto(car(args)))); +} + +static inline s7_pointer make_goto(s7_scheme *sc, s7_pointer name) /* inline for 73=1% in tgc */ +{ + s7_pointer x; + new_cell(sc, x, T_GOTO); + call_exit_goto_loc(x) = stack_top(sc); + call_exit_op_loc(x) = (int32_t)(sc->op_stack_now - sc->op_stack); + call_exit_active(x) = true; + call_exit_name(x) = name; + return(x); +} + +static s7_pointer g_call_with_exit(s7_scheme *sc, s7_pointer args) /* (call-with-exit (lambda (return) ...)) */ +{ + #define H_call_with_exit "(call-with-exit (lambda (exiter) ...)) is call/cc without the ability to jump back into a previous computation." + #define Q_call_with_exit s7_make_signature(sc, 2, sc->values_symbol, sc->is_procedure_symbol) + + s7_pointer p = car(args), x; + if (is_any_closure(p)) + { + x = make_goto(sc, ((is_pair(closure_args(p))) && (is_symbol(car(closure_args(p))))) ? car(closure_args(p)) : sc->F); + push_stack(sc, OP_DEACTIVATE_GOTO, x, p); /* this means call-with-exit is not tail-recursive */ + push_stack(sc, OP_APPLY, cons_unchecked(sc, x, sc->nil), p); + return(sc->nil); + } + /* maybe just return an error here -- these gotos as args are stupid; also an error above if closure not aritable 1 */ + if (!is_t_procedure(p)) + return(method_or_bust_p(sc, p, sc->call_with_exit_symbol, a_procedure_string)); + if (!s7_is_aritable(sc, p, 1)) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_2(sc, wrap_string(sc, "call-with-exit argument should be a function of one argument: ~S", 64), p)); + x = make_goto(sc, sc->F); + call_exit_active(x) = false; + return((is_c_function(p)) ? c_function_call(p)(sc, set_plist_1(sc, x)) : s7_apply_function_star(sc, p, set_plist_1(sc, x))); +} + +static inline void op_call_with_exit(s7_scheme *sc) +{ + s7_pointer args = opt2_pair(sc->code); + s7_pointer go = make_goto(sc, caar(args)); + push_stack_no_let_no_code(sc, OP_DEACTIVATE_GOTO, go); /* was also pushing code */ + sc->curlet = inline_make_let_with_slot(sc, sc->curlet, caar(args), go); + sc->code = T_Pair(cdr(args)); +} + +static void op_call_with_exit_o(s7_scheme *sc) +{ + op_call_with_exit(sc); + sc->code = car(sc->code); +} + +static bool op_implicit_goto(s7_scheme *sc) +{ + s7_pointer g = lookup_checked(sc, car(sc->code)); + if (!is_goto(g)) {sc->last_function = g; return(false);} + sc->args = sc->nil; + sc->code = g; + call_with_exit(sc); + return(true); +} + +static bool op_implicit_goto_a(s7_scheme *sc) +{ + s7_pointer g = lookup_checked(sc, car(sc->code)); + if (!is_goto(g)) {sc->last_function = g; return(false);} + sc->args = set_plist_1(sc, fx_call(sc, cdr(sc->code))); + sc->code = g; + call_with_exit(sc); + return(true); +} + + +/* -------------------------------- numbers -------------------------------- */ +static block_t *string_to_block(s7_scheme *sc, const char *p, s7_int len) +{ + block_t *b = inline_mallocate(sc, len + 1); + char *bp = (char *)block_data(b); + memcpy((void *)bp, (const void *)p, len); + bp[len] = '\0'; + return(b); +} + +static Inline s7_pointer inline_block_to_string(s7_scheme *sc, block_t *block, s7_int len) +{ + s7_pointer x; + new_cell(sc, x, T_STRING | T_SAFE_PROCEDURE); + string_block(x) = block; + string_value(x) = (char *)block_data(block); + string_length(x) = len; + string_value(x)[len] = '\0'; + string_hash(x) = 0; + add_string(sc, x); + return(x); +} + +static s7_pointer block_to_string(s7_scheme *sc, block_t *block, s7_int len) {return(inline_block_to_string(sc, block, len));} + +static inline s7_pointer make_simple_ratio(s7_scheme *sc, s7_int num, s7_int den) +{ + s7_pointer x; + if (den == 1) + return(make_integer(sc, num)); + if (den == -1) + return(make_integer(sc, -num)); + if ((den == S7_INT64_MIN) && ((num & 1) != 0)) + return(make_real(sc, (long_double)num / (long_double)den)); + new_cell(sc, x, T_RATIO); + if (den < 0) /* this is noticeably faster in callgrind than using (den < 0) ? ... twice */ + { + numerator(x) = -num; + denominator(x) = -den; + } + else + { + numerator(x) = num; + denominator(x) = den; + } + return(x); +} + +static bool is_zero(s7_pointer x); +static bool is_positive(s7_scheme *sc, s7_pointer x); +static bool is_negative(s7_scheme *sc, s7_pointer x); +static s7_pointer make_ratio(s7_scheme *sc, s7_int a, s7_int b); + +static bool is_NaN(s7_double x) {return(x != x);} +/* callgrind says this is faster than isnan, I think (very confusing data...) */ + +#if defined(__sun) && defined(__SVR4) + static bool is_inf(s7_double x) {return((x == x) && (is_NaN(x - x)));} /* there's no isinf in Solaris */ +#else +#if (!MS_WINDOWS) + #if __cplusplus + #define is_inf(x) std::isinf(x) + #else + #define is_inf(x) isinf(x) + #endif +#else + static bool is_inf(s7_double x) {return((x == x) && (is_NaN(x - x)));} /* Another possibility: (x * 0) != 0 */ + +#if (_MSC_VER < 1700) + /* in MS C, we need to provide inverse hyperbolic trig funcs and cbrt */ + static double asinh(double x) {return(log(x + sqrt(1.0 + x * x)));} + static double acosh(double x) {return(log(x + sqrt(x * x - 1.0)));} + /* perhaps less prone to numerical troubles (untested): 2.0 * log(sqrt(0.5 * (x + 1.0)) + sqrt(0.5 * (x - 1.0))) */ + static double atanh(double x) {return(log((1.0 + x) / (1.0 - x)) / 2.0);} + static double cbrt(double x) {if (x >= 0.0) return(pow(x, 1.0 / 3.0)); return(-pow(-x, 1.0 / 3.0));} +#endif +#endif /* windows */ +#endif /* not sun */ + + +/* -------------------------------- NaN payloads -------------------------------- */ +typedef union {int64_t ix; double fx;} decode_float_t; + +static double nan_with_payload(int64_t payload) +{ + decode_float_t num; + if (payload <= 0) return(NAN); + num.fx = NAN; + num.ix = num.ix | payload; + return(num.fx); +} + +static s7_pointer make_nan_with_payload(s7_scheme *sc, s7_int payload) +{ + s7_pointer x = make_real(sc, nan_with_payload(payload)); + char buf[32]; + s7_int nlen = 0; + nlen = snprintf(buf, 32, "+nan.%" ld64, payload); + set_number_name(x, buf, nlen); + return(x); +} + +static s7_pointer g_nan(s7_scheme *sc, s7_pointer args) +{ + #define H_nan "(nan int) returns a NaN with payload int" + #define Q_nan s7_make_signature(sc, 2, sc->is_real_symbol, sc->is_integer_symbol) + #define NAN_PAYLOAD_LIMIT (1LL << 51LL) /* 53 is probably ok, (nan (- (ash 1 53) 1)): +nan.9007199254740991 -- 52 bits available? */ + s7_pointer x; + if (is_null(args)) return(real_NaN); + x = car(args); + if (!is_t_integer(x)) + sole_arg_wrong_type_error_nr(sc, sc->nan_symbol, x, sc->type_names[T_INTEGER]); + if (integer(x) < 0) + sole_arg_out_of_range_error_nr(sc, sc->nan_symbol, set_elist_1(sc, x), it_is_negative_string); + if (integer(x) >= NAN_PAYLOAD_LIMIT) + sole_arg_out_of_range_error_nr(sc, sc->nan_symbol, set_elist_1(sc, x), it_is_too_large_string); + return(make_nan_with_payload(sc, integer(x))); +} + +static s7_int nan_payload(double x) +{ + decode_float_t num; + num.fx = x; + return(num.ix & 0xffffffffffff); +} + +static s7_pointer g_nan_payload(s7_scheme *sc, s7_pointer args) +{ + #define H_nan_payload "(nan-payload x) returns the payload associated with the NaN x" + #define Q_nan_payload s7_make_signature(sc, 2, sc->is_integer_symbol, sc->is_real_symbol) + s7_pointer x = car(args); + if ((!is_t_real(x)) || (!is_NaN(real(x)))) /* for complex case, use real-part etc (see s7test.scm) */ + sole_arg_wrong_type_error_nr(sc, sc->nan_payload_symbol, x, wrap_string(sc, "a NaN", 5)); + return(make_integer(sc, nan_payload(real(x)))); +} + +/* no similar support for +inf.0 because inf is just a single bit pattern in ieee754 */ + + +/* -------- gmp stuff -------- */ +#if WITH_GMP +static mp_prec_t mpc_precision = DEFAULT_BIGNUM_PRECISION; +static mp_prec_t mpc_set_default_precision(mp_prec_t prec) {mpc_precision = prec; return(prec);} +#define mpc_init(Z) mpc_init2(Z, mpc_precision) + +static bigint *alloc_bigint(s7_scheme *sc) +{ + bigint *p; + if (sc->bigints) + { + p = sc->bigints; + sc->bigints = p->nxt; + } + else + { + p = (bigint *)Malloc(sizeof(bigint)); + /* not permalloc here: gmp must be playing tricky games with realloc or something. permalloc can lead + * to mpz_set_si overwriting adjacent memory (valgrind does not catch this), clobbering at least the + * bigint nxt field. Someday I need to look at the source. + */ + mpz_init(p->n); + } + return(p); +} + +static bigrat *alloc_bigrat(s7_scheme *sc) +{ + bigrat *p; + if (sc->bigrats) + { + p = sc->bigrats; + sc->bigrats = p->nxt; + } + else + { + p = (bigrat *)Malloc(sizeof(bigrat)); + mpq_init(p->q); + } + return(p); +} + +static bigflt *alloc_bigflt(s7_scheme *sc) +{ + bigflt *p; + if (sc->bigflts) + { + p = sc->bigflts; + sc->bigflts = p->nxt; + mpfr_set_prec(p->x, sc->bignum_precision); + } + else + { + p = (bigflt *)Malloc(sizeof(bigflt)); + mpfr_init2(p->x, sc->bignum_precision); + } + return(p); +} + +static bigcmp *alloc_bigcmp(s7_scheme *sc) +{ + bigcmp *p; + if (sc->bigcmps) + { + p = sc->bigcmps; + sc->bigcmps = p->nxt; + mpc_set_prec(p->z, sc->bignum_precision); + } + else + { + p = (bigcmp *)Malloc(sizeof(bigcmp)); + mpc_init(p->z); + } + return(p); +} + +static s7_pointer mpz_to_big_integer(s7_scheme *sc, mpz_t val) +{ + s7_pointer x; + new_cell(sc, x, T_BIG_INTEGER); + big_integer_bgi(x) = alloc_bigint(sc); + mpz_set(big_integer(x), val); + add_big_integer(sc, x); + return(x); +} + +static s7_pointer mpz_to_integer(s7_scheme *sc, mpz_t val) +{ + if (mpz_fits_slong_p(val)) + return(make_integer(sc, mpz_get_si(val))); + return(mpz_to_big_integer(sc, val)); +} + +#if (!WITH_PURE_S7) +static s7_pointer mpz_to_big_real(s7_scheme *sc, mpz_t val) +{ + s7_pointer x; + new_cell(sc, x, T_BIG_REAL); + big_real_bgf(x) = alloc_bigflt(sc); + add_big_real(sc, x); + mpfr_set_z(big_real(x), val, MPFR_RNDN); + return(x); +} +#endif + +static s7_pointer mpq_to_big_ratio(s7_scheme *sc, mpq_t val) +{ + s7_pointer x; + new_cell(sc, x, T_BIG_RATIO); + big_ratio_bgr(x) = alloc_bigrat(sc); + add_big_ratio(sc, x); + mpq_set(big_ratio(x), val); + return(x); +} + +static s7_pointer mpq_to_rational(s7_scheme *sc, mpq_t val) +{ + if (mpz_cmp_ui(mpq_denref(val), 1) == 0) + return(mpz_to_integer(sc, mpq_numref(val))); +#if S7_DEBUGGING + mpq_canonicalize(val); + if (mpz_cmp_ui(mpq_denref(val), 1) == 0) + { + fprintf(stderr, "mpq_to_rational: missing canonicalize\n"); + return(mpz_to_integer(sc, mpq_numref(val))); + } +#endif + if ((mpz_fits_slong_p(mpq_numref(val))) && (mpz_fits_slong_p(mpq_denref(val)))) + return(make_simple_ratio(sc, mpz_get_si(mpq_numref(val)), mpz_get_si(mpq_denref(val)))); + return(mpq_to_big_ratio(sc, val)); +} + +static s7_pointer mpq_to_canonicalized_rational(s7_scheme *sc, mpq_t mpq) +{ + mpq_canonicalize(mpq); + return(mpq_to_rational(sc, mpq)); +} + +static s7_pointer mpz_to_rational(s7_scheme *sc, mpz_t n, mpz_t d) /* mpz_3 and mpz_4 */ +{ + if (mpz_cmp_ui(d, 1) == 0) + return(mpz_to_integer(sc, n)); + mpq_set_num(sc->mpq_1, n); + mpq_set_den(sc->mpq_1, d); + return(mpq_to_canonicalized_rational(sc, sc->mpq_1)); +} + +#if (!WITH_PURE_S7) +static s7_pointer mpq_to_big_real(s7_scheme *sc, mpq_t val) +{ + s7_pointer x; + new_cell(sc, x, T_BIG_REAL); + big_real_bgf(x) = alloc_bigflt(sc); + add_big_real(sc, x); + mpfr_set_q(big_real(x), val, MPFR_RNDN); + return(x); +} +#endif + +static s7_pointer any_rational_to_mpq(s7_scheme *sc, s7_pointer z, mpq_t bigq) +{ + switch (type(z)) + { + case T_INTEGER: mpq_set_si(bigq, integer(z), 1); break; + case T_BIG_INTEGER: mpq_set_z(bigq, big_integer(z)); break; + case T_RATIO: mpq_set_si(bigq, numerator(z), denominator(z)); break; + case T_BIG_RATIO: mpq_set(bigq, big_ratio(z)); break; + } + return(z); +} + +static s7_pointer mpfr_to_integer(s7_scheme *sc, mpfr_t val) +{ + mpfr_get_z(sc->mpz_4, val, MPFR_RNDN); + return(mpz_to_integer(sc, sc->mpz_4)); +} + +static s7_pointer mpfr_to_big_real(s7_scheme *sc, mpfr_t val) +{ + s7_pointer x; + new_cell(sc, x, T_BIG_REAL); + add_big_real(sc, x); + big_real_bgf(x) = alloc_bigflt(sc); + mpfr_set(big_real(x), val, MPFR_RNDN); + return(x); +} + +static s7_pointer mpc_to_number(s7_scheme *sc, mpc_t val) +{ + s7_pointer x; + if (mpfr_zero_p(mpc_imagref(val))) + return(mpfr_to_big_real(sc, mpc_realref(val))); + new_cell(sc, x, T_BIG_COMPLEX); + big_complex_bgc(x) = alloc_bigcmp(sc); + add_big_complex(sc, x); + mpc_set(big_complex(x), val, MPC_RNDNN); + return(x); +} + +/* s7.h */ +mpz_t *s7_big_integer(s7_pointer x) {return(&big_integer(x));} +mpq_t *s7_big_ratio(s7_pointer x) {return(&big_ratio(x));} +mpfr_t *s7_big_real(s7_pointer x) {return(&big_real(x));} +mpc_t *s7_big_complex(s7_pointer x) {return(&big_complex(x));} + +bool s7_is_big_integer(s7_pointer x) {return(is_t_big_integer(x));} +bool s7_is_big_ratio(s7_pointer x) {return(is_t_big_ratio(x));} +bool s7_is_big_real(s7_pointer x) {return(is_t_big_real(x));} +bool s7_is_big_complex(s7_pointer x) {return(is_t_big_complex(x));} + +s7_pointer s7_make_big_integer(s7_scheme *sc, mpz_t *val) {return(mpz_to_integer(sc, *val));} +s7_pointer s7_make_big_ratio(s7_scheme *sc, mpq_t *val) {return(mpq_to_rational(sc, *val));} +s7_pointer s7_make_big_real(s7_scheme *sc, mpfr_t *val) {return(mpfr_to_big_real(sc, *val));} +s7_pointer s7_make_big_complex(s7_scheme *sc, mpc_t *val) {return(mpc_to_number(sc, *val));} + +#if (!WITH_PURE_S7) +static s7_pointer big_integer_to_big_real(s7_scheme *sc, s7_pointer x) {return(mpz_to_big_real(sc, big_integer(x)));} +static s7_pointer big_ratio_to_big_real(s7_scheme *sc, s7_pointer x) {return(mpq_to_big_real(sc, big_ratio(x)));} +#endif + +static s7_pointer s7_int_to_big_integer(s7_scheme *sc, s7_int val) +{ + s7_pointer x; + new_cell(sc, x, T_BIG_INTEGER); + big_integer_bgi(x) = alloc_bigint(sc); + mpz_set_si(big_integer(x), val); + add_big_integer(sc, x); + return(x); +} + +static s7_pointer s7_int_to_big_ratio(s7_scheme *sc, s7_int num, s7_int den) +{ + /* (called only in g_bignum), den here always comes from denominator(x) or some positive constant so it is not negative */ + s7_pointer x; + new_cell(sc, x, T_BIG_RATIO); + big_ratio_bgr(x) = alloc_bigrat(sc); + add_big_ratio(sc, x); + mpq_set_si(big_ratio(x), num, den); + return(x); +} + +static s7_pointer s7_double_to_big_real(s7_scheme *sc, s7_double rl) +{ + s7_pointer x; + new_cell(sc, x, T_BIG_REAL); + big_real_bgf(x) = alloc_bigflt(sc); + add_big_real(sc, x); + mpfr_set_d(big_real(x), rl, MPFR_RNDN); + return(x); +} + +static s7_pointer s7_double_to_big_complex(s7_scheme *sc, s7_double rl, s7_double im) +{ + s7_pointer x; + new_cell(sc, x, T_BIG_COMPLEX); + add_big_complex(sc, x); + big_complex_bgc(x) = alloc_bigcmp(sc); + mpc_set_d_d(big_complex(x), rl, im, MPC_RNDNN); + return(x); +} + +static s7_pointer big_pi(s7_scheme *sc) +{ + s7_pointer x; + new_cell(sc, x, T_BIG_REAL | T_IMMUTABLE); + big_real_bgf(x) = alloc_bigflt(sc); + add_big_real(sc, x); + mpfr_const_pi(big_real(x), MPFR_RNDN); + return(x); +} + +static bool is_integer_via_method(s7_scheme *sc, s7_pointer p) +{ + if (s7_is_integer(p)) + return(true); + if (has_active_methods(sc, p)) + { + s7_pointer f = find_method_with_let(sc, p, sc->is_integer_symbol); + if (f != sc->undefined) + return(is_true(sc, s7_apply_function(sc, f, set_plist_1(sc, p)))); + } + return(false); +} + +#if (!WITH_PURE_S7) +static s7_pointer s7_number_to_big_real(s7_scheme *sc, s7_pointer p) +{ + s7_pointer x; + new_cell(sc, x, T_BIG_REAL); + big_real_bgf(x) = alloc_bigflt(sc); + add_big_real(sc, x); + + switch (type(p)) + { + case T_INTEGER: + mpfr_set_si(big_real(x), integer(p), MPFR_RNDN); + break; + case T_RATIO: + /* here we can't use fraction(number(p)) even though that uses long_double division because + * there are lots of int64_t ratios that will still look the same. + * We have to do the actual bignum divide by hand. + */ + mpq_set_si(sc->mpq_1, numerator(p), denominator(p)); + mpfr_set_q(big_real(x), sc->mpq_1, MPFR_RNDN); + break; + default: + mpfr_set_d(big_real(x), s7_real(p), MPFR_RNDN); + break; + } + return(x); +} +#endif + +static s7_pointer s7_number_to_big_complex(s7_scheme *sc, s7_pointer p) +{ + s7_pointer x; + new_cell(sc, x, T_BIG_COMPLEX); + big_complex_bgc(x) = alloc_bigcmp(sc); + add_big_complex(sc, x); + + switch (type(p)) + { + case T_INTEGER: + mpc_set_si(big_complex(x), integer(p), MPC_RNDNN); + break; + case T_RATIO: + /* can't use fraction here */ + mpq_set_si(sc->mpq_1, numerator(p), denominator(p)); + mpfr_set_q(sc->mpfr_1, sc->mpq_1, MPFR_RNDN); + mpc_set_fr(big_complex(x), sc->mpfr_1, MPC_RNDNN); + break; + case T_REAL: + mpc_set_d(big_complex(x), s7_real(p), MPC_RNDNN); + break; + default: + mpc_set_d_d(big_complex(x), real_part(p), imag_part(p), MPC_RNDNN); + break; + } + return(x); +} + +static s7_pointer any_real_to_mpfr(s7_scheme *sc, s7_pointer p, mpfr_t bigx) +{ + switch (type(p)) + { + case T_INTEGER: + mpfr_set_si(bigx, integer(p), MPFR_RNDN); + break; + case T_RATIO: + mpq_set_si(sc->mpq_1, numerator(p), denominator(p)); + mpfr_set_q(bigx, sc->mpq_1, MPFR_RNDN); + break; + case T_REAL: + mpfr_set_d(bigx, real(p), MPFR_RNDN); + if (is_NaN(real(p))) return(real_NaN); + if (is_inf(real(p))) return(real_infinity); + break; + case T_BIG_INTEGER: + mpfr_set_z(bigx, big_integer(p), MPFR_RNDN); + break; + case T_BIG_RATIO: + mpfr_set_q(bigx, big_ratio(p), MPFR_RNDN); + break; + case T_BIG_REAL: + mpfr_set(bigx, big_real(p), MPFR_RNDN); + if (mpfr_nan_p(big_real(p))) return(real_NaN); + if (mpfr_inf_p(big_real(p))) return(real_infinity); + break; + } + return(NULL); +} + +#define mpc_zero_p(z) ((mpfr_zero_p(mpc_realref(z))) && (mpfr_zero_p(mpc_imagref(z)))) + +static s7_pointer any_number_to_mpc(s7_scheme *sc, s7_pointer p, mpc_t bigz) +{ + switch (type(p)) + { + case T_INTEGER: + mpc_set_si(bigz, integer(p), MPC_RNDNN); + break; + case T_RATIO: + mpq_set_si(sc->mpq_1, numerator(p), denominator(p)); + mpc_set_q(bigz, sc->mpq_1, MPC_RNDNN); + break; + case T_REAL: + if (is_NaN(real(p))) return(real_NaN); + if (is_inf(real(p))) return(real_infinity); + mpc_set_d(bigz, real(p), MPC_RNDNN); + break; + case T_COMPLEX: + if (is_NaN(imag_part(p))) return(complex_NaN); + if (is_NaN(real_part(p))) return(real_NaN); + mpc_set_d_d(bigz, real_part(p), imag_part(p), MPC_RNDNN); + break; + case T_BIG_INTEGER: + mpc_set_z(bigz, big_integer(p), MPC_RNDNN); + break; + case T_BIG_RATIO: + mpc_set_q(bigz, big_ratio(p), MPC_RNDNN); + break; + case T_BIG_REAL: + mpc_set_fr(bigz, big_real(p), MPC_RNDNN); + if (mpfr_nan_p(big_real(p))) return(real_NaN); + if (mpfr_inf_p(big_real(p))) return(real_infinity); + break; + case T_BIG_COMPLEX: + if (mpfr_nan_p(mpc_imagref(big_complex(p)))) return(complex_NaN); + if (mpfr_nan_p(mpc_realref(big_complex(p)))) return(real_NaN); + mpc_set(bigz, big_complex(p), MPC_RNDNN); + break; + } + return(NULL); +} + +static s7_pointer make_big_complex(s7_scheme *sc, mpfr_t rl, mpfr_t im) +{ + /* there is no mpc_get_str equivalent, so we need to split up str, use make_big_real to get the 2 halves, then mpc_init, then mpc_set_fr_fr */ + s7_pointer x; + new_cell(sc, x, T_BIG_COMPLEX); + big_complex_bgc(x) = alloc_bigcmp(sc); + add_big_complex(sc, x); + mpc_set_fr_fr(big_complex(x), rl ,im, MPC_RNDNN); + return(x); +} + +static block_t *mpfr_to_string(s7_scheme *sc, mpfr_t val, int32_t radix) +{ + char *str; + mp_exp_t expptr; + int32_t ep; + s7_int i, len; + block_t *b, *btmp; + + if (mpfr_zero_p(val)) + return(string_to_block(sc, "0.0", 3)); + if (mpfr_nan_p(val)) + return(string_to_block(sc, "+nan.0", 6)); + if (mpfr_inf_p(val)) + return((mpfr_signbit(val) == 0) ? string_to_block(sc, "+inf.0", 6) : string_to_block(sc, "-inf.0", 6)); + + b = callocate(sc, sc->bignum_precision + 32); + str = mpfr_get_str((char *)block_data(b), &expptr, radix, 0, val, MPFR_RNDN); + ep = (int32_t)expptr; + len = safe_strlen(str); + + /* remove trailing 0's */ + for (i = len - 1; i > 3; i--) + if (str[i] != '0') + break; + if (i < len - 1) + str[i + 1] = '\0'; + + btmp = mallocate(sc, len + 64); + if (str[0] == '-') + snprintf((char *)block_data(btmp), len + 64, "-%c.%s%c%d", str[1], (char *)(str + 2), (radix <= 10) ? 'E' : '@', ep - 1); + else snprintf((char *)block_data(btmp), len + 64, "%c.%s%c%d", str[0], (char *)(str + 1), (radix <= 10) ? 'E' : '@', ep - 1); + + liberate(sc, b); + return(btmp); +} + +static block_t *mpc_to_string(s7_scheme *sc, mpc_t val, int32_t radix, use_write_t use_write) +{ + block_t *rl, *im, *tmp; + s7_int len; + + mpc_real(sc->mpfr_1, val, MPFR_RNDN); + rl = mpfr_to_string(sc, sc->mpfr_1, radix); + mpc_imag(sc->mpfr_2, val, MPFR_RNDN); + im = mpfr_to_string(sc, sc->mpfr_2, radix); + + len = safe_strlen((char *)block_data(rl)) + safe_strlen((char *)block_data(im)) + 128; + tmp = mallocate(sc, len); + snprintf((char *)block_data(tmp), len, "%s%s%si", + (char *)block_data(rl), + ((((char *)block_data(im))[0] == '-') || (((char *)block_data(im))[0] == '+')) ? "" : "+", (char *)block_data(im)); + + liberate(sc, rl); + liberate(sc, im); + return(tmp); +} + +static block_t *big_number_to_string_with_radix(s7_scheme *sc, s7_pointer p, int32_t radix, s7_int width, s7_int *nlen, use_write_t use_write) +{ + block_t *str; + switch (type(p)) + { + case T_BIG_INTEGER: + str = callocate(sc, mpz_sizeinbase(big_integer(p), radix) + 64); + mpz_get_str((char *)block_data(str), radix, big_integer(p)); + break; + case T_BIG_RATIO: + mpz_set(sc->mpz_1, mpq_numref(big_ratio(p))); + mpz_set(sc->mpz_2, mpq_denref(big_ratio(p))); + str = callocate(sc, mpz_sizeinbase(sc->mpz_1, radix) + mpz_sizeinbase(sc->mpz_2, radix) + 64); + mpq_get_str((char *)block_data(str), radix, big_ratio(p)); + break; + case T_BIG_REAL: + str = mpfr_to_string(sc, big_real(p), radix); + break; + default: + str = mpc_to_string(sc, big_complex(p), radix, use_write); + break; + } + if (width > 0) + { + s7_int len = safe_strlen((char *)block_data(str)); + if (width > len) + { + int32_t spaces = width - len; + block_t *tmp = (block_t *)mallocate(sc, width + 1); + ((char *)block_data(tmp))[width] = '\0'; + memmove((void *)((char *)block_data(tmp) + spaces), (void *)block_data(str), len); + local_memset((void *)block_data(tmp), (int)' ', spaces); + (*nlen) = width; + liberate(sc, str); + return(tmp); + } + (*nlen) = len; + } + else (*nlen) = safe_strlen((char *)block_data(str)); + return(str); +} + +static s7_pointer string_to_big_integer(s7_scheme *sc, const char *str, int32_t radix) +{ + mpz_set_str(sc->mpz_4, (str[0] == '+') ? (const char *)(str + 1) : str, radix); + return(mpz_to_integer(sc, sc->mpz_4)); +} + +static s7_pointer string_to_big_ratio(s7_scheme *sc, const char *str, int32_t radix) +{ + s7_pointer x; + mpq_set_str(sc->mpq_1, str, radix); + mpq_canonicalize(sc->mpq_1); + if (mpz_cmp_ui(mpq_denref(sc->mpq_1), 1) == 0) + return(mpz_to_integer(sc, mpq_numref(sc->mpq_1))); + new_cell(sc, x, T_BIG_RATIO); + big_ratio_bgr(x) = alloc_bigrat(sc); + add_big_ratio(sc, x); + mpq_set(big_ratio(x), sc->mpq_1); + return(x); +} + +static s7_pointer string_to_big_real(s7_scheme *sc, const char *str, int32_t radix) +{ + s7_pointer x; + new_cell(sc, x, T_BIG_REAL); + big_real_bgf(x) = alloc_bigflt(sc); + add_big_real(sc, x); + mpfr_set_str(big_real(x), str, radix, MPFR_RNDN); + return(x); +} + +static s7_int string_to_integer(const char *str, int32_t radix, bool *overflow); + +static s7_pointer string_to_either_integer(s7_scheme *sc, const char *str, int32_t radix) +{ + bool overflow = false; + s7_int val = string_to_integer(str, radix, &overflow); + if (!overflow) + return(make_integer(sc, val)); + return(string_to_big_integer(sc, str, radix)); +} + +static s7_pointer string_to_either_ratio(s7_scheme *sc, const char *nstr, const char *dstr, int32_t radix) +{ + bool overflow = false; + /* gmp segfaults if passed a bignum/0 so this needs to check first that the denominator is not 0 before letting gmp screw up. + * Also, if the first character is '+', gmp returns 0! + */ + s7_int d = string_to_integer(dstr, radix, &overflow); + if (!overflow) + { + s7_int n; + if (d == 0) return(real_NaN); + n = string_to_integer(nstr, radix, &overflow); + if (!overflow) + return(make_ratio(sc, n, d)); + } + if (nstr[0] == '+') + return(string_to_big_ratio(sc, (const char *)(nstr + 1), radix)); + return(string_to_big_ratio(sc, nstr, radix)); +} + +static s7_double string_to_double_with_radix(const char *ur_str, int32_t radix, bool *overflow); + +static s7_pointer string_to_either_real(s7_scheme *sc, const char *str, int32_t radix) +{ + bool overflow = false; + s7_double val = string_to_double_with_radix((char *)str, radix, &overflow); + if (!overflow) return(make_real(sc, val)); + return(string_to_big_real(sc, str, radix)); +} + +static s7_pointer string_to_either_complex_1(s7_scheme *sc, char *q, char *slash1, char *ex1, bool has_dec_point1, int32_t radix, s7_double *d_rl) +{ + bool overflow = false; + /* there's a real problem here -- we don't want to promote s7_double .1 to a bignum because + * its low order digits are garbage, causing (rationalize .1 0) to return 3602879701896397/36028797018963968 + * no matter what the bignum-precision. But we can't just fallback on gmp's reader because (for example) + * it reads 1/2+i or 1+0/0i as 1.0. Also format gets screwed up. And string->number signals an error + * where it should return #f. I wonder what to do. + */ + if ((has_dec_point1) || + (ex1)) + { + (*d_rl) = string_to_double_with_radix(q, radix, &overflow); + if (overflow) return(string_to_big_real(sc, q, radix)); + } + else + { + if (slash1) + { + s7_int d, n = string_to_integer(q, radix, &overflow); /* q can include the slash and denominator */ + if (overflow) return(string_to_big_ratio(sc, q, radix)); + d = string_to_integer(slash1, radix, &overflow); + if (overflow) return(string_to_big_ratio(sc, q, radix)); + (*d_rl) = (s7_double)n / (s7_double)d; + } + else + { + s7_int val = string_to_integer(q, radix, &overflow); + if (overflow) return(string_to_big_integer(sc, q, radix)); + (*d_rl) = (s7_double)val; + }} + if ((*d_rl) == -0.0) (*d_rl) = 0.0; + return(NULL); +} + +static s7_pointer string_to_either_complex(s7_scheme *sc, char *q, char *slash1, char *ex1, bool has_dec_point1, + char *plus, char *slash2, char *ex2, bool has_dec_point2, + int32_t radix, int32_t has_plus_or_minus) +{ + /* this can be just about anything involving 2 real/ratio/int portions, +/- in between and 'i' at the end */ + double d_rl = 0.0, d_im = 0.0; + s7_pointer p_rl = string_to_either_complex_1(sc, q, slash1, ex1, has_dec_point1, radix, &d_rl); + s7_pointer p_im = string_to_either_complex_1(sc, plus, slash2, ex2, has_dec_point2, radix, &d_im); + + if ((d_im == 0.0) && /* 1.0+0.0000000000000000000000000000i */ + ((!p_im) || (is_zero(p_im)))) + return((p_rl) ? p_rl : make_real(sc, d_rl)); + + if ((!p_rl) && (!p_im)) + return(s7_make_complex(sc, d_rl, (has_plus_or_minus == -1) ? (-d_im) : d_im)); + + if (p_rl) + any_real_to_mpfr(sc, p_rl, sc->mpfr_1); + else mpfr_set_d(sc->mpfr_1, d_rl, MPFR_RNDN); + + if (p_im) + any_real_to_mpfr(sc, p_im, sc->mpfr_2); + else mpfr_set_d(sc->mpfr_2, d_im, MPFR_RNDN); + + if (has_plus_or_minus == -1) + mpfr_neg(sc->mpfr_2, sc->mpfr_2, MPFR_RNDN); + return(make_big_complex(sc, sc->mpfr_1, sc->mpfr_2)); +} + +static bool big_numbers_are_eqv(s7_scheme *sc, s7_pointer a, s7_pointer b) +{ + /* either or both can be big here, but not neither, and types might not match at all */ + switch (type(a)) + { + case T_INTEGER: + return((is_t_big_integer(b)) && (mpz_cmp_si(big_integer(b), integer(a)) == 0)); + case T_BIG_INTEGER: + if (is_t_big_integer(b)) return(mpz_cmp(big_integer(a), big_integer(b)) == 0); + return((is_t_integer(b)) && (mpz_cmp_si(big_integer(a), integer(b)) == 0)); + case T_RATIO: + if (!is_t_big_ratio(b)) return(false); + mpq_set_si(sc->mpq_1, numerator(a), denominator(a)); + return(mpq_equal(sc->mpq_1, big_ratio(b))); + case T_BIG_RATIO: + if (is_t_big_ratio(b)) return(mpq_equal(big_ratio(a), big_ratio(b))); + if (!is_t_ratio(b)) return(false); + mpq_set_si(sc->mpq_1, numerator(b), denominator(b)); + return(mpq_equal(sc->mpq_1, big_ratio(a))); + case T_REAL: + if (is_NaN(real(a))) return(false); + return((is_t_big_real(b)) && (!mpfr_nan_p(big_real(b))) && (mpfr_cmp_d(big_real(b), real(a)) == 0)); + case T_BIG_REAL: + if (mpfr_nan_p(big_real(a))) return(false); + if (is_t_big_real(b)) return((!mpfr_nan_p(big_real(b))) && (mpfr_equal_p(big_real(a), big_real(b)))); + return((is_t_real(b)) && (!is_NaN(real(b))) && (mpfr_cmp_d(big_real(a), real(b)) == 0)); + case T_COMPLEX: + if ((is_NaN(real_part(a))) || (is_NaN(imag_part(a)))) return(false); + if (!is_t_big_complex(b)) return(false); + if ((mpfr_nan_p(mpc_realref(big_complex(b)))) || (mpfr_nan_p(mpc_imagref(big_complex(b))))) + return(false); + mpc_set_d_d(sc->mpc_1, real_part(a), imag_part(a), MPC_RNDNN); + return(mpc_cmp(sc->mpc_1, big_complex(b)) == 0); + case T_BIG_COMPLEX: + if ((mpfr_nan_p(mpc_realref(big_complex(a)))) || (mpfr_nan_p(mpc_imagref(big_complex(a))))) + return(false); + if (is_t_big_complex(b)) + { + if ((mpfr_nan_p(mpc_realref(big_complex(b)))) || (mpfr_nan_p(mpc_imagref(big_complex(b))))) + return(false); + return(mpc_cmp(big_complex(a), big_complex(b)) == 0); + } + if (is_t_complex(b)) + { + if ((is_NaN(real_part(b))) || (is_NaN(imag_part(b)))) return(false); + mpc_set_d_d(sc->mpc_2, real_part(b), imag_part(b), MPC_RNDNN); + return(mpc_cmp(big_complex(a), sc->mpc_1) == 0); + }} + return(false); +} + +static s7_int big_integer_to_s7_int(s7_scheme *sc, mpz_t n) +{ + if (!mpz_fits_slong_p(n)) + error_nr(sc, sc->out_of_range_symbol, set_elist_2(sc, wrap_string(sc, "bigint does not fit in s7_int: ~S", 33), mpz_to_big_integer(sc, n))); + return(mpz_get_si(n)); +} +#endif + +#ifndef HAVE_OVERFLOW_CHECKS + #if ((defined(__clang__) && (!POINTER_32) && ((__clang_major__ > 3) || (__clang_major__ == 3 && __clang_minor__ >= 4))) || (defined(__GNUC__) && __GNUC__ >= 5)) + #define HAVE_OVERFLOW_CHECKS 1 + #else + #define HAVE_OVERFLOW_CHECKS 0 + #pragma message("no arithmetic overflow checks in this version of s7") + /* these are untested */ + static bool add_overflow(s7_int A, s7_int B, s7_int *C) {*C = A + B; return(false);} /* #define add_overflow(A, B, C) 0 */ + static bool subtract_overflow(s7_int A, s7_int B, s7_int *C) {*C = A - B; return(false);} /* #define subtract_overflow(A, B, C) 0 */ + static bool multiply_overflow(s7_int A, s7_int B, s7_int *C) {*C = A * B; return(false);} /* #define multiply_overflow(A, B, C) 0 */ + #endif +#endif + +#if (defined(__clang__) && (!POINTER_32) && ((__clang_major__ > 3) || (__clang_major__ == 3 && __clang_minor__ >= 4))) + #define subtract_overflow(A, B, C) __builtin_ssubll_overflow((long long)A, (long long)B, (long long *)C) + #define add_overflow(A, B, C) __builtin_saddll_overflow((long long)A, (long long)B, (long long *)C) + #define multiply_overflow(A, B, C) __builtin_smulll_overflow((long long)A, (long long)B, (long long *)C) + #define int32_add_overflow(A, B, C) __builtin_sadd_overflow(A, B, C) + #define int32_multiply_overflow(A, B, C) __builtin_smul_overflow(A, B, C) +#else +#if (defined(__GNUC__) && __GNUC__ >= 5) + #define subtract_overflow(A, B, C) __builtin_sub_overflow(A, B, C) + #define add_overflow(A, B, C) __builtin_add_overflow(A, B, C) + #define multiply_overflow(A, B, C) __builtin_mul_overflow(A, B, C) + #define int32_add_overflow(A, B, C) __builtin_add_overflow(A, B, C) + #define int32_multiply_overflow(A, B, C) __builtin_mul_overflow(A, B, C) +#endif +#endif + +#if WITH_GCC +#define s7_int_abs(x) ({s7_int _X_; _X_ = x; _X_ >= 0 ? _X_ : -_X_;}) +#else +#define s7_int_abs(x) ((x) >= 0 ? (x) : -(x)) +#endif +/* can't use abs even in gcc -- it doesn't work with int64_ts! */ + +#if (!__NetBSD__) + #define s7_fabsl(X) fabsl(X) +#else + static double s7_fabsl(long_double x) {return((signbit(x)) ? -x : x);} +#endif + +/* for g_log, we also need round. this version is from stackoverflow, see also r5rs_round below */ +static double s7_round(double number) {return((number < 0.0) ? ceil(number - 0.5) : floor(number + 0.5));} + +#if HAVE_COMPLEX_NUMBERS +#if __cplusplus + #define _Complex_I (complex(0.0, 1.0)) + #define creal(x) Real(x) + #define cimag(x) Imag(x) + #define carg(x) arg(x) + #define cabs(x) abs(x) + #define csqrt(x) sqrt(x) + #define cpow(x, y) pow(x, y) + #define clog(x) log(x) + #define cexp(x) exp(x) + #define csin(x) sin(x) + #define ccos(x) cos(x) + #define ctan(x) tan(x) + #define csinh(x) sinh(x) + #define ccosh(x) cosh(x) + #define ctanh(x) tanh(x) + #define casin(x) asin(x) + #define cacos(x) acos(x) + #define catan(x) atan(x) + #define casinh(x) asinh(x) + #define cacosh(x) acosh(x) + #define catanh(x) atanh(x) +#else + typedef double complex s7_complex; +#endif + + +#if (!HAVE_COMPLEX_TRIG) +#if (__cplusplus) + + static s7_complex ctan(s7_complex z) {return(csin(z) / ccos(z));} + static s7_complex ctanh(s7_complex z) {return(csinh(z) / ccosh(z));} + static s7_complex casin(s7_complex z) {return(-s7_complex_i * clog(s7_complex_i * z + csqrt(1.0 - z * z)));} + static s7_complex cacos(s7_complex z) {return(-s7_complex_i * clog(z + s7_complex_i * csqrt(1.0 - z * z)));} + static s7_complex catan(s7_complex z) {return(s7_complex_i * clog((s7_complex_i + z) / (s7_complex_i - z)) / 2.0);} + static s7_complex casinh(s7_complex z) {return(clog(z + csqrt(1.0 + z * z)));} + static s7_complex cacosh(s7_complex z) {return(clog(z + csqrt(z * z - 1.0)));} + static s7_complex catanh(s7_complex z) {return(clog((1.0 + z) / (1.0 - z)) / 2.0);} +#else + +#if (!defined(__FreeBSD__)) || (__FreeBSD__ < 12) +static s7_complex clog(s7_complex z) {return(log(fabs(cabs(z))) + carg(z) * s7_complex_i);} +static s7_complex cpow(s7_complex x, s7_complex y) +{ + s7_double r = cabs(x); + s7_double theta = carg(x); + s7_double yre = creal(y); + s7_double yim = cimag(y); + s7_double nr = exp(yre * log(r) - yim * theta); + s7_double ntheta = yre * theta + yim * log(r); + return(nr * cos(ntheta) + (nr * sin(ntheta)) * s7_complex_i); +} +#endif +#if (!defined(__FreeBSD__)) || (__FreeBSD__ < 9) /* untested -- this orignally looked at __FreeBSD_version which apparently no longer exists */ + static s7_complex cexp(s7_complex z) {return(exp(creal(z)) * cos(cimag(z)) + (exp(creal(z)) * sin(cimag(z))) * s7_complex_i);} +#endif + +#if (!defined(__FreeBSD__)) || (__FreeBSD__ < 10) + static s7_complex csin(s7_complex z) {return(sin(creal(z)) * cosh(cimag(z)) + (cos(creal(z)) * sinh(cimag(z))) * s7_complex_i);} + static s7_complex ccos(s7_complex z) {return(cos(creal(z)) * cosh(cimag(z)) + (-sin(creal(z)) * sinh(cimag(z))) * s7_complex_i);} + static s7_complex csinh(s7_complex z) {return(sinh(creal(z)) * cos(cimag(z)) + (cosh(creal(z)) * sin(cimag(z))) * s7_complex_i);} + static s7_complex ccosh(s7_complex z) {return(cosh(creal(z)) * cos(cimag(z)) + (sinh(creal(z)) * sin(cimag(z))) * s7_complex_i);} + static s7_complex ctan(s7_complex z) {return(csin(z) / ccos(z));} + static s7_complex ctanh(s7_complex z) {return(csinh(z) / ccosh(z));} + static s7_complex casin(s7_complex z) {return(-s7_complex_i * clog(s7_complex_i * z + csqrt(1.0 - z * z)));} + static s7_complex cacos(s7_complex z) {return(-s7_complex_i * clog(z + s7_complex_i * csqrt(1.0 - z * z)));} + static s7_complex catan(s7_complex z) {return(s7_complex_i * clog((s7_complex_i + z) / (s7_complex_i - z)) / 2.0);} + static s7_complex catanh(s7_complex z) {return(clog((1.0 + z) / (1.0 - z)) / 2.0);} + static s7_complex casinh(s7_complex z) {return(clog(z + csqrt(1.0 + z * z)));} + static s7_complex cacosh(s7_complex z) {return(clog(z + csqrt(z * z - 1.0)));} +#endif /* not FreeBSD 10 */ +#endif /* not c++ */ +#endif /* not HAVE_COMPLEX_TRIG */ + +#else /* not HAVE_COMPLEX_NUMBERS */ + typedef double s7_complex; + #define _Complex_I 1.0 + #define creal(x) x + #define cimag(x) x + #define csin(x) sin(x) + #define casin(x) x + #define ccos(x) cos(x) + #define cacos(x) x + #define ctan(x) x + #define catan(x) x + #define csinh(x) x + #define casinh(x) x + #define ccosh(x) x + #define cacosh(x) x + #define ctanh(x) x + #define catanh(x) x + #define cexp(x) exp(x) + #define cpow(x, y) pow(x, y) + #define clog(x) log(x) + #define csqrt(x) sqrt(x) + #define conj(x) x +#endif + +#ifdef __OpenBSD__ + /* openbsd's builtin versions of these functions are not usable */ + static s7_complex catanh_1(s7_complex z) {return(clog((1.0 + z) / (1.0 - z)) / 2.0);} + static s7_complex casinh_1(s7_complex z) {return(clog(z + csqrt(1.0 + z * z)));} + static s7_complex cacosh_1(s7_complex z) {return(clog(z + csqrt(z * z - 1.0)));} +#endif +#ifdef __NetBSD__ + static s7_complex catanh_1(s7_complex z) {return(clog((1.0 + z) / (1.0 - z)) / 2.0);} + static s7_complex casinh_1(s7_complex z) {return(clog(z + csqrt(1.0 + z * z)));} +#endif + + +bool s7_is_number(s7_pointer p) {return(is_number(p));} +bool s7_is_complex(s7_pointer p) {return(is_number(p));} +bool s7_is_real(s7_pointer p) {return(is_real(p));} +bool s7_is_rational(s7_pointer p) {return(is_rational(p));} + +bool s7_is_integer(s7_pointer p) +{ +#if WITH_GMP + return((is_t_integer(p)) || (is_t_big_integer(p))); +#else + return(is_t_integer(p)); +#endif +} + +bool s7_is_ratio(s7_pointer p) +{ +#if WITH_GMP + return((is_t_ratio(p)) || (is_t_big_ratio(p))); +#else + return(is_t_ratio(p)); +#endif +} + +static s7_int c_gcd(s7_int u, s7_int v) +{ + /* #if __cplusplus\n return std::gcd(u, v);\n #else... but this requires #include (else gcd is not defined in std::) + * and C++'s gcd returns negative results sometimes -- isn't gcd defined to be positive? std::gcd is ca 25% faster than the code below. + */ + s7_int a, b; + if ((u == s7_int_min) || (v == s7_int_min)) + { + /* can't take abs of these (below) so do it by hand */ + s7_int divisor = 1; + if (u == v) return(u); + while (((u & 1) == 0) && ((v & 1) == 0)) + { + u /= 2; + v /= 2; + divisor *= 2; + } + return(divisor); + } + a = s7_int_abs(u); + b = s7_int_abs(v); + /* there are faster gcd algorithms but does it ever matter? */ + while (b != 0) + { + s7_int temp = a % b; + a = b; + b = temp; + } + return(a); +} + +#define RATIONALIZE_LIMIT 1.0e12 + +static bool c_rationalize(s7_double ux, s7_double error, s7_int *numer, s7_int *denom) +{ + /* from CL code in Canny, Donald, Ressler, "A Rational Rotation Method for Robust Geometric Algorithms" */ + double x0, x1; + s7_int i, p0, q0 = 1, p1, q1 = 1; + double e0, e1, e0p, e1p; + int32_t tries = 0; + /* don't use long_double: the loop below will hang */ + + /* #e1e19 is a killer -- it's bigger than most-positive-fixnum, but if we ceil(ux) below + * it turns into most-negative-fixnum. 1e19 is trouble in many places. + */ + if (fabs(ux) > RATIONALIZE_LIMIT) + { + /* (rationalize most-positive-fixnum) should not return most-negative-fixnum + * but any number > 1e14 here is so inaccurate that rationalize is useless + * for example, + * default: (rationalize (/ (*s7* 'most-positive-fixnum) 31111.0)) -> 1185866354261165/4 + * gmp: (rationalize (/ (*s7* 'most-positive-fixnum) 31111.0)) -> 9223372036854775807/31111 + * can't return false here because that confuses some of the callers! + */ + (*numer) = (s7_int)ux; + (*denom) = 1; + return(true); + } + + if (error < 0.0) error = -error; + x0 = ux - error; + x1 = ux + error; + i = (s7_int)ceil(x0); + + if (error >= 1.0) /* aw good grief! */ + { + if (x0 < 0.0) + (*numer) = (x1 < 0.0) ? (s7_int)floor(x1) : 0; + else (*numer) = i; + (*denom) = 1; + return(true); + } + if (x1 >= i) + { + (*numer) = (i >= 0) ? i : (s7_int)floor(x1); + (*denom) = 1; + return(true); + } + + p0 = (s7_int)floor(x0); + p1 = (s7_int)ceil(x1); + e0 = p1 - x0; + e1 = x0 - p0; + e0p = p1 - x1; + e1p = x1 - p0; + while (true) + { + s7_int old_p1, old_q1; + double old_e0, old_e1, old_e0p, r, r1; + double val = (double)p0 / (double)q0; + + if (((x0 <= val) && (val <= x1)) || (e1 == 0.0) || (e1p == 0.0) || (tries > 100)) + { + if ((q0 == s7_int_min) && (p0 == 1)) /* (rationalize 1.000000004297917e-12) when error is 1e-12 */ + { + (*numer) = 0; + (*denom) = 1; + } + else + { + (*numer) = p0; + (*denom) = q0; + if ((S7_DEBUGGING) && (q0 == 0)) fprintf(stderr, "%f %" ld64 "/0\n", ux, p0); + } + return(true); + } + tries++; + r = (s7_int)floor(e0 / e1); + r1 = (s7_int)ceil(e0p / e1p); + if (r1 < r) r = r1; + /* do handles all step vars in parallel */ + old_p1 = p1; + p1 = p0; + old_q1 = q1; + q1 = q0; + old_e0 = e0; + e0 = e1p; + old_e0p = e0p; + e0p = e1; + old_e1 = e1; + p0 = old_p1 + r * p0; + q0 = old_q1 + r * q0; + e1 = old_e0p - r * e1p; /* if the error is set too low, we can get e1 = 0 here: (rationalize (/ pi) 1e-17) */ + e1p = old_e0 - r * old_e1; + } + return(false); +} + +s7_pointer s7_rationalize(s7_scheme *sc, s7_double x, s7_double error) +{ + s7_int numer = 0, denom = 1; + if (c_rationalize(x, error, &numer, &denom)) + return(make_ratio(sc, numer, denom)); + return(make_real(sc, x)); +} + +s7_pointer s7_make_integer(s7_scheme *sc, s7_int n) +{ + s7_pointer x; + if (is_small_int(n)) + return(small_int(n)); + new_cell(sc, x, T_INTEGER); + integer(x) = n; + return(x); +} + +static s7_pointer make_mutable_integer(s7_scheme *sc, s7_int n) +{ + s7_pointer x; + new_cell(sc, x, T_INTEGER | T_MUTABLE | T_IMMUTABLE); + integer(x) = n; + return(x); +} + +s7_pointer s7_make_real(s7_scheme *sc, s7_double n) +{ + s7_pointer x; + new_cell(sc, x, T_REAL); + set_real(x, n); + return(x); +} + +s7_pointer s7_make_mutable_real(s7_scheme *sc, s7_double n) +{ + s7_pointer x; + new_cell(sc, x, T_REAL | T_MUTABLE | T_IMMUTABLE); + set_real(x, n); + return(x); +} + +s7_pointer s7_make_complex(s7_scheme *sc, s7_double a, s7_double b) +{ + s7_pointer x; + if (b == 0.0) + { + new_cell(sc, x, T_REAL); + set_real(x, a); + } + else + { + new_cell(sc, x, T_COMPLEX); + set_real_part(x, a); + set_imag_part(x, b); + } + return(x); +} + +static s7_complex s7_to_c_complex(s7_pointer p) +{ +#if HAVE_COMPLEX_NUMBERS + return(CMPLX(s7_real_part(p), s7_imag_part(p))); +#else + return(0.0); +#endif +} + +static inline s7_pointer c_complex_to_s7(s7_scheme *sc, s7_complex z) {return(make_complex(sc, creal(z), cimag(z)));} + +static noreturn void division_by_zero_error_1_nr(s7_scheme *sc, s7_pointer caller, s7_pointer x) +{ + error_nr(sc, sc->division_by_zero_symbol, + set_elist_4(sc, wrap_string(sc, "~A: division by zero, (~A ~S)", 29), caller, caller, x)); +} + +static noreturn void division_by_zero_error_2_nr(s7_scheme *sc, s7_pointer caller, s7_pointer x, s7_pointer y) +{ + error_nr(sc, sc->division_by_zero_symbol, + set_elist_5(sc, wrap_string(sc, "~A: division by zero, (~A ~S ~S)", 32), caller, caller, x, y)); +} + +static s7_pointer make_ratio(s7_scheme *sc, s7_int a, s7_int b) +{ + s7_pointer x; + if (b == s7_int_min) + { + /* This should not trigger an error during reading -- we might have the + * ratio on a switch with-bignums or whatever, so its mere occurrence is just an annoyance. + */ + if (a & 1) + return(make_real(sc, (long_double)a / (long_double)b)); + a /= 2; + b /= 2; + } + if (b < 0) + { + a = -a; + b = -b; + } + if (a == s7_int_min) + { + while (((a & 1) == 0) && ((b & 1) == 0)) + { + a /= 2; + b /= 2; + }} + else + { + s7_int b1 = b, divisor = s7_int_abs(a); + do { + s7_int temp = divisor % b1; + divisor = b1; + b1 = temp; + } while (b1 != 0); + if (divisor != 1) + { + a /= divisor; + b /= divisor; + }} + if (b == 1) + return(make_integer(sc, a)); + + new_cell(sc, x, T_RATIO); + numerator(x) = a; + denominator(x) = b; + return(x); +} + +/* using "make-ratio" here is a desperate kludge trying to maintain backwards compatibility; internally we use make_ratio_with_div_check below */ +s7_pointer s7_make_ratio(s7_scheme *sc, s7_int a, s7_int b) +{ + if (b == 0) + division_by_zero_error_2_nr(sc, wrap_string(sc, "make-ratio", 10), wrap_integer(sc, a), int_zero); + return(make_ratio(sc, a, b)); +} + +static s7_pointer make_ratio_with_div_check(s7_scheme *sc, s7_pointer caller, s7_int a, s7_int b) +{ + if (b == 0) + division_by_zero_error_2_nr(sc, caller, wrap_integer(sc, a), int_zero); + return(make_ratio(sc, a, b)); +} + + +#define WITH_OVERFLOW_ERROR true +#define WITHOUT_OVERFLOW_ERROR false + +#define INT64_TO_DOUBLE_LIMIT (1LL << 53) +#define DOUBLE_TO_INT64_LIMIT (1LL << 53) + +/* 9007199254740991LL is where a truncated double starts to skip integers (expt 2 53) = ca 1e16 + * (ceiling (+ 1e16 1)) -> 10000000000000000 + * (> 9007199254740993.0 9007199254740992.0) -> #f ; in non-gmp 64-bit doubles + * but we can't fix this except in the gmp case because: + * (integer-decode-float (+ (expt 2.0 62) 100)) -> (4503599627370496 10 1) + * (integer-decode-float (+ (expt 2.0 62) 500)) -> (4503599627370496 10 1) + * (> (+ (expt 2.0 62) 500) (+ (expt 2.0 62) 100)) -> #f ; non-gmp again + * i.e. the bits are identical. We can't even detect when it has happened (without tedious effort), so should + * we just give an error for any floor (or whatever) of an arg>1e16? (sin has a similar problem)? + * I think in the non-gmp case I'll throw an error in these cases because the results are bogus: + * (floor (+ (expt 2.0 62) 512)) -> 4611686018427387904 + * (floor (+ (expt 2.0 62) 513)) -> 4611686018427388928 + * another case at the edge: (round 9007199254740992.51) -> 9007199254740992 + * This spells trouble for normal arithmetic in this range. If no gmp, + * (- (+ (expt 2.0 62) 512) (+ (expt 2.0 62) 513)) = -1024.0 (should be -1.0) + * but we don't currently give an error in this case -- not sure what the right thing is. + */ + +s7_double s7_number_to_real_with_caller(s7_scheme *sc, s7_pointer x, const char *caller) +{ + if (is_t_real(x)) return(real(x)); + switch (type(x)) + { + case T_INTEGER: return((s7_double)integer(x)); + case T_RATIO: return(fraction(x)); +#if WITH_GMP + case T_BIG_INTEGER: return((s7_double)big_integer_to_s7_int(sc, big_integer(x))); + case T_BIG_RATIO: return((s7_double)((long_double)big_integer_to_s7_int(sc, mpq_numref(big_ratio(x))) / + (long_double)big_integer_to_s7_int(sc, mpq_denref(big_ratio(x))))); + case T_BIG_REAL: return((s7_double)mpfr_get_d(big_real(x), MPFR_RNDN)); +#endif + default: + sole_arg_wrong_type_error_nr(sc, wrap_string(sc, caller, safe_strlen(caller)), x, sc->type_names[T_REAL]); + } + return(0.0); +} + +s7_double s7_number_to_real_with_location(s7_scheme *sc, s7_pointer x, s7_pointer caller) +{ + if (is_t_real(x)) return(real(x)); + switch (type(x)) + { + case T_INTEGER: return((s7_double)integer(x)); + case T_RATIO: return(fraction(x)); +#if WITH_GMP + case T_BIG_INTEGER: return((s7_double)big_integer_to_s7_int(sc, big_integer(x))); + case T_BIG_RATIO: return((s7_double)((long_double)big_integer_to_s7_int(sc, mpq_numref(big_ratio(x))) / + (long_double)big_integer_to_s7_int(sc, mpq_denref(big_ratio(x))))); + case T_BIG_REAL: return((s7_double)mpfr_get_d(big_real(x), MPFR_RNDN)); +#endif + default: + sole_arg_wrong_type_error_nr(sc, caller, x, sc->type_names[T_REAL]); + } + return(0.0); +} + +s7_double s7_number_to_real(s7_scheme *sc, s7_pointer x) {return(s7_number_to_real_with_location(sc, x, sc->number_to_real_symbol));} + +s7_int s7_number_to_integer_with_caller(s7_scheme *sc, s7_pointer x, const char *caller) +{ + if (is_t_integer(x)) return(integer(x)); +#if WITH_GMP + if (is_t_big_integer(x)) return(big_integer_to_s7_int(sc, big_integer(x))); +#endif + sole_arg_wrong_type_error_nr(sc, wrap_string(sc, caller, safe_strlen(caller)), x, sc->type_names[T_INTEGER]); + return(0); +} + +s7_int s7_number_to_integer(s7_scheme *sc, s7_pointer x) {return(s7_number_to_integer_with_caller(sc, x, "s7_number_to_integer"));} + +s7_int s7_numerator(s7_pointer x) +{ + switch (type(x)) + { + case T_INTEGER: return(integer(x)); + case T_RATIO: return(numerator(x)); +#if WITH_GMP + case T_BIG_INTEGER: return(mpz_get_si(big_integer(x))); /* big_integer_to_s7_int but no sc -- no error if out of range */ + case T_BIG_RATIO: return(mpz_get_si(mpq_numref(big_ratio(x)))); +#endif + } + return(0); +} + +s7_int s7_denominator(s7_pointer x) +{ + if (is_t_ratio(x)) return(denominator(x)); +#if WITH_GMP + if (is_t_big_ratio(x)) return(mpz_get_si(mpq_denref(big_ratio(x)))); +#endif + return(1); +} + +s7_int s7_integer(s7_pointer p) +{ + if (is_t_integer(p)) return(integer(p)); +#if WITH_GMP + if (is_t_big_integer(p)) return(mpz_get_si(big_integer(p))); +#endif + return(0); +} + +s7_double s7_real(s7_pointer x) +{ + if (is_t_real(x)) return(real(x)); + switch (type(x)) + { + case T_RATIO: return(fraction(x)); + case T_INTEGER: return((s7_double)integer(x)); +#if WITH_GMP + case T_BIG_INTEGER: return((s7_double)mpz_get_si(big_integer(x))); + case T_BIG_REAL: return((s7_double)mpfr_get_d(big_real(x), MPFR_RNDN)); + case T_BIG_RATIO: + { + s7_double result; + mpfr_t bx; + mpfr_init2(bx, DEFAULT_BIGNUM_PRECISION); + mpfr_set_q(bx, big_ratio(x), MPFR_RNDN); + result = mpfr_get_d(bx, MPFR_RNDN); + mpfr_clear(bx); + return(result); + } +#endif + } + return(0.0); +} + +static bool is_one(s7_pointer x) +{ + return(((is_t_integer(x)) && (integer(x) == 1)) || + ((is_t_real(x)) && (real(x) == 1.0))); +} + + +/* -------- optimize exponents -------- */ + +#define MAX_POW 64 +static double **pepow = NULL; /* [17][MAX_POW * 2]; */ + +static void init_pows(void) +{ + pepow = (double **)Malloc(17 * sizeof(double *)); + pepow[0] = NULL; + pepow[1] = NULL; + for (int32_t i = 2; i < 17; i++) pepow[i] = (double *)Malloc((MAX_POW * 2) * sizeof(double)); + for (int32_t i = 2; i < 17; i++) /* radix between 2 and 16 */ + for (int32_t j = -MAX_POW; j < MAX_POW; j++) /* saved exponent between 0 and +/- MAX_POW */ + pepow[i][j + MAX_POW] = pow((double)i, (double)j); +} + +static inline double dpow(int32_t x, int32_t y) +{ + if ((y >= MAX_POW) || (y < -MAX_POW)) /* this can happen (once in a blue moon) */ + return(pow((double)x, (double)y)); + return(pepow[x][y + MAX_POW]); +} + + +/* -------------------------------- number->string -------------------------------- */ +#ifndef WITH_DTOA + #define WITH_DTOA 1 +#endif +/* there was a time when libc was so slow that this code was all but mandatory, but now (Aug-2023) the difference is much smaller */ + +#if WITH_DTOA +/* fpconv, revised to fit the local coding style + + The MIT License + +Copyright (c) 2013 Andreas Samoljuk + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#define dtoa_npowers 87 +#define dtoa_steppowers 8 +#define dtoa_firstpower -348 /* 10 ^ -348 */ +#define dtoa_expmax -32 +#define dtoa_expmin -60 + +typedef struct dtoa_np {uint64_t frac; int32_t exp;} dtoa_np; + +static const dtoa_np dtoa_powers_ten[] = { + { 18054884314459144840U, -1220 }, { 13451937075301367670U, -1193 }, { 10022474136428063862U, -1166 }, { 14934650266808366570U, -1140 }, + { 11127181549972568877U, -1113 }, { 16580792590934885855U, -1087 }, { 12353653155963782858U, -1060 }, { 18408377700990114895U, -1034 }, + { 13715310171984221708U, -1007 }, { 10218702384817765436U, -980 }, { 15227053142812498563U, -954 }, { 11345038669416679861U, -927 }, + { 16905424996341287883U, -901 }, { 12595523146049147757U, -874 }, { 9384396036005875287U, -847 }, { 13983839803942852151U, -821 }, + { 10418772551374772303U, -794 }, { 15525180923007089351U, -768 }, { 11567161174868858868U, -741 }, { 17236413322193710309U, -715 }, + { 12842128665889583758U, -688 }, { 9568131466127621947U, -661 }, { 14257626930069360058U, -635 }, { 10622759856335341974U, -608 }, + { 15829145694278690180U, -582 }, { 11793632577567316726U, -555 }, { 17573882009934360870U, -529 }, { 13093562431584567480U, -502 }, + { 9755464219737475723U, -475 }, { 14536774485912137811U, -449 }, { 10830740992659433045U, -422 }, { 16139061738043178685U, -396 }, + { 12024538023802026127U, -369 }, { 17917957937422433684U, -343 }, { 13349918974505688015U, -316 }, { 9946464728195732843U, -289 }, + { 14821387422376473014U, -263 }, { 11042794154864902060U, -236 }, { 16455045573212060422U, -210 }, { 12259964326927110867U, -183 }, + { 18268770466636286478U, -157 }, { 13611294676837538539U, -130 }, { 10141204801825835212U, -103 }, { 15111572745182864684U, -77 }, + { 11258999068426240000U, -50 }, { 16777216000000000000U, -24 }, { 12500000000000000000U, 3 }, { 9313225746154785156U, 30 }, + { 13877787807814456755U, 56 }, { 10339757656912845936U, 83 }, { 15407439555097886824U, 109 }, { 11479437019748901445U, 136 }, + { 17105694144590052135U, 162 }, { 12744735289059618216U, 189 }, { 9495567745759798747U, 216 }, { 14149498560666738074U, 242 }, + { 10542197943230523224U, 269 }, { 15709099088952724970U, 295 }, { 11704190886730495818U, 322 }, { 17440603504673385349U, 348 }, + { 12994262207056124023U, 375 }, { 9681479787123295682U, 402 }, { 14426529090290212157U, 428 }, { 10748601772107342003U, 455 }, + { 16016664761464807395U, 481 }, { 11933345169920330789U, 508 }, { 17782069995880619868U, 534 }, { 13248674568444952270U, 561 }, + { 9871031767461413346U, 588 }, { 14708983551653345445U, 614 }, { 10959046745042015199U, 641 }, { 16330252207878254650U, 667 }, + { 12166986024289022870U, 694 }, { 18130221999122236476U, 720 }, { 13508068024458167312U, 747 }, { 10064294952495520794U, 774 }, + { 14996968138956309548U, 800 }, { 11173611982879273257U, 827 }, { 16649979327439178909U, 853 }, { 12405201291620119593U, 880 }, + { 9242595204427927429U, 907 }, { 13772540099066387757U, 933 }, { 10261342003245940623U, 960 }, { 15290591125556738113U, 986 }, + { 11392378155556871081U, 1013 }, { 16975966327722178521U, 1039 }, + { 12648080533535911531U, 1066 }}; + +static dtoa_np dtoa_find_cachedpow10(int exp, int* k) +{ + const double one_log_ten = 0.30102999566398114; + int32_t approx = -(exp + dtoa_npowers) * one_log_ten; + int32_t idx = (approx - dtoa_firstpower) / dtoa_steppowers; + while (true) + { + int32_t current = exp + dtoa_powers_ten[idx].exp + 64; + if (current < dtoa_expmin) + { + idx++; + continue; + } + if (current > dtoa_expmax) + { + idx--; + continue; + } + *k = (dtoa_firstpower + idx * dtoa_steppowers); + return(dtoa_powers_ten[idx]); + } +} + +#define dtoa_fracmask 0x000FFFFFFFFFFFFFU +#define dtoa_expmask 0x7FF0000000000000U +#define dtoa_hiddenbit 0x0010000000000000U +#define dtoa_signmask 0x8000000000000000U +#define dtoa_expbias (1023 + 52) +#define dtoa_absv(n) ((n) < 0 ? -(n) : (n)) +#define dtoa_minv(a, b) ((a) < (b) ? (a) : (b)) + +static uint64_t dtoa_tens[] = + { 10000000000000000000U, 1000000000000000000U, 100000000000000000U, + 10000000000000000U, 1000000000000000U, 100000000000000U, + 10000000000000U, 1000000000000U, 100000000000U, + 10000000000U, 1000000000U, 100000000U, + 10000000U, 1000000U, 100000U, + 10000U, 1000U, 100U, + 10U, 1U}; + +static uint64_t dtoa_get_dbits(double d) +{ + union {double dbl; uint64_t i;} dbl_bits = {d}; + return(dbl_bits.i); +} + +static dtoa_np dtoa_build_np(double d) +{ + uint64_t bits = dtoa_get_dbits(d); + dtoa_np fp; + fp.frac = bits & dtoa_fracmask; + fp.exp = (bits & dtoa_expmask) >> 52; + if (fp.exp) + { + fp.frac += dtoa_hiddenbit; + fp.exp -= dtoa_expbias; + } + else fp.exp = -dtoa_expbias + 1; + return(fp); +} + +static void dtoa_normalize(dtoa_np* fp) +{ + int32_t shift = 64 - 52 - 1; + while ((fp->frac & dtoa_hiddenbit) == 0) + { + fp->frac <<= 1; + fp->exp--; + } + fp->frac <<= shift; + fp->exp -= shift; +} + +static void dtoa_get_normalized_boundaries(const dtoa_np* fp, dtoa_np* lower, dtoa_np* upper) +{ + int32_t u_shift, l_shift; + upper->frac = (fp->frac << 1) + 1; + upper->exp = fp->exp - 1; + while ((upper->frac & (dtoa_hiddenbit << 1)) == 0) + { + upper->frac <<= 1; + upper->exp--; + } + u_shift = 64 - 52 - 2; + upper->frac <<= u_shift; + upper->exp = upper->exp - u_shift; + l_shift = fp->frac == dtoa_hiddenbit ? 2 : 1; + lower->frac = (fp->frac << l_shift) - 1; + lower->exp = fp->exp - l_shift; + lower->frac <<= lower->exp - upper->exp; + lower->exp = upper->exp; +} + +static dtoa_np dtoa_multiply(dtoa_np* a, dtoa_np* b) +{ + dtoa_np fp; + const uint64_t lomask = 0x00000000FFFFFFFF; + uint64_t ah_bl = (a->frac >> 32) * (b->frac & lomask); + uint64_t al_bh = (a->frac & lomask) * (b->frac >> 32); + uint64_t al_bl = (a->frac & lomask) * (b->frac & lomask); + uint64_t ah_bh = (a->frac >> 32) * (b->frac >> 32); + uint64_t tmp = (ah_bl & lomask) + (al_bh & lomask) + (al_bl >> 32); + /* round up */ + tmp += 1U << 31; + fp.frac = ah_bh + (ah_bl >> 32) + (al_bh >> 32) + (tmp >> 32); + fp.exp = a->exp + b->exp + 64; + return(fp); +} + +static void dtoa_round_digit(char* digits, int32_t ndigits, uint64_t delta, uint64_t rem, uint64_t kappa, uint64_t frac) +{ + while ((rem < frac) && (delta - rem >= kappa) && + ((rem + kappa < frac) || (frac - rem > rem + kappa - frac))) + { + digits[ndigits - 1]--; + rem += kappa; + } +} + +static int32_t dtoa_generate_digits(dtoa_np* fp, dtoa_np* upper, dtoa_np* lower, char* digits, int* K) +{ + uint64_t part1, part2, wfrac = upper->frac - fp->frac, delta = upper->frac - lower->frac; + uint64_t *unit; + int32_t idx = 0, kappa = 10; + dtoa_np one; + + one.frac = 1ULL << -upper->exp; + one.exp = upper->exp; + part1 = upper->frac >> -one.exp; + part2 = upper->frac & (one.frac - 1); + + /* 1000000000 */ + for (uint64_t *divp = dtoa_tens + 10; kappa > 0; divp++) + { + uint64_t tmp, div = *divp; + unsigned digit = part1 / div; + if (digit || idx) + digits[idx++] = digit + '0'; + part1 -= digit * div; + kappa--; + tmp = (part1 << -one.exp) + part2; + if (tmp <= delta) + { + *K += kappa; + dtoa_round_digit(digits, idx, delta, tmp, div << -one.exp, wfrac); + return(idx); + }} + + /* 10 */ + unit = dtoa_tens + 18; + while(true) + { + unsigned digit; + part2 *= 10; + delta *= 10; + kappa--; + digit = part2 >> -one.exp; + if (digit || idx) + digits[idx++] = digit + '0'; + part2 &= one.frac - 1; + if (part2 < delta) + { + *K += kappa; + dtoa_round_digit(digits, idx, delta, part2, one.frac, wfrac * *unit); + return(idx); + } + unit--; + } +} + +static int32_t dtoa_grisu2(double d, char* digits, int* K) +{ + int32_t k; + dtoa_np cp, lower, upper; + dtoa_np w = dtoa_build_np(d); + dtoa_get_normalized_boundaries(&w, &lower, &upper); + dtoa_normalize(&w); + cp = dtoa_find_cachedpow10(upper.exp, &k); + w = dtoa_multiply(&w, &cp); + upper = dtoa_multiply(&upper, &cp); + lower = dtoa_multiply(&lower, &cp); + lower.frac++; + upper.frac--; + *K = -k; + return(dtoa_generate_digits(&w, &upper, &lower, digits, K)); +} + +static int32_t dtoa_emit_digits(char* digits, int32_t ndigits, char* dest, int32_t K, bool neg) +{ + int32_t idx, cent; + char sign; + int32_t exp = dtoa_absv(K + ndigits - 1); + + /* write plain integer */ + if ((K >= 0) && (exp < (ndigits + 7))) + { + memcpy(dest, digits, ndigits); + local_memset(dest + ndigits, '0', K); + dest[ndigits + K] = '.'; + dest[ndigits + K + 1] = '0'; + return(ndigits + K + 2); + } + + /* write decimal w/o scientific notation */ + if ((K < 0) && (K > -7 || exp < 4)) + { + int32_t offset = ndigits - dtoa_absv(K); + /* fp < 1.0 -> write leading zero */ + if (offset <= 0) + { + offset = -offset; + dest[0] = '0'; + dest[1] = '.'; + local_memset(dest + 2, '0', offset); + memcpy(dest + offset + 2, digits, ndigits); + return(ndigits + 2 + offset); + /* fp > 1.0 */ + } + else + { + memcpy(dest, digits, offset); + dest[offset] = '.'; + memcpy(dest + offset + 1, digits + offset, ndigits - offset); + return(ndigits + 1); + }} + + /* write decimal w/ scientific notation */ + ndigits = dtoa_minv(ndigits, 18 - neg); + idx = 0; + dest[idx++] = digits[0]; + if (ndigits > 1) + { + dest[idx++] = '.'; + memcpy(dest + idx, digits + 1, ndigits - 1); + idx += ndigits - 1; + } + dest[idx++] = 'e'; + sign = K + ndigits - 1 < 0 ? '-' : '+'; + dest[idx++] = sign; + cent = 0; + if (exp > 99) + { + cent = exp / 100; + dest[idx++] = cent + '0'; + exp -= cent * 100; + } + if (exp > 9) + { + int32_t dec = exp / 10; + dest[idx++] = dec + '0'; + exp -= dec * 10; + } + else + if (cent) + dest[idx++] = '0'; + + dest[idx++] = exp % 10 + '0'; + return(idx); +} + +static int32_t dtoa_filter_special(double fp, char* dest, bool neg) +{ + uint64_t bits; + bool nan; + if (fp == 0.0) + { + dest[0] = '0'; dest[1] = '.'; dest[2] = '0'; + return(3); + } + bits = dtoa_get_dbits(fp); + nan = (bits & dtoa_expmask) == dtoa_expmask; + if (!nan) return(0); + + if (!neg) + { + dest[0] = '+'; /* else 1.0-nan...? */ + dest++; + } + if (bits & dtoa_fracmask) + { + s7_int payload = nan_payload(fp); + int32_t len; + len = (int32_t)snprintf(dest, 22, "nan.%" ld64, payload); + return((neg) ? len : len + 1); + } + dest[0] = 'i'; dest[1] = 'n'; dest[2] = 'f'; dest[3] = '.'; dest[4] = '0'; + return((neg) ? 5 : 6); +} + +static inline int32_t fpconv_dtoa(double d, char dest[24]) +{ + char digit[23]; + int32_t str_len = 0, spec, K, ndigits; + bool neg = false; + + if (dtoa_get_dbits(d) & dtoa_signmask) + { + dest[0] = '-'; + str_len++; + neg = true; + } + spec = dtoa_filter_special(d, dest + str_len, neg); + if (spec) return(str_len + spec); + K = 0; + ndigits = dtoa_grisu2(d, digit, &K); + str_len += dtoa_emit_digits(digit, ndigits, dest + str_len, K, neg); + return(str_len); +} +#endif + + +/* -------------------------------- number->string -------------------------------- */ +static const char dignum[] = "0123456789abcdef"; + +static size_t integer_to_string_any_base(char *p, s7_int n, int32_t radix) /* called by number_to_string_with_radix */ +{ + s7_int i, len, end; + bool sign; + s7_int pown; + + if ((radix < 2) || (radix > 16)) + return(0); + + if (n == S7_INT64_MIN) /* can't negate this, so do it by hand */ + { + static const char *mnfs[17] = {"","", + "-1000000000000000000000000000000000000000000000000000000000000000", "-2021110011022210012102010021220101220222", + "-20000000000000000000000000000000", "-1104332401304422434310311213", "-1540241003031030222122212", + "-22341010611245052052301", "-1000000000000000000000", "-67404283172107811828", "-9223372036854775808", + "-1728002635214590698", "-41a792678515120368", "-10b269549075433c38", "-4340724c6c71dc7a8", "-160e2ad3246366808", "-8000000000000000"}; + + len = safe_strlen(mnfs[radix]); + memcpy((void *)p, (const void *)mnfs[radix], len); + p[len] = '\0'; + return(len); + } + + sign = (n < 0); + if (sign) n = -n; + + /* the previous version that counted up to n, rather than dividing down below n, as here, + * could be confused by large ints on 64 bit machines + */ + pown = n; + for (i = 1; i < 100; i++) + { + if (pown < radix) + break; + pown /= (s7_int)radix; + } + len = i - 1; + if (sign) len++; + end = 0; + if (sign) + { + p[0] = '-'; + end++; + } + for (i = len; i >= end; i--) + { + p[i] = dignum[n % radix]; + n /= radix; + } + p[len + 1] = '\0'; + return(len + 1); +} + +static const char *integer_to_string(s7_scheme *sc, s7_int num, s7_int *nlen) /* do not free the returned string */ +{ + char *p, *op; + bool sign; + + if (num == S7_INT64_MIN) + { + (*nlen) = 20; + return((const char *)"-9223372036854775808"); + } + p = (char *)(sc->int_to_str1 + INT_TO_STR_SIZE - 1); + op = p; + *p-- = '\0'; + + sign = (num < 0); + if (sign) num = -num; /* we need a positive index below */ + do {*p-- = "0123456789"[num % 10]; num /= 10;} while (num); + if (sign) + { + *p = '-'; + (*nlen) = op - p; + return(p); + } + (*nlen) = op - p - 1; + return(++p); +} + +static char *integer_to_string_no_length(s7_scheme *sc, s7_int num) /* do not free the returned string */ +{ + char *p; + bool sign; + + if (num == S7_INT64_MIN) + return(number_name(leastfix)); /* "-9223372036854775808" but avoids a compiler complaint */ + p = (char *)(sc->int_to_str2 + INT_TO_STR_SIZE - 1); + *p-- = '\0'; + sign = (num < 0); + if (sign) num = -num; + do {*p-- = "0123456789"[num % 10]; num /= 10;} while (num); + if (sign) + { + *p = '-'; + return(p); + } + return(++p); +} + +static char *floatify(char *str, s7_int *nlen) +{ + if ((!strchr(str, '.')) && (!strchr(str, 'e'))) /* faster than (strcspn(str, ".e") >= (size_t)(*nlen)) */ + { + s7_int len = *nlen; + /* snprintf returns "nan" and "inf" but we (stupidly) want "+nan.0" and "+inf.0"; "-nan" and "-inf" will be handled by the normal case */ + if (len == 3) + { + if (str[0] == 'n') + { + str[0] = '+'; str[1] = 'n'; str[2] = 'a'; str[3] = 'n'; + len = 4; + } + if (str[0] == 'i') + { + str[0] = '+'; str[1] = 'i'; str[2] = 'n'; str[3] = 'f'; + len = 4; + }} + str[len]='.'; + str[len + 1]='0'; + str[len + 2]='\0'; + (*nlen) = len + 2; + } + return(str); +} + +static void insert_spaces(s7_scheme *sc, const char *src, s7_int width, s7_int len) +{ + s7_int spaces = width - len; + if (width >= sc->num_to_str_size) + { + sc->num_to_str_size = width + 1; + sc->num_to_str = (char *)Realloc(sc->num_to_str, sc->num_to_str_size); + } + sc->num_to_str[width] = '\0'; + memmove((void *)(sc->num_to_str + spaces), (const void *)src, len); + local_memset((void *)(sc->num_to_str), (int)' ', spaces); +} + +static char *number_to_string_base_10(s7_scheme *sc, s7_pointer obj, s7_int width, s7_int precision, + char float_choice, s7_int *nlen, use_write_t choice) /* don't free result */ +{ + /* called by number_to_string_with_radix g_number_to_string, number_to_string_p_p number_to_port format_number */ + /* the rest of s7 assumes nlen is set to the correct length + * a tricky case: (format #f "~f" 1e308) -- tries to print 308 digits! so 256 as default len is too small. + * but then even worse: (format #f "~F" 1e308+1e308i)! + */ + s7_int len = width + precision; + len = (len > 512) ? (512 + 2 * len) : 1024; + if (len > sc->num_to_str_size) + { + sc->num_to_str = (sc->num_to_str) ? (char *)Realloc(sc->num_to_str, len) : (char *)Malloc(len); + sc->num_to_str_size = len; + } + + /* bignums can't happen here */ + if (is_t_integer(obj)) + { + const char *p; + if (width == 0) + { + if (has_number_name(obj)) + { + (*nlen) = number_name_length(obj); + return((char *)number_name(obj)); + } + return((char *)integer_to_string(sc, integer(obj), nlen)); + } + p = integer_to_string(sc, integer(obj), &len); + if (width > len) + { + insert_spaces(sc, p, width, len); /* writes sc->num_to_str */ + (*nlen) = width; + return(sc->num_to_str); + } + (*nlen) = len; + return((char *)p); + } + + if (is_t_real(obj)) + { + if (width == 0) + { +#if WITH_DTOA + if ((float_choice == 'g') && + (precision == WRITE_REAL_PRECISION)) + { + /* (number->string 0.0000001) is sensitive to (*s7* 'float-format-precision) and inconsistent: either 1e-7 or 0.0000001 + * because fpconv_dtoa has some complicated decision about 'g' vs 'f' -- not sure if this is a bug. + */ + len = fpconv_dtoa(real(obj), sc->num_to_str); + sc->num_to_str[len] = '\0'; + (*nlen) = len; + return(sc->num_to_str); + } +#endif + len = snprintf(sc->num_to_str, sc->num_to_str_size - 4, + (float_choice == 'g') ? "%.*g" : ((float_choice == 'f') ? "%.*f" : "%.*e"), + (int32_t)precision, real(obj)); /* -4 for floatify */ + } + else len = snprintf(sc->num_to_str, sc->num_to_str_size - 4, + (float_choice == 'g') ? "%*.*g" : ((float_choice == 'f') ? "%*.*f" : "%*.*e"), + (int32_t)width, (int32_t)precision, real(obj)); /* -4 for floatify */ + (*nlen) = len; + floatify(sc->num_to_str, nlen); + return(sc->num_to_str); + } + + if (is_t_complex(obj)) + { + char *imag; + sc->num_to_str[0] = '\0'; + imag = copy_string(number_to_string_base_10(sc, wrap_real(sc, imag_part(obj)), 0, precision, float_choice, &len, choice)); + + sc->num_to_str[0] = '\0'; + number_to_string_base_10(sc, wrap_real(sc, real_part(obj)), 0, precision, float_choice, &len, choice); + + sc->num_to_str[len] = '\0'; + len = catstrs(sc->num_to_str, sc->num_to_str_size, ((imag[0] == '+') || (imag[0] == '-')) ? "" : "+", imag, "i", (char *)NULL); + free(imag); + + if (width > len) /* (format #f "~20g" 1+i) */ + { + insert_spaces(sc, sc->num_to_str, width, len); /* this checks sc->num_to_str_size */ + (*nlen) = width; + } + else (*nlen) = len; + return(sc->num_to_str); + } + + /* ratio */ + len = catstrs_direct(sc->num_to_str, integer_to_string_no_length(sc, numerator(obj)), "/", pos_int_to_str_direct(sc, denominator(obj)), (const char *)NULL); + if (width > len) + { + insert_spaces(sc, sc->num_to_str, width, len); + (*nlen) = width; + } + else (*nlen) = len; + return(sc->num_to_str); +} + +static block_t *number_to_string_with_radix(s7_scheme *sc, s7_pointer obj, int32_t radix, s7_int width, s7_int precision, char float_choice, s7_int *nlen) +{ + /* called by s7_number_to_string (char*), g_number_to_string (strp), number_to_string_p_pp (strp), format_number (strp basically) */ + /* the rest of s7 assumes nlen is set to the correct length */ + block_t *b; + char *p; + s7_int len, str_len; + +#if WITH_GMP + if (s7_is_bignum(obj)) + return(big_number_to_string_with_radix(sc, obj, radix, width, nlen, P_WRITE)); + /* this ignores precision because it's way too hard to get the mpfr string to look like + * C's output -- we either have to call mpfr_get_str twice (the first time just to + * find out what the exponent is and how long the string actually is), or we have + * to do messy string manipulations. So (format #f "",3F" pi) ignores the "3" and + * prints the full string. And don't even think about mpfr_snprintf! + */ +#endif + if (radix == 10) + { + p = number_to_string_base_10(sc, obj, width, precision, float_choice, nlen, P_WRITE); + return(string_to_block(sc, p, *nlen)); + } + + switch (type(obj)) + { + case T_INTEGER: + { + size_t len1; + b = inline_mallocate(sc, (128 + width)); + p = (char *)block_data(b); + len1 = integer_to_string_any_base(p, integer(obj), radix); + if ((size_t)width > len1) + { + size_t start = width - len1; + memmove((void *)(p + start), (void *)p, len1); + local_memset((void *)p, (int)' ', start); + p[width] = '\0'; + *nlen = width; + } + else *nlen = len1; + return(b); + } + + case T_RATIO: + { + size_t len1, len2; + str_len = 256 + width; + b = inline_mallocate(sc, str_len); + p = (char *)block_data(b); + len1 = integer_to_string_any_base(p, numerator(obj), radix); + p[len1] = '/'; + len2 = integer_to_string_any_base((char *)(p + len1 + 1), denominator(obj), radix); + len = len1 + 1 + len2; + p[len] = '\0'; + } + break; + + case T_REAL: + { + int32_t i; + s7_int int_part, nsize; + s7_double x = real(obj), frac_part, min_frac, base; + bool sign = false; + char n[128], d[256]; + + if (is_NaN(x)) + return(string_to_block(sc, "+nan.0", *nlen = 6)); + if (is_inf(x)) + { + if (x < 0.0) + return(string_to_block(sc, "-inf.0", *nlen = 6)); + return(string_to_block(sc, "+inf.0", *nlen = 6)); + } + if (x < 0.0) + { + sign = true; + x = -x; + } + if (x > 1.0e18) /* i.e. close to or greater than most-positive-fixnum (9.22e18), so the code below is unlikely to work, (format #f "~X" 1e19) */ + { + int32_t ep = (int32_t)floor(log(x) / log((double)radix)); + block_t *b1; + len = 0; + b = number_to_string_with_radix(sc, wrap_real(sc, x / pow((double)radix, (double)ep)), /* divide it down to one digit, then the fractional part */ + radix, width, precision, float_choice, &len); + b1 = inline_mallocate(sc, len + 8); + p = (char *)block_data(b1); + p[0] = '\0'; + (*nlen) = catstrs(p, len + 8, (sign) ? "-" : "", (char *)block_data(b), (radix == 16) ? "@" : "e", integer_to_string_no_length(sc, ep), (char *)NULL); + liberate(sc, b); + return(b1); + } + + int_part = (s7_int)floor(x); + frac_part = x - int_part; + nsize = integer_to_string_any_base(n, int_part, radix); + min_frac = dpow(radix, -precision); + + /* doesn't this assume precision < 128/256 and that we can fit in 256 digits (1e308)? */ + for (i = 0, base = radix; (i < precision) && (frac_part > min_frac); i++, base *= radix) + { + s7_int ipart = (s7_int)(frac_part * base); + if (ipart >= radix) /* rounding confusion */ + ipart = radix - 1; + frac_part -= (ipart / base); + /* d[i] = ((const char *)"0123456789abcdef")[ipart]; */ + d[i] = dignum[ipart]; + } + if (i == 0) + d[i++] = '0'; + d[i] = '\0'; + b = inline_mallocate(sc, 256); + p = (char *)block_data(b); + /* much faster than catstrs because we know the string lengths */ + { + char *pt = p; + if (sign) {pt[0] = '-'; pt++;} + memcpy(pt, n, nsize); + pt += nsize; + pt[0] = '.'; + pt++; + memcpy(pt, d, i); + pt[i] = '\0'; + /* len = ((sign) ? 1 : 0) + 1 + nsize + i; */ + len = pt + i - p; + } + str_len = 256; + } + break; + + default: + { + char *pt; + s7_int real_len = 0, imag_len = 0; + block_t *n = number_to_string_with_radix(sc, wrap_real(sc, real_part(obj)), radix, 0, precision, float_choice, &real_len); /* include floatify */ + block_t *d = number_to_string_with_radix(sc, wrap_real(sc, imag_part(obj)), radix, 0, precision, float_choice, &imag_len); + char *dp = (char *)block_data(d); + b = inline_mallocate(sc, 512); + p = (char *)block_data(b); + pt = p; + memcpy(pt, (void *)block_data(n), real_len); + pt += real_len; + if ((dp[0] != '+') && (dp[0] != '-')) {pt[0] = '+'; pt++;} + memcpy(pt, dp, imag_len); + pt[imag_len] = 'i'; + pt[imag_len + 1] = '\0'; + len = pt + imag_len + 1 - p; + str_len = 512; + liberate(sc, n); + liberate(sc, d); + } + break; + } + + if (width > len) + { + s7_int spaces; + if (width >= str_len) + { + str_len = width + 1; + b = reallocate(sc, b, str_len); + p = (char *)block_data(b); + } + spaces = width - len; + p[width] = '\0'; + memmove((void *)(p + spaces), (void *)p, len); + local_memset((void *)p, (int)' ', spaces); + (*nlen) = width; + } + else (*nlen) = len; + return(b); +} + +char *s7_number_to_string(s7_scheme *sc, s7_pointer obj, s7_int radix) +{ + s7_int nlen = 0; + block_t *b = number_to_string_with_radix(sc, obj, radix, 0, 20, 'g', &nlen); /* (log top 10) so we get all the digits in base 10 (??) */ + char *str = copy_string_with_length((char *)block_data(b), nlen); + liberate(sc, b); + return(str); +} + +static s7_pointer g_number_to_string(s7_scheme *sc, s7_pointer args) +{ + #define H_number_to_string "(number->string num (radix 10)) converts the number num into a string." + #define Q_number_to_string s7_make_signature(sc, 3, sc->is_string_symbol, sc->is_number_symbol, sc->is_integer_symbol) + + s7_int nlen = 0, radix; /* ignore cppcheck complaint about radix! */ + const char *res; + s7_pointer x = car(args); + + if (!is_number(x)) + return(method_or_bust(sc, x, sc->number_to_string_symbol, args, a_number_string, 1)); + + if (is_pair(cdr(args))) + { + s7_pointer y = cadr(args); + if (s7_is_integer(y)) + radix = s7_integer_clamped_if_gmp(sc, y); + else return(method_or_bust(sc, y, sc->number_to_string_symbol, args, sc->type_names[T_INTEGER], 2)); + if ((radix < 2) || (radix > 16)) + out_of_range_error_nr(sc, sc->number_to_string_symbol, int_two, y, a_valid_radix_string); +#if (WITH_GMP) + if (!s7_is_bignum(x)) +#endif + { + block_t *b = number_to_string_with_radix(sc, x, radix, 0, sc->float_format_precision, 'g', &nlen); + return(block_to_string(sc, b, nlen)); + }} +#if WITH_GMP + else radix = 10; + if (s7_is_bignum(x)) + { + block_t *b = big_number_to_string_with_radix(sc, x, radix, 0, &nlen, P_WRITE); + return(block_to_string(sc, b, nlen)); + } + res = number_to_string_base_10(sc, x, 0, sc->float_format_precision, 'g', &nlen, P_WRITE); +#else + if (is_t_integer(x)) + { + if (has_number_name(x)) + { + nlen = number_name_length(x); + res = (const char *)number_name(x); + } + else res = integer_to_string(sc, integer(x), &nlen); + } + else res = number_to_string_base_10(sc, x, 0, sc->float_format_precision, 'g', &nlen, P_WRITE); +#endif + return(inline_make_string_with_length(sc, res, nlen)); +} + +static s7_pointer number_to_string_p_p(s7_scheme *sc, s7_pointer p) +{ +#if WITH_GMP + return(g_number_to_string(sc, set_plist_1(sc, p))); +#else + s7_int nlen = 0; + char *res; + if (!is_number(p)) + return(method_or_bust_p(sc, p, sc->number_to_string_symbol, a_number_string)); + res = number_to_string_base_10(sc, p, 0, sc->float_format_precision, 'g', &nlen, P_WRITE); + return(inline_make_string_with_length(sc, res, nlen)); +#endif +} + +static s7_pointer number_to_string_p_i(s7_scheme *sc, s7_int p) +{ + s7_int nlen = 0; + const char *res = integer_to_string(sc, p, &nlen); + return(inline_make_string_with_length(sc, res, nlen)); +} +/* not number_to_string_p_d! */ + +static s7_pointer number_to_string_p_pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ +#if WITH_GMP + return(g_number_to_string(sc, set_plist_2(sc, p1, p2))); +#else + s7_int nlen = 0, radix; + block_t *b; + + if (!is_number(p1)) + wrong_type_error_nr(sc, sc->number_to_string_symbol, 1, p1, a_number_string); + if (!is_t_integer(p2)) + wrong_type_error_nr(sc, sc->number_to_string_symbol, 2, p2, sc->type_names[T_INTEGER]); + radix = integer(p2); + if ((radix < 2) || (radix > 16)) + out_of_range_error_nr(sc, sc->number_to_string_symbol, int_two, p2, a_valid_radix_string); + + b = number_to_string_with_radix(sc, p1, radix, 0, sc->float_format_precision, 'g', &nlen); + return(block_to_string(sc, b, nlen)); +#endif +} + + +/* -------------------------------------------------------------------------------- */ +#define CTABLE_SIZE 256 +static bool *exponent_table, *slashify_table, *char_ok_in_a_name, *white_space, *number_table, *symbol_slashify_table; +static int32_t *digits; + +static void init_ctables(void) +{ + exponent_table = (bool *)Calloc(CTABLE_SIZE, sizeof(bool)); + slashify_table = (bool *)Calloc(CTABLE_SIZE, sizeof(bool)); + symbol_slashify_table = (bool *)Calloc(CTABLE_SIZE, sizeof(bool)); + char_ok_in_a_name = (bool *)Calloc(CTABLE_SIZE, sizeof(bool)); + white_space = (bool *)Calloc(CTABLE_SIZE + 1, sizeof(bool)); + white_space++; /* leave white_space[-1] false for white_space[EOF] */ + number_table = (bool *)Calloc(CTABLE_SIZE, sizeof(bool)); + digits = (int32_t *)Calloc(CTABLE_SIZE, sizeof(int32_t)); + + for (int32_t i = 0; i < CTABLE_SIZE; i++) + { + char_ok_in_a_name[i] = true; + white_space[i] = false; + digits[i] = 256; + number_table[i] = false; + } + + char_ok_in_a_name[0] = false; + char_ok_in_a_name[(uint8_t)'('] = false; /* cast for C++ */ + char_ok_in_a_name[(uint8_t)')'] = false; + char_ok_in_a_name[(uint8_t)';'] = false; + char_ok_in_a_name[(uint8_t)'\t'] = false; + char_ok_in_a_name[(uint8_t)'\n'] = false; + char_ok_in_a_name[(uint8_t)'\r'] = false; + char_ok_in_a_name[(uint8_t)' '] = false; + char_ok_in_a_name[(uint8_t)'"'] = false; + + white_space[(uint8_t)'\t'] = true; + white_space[(uint8_t)'\n'] = true; + white_space[(uint8_t)'\r'] = true; + white_space[(uint8_t)'\f'] = true; + white_space[(uint8_t)'\v'] = true; + white_space[(uint8_t)' '] = true; + white_space[(uint8_t)'\205'] = true; /* 133 */ + white_space[(uint8_t)'\240'] = true; /* 160 */ + + /* surely only 'e' is needed... */ + exponent_table[(uint8_t)'e'] = true; exponent_table[(uint8_t)'E'] = true; + exponent_table[(uint8_t)'@'] = true; +#if WITH_EXTRA_EXPONENT_MARKERS + exponent_table[(uint8_t)'s'] = true; exponent_table[(uint8_t)'S'] = true; + exponent_table[(uint8_t)'f'] = true; exponent_table[(uint8_t)'F'] = true; + exponent_table[(uint8_t)'d'] = true; exponent_table[(uint8_t)'D'] = true; + exponent_table[(uint8_t)'l'] = true; exponent_table[(uint8_t)'L'] = true; +#endif + for (int32_t i = 0; i < 32; i++) slashify_table[i] = true; + for (int32_t i = 127; i < 160; i++) slashify_table[i] = true; + slashify_table[(uint8_t)'\\'] = true; + slashify_table[(uint8_t)'"'] = true; + slashify_table[(uint8_t)'\n'] = false; + + for (int32_t i = 0; i < CTABLE_SIZE; i++) + symbol_slashify_table[i] = ((slashify_table[i]) || (!char_ok_in_a_name[i])); /* force use of (symbol ...) for cases like '(ab) as symbol */ + + digits[(uint8_t)'0'] = 0; digits[(uint8_t)'1'] = 1; digits[(uint8_t)'2'] = 2; digits[(uint8_t)'3'] = 3; digits[(uint8_t)'4'] = 4; + digits[(uint8_t)'5'] = 5; digits[(uint8_t)'6'] = 6; digits[(uint8_t)'7'] = 7; digits[(uint8_t)'8'] = 8; digits[(uint8_t)'9'] = 9; + digits[(uint8_t)'a'] = 10; digits[(uint8_t)'A'] = 10; + digits[(uint8_t)'b'] = 11; digits[(uint8_t)'B'] = 11; + digits[(uint8_t)'c'] = 12; digits[(uint8_t)'C'] = 12; + digits[(uint8_t)'d'] = 13; digits[(uint8_t)'D'] = 13; + digits[(uint8_t)'e'] = 14; digits[(uint8_t)'E'] = 14; + digits[(uint8_t)'f'] = 15; digits[(uint8_t)'F'] = 15; + + number_table[(uint8_t)'0'] = true; number_table[(uint8_t)'1'] = true; number_table[(uint8_t)'2'] = true; number_table[(uint8_t)'3'] = true; + number_table[(uint8_t)'4'] = true; number_table[(uint8_t)'5'] = true; number_table[(uint8_t)'6'] = true; number_table[(uint8_t)'7'] = true; + number_table[(uint8_t)'8'] = true; number_table[(uint8_t)'9'] = true; number_table[(uint8_t)'.'] = true; + number_table[(uint8_t)'+'] = true; + number_table[(uint8_t)'-'] = true; + number_table[(uint8_t)'#'] = true; +} + +#define is_white_space(C) white_space[C] + /* this is much faster than C's isspace, and does not depend on the current locale. + * if c == EOF (-1), it indexes into the empty (0) slot we preallocated below white_space + */ + +/* -------------------------------- *#readers* -------------------------------- */ +static s7_pointer check_sharp_readers(s7_scheme *sc, const char *name) +{ + s7_pointer value = sc->F, args = sc->F; + bool need_loader_port = is_loader_port(current_input_port(sc)); + + /* *#reader* is assumed to be an alist of (char . proc) + * where each proc takes one argument, the string from just beyond the "#" to the next delimiter. + * The procedure can call read-char to read ahead in the current-input-port. + * If it returns anything other than #f, that is the value of the sharp expression. + * Since #f means "nothing found", it is tricky to handle #F: + * (cons #\F (lambda (str) (and (string=? str "F") (list 'not #t)))) ; or ''#f used in lint.scm + * This search happens after #|, #t, and #f (and #nD for multivectors?). #! has a fallback. Added #_ later) + */ + if (need_loader_port) + clear_loader_port(current_input_port(sc)); + + /* normally read* can't read from current_input_port(sc) if it is in use by the loader, but here we are deliberately making that possible */ + for (s7_pointer reader = slot_value(sc->sharp_readers); is_not_null(reader); reader = cdr(reader)) + if (name[0] == s7_character(caar(reader))) + { + if (args == sc->F) + args = set_plist_1(sc, s7_make_string_wrapper(sc, name)); + /* args is GC protected by s7_apply_function?? (placed on the stack) */ + value = s7_apply_function(sc, cdar(reader), args); /* this is much less error-safe than s7_call */ + if (value != sc->F) + break; + } + if (need_loader_port) + set_loader_port(current_input_port(sc)); + return(value); +} + +static s7_pointer g_sharp_readers_set(s7_scheme *sc, s7_pointer args) +{ + /* new value must be either () or a proper list of conses (char . func) */ + s7_pointer x; + if (is_null(cadr(args))) return(cadr(args)); + if (!is_pair(cadr(args))) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "can't set *#readers* to ~S", 26), cadr(args))); + for (x = cadr(args); is_pair(x); x = cdr(x)) + if ((!is_pair(car(x))) || + (!is_character(caar(x))) || + (!is_procedure(cdar(x)))) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_2(sc, wrap_string(sc, "can't set *#readers* to ~S", 26), cadr(args))); + if (!is_null(x)) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "can't set *#readers* to ~S", 26), cadr(args))); + return(cadr(args)); +} + +static s7_pointer make_undefined(s7_scheme *sc, const char* name) +{ + s7_int len = safe_strlen(name); + char *newstr = (char *)Malloc(len + 2); + s7_pointer p; + new_cell(sc, p, T_UNDEFINED | T_IMMUTABLE); + newstr[0] = '#'; + memcpy((void *)(newstr + 1), (const void *)name, len); + newstr[len + 1] = '\0'; + if (sc->undefined_constant_warnings) s7_warn(sc, len + 32, "%s is undefined\n", newstr); + undefined_set_name_length(p, len + 1); + undefined_name(p) = newstr; + add_undefined(sc, p); + return(p); +} + +static int32_t inchar(s7_pointer pt) +{ + int32_t c; + if (is_file_port(pt)) + c = fgetc(port_file(pt)); /* not uint8_t! -- could be EOF */ + else + { + if (port_data_size(pt) <= port_position(pt)) + return(EOF); + c = (uint8_t)port_data(pt)[port_position(pt)++]; + } + if (c == '\n') + port_line_number(pt)++; + return(c); +} + +static void backchar(char c, s7_pointer pt) +{ + if (c == '\n') + port_line_number(pt)--; + + if (is_file_port(pt)) + ungetc(c, port_file(pt)); + else + if (port_position(pt) > 0) + port_position(pt)--; +} + +static void resize_strbuf(s7_scheme *sc, s7_int needed_size) +{ + s7_int old_size = sc->strbuf_size; + while (sc->strbuf_size <= needed_size) sc->strbuf_size *= 2; + sc->strbuf = (char *)Realloc(sc->strbuf, sc->strbuf_size); + for (s7_int i = old_size; i < sc->strbuf_size; i++) sc->strbuf[i] = '\0'; +} + +static s7_pointer *chars; + +static s7_pointer unknown_sharp_constant(s7_scheme *sc, const char *name, s7_pointer pt) +{ + /* if name[len - 1] != '>' there's no > delimiter at the end */ + + if (hook_has_functions(sc->read_error_hook)) /* check *read-error-hook* */ + { + bool old_history_enabled = s7_set_history_enabled(sc, false); /* see sc->error_hook for a more robust way to handle this */ + s7_pointer result = s7_call(sc, sc->read_error_hook, set_plist_2(sc, sc->T, s7_make_string_wrapper(sc, name))); + s7_set_history_enabled(sc, old_history_enabled); + if (result != sc->unspecified) + return(result); + } + if (pt) /* #<"..."> which gets here as name="#<" */ + { + s7_int len = safe_strlen(name); + if ((name[len - 1] != '>') && + (is_input_port(pt)) && + (pt != sc->standard_input)) + { + if (s7_peek_char(sc, pt) != chars[(uint8_t)'"']) /* if not #<"...">, just return it */ + return(make_undefined(sc, name)); + /* PERHAPS: strchr port-data '>'?? it might be # etc -- what would this break? maybe extend section below */ + + if (is_string_port(pt)) /* probably unnecessary (see below) */ + { + s7_int c = inchar(pt); + const char *pstart = (const char *)(port_data(pt) + port_position(pt)); + const char *p = strchr(pstart, (int)'"'); + s7_int added_len; + char *buf; + s7_pointer res; + + if (!p) + { + backchar(c, pt); + return(make_undefined(sc, name)); + } + p++; + while (char_ok_in_a_name[(uint8_t)(*p)]) {p++;} + added_len = (s7_int)(p - pstart); /* p is one past '>' presumably */ + /* we can't use strbuf here -- it might be the source of the "name" argument! */ + buf = (char *)Malloc(len + added_len + 2); + memcpy((void *)buf, (const void *)name, len); + buf[len] = '"'; /* from inchar */ + memcpy((void *)(buf + len + 1), (const void *)pstart, added_len); + buf[len + added_len + 1] = 0; + port_position(pt) += added_len; + res = make_undefined(sc, (const char *)buf); + free(buf); + return(res); + }}} + return(make_undefined(sc, name)); +} + +static s7_pointer make_atom(s7_scheme *sc, char *q, int32_t radix, bool want_symbol, bool with_error); +#define SYMBOL_OK true +#define NO_SYMBOLS false + +static s7_pointer make_sharp_constant(s7_scheme *sc, const char *name, bool with_error, s7_pointer pt, bool error_if_bad_number) +{ + /* name is the stuff after the '#', return sc->nil if not a recognized #... entity */ + if ((!name) || (!*name)) /* (string->number "#") for example */ + return(make_undefined(sc, name)); + + /* stupid r7rs special cases */ + if ((name[0] == 't') && + ((name[1] == '\0') || (c_strings_are_equal(name, "true")))) + return(sc->T); + + if ((name[0] == 'f') && + ((name[1] == '\0') || (c_strings_are_equal(name, "false")))) + return(sc->F); + + if (name[0] == '_') + { + /* this needs to be unsettable via *#readers*: + * (set! *#readers* (list (cons #\_ (lambda (str) (string->symbol (substring str 1)))))) + * (let ((+ -)) (#_+ 1 2)): -1 + */ + s7_pointer sym = make_symbol_with_strlen(sc, (const char *)(name + 1)); + if ((!is_gensym(sym)) && (is_slot(initial_slot(sym)))) + return(initial_value(sym)); + /* here we should not necessarily raise an error that *_... is undefined. reader-cond, for example, needs to + * read undefined #_ vals that it will eventually discard. + */ + return(make_undefined(sc, name)); /* (define x (with-input-from-string "(#_asdf 1 2)" read)) (type-of (car x)) -> undefined? */ + } + + if (is_not_null(slot_value(sc->sharp_readers))) + { + s7_pointer x = check_sharp_readers(sc, name); + if (x != sc->F) + return(x); + } + + if ((name[0] == '\0') || name[1] == '\0') + return(unknown_sharp_constant(sc, name, pt)); /* pt here because #<"..."> comes here as "<" so name[1] is '\0'! */ + + switch (name[0]) + { + /* -------- #< ... > -------- */ + case '<': + if (c_strings_are_equal(name, "")) return(sc->unspecified); + if (c_strings_are_equal(name, "")) return(sc->undefined); + if (c_strings_are_equal(name, "")) return(eof_object); + return(unknown_sharp_constant(sc, name, pt)); + + /* -------- #o #x #b -------- */ + case 'o': /* #o (octal) */ + case 'x': /* #x (hex) */ + case 'b': /* #b (binary) */ + { + s7_pointer res = make_atom(sc, (char *)(name + 1), (name[0] == 'o') ? 8 : ((name[0] == 'x') ? 16 : 2), NO_SYMBOLS, with_error); + if ((error_if_bad_number) && (res == sc->F)) /* #b32 etc but not if called from string->number */ + { + char buf[256]; + size_t len = snprintf(buf, 256, "#%s is not a number", name); + error_nr(sc, sc->read_error_symbol, + set_elist_1(sc, s7_make_string_with_length(sc, buf, len))); /* can't use wrap_string here (buf is local) */ + } + return(res); + } + + /* -------- #\... -------- */ + case '\\': + if (name[2] == 0) /* the most common case: #\a */ + return(chars[(uint8_t)(name[1])]); + /* not uint32_t here! (uint32_t)255 (as a char) returns -1!! */ + switch (name[1]) + { + case 'n': + if ((c_strings_are_equal(name + 1, "null")) || + (c_strings_are_equal(name + 1, "nul"))) + return(chars[0]); + + if (c_strings_are_equal(name + 1, "newline")) + return(chars[(uint8_t)'\n']); + break; + + case 's': if (c_strings_are_equal(name + 1, "space")) return(chars[(uint8_t)' ']); break; + case 'r': if (c_strings_are_equal(name + 1, "return")) return(chars[(uint8_t)'\r']); break; + case 'l': if (c_strings_are_equal(name + 1, "linefeed")) return(chars[(uint8_t)'\n']); break; + case 't': if (c_strings_are_equal(name + 1, "tab")) return(chars[(uint8_t)'\t']); break; + case 'a': if (c_strings_are_equal(name + 1, "alarm")) return(chars[7]); break; + case 'b': if (c_strings_are_equal(name + 1, "backspace")) return(chars[8]); break; + case 'e': if (c_strings_are_equal(name + 1, "escape")) return(chars[0x1b]); break; + case 'd': if (c_strings_are_equal(name + 1, "delete")) return(chars[0x7f]); break; + + case 'x': + /* #\x is just x, but apparently #\x is int->char? #\x65 -> #\e, and #\xcebb is lambda? */ + { + /* sscanf here misses errors like #\x1.4, but make_atom misses #\x6/3, + * #\x#b0, #\x#e0.0, #\x-0, #\x#e0e100 etc, so we have to do it at an even lower level. + * another problem: #\xbdca2cbec overflows so lval is -593310740 -> segfault unless caught + */ + bool happy = true; + const char *tmp = (const char *)(name + 2); + int32_t lval = 0; + + while ((*tmp) && (happy) && (lval >= 0) && (lval < 256)) + { + int32_t dig = digits[(int32_t)(*tmp++)]; + if (dig < 16) + lval = dig + (lval * 16); + else happy = false; + } + if ((happy) && + (lval < 256) && + (lval >= 0)) + return(chars[lval]); + } + break; + }} + return(unknown_sharp_constant(sc, name, NULL)); +} + +static s7_int string_to_integer(const char *str, int32_t radix, bool *overflow) +{ + bool negative = false; + s7_int lval = 0; + int32_t dig; + const char *tmp = (const char *)str; +#if WITH_GMP + const char *tmp1; +#endif + if (str[0] == '+') + tmp++; + else + if (str[0] == '-') + { + negative = true; + tmp++; + } + while (*tmp == '0') {tmp++;}; +#if WITH_GMP + tmp1 = tmp; +#endif + if (radix == 10) + { + while (true) + { + dig = digits[(uint8_t)(*tmp++)]; + if (dig > 9) break; +#if HAVE_OVERFLOW_CHECKS + if ((multiply_overflow(lval, (s7_int)10, &lval)) || + (add_overflow(lval, (s7_int)dig, &lval))) + { + if ((radix == 10) && + (strncmp(str, "-9223372036854775808", 20) == 0) && + (digits[(uint8_t)(*tmp++)] > 9)) /* i.e. next thing is not a digit -- "/" for example */ + return(S7_INT64_MIN); + *overflow = true; + return((negative) ? S7_INT64_MIN : S7_INT64_MAX); + } +#else + lval = dig + (lval * 10); + dig = digits[(uint8_t)(*tmp++)]; + if (dig > 9) break; + lval = dig + (lval * 10); +#endif + }} + else + while (true) + { + dig = digits[(uint8_t)(*tmp++)]; + if (dig >= radix) break; +#if HAVE_OVERFLOW_CHECKS && (!WITH_GMP) + { + s7_int oval = 0; + if (multiply_overflow(lval, (s7_int)radix, &oval)) + { + /* maybe a bad idea! #xffffffffffffffff -> -1??? this is needed for 64-bit number hacks (see s7test.scm bit-reverse) */ + if ((radix == 16) && + (digits[(uint8_t)(*tmp)] >= radix)) + { + lval -= 576460752303423488LL; /* turn off sign bit */ + lval *= radix; + lval += dig; + lval -= 9223372036854775807LL; + return(lval - 1); + } + lval = oval; /* old case */ + if ((lval == S7_INT64_MIN) && (digits[(uint8_t)(*tmp++)] > 9)) + return(lval); + *overflow = true; + break; + } + else lval = oval; + if (add_overflow(lval, (s7_int)dig, &lval)) + { + if (lval == S7_INT64_MIN) return(lval); + *overflow = true; + break; + }} +#else + lval = dig + (lval * radix); + dig = digits[(uint8_t)(*tmp++)]; + if (dig >= radix) break; + lval = dig + (lval * radix); +#endif + } + +#if WITH_GMP + if (!(*overflow)) + (*overflow) = ((lval > S7_INT32_MAX) || + ((tmp - tmp1) > s7_int_digits_by_radix[radix])); + /* this tells the string->number readers to create a bignum. We need to be very conservative here to catch contexts such as (/ 1/524288 19073486328125) */ +#endif + return((negative) ? -lval : lval); +} + +/* 9223372036854775807 9223372036854775807 + * -9223372036854775808 -9223372036854775808 + * 0000000000000000000000000001.0 1.0 + * 1.0000000000000000000000000000 1.0 + * 1000000000000000000000000000.0e-40 1.0e-12 + * 0.0000000000000000000000000001e40 1.0e12 + * 1.0e00000000000000000001 10.0 + */ + +#if WITH_GMP +static s7_double string_to_double_with_radix(const char *ur_str, int32_t radix, bool *overflow) +#else +#define string_to_double_with_radix(Str, Rad, Over) string_to_double_with_radix_1(Str, Rad) +static s7_double string_to_double_with_radix_1(const char *ur_str, int32_t radix) +#endif +{ + /* strtod follows LANG which is not what we want (only "." is decimal point in Scheme). + * To overcome LANG in strtod would require screwing around with setlocale which never works. + * So we use our own code -- according to valgrind, this function is much faster than strtod. + * comma as decimal point causes ambiguities: `(+ ,1 2) etc + */ + int32_t i, sign = 1, frac_len, int_len, dig, exponent = 0; + int32_t max_len = s7_int_digits_by_radix[radix]; + int64_t int_part = 0, frac_part = 0; + const char *str = ur_str; + const char *ipart, *fpart; + s7_double dval = 0.0; + + /* there's an ambiguity in number notation here if we allow "1e1" or "1.e1" in base 16 (or 15) -- is e a digit or an exponent marker? + * but 1e+1, for example disambiguates it -- kind of messy! -- the scheme spec says "e" can only occur in base 10. + * '@' can now be used as the exponent marker (26-Mar-12). + * Another slight ambiguity: 1+1/2i is parsed as 1 + 0.5i, not 1+1/(2i), or (1+1)/(2i) or (1+1/2)i etc + */ + if (*str == '-') + { + str++; + sign = -1; + } + else + if (*str == '+') + str++; + while (*str == '0') {str++;}; + + ipart = str; + while (digits[(int32_t)(*str)] < radix) str++; + int_len = str - ipart; + + if (*str == '.') str++; + fpart = str; + while (digits[(int32_t)(*str)] < radix) str++; + frac_len = str - fpart; + + if ((*str) && (exponent_table[(uint8_t)(*str)])) + { + bool exp_negative = false; + str++; + if (*str == '+') + str++; + else + if (*str == '-') + { + str++; + exp_negative = true; + } + while ((dig = digits[(int32_t)(*str++)]) < 10) /* exponent itself is always base 10 */ + { +#if HAVE_OVERFLOW_CHECKS + if ((int32_multiply_overflow(exponent, 10, &exponent)) || + (int32_add_overflow(exponent, dig, &exponent))) + { + exponent = 1000000; /* see below */ + break; + } +#else + exponent = dig + (exponent * 10); +#endif + } +#if (!defined(__GNUC__)) || ((__GNUC__ < 5) && (!defined(__clang__))) + if (exponent < 0) /* we overflowed, so make sure we notice it below (need to check for 0.0e... first) (Brian Damgaard) */ + exponent = 1000000; /* see below for examples -- this number needs to be very big but not too big for add */ +#endif + if (exp_negative) + exponent = -exponent; + + /* 2e12341234123123123123213123123123 -> 0.0 + * but exp len is not the decider: 2e00000000000000000000000000000000000000001 -> 20.0 + * first zero: 2e123412341231231231231 + * then: 2e12341234123123123123123123 -> inf + * then: 2e123412341231231231231231231231231231 -> 0.0 + * 2e-123412341231231231231 -> inf + * but: 0e123412341231231231231231231231231231 + */ + } + +#if WITH_GMP + /* 9007199254740995.0 */ + if (int_len + frac_len >= max_len) + { + (*overflow) = true; + return(0.0); + } +#endif + str = ipart; + if ((int_len + exponent) > max_len) + { + /* 12341234.56789e12 12341234567889999872.0 1.234123456789e+19 + * -1234567890123456789.0 -1234567890123456768.0 -1.2345678901235e+18 + * 12345678901234567890.0 12345678901234567168.0 1.2345678901235e+19 + * 123.456e30 123456000000000012741097792995328.0 1.23456e+32 + * 12345678901234567890.0e12 12345678901234569054409354903552.0 1.2345678901235e+31 + * 1.234567890123456789012e30 1234567890123456849145940148224.0 1.2345678901235e+30 + * 1e20 100000000000000000000.0 1e+20 + * 1234567890123456789.0 1234567890123456768.0 1.2345678901235e+18 + * 123.456e16 1234560000000000000.0 1.23456e+18 + * 98765432101234567890987654321.0e-5 987654321012345728401408.0 9.8765432101235e+23 + * 98765432101234567890987654321.0e-10 9876543210123456512.0 9.8765432101235e+18 + * 0.00000000000000001234e20 1234.0 + * 0.000000000000000000000000001234e30 1234.0 + * 0.0000000000000000000000000000000000001234e40 1234.0 + * 0.000000000012345678909876543210e15 12345.678909877 + * 0e1000 0.0 + */ + + for (i = 0; i < max_len; i++) + { + dig = digits[(int32_t)(*str++)]; + if (dig < radix) + int_part = dig + (int_part * radix); + else break; + } + + /* if the exponent is huge, check for 0 int_part and frac_part before complaining (0e1000 or 0.0e1000) + */ + if ((int_part == 0) && + (exponent > max_len)) + { + /* if frac_part is also 0, return 0.0 */ + if (frac_len == 0) return(0.0); + str = fpart; + while ((dig = digits[(int32_t)(*str++)]) < radix) + frac_part = dig + (frac_part * radix); + if (frac_part == 0) return(0.0); +#if WITH_GMP + (*overflow) = true; +#endif + } +#if WITH_GMP + (*overflow) = ((int_part > 0) || (exponent > 20)); /* .1e310 is a tricky case */ +#endif + if (int_part != 0) /* 0.<310 zeros here>1e310 for example -- pow (via dpow) thinks it has to be too big, returns Nan, + * then Nan * 0 -> Nan and the NaN propagates + */ + { + if (int_len <= max_len) + dval = int_part * dpow(radix, exponent); + else dval = int_part * dpow(radix, exponent + int_len - max_len); + } + else dval = 0.0; + + /* shift by exponent, but if int_len > max_len then we assumed (see below) int_len - max_len 0's on the left */ + /* using int_to_int or table lookups here instead of pow did not make any difference in speed */ + + if (int_len < max_len) + { + str = fpart; + for (int32_t k = 0; (frac_len > 0) && (k < exponent); k += max_len) + { + int32_t flen = (frac_len > max_len) ? max_len : frac_len; /* ? */ + frac_len -= max_len; + frac_part = 0; + for (i = 0; i < flen; i++) + frac_part = digits[(int32_t)(*str++)] + (frac_part * radix); + if (frac_part != 0) /* same pow->NaN problem as above can occur here */ + dval += frac_part * dpow(radix, exponent - flen - k); + }} + else + /* some of the fraction is in the integer part before the negative exponent shifts it over */ + if (int_len > max_len) + { + int32_t ilen = int_len - max_len; /* we read these above */ + /* str should be at the last digit we read */ + if (ilen > max_len) + ilen = max_len; + for (i = 0; i < ilen; i++) + frac_part = digits[(int32_t)(*str++)] + (frac_part * radix); + dval += frac_part * dpow(radix, exponent - ilen); + } + return(sign * dval); + } + + /* int_len + exponent <= max_len */ + if (int_len <= max_len) + { + int32_t int_exponent = exponent; + /* a better algorithm (since the inaccuracies are in the radix^exponent portion): + * strip off leading zeros and possible sign, + * strip off digits beyond max_len, then remove any trailing zeros. + * (maybe fiddle with the lowest order digit here for rounding, but I doubt it matters) + * read digits until end of number or max_len reached, ignoring the decimal point + * get exponent and use it and decimal point location to position the current result integer + * this always combines the same integer and the same exponent no matter how the number is expressed. + */ + if (int_len > 0) + { + const char *iend = (const char *)(str + int_len - 1); + while ((*iend == '0') && (iend != str)) {iend--; int_exponent++;} + while (str <= iend) + int_part = digits[(int32_t)(*str++)] + (int_part * radix); + } + dval = (int_exponent == 0) ? (s7_double)int_part : int_part * dpow(radix, int_exponent); + } + else + { + int32_t flen, len = int_len + exponent; + int64_t frpart = 0; + + /* 98765432101234567890987654321.0e-20 987654321.012346 + * 98765432101234567890987654321.0e-29 0.98765432101235 + * 98765432101234567890987654321.0e-30 0.098765432101235 + * 98765432101234567890987654321.0e-28 9.8765432101235 + */ + for (i = 0; i < len; i++) + int_part = digits[(int32_t)(*str++)] + (int_part * radix); + flen = -exponent; + if (flen > max_len) + flen = max_len; + for (i = 0; i < flen; i++) + frpart = digits[(int32_t)(*str++)] + (frpart * radix); + if (len <= 0) + dval = int_part + frpart * dpow(radix, len - flen); + else dval = int_part + frpart * dpow(radix, -flen); + } + + if (frac_len > 0) + { + str = fpart; + if (frac_len <= max_len) + { + /* splitting out base 10 case saves very little here */ + /* this ignores trailing zeros, so that 0.3 equals 0.300 */ + const char *fend = (const char *)(str + frac_len - 1); + + while ((*fend == '0') && (fend != str)) {fend--; frac_len--;} /* (= .6 0.6000) */ + if ((frac_len & 1) == 0) + { + while (str <= fend) + { + frac_part = digits[(int32_t)(*str++)] + (frac_part * radix); + frac_part = digits[(int32_t)(*str++)] + (frac_part * radix); + }} + else + while (str <= fend) + frac_part = digits[(int32_t)(*str++)] + (frac_part * radix); + + dval += frac_part * dpow(radix, exponent - frac_len); + + /* 0.6: frac: 6, exp: 0.10000000000000000555, val: 0.60000000000000008882 + * 0.60: frac: 60, exp: 0.01000000000000000021, val: 0.59999999999999997780 + * 0.6000: frac: 6000, exp: 0.00010000000000000000, val: 0.59999999999999997780 + * (= 0.6 0.60): #f + * (= #i3/5 0.6): #f + * so (string->number (number->string num)) == num only if both num's are the same text (or you get lucky) + * (= 0.6 6e-1): #t ; but not 60e-2 + * to fix the 0.60 case, we need to ignore trailing post-dot zeros. + */ + } + else + { + if (exponent <= 0) + { + for (i = 0; i < max_len; i++) + frac_part = digits[(int32_t)(*str++)] + (frac_part * radix); + + dval += frac_part * dpow(radix, exponent - max_len); + } + else + { + /* 1.0123456789876543210e1 10.12345678987654373771 + * 1.0123456789876543210e10 10123456789.87654304504394531250 + * 0.000000010000000000000000e10 100.0 + * 0.000000010000000000000000000000000000000000000e10 100.0 + * 0.000000012222222222222222222222222222222222222e10 122.22222222222222 + * 0.000000012222222222222222222222222222222222222e17 1222222222.222222 + */ + int_part = 0; + for (i = 0; i < exponent; i++) + int_part = digits[(int32_t)(*str++)] + (int_part * radix); + frac_len -= exponent; + if (frac_len > max_len) + frac_len = max_len; + for (i = 0; i < frac_len; i++) + frac_part = digits[(int32_t)(*str++)] + (frac_part * radix); + dval += int_part + frac_part * dpow(radix, -frac_len); + }}} +#if WITH_GMP + if ((int_part == 0) && + (frac_part == 0)) + return(0.0); + (*overflow) = ((frac_len - exponent) > max_len); +#endif + return(sign * dval); +} + +#if (!WITH_GMP) +static s7_pointer make_undefined_bignum(s7_scheme *sc, const char *name) +{ + s7_int len = safe_strlen(name) + 16; + block_t *b = mallocate(sc, len); + char *buf = (char *)block_data(b); + s7_pointer res; + snprintf(buf, len, "", name); + res = make_undefined(sc, (const char *)buf); /* 123123123123123123123123123123 -> +inf.0 originally, but now # */ + liberate(sc, b); + return(res); +} +#endif + +static s7_pointer nan1_or_bust(s7_scheme *sc, s7_double x, const char *p, const char *q, int32_t radix, bool want_symbol, int32_t offset) +{ + s7_int len = safe_strlen(p); + if (p[len - 1] == 'i') /* +nan.0[+/-]...i */ + { + if (len == (offset + 2)) /* +nan.0+i */ + return(make_complex_not_0i(sc, x, (p[offset] == '+') ? 1.0 : -1.0)); + if ((len > (offset + 1)) && (len < 1024)) /* make compiler happy */ + { + char *ip = copy_string_with_length((const char *)(p + offset), len - offset - 1); + s7_pointer imag = make_atom(sc, ip, radix, NO_SYMBOLS, WITHOUT_OVERFLOW_ERROR); + free(ip); + if (is_real(imag)) + return(make_complex(sc, x, real_to_double(sc, imag, __func__))); /* +nan.0+2/3i etc */ + }} + return((want_symbol) ? make_symbol_with_strlen(sc, q) : sc->F); +} + +static s7_pointer nan2_or_bust(s7_scheme *sc, s7_double x, const char *q, int32_t radix, bool want_symbol, int64_t rl_len) +{ + s7_int len = safe_strlen(q); + if ((len > rl_len) && (len < 1024)) /* make compiler happy */ + { + char *ip = copy_string_with_length(q, rl_len); + s7_pointer rl = make_atom(sc, ip, radix, NO_SYMBOLS, WITHOUT_OVERFLOW_ERROR); + free(ip); + if (is_real(rl)) + return(make_complex(sc, real_to_double(sc, rl, __func__), x)); + } + return((want_symbol) ? make_symbol_with_strlen(sc, q) : sc->F); +} + +#if WITH_NUMBER_SEPARATOR +static s7_pointer string_to_number(s7_scheme *sc, char *str, int32_t radix); + +static s7_pointer make_symbol_or_number(s7_scheme *sc, const char *name, int32_t radix, bool want_symbol) +{ + block_t *b; + char *new_name; + char sep = sc->number_separator; + s7_int len, i, j; + s7_pointer res; + + if (name[0] == sep) + return((want_symbol) ? make_symbol_with_strlen(sc, name) : sc->F); + len = safe_strlen(name); + b = mallocate(sc, len + 1); + new_name = (char *)block_data(b); + memcpy((void *)new_name, (const void *)name, len); + new_name[len] = 0; + + for (i = 0, j = 0; i < len; i++) + if (name[i] != sep) + { + if ((digits[(uint8_t)(name[i])] < radix) || (!t_number_separator_p[(uint8_t)name[i]])) + new_name[j++] = name[i]; + else + { + liberate(sc, b); + return((want_symbol) ? make_symbol_with_strlen(sc, name) : sc->F); + }} + else /* sep has to be between two digits */ + if ((digits[(uint8_t)(name[i - 1])] >= radix) || (digits[(uint8_t)(name[i + 1])] >= radix)) + { + liberate(sc, b); + return((want_symbol) ? make_symbol_with_strlen(sc, name) : sc->F); + } + + new_name[j] = '\0'; + res = string_to_number(sc, new_name, radix); + liberate(sc, b); + return(res); +} +#endif + +static s7_pointer make_atom(s7_scheme *sc, char *q, int32_t radix, bool want_symbol, bool with_error) +{ + /* make symbol or number from string, a number starts with + - . or digit, but so does 1+ for example */ +#if WITH_NUMBER_SEPARATOR + #define is_digit(Chr, Rad) ((digits[(uint8_t)Chr] < Rad) || ((Chr == sc->number_separator) && (sc->number_separator != '\0'))) +#else + #define is_digit(Chr, Rad) (digits[(uint8_t)Chr] < Rad) +#endif + char c, *p = q; + bool has_dec_point1 = false; + + c = *p++; + switch (c) + { + case '#': + /* from string->number, (string->number #xc) */ + return(make_sharp_constant(sc, p, with_error, NULL, false)); /* make_sharp_constant expects the '#' to be removed */ + + case '+': + case '-': + c = *p++; + if (c == '.') + { + has_dec_point1 = true; + c = *p++; + } + if (!c) + return((want_symbol) ? make_symbol_with_strlen(sc, q) : sc->F); + if (!is_digit(c, radix)) + { + if (has_dec_point1) + return((want_symbol) ? make_symbol_with_strlen(sc, q) : sc->F); + if (c == 'n') + { + if (local_strcmp(p, "an.0")) /* +nan.0, even if we read -nan.0 -- what's the point of a negative NaN? */ + return(real_NaN); + if ((local_strncmp(p, "an.0", 4)) && /* +nan.0[+/-]...i */ + ((p[4] == '+') || (p[4] == '-'))) + return(nan1_or_bust(sc, NAN, p, q, radix, want_symbol, 4)); + /* read +/-nan. or +/-nan.+/-...i */ + if (local_strncmp(p, "an.", 3)) /* +nan. */ + { + bool overflow = false; + int32_t i; + for (i = 3; is_digit(p[i], 10); i++); + if ((p[i] == '+') || (p[i] == '-')) /* complex case */ + { + int64_t payload = string_to_integer((char *)(p + 3), 10, &overflow); + return(nan1_or_bust(sc, nan_with_payload(payload), p, q, radix, want_symbol, i)); + } + if ((p[i] != '\0') && (!white_space[(uint8_t)(p[i])])) /* check for +nan.0i etc, '\0' is not white_space apparently */ + return((want_symbol) ? make_symbol_with_strlen(sc, q) : sc->F); + return(make_nan_with_payload(sc, string_to_integer((char *)(p + 3), 10, &overflow))); + }} + if (c == 'i') + { + if (local_strcmp(p, "nf.0")) /* +inf.0 */ + return((q[0] == '+') ? real_infinity : real_minus_infinity); + if ((local_strncmp(p, "nf.0", 4)) && + ((p[4] == '+') || (p[4] == '-'))) + return(nan1_or_bust(sc, (q[0] == '-') ? -INFINITY : INFINITY, p, q, radix, want_symbol, 4)); + } + return((want_symbol) ? make_symbol_with_strlen(sc, q) : sc->F); + } + break; + + case '.': + has_dec_point1 = true; + c = *p++; + if ((!c) || (!is_digit(c, radix))) + return((want_symbol) ? make_symbol_with_strlen(sc, q) : sc->F); + break; + + case 'n': + return((want_symbol) ? make_symbol_with_strlen(sc, q) : sc->F); + + case 'i': + return((want_symbol) ? make_symbol_with_strlen(sc, q) : sc->F); + + case '0': /* these two are always digits */ + case '1': + break; + + default: + if (!is_digit(c, radix)) + return((want_symbol) ? make_symbol_with_strlen(sc, q) : sc->F); + break; + } + + /* now it's possibly a number -- the first character(s) could be part of a number in the current radix */ + { + char *slash1 = NULL, *slash2 = NULL, *plus = NULL, *ex1 = NULL, *ex2 = NULL; + bool has_i = false, has_dec_point2 = false; + int32_t has_plus_or_minus = 0, current_radix; +#if (!WITH_GMP) + bool overflow = false; /* for string_to_integer */ +#endif + current_radix = radix; /* current_radix is 10 for the exponent portions, but radix for all the rest */ + + for ( ; (c = *p) != 0; ++p) + { + /* what about embedded null? (string->number (string #\1 (integer->char 0) #\0)) + * currently we stop and return 1, but Guile returns #f. + * this also means we can't use substring_uncopied if (string->number (substring...)) + */ + if (!is_digit(c, current_radix)) /* moving this inside the switch statement was much slower */ + { + current_radix = radix; + + switch (c) + { + /* -------- decimal point -------- */ + case '.': + if ((!is_digit(p[1], current_radix)) && + (!is_digit(p[-1], current_radix))) + return((want_symbol) ? make_symbol_with_strlen(sc, q) : sc->F); + + if (has_plus_or_minus == 0) + { + if ((has_dec_point1) || (slash1)) + return((want_symbol) ? make_symbol_with_strlen(sc, q) : sc->F); + has_dec_point1 = true; + } + else + { + if ((has_dec_point2) || (slash2)) + return((want_symbol) ? make_symbol_with_strlen(sc, q) : sc->F); + has_dec_point2 = true; + } + continue; + + /* -------- exponent marker -------- */ +#if WITH_EXTRA_EXPONENT_MARKERS + /* 1st 3d-perspective 0.0f 128.0f 3d 1s -- in 2 million lines of public scheme code, not one actual use! */ + case 's': case 'S': + case 'd': case 'D': + case 'f': case 'F': + case 'l': case 'L': +#endif + case 'e': case 'E': + if (current_radix > 10) /* see above */ + return((want_symbol) ? make_symbol_with_strlen(sc, q) : sc->F); + /* fall through -- if '@' used, radices>10 are ok */ + + case '@': + current_radix = 10; + + if (((ex1) || + (slash1)) && + (has_plus_or_minus == 0)) /* ee */ + return((want_symbol) ? make_symbol_with_strlen(sc, q) : sc->F); + + if (((ex2) || + (slash2)) && + (has_plus_or_minus != 0)) /* 1+1.0ee */ + return((want_symbol) ? make_symbol_with_strlen(sc, q) : sc->F); + + if ((!is_digit(p[-1], radix)) && /* was current_radix but that's always 10! */ + (p[-1] != '.')) + return((want_symbol) ? make_symbol_with_strlen(sc, q) : sc->F); + + if (has_plus_or_minus == 0) + { + ex1 = p; + has_dec_point1 = true; /* decimal point illegal from now on */ + } + else + { + ex2 = p; + has_dec_point2 = true; + } + p++; + if ((*p == '-') || (*p == '+')) p++; + if (is_digit(*p, current_radix)) + continue; + break; + + /* -------- internal + or - -------- */ + case '+': + case '-': + if (has_plus_or_minus != 0) /* already have the separator */ + return((want_symbol) ? make_symbol_with_strlen(sc, q) : sc->F); + + has_plus_or_minus = (c == '+') ? 1 : -1; + plus = (char *)(p + 1); + /* now check for nan/inf as imaginary part */ + + if ((plus[0] == 'n') && + (local_strncmp(plus, "nan.", 4))) + { + bool overflow1 = false; + int64_t payload = string_to_integer((char *)(p + 5), 10, &overflow1); + return(nan2_or_bust(sc, nan_with_payload(payload), q, radix, want_symbol, (intptr_t)(p - q))); + } + if ((plus[0] == 'i') && + (local_strcmp(plus, "inf.0i"))) + return(nan2_or_bust(sc, (c == '+') ? INFINITY : -INFINITY, q, radix, want_symbol, (intptr_t)(p - q))); + continue; + + /* ratio marker */ + case '/': + if ((has_plus_or_minus == 0) && + ((ex1) || + (slash1) || + (has_dec_point1))) + return((want_symbol) ? make_symbol_with_strlen(sc, q) : sc->F); + + if ((has_plus_or_minus != 0) && + ((ex2) || + (slash2) || + (has_dec_point2))) + return((want_symbol) ? make_symbol_with_strlen(sc, q) : sc->F); + + if (has_plus_or_minus == 0) + slash1 = (char *)(p + 1); + else slash2 = (char *)(p + 1); + + if ((!is_digit(p[1], current_radix)) || + (!is_digit(p[-1], current_radix))) + return((want_symbol) ? make_symbol_with_strlen(sc, q) : sc->F); + + continue; + + /* -------- i for the imaginary part -------- */ + case 'i': + if ((has_plus_or_minus != 0) && + (!has_i)) + { + has_i = true; + continue; + } + break; + + default: break; + } + return((want_symbol) ? make_symbol_with_strlen(sc, q) : sc->F); + }} + + if ((has_plus_or_minus != 0) && /* that is, we have an internal + or - */ + (!has_i)) /* but no i for the imaginary part */ + return((want_symbol) ? make_symbol_with_strlen(sc, q) : sc->F); + +#if WITH_NUMBER_SEPARATOR + if ((sc->number_separator != '\0') && (strchr(q, (int)(sc->number_separator)))) + return(make_symbol_or_number(sc, q, radix, want_symbol)); +#endif + + if (has_i) + { +#if (!WITH_GMP) + s7_double rl = 0.0, im = 0.0; +#else + char e1 = 0, e2 = 0; +#endif + s7_pointer result; + s7_int len = safe_strlen(q); + char ql1, pl1; + + if (q[len - 1] != 'i') + return((want_symbol) ? make_symbol_with_strlen(sc, q) : sc->F); + + /* save original string */ + ql1 = q[len - 1]; + pl1 = (*(plus - 1)); +#if WITH_GMP + if (ex1) {e1 = *ex1; (*ex1) = '@';} /* for mpfr */ + if (ex2) {e2 = *ex2; (*ex2) = '@';} +#endif + /* look for cases like 1+i */ + q[len - 1] = ((q[len - 2] == '+') || (q[len - 2] == '-')) ? '1' : '\0'; /* remove 'i' */ + + (*((char *)(plus - 1))) = '\0'; + +#if (!WITH_GMP) + if ((has_dec_point1) || + (ex1)) /* (string->number "1100.1+0.11i" 2) -- need to split into 2 honest reals before passing to non-base-10 str->dbl */ + rl = string_to_double_with_radix(q, radix, ignored); + else /* no decimal point, no exponent, a ratio (1/2+i for example, but 1+2/3i is handled below) */ + { + if (slash1) + { + /* here the overflow could be innocuous if it's in the denominator and the numerator is 0: 0/100000000000000000000000000000000000000 */ + s7_int den, num = string_to_integer(q, radix, &overflow); + if (overflow) return(make_undefined_bignum(sc, q)); + den = string_to_integer(slash1, radix, &overflow); + if (den == 0) + rl = NAN; /* real_part if complex */ + else + { + if (num == 0) + { + rl = 0.0; + overflow = false; + } + else + { + if (overflow) return(make_undefined_bignum(sc, q)); /* denominator overflow */ + rl = (long_double)num / (long_double)den; /* no gmp, so we do what we can */ + }}} + else + { + rl = (s7_double)string_to_integer(q, radix, &overflow); + if (overflow) return(make_undefined_bignum(sc, q)); + }} + if (rl == -0.0) rl = 0.0; + + if ((has_dec_point2) || + (ex2)) + im = string_to_double_with_radix(plus, radix, ignored); + else + { + if (slash2) /* complex part I think */ + { + /* same as above: 0-0/100000000000000000000000000000000000000i */ + s7_int den, num = string_to_integer(plus, radix, &overflow); + if (overflow) return(make_undefined_bignum(sc, q)); + den = string_to_integer(slash2, radix, &overflow); + if (den == 0) + im = NAN; + else + { + if (num == 0) + { + im = 0.0; + overflow = false; + } + else + { + if (overflow) return(make_undefined_bignum(sc, q)); /* denominator overflow */ + im = (long_double)num / (long_double)den; + }}} + else + { + im = (s7_double)string_to_integer(plus, radix, &overflow); + if (overflow) return(make_undefined_bignum(sc, q)); + }} + if ((has_plus_or_minus == -1) && + (im != 0.0)) + im = -im; + result = s7_make_complex(sc, rl, im); +#else + result = string_to_either_complex(sc, q, slash1, ex1, has_dec_point1, plus, slash2, ex2, has_dec_point2, radix, has_plus_or_minus); +#endif + /* restore original string */ + q[len - 1] = ql1; + (*((char *)(plus - 1))) = pl1; +#if WITH_GMP + if (ex1) (*ex1) = e1; + if (ex2) (*ex2) = e2; +#endif + return(result); + } + + /* not complex */ + if ((has_dec_point1) || + (ex1)) + { + s7_pointer result; + if (slash1) /* not complex, so slash and "." is not a number */ + return((want_symbol) ? make_symbol_with_strlen(sc, q) : sc->F); + +#if (!WITH_GMP) + result = make_real(sc, string_to_double_with_radix(q, radix, ignored)); +#else + { + char old_e = 0; + if (ex1) + { + old_e = (*ex1); + (*ex1) = '@'; + } + result = string_to_either_real(sc, q, radix); + if (ex1) + (*ex1) = old_e; + } +#endif + return(result); + } + + /* rational */ + if (slash1) +#if (!WITH_GMP) + { + s7_int d, n = string_to_integer(q, radix, &overflow); + if (overflow) return(make_undefined_bignum(sc, q)); + d = string_to_integer(slash1, radix, &overflow); + + if ((n == 0) && (d != 0)) /* 0/100000000000000000000000000000000000000 */ + return(int_zero); + if (d == 0) return(real_NaN); + if (overflow) return(make_undefined_bignum(sc, q)); + /* it would be neat to return 1 from 10000000000000000000000000000/10000000000000000000000000000 + * but q is the entire number ('/' included) and slash1 is the stuff after the '/', and every + * big number comes through here, so there's no clean and safe way to check that q == slash1. + */ + return(make_ratio(sc, n, d)); + } +#else + return(string_to_either_ratio(sc, q, slash1, radix)); +#endif + /* integer */ +#if (!WITH_GMP) + { + s7_int x = string_to_integer(q, radix, &overflow); + if (overflow) return(make_undefined_bignum(sc, q)); + return(make_integer(sc, x)); + } +#else + return(string_to_either_integer(sc, q, radix)); +#endif + } +} + + +/* -------------------------------- string->number -------------------------------- */ +static s7_pointer string_to_number(s7_scheme *sc, char *str, int32_t radix) +{ + s7_pointer x = make_atom(sc, str, radix, NO_SYMBOLS, WITHOUT_OVERFLOW_ERROR); + return((is_number(x)) ? x : sc->F); /* only needed because str might start with '#' and not be a number (#t for example) */ +} + +static s7_pointer string_to_number_p_p(s7_scheme *sc, s7_pointer str1) +{ + char *str; + if (!is_string(str1)) + wrong_type_error_nr(sc, sc->string_to_number_symbol, 1, str1, sc->type_names[T_STRING]); + str = (char *)string_value(str1); + return(((!str) || (!(*str))) ? sc->F : string_to_number(sc, str, 10)); +} + +static s7_pointer string_to_number_p_pp(s7_scheme *sc, s7_pointer str1, s7_pointer radix1) +{ + s7_int radix; + char *str; + if (!is_string(str1)) + wrong_type_error_nr(sc, sc->string_to_number_symbol, 1, str1, sc->type_names[T_STRING]); + + if (!is_t_integer(radix1)) + wrong_type_error_nr(sc, sc->string_to_number_symbol, 2, radix1, sc->type_names[T_INTEGER]); + radix = integer(radix1); + if ((radix < 2) || (radix > 16)) + out_of_range_error_nr(sc, sc->string_to_number_symbol, int_two, radix1, a_valid_radix_string); + + str = (char *)string_value(str1); + if ((!str) || (!(*str))) + return(sc->F); + return(string_to_number(sc, str, radix)); +} + +static s7_pointer g_string_to_number_1(s7_scheme *sc, s7_pointer args, s7_pointer caller) +{ + s7_int radix; + char *str; + if (!is_string(car(args))) + return(method_or_bust(sc, car(args), caller, args, sc->type_names[T_STRING], 1)); + + if (is_pair(cdr(args))) + { + s7_pointer rad = cadr(args); + if (!s7_is_integer(rad)) + return(method_or_bust(sc, rad, caller, args, sc->type_names[T_INTEGER], 2)); + radix = s7_integer_clamped_if_gmp(sc, rad); + if ((radix < 2) || (radix > 16)) + out_of_range_error_nr(sc, caller, int_two, rad, a_valid_radix_string); + } + else radix = 10; + str = (char *)string_value(car(args)); + if ((!str) || (!(*str))) + return(sc->F); + return(string_to_number(sc, str, radix)); +} + +static s7_pointer g_string_to_number(s7_scheme *sc, s7_pointer args) +{ + #define H_string_to_number "(string->number str (radix 10)) converts str into a number. \ +If str does not represent a number, string->number returns #f. If 'str' has an embedded radix, \ +the optional 'radix' argument is ignored: (string->number \"#x11\" 2) -> 17 not 3." + #define Q_string_to_number s7_make_signature(sc, 3, \ + s7_make_signature(sc, 2, sc->is_number_symbol, sc->not_symbol), \ + sc->is_string_symbol, sc->is_integer_symbol) + return(g_string_to_number_1(sc, args, sc->string_to_number_symbol)); +} + + +/* -------------------------------- abs -------------------------------- */ +static inline s7_pointer abs_p_p(s7_scheme *sc, s7_pointer x) +{ +#if (!WITH_GMP) + if (is_t_integer(x)) + { + if (integer(x) >= 0) return(x); + if (integer(x) > S7_INT64_MIN) return(make_integer(sc, -integer(x))); + } + if (is_t_real(x)) + { + if (is_NaN(real(x))) + return((nan_payload(real(x)) > 0) ? x : real_NaN); /* (abs -nan.0) -> +nan.0?? */ + return((signbit(real(x))) ? make_real(sc, -real(x)) : x); + } +#endif + switch (type(x)) + { + case T_INTEGER: + if (integer(x) >= 0) return(x); +#if WITH_GMP + if (integer(x) == S7_INT64_MIN) + { + x = s7_int_to_big_integer(sc, integer(x)); + mpz_neg(big_integer(x), big_integer(x)); + return(x); + } +#else + if (integer(x) == S7_INT64_MIN) + sole_arg_out_of_range_error_nr(sc, sc->abs_symbol, set_elist_1(sc, x), result_is_too_large_string); +#endif + return(make_integer(sc, -integer(x))); + + case T_RATIO: + if (numerator(x) >= 0) return(x); +#if WITH_GMP && (!POINTER_32) + if (numerator(x) == S7_INT64_MIN) + { + s7_pointer p; + mpz_set_si(sc->mpz_1, S7_INT64_MIN); + mpz_neg(sc->mpz_1, sc->mpz_1); + mpz_set_si(sc->mpz_2, denominator(x)); + new_cell(sc, p, T_BIG_RATIO); + big_ratio_bgr(p) = alloc_bigrat(sc); + add_big_ratio(sc, p); + mpq_set_num(big_ratio(p), sc->mpz_1); + mpq_set_den(big_ratio(p), sc->mpz_2); + return(p); + } +#else + if (numerator(x) == S7_INT64_MIN) + return(make_ratio(sc, S7_INT64_MAX, denominator(x))); +#endif + return(make_simple_ratio(sc, -numerator(x), denominator(x))); + + case T_REAL: + if (is_NaN(real(x))) /* (abs -nan.0) -> +nan.0, not -nan.0 */ + return((nan_payload(real(x)) > 0) ? x : real_NaN); + return((signbit(real(x))) ? make_real(sc, -real(x)) : x); /* (abs -0.0) returns -0.0 -- Shiro Kawai */ +#if WITH_GMP + case T_BIG_INTEGER: + mpz_abs(sc->mpz_1, big_integer(x)); + return(mpz_to_integer(sc, sc->mpz_1)); + case T_BIG_RATIO: + mpq_abs(sc->mpq_1, big_ratio(x)); + return(mpq_to_rational(sc, sc->mpq_1)); + case T_BIG_REAL: + mpfr_abs(sc->mpfr_1, big_real(x), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); +#endif + default: + return(method_or_bust_p(sc, x, sc->abs_symbol, sc->type_names[T_REAL])); + } +} + +static s7_pointer g_abs(s7_scheme *sc, s7_pointer args) +{ + #define H_abs "(abs x) returns the absolute value of the real number x" + #define Q_abs s7_make_signature(sc, 2, sc->is_real_symbol, sc->is_real_symbol) + return(abs_p_p(sc, car(args))); +} + +static s7_double abs_d_d(s7_double x) {return((signbit(x)) ? (-x) : x);} +static s7_int abs_i_i(s7_int x) {return((x < 0) ? (-x) : x);} + + +/* -------------------------------- magnitude -------------------------------- */ +static double my_hypot(double x, double y) +{ + /* according to callgrind, this is much faster than libc's hypot */ + if (x == 0.0) return(fabs(y)); + if (y == 0.0) return(fabs(x)); + if (x == y) return(1.414213562373095 * fabs(x)); + if (is_NaN(x)) return(x); + if (is_NaN(y)) return(y); + return(sqrt(x * x + y * y)); +} + +static s7_pointer magnitude_p_p(s7_scheme *sc, s7_pointer x) +{ + if (is_t_complex(x)) + return(make_real(sc, my_hypot(real_part(x), imag_part(x)))); /* was reversed? 8-Nov-22 */ + + switch (type(x)) + { + case T_INTEGER: + if (integer(x) == S7_INT64_MIN) return(mostfix); + /* (magnitude -9223372036854775808) -> -9223372036854775808 + * same thing happens in abs, lcm and gcd: (gcd -9223372036854775808) -> -9223372036854775808 + */ + return((integer(x) < 0) ? make_integer(sc, -integer(x)) : x); + case T_RATIO: + return((numerator(x) < 0) ? make_simple_ratio(sc, -numerator(x), denominator(x)) : x); + case T_REAL: + if (is_NaN(real(x))) /* (magnitude -nan.0) -> +nan.0, not -nan.0 */ + return((nan_payload(real(x)) > 0) ? x : real_NaN); + return((signbit(real(x))) ? make_real(sc, -real(x)) : x); +#if WITH_GMP + case T_BIG_INTEGER: + case T_BIG_RATIO: + case T_BIG_REAL: + return(abs_p_p(sc, x)); + case T_BIG_COMPLEX: + mpc_abs(sc->mpfr_1, big_complex(x), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); +#endif + default: + return(method_or_bust_p(sc, x, sc->magnitude_symbol, a_number_string)); + } +} + +static s7_pointer g_magnitude(s7_scheme *sc, s7_pointer args) +{ + #define H_magnitude "(magnitude z) returns the magnitude of z" + #define Q_magnitude s7_make_signature(sc, 2, sc->is_real_symbol, sc->is_number_symbol) + return(magnitude_p_p(sc, car(args))); +} + +static s7_int magnitude_i_i(s7_int x) {return((x < 0) ? (-x) : x);} +static s7_double magnitude_d_d(s7_double x) {return((signbit(x)) ? (-x) : x);} + + +/* -------------------------------- rationalize -------------------------------- */ +#if WITH_GMP + +static rat_locals_t *init_rat_locals_t(s7_scheme *sc) +{ + rat_locals_t *r = (rat_locals_t *)Malloc(sizeof(rat_locals_t)); + sc->ratloc = r; + mpz_inits(r->i, r->i0, r->i1, r->n, r->p0, r->q0, r->r, r->r1, r->p1, r->q1, r->old_p1, r->old_q1, NULL); + mpq_init(r->q); + mpfr_inits2(sc->bignum_precision, r->error, r->ux, r->x0, r->x1, r->val, r->e0, r->e1, r->e0p, r->e1p, r->old_e0, r->old_e1, r->old_e0p, NULL); + return(r); +} + +static void free_rat_locals(s7_scheme *sc) +{ + rat_locals_t *r = sc->ratloc; + mpz_clears(r->i, r->i0, r->i1, r->n, r->p0, r->q0, r->r, r->r1, r->p1, r->q1, r->old_p1, r->old_q1, NULL); + mpq_clear(r->q); + mpfr_clears(r->error, r->ux, r->x0, r->x1, r->val, r->e0, r->e1, r->e0p, r->e1p, r->old_e0, r->old_e1, r->old_e0p, NULL); + free(r); +} + +static s7_pointer big_rationalize(s7_scheme *sc, s7_pointer args) +{ + /* can return be non-rational? */ + /* currently (rationalize 1/0 1e18) -> 0 + * remember to pad with many trailing zeros: + * (rationalize 0.1 0) -> 3602879701896397/36028797018963968 + * (rationalize 0.1000000000000000 0) -> 1/10 + * perhaps gmp number reader used if gmp -- could this be the trailing zeros problem? (why is the non-gmp case ok?) + * also the bignum function is faking it. + * (rationalize (real-part (bignum "0.1+i")) 0) -> 3602879701896397/36028797018963968 + * a confusing case: + * (rationalize 5925563891587147521650777143.74135805596e05) should be 148139097289678688041269428593533951399/250000 + * but that requires more than 128 bits of bignum-precision. + */ + + s7_pointer pp0 = car(args); + rat_locals_t *r = (sc->ratloc) ? sc->ratloc : init_rat_locals_t(sc); + + switch (type(pp0)) + { + case T_INTEGER: + mpfr_set_si(r->ux, integer(pp0), MPFR_RNDN); + break; + case T_RATIO: + mpq_set_si(sc->mpq_1, numerator(pp0), denominator(pp0)); + mpfr_set_q(r->ux, sc->mpq_1, MPFR_RNDN); + break; + case T_REAL: + if (is_NaN(real(pp0))) + out_of_range_error_nr(sc, sc->rationalize_symbol, int_one, pp0, it_is_nan_string); + if (is_inf(real(pp0))) + out_of_range_error_nr(sc, sc->rationalize_symbol, int_one, pp0, it_is_infinite_string); + mpfr_set_d(r->ux, real(pp0), MPFR_RNDN); + break; + case T_BIG_INTEGER: + mpfr_set_z(r->ux, big_integer(pp0), MPFR_RNDN); + break; + case T_BIG_RATIO: + mpfr_set_q(r->ux, big_ratio(pp0), MPFR_RNDN); + break; + case T_BIG_REAL: + if (mpfr_nan_p(big_real(pp0))) + out_of_range_error_nr(sc, sc->rationalize_symbol, int_one, pp0, it_is_nan_string); + if (mpfr_inf_p(big_real(pp0))) + out_of_range_error_nr(sc, sc->rationalize_symbol, int_one, pp0, it_is_infinite_string); + mpfr_set(r->ux, big_real(pp0), MPFR_RNDN); + break; + case T_COMPLEX: + case T_BIG_COMPLEX: + wrong_type_error_nr(sc, sc->rationalize_symbol, 1, pp0, sc->type_names[T_REAL]); + default: + return(method_or_bust(sc, pp0, sc->rationalize_symbol, args, sc->type_names[T_REAL], 1)); + } + + if (is_null(cdr(args))) + mpfr_set_d(r->error, sc->default_rationalize_error, MPFR_RNDN); + else + { + s7_pointer pp1 = cadr(args); + switch (type(pp1)) + { + case T_INTEGER: + mpfr_set_si(r->error, integer(pp1), MPFR_RNDN); + break; + case T_RATIO: + mpq_set_si(sc->mpq_1, numerator(pp1), denominator(pp1)); + mpfr_set_q(r->error, sc->mpq_1, MPFR_RNDN); + break; + case T_REAL: + if (is_NaN(real(pp1))) + out_of_range_error_nr(sc, sc->rationalize_symbol, int_two, pp1, it_is_nan_string); + if (is_inf(real(pp1))) + return(int_zero); + mpfr_set_d(r->error, real(pp1), MPFR_RNDN); + break; + case T_BIG_INTEGER: + mpfr_set_z(r->error, big_integer(pp1), MPFR_RNDN); + break; + case T_BIG_RATIO: + mpfr_set_q(r->error, big_ratio(pp1), MPFR_RNDN); + break; + case T_BIG_REAL: + if (mpfr_nan_p(big_real(pp1))) + out_of_range_error_nr(sc, sc->rationalize_symbol, int_two, pp1, it_is_nan_string); + if (mpfr_inf_p(big_real(pp1))) + return(int_zero); + mpfr_set(r->error, big_real(pp1), MPFR_RNDN); + break; + case T_COMPLEX: + case T_BIG_COMPLEX: + wrong_type_error_nr(sc, sc->rationalize_symbol, 2, pp1, sc->type_names[T_REAL]); + default: + return(method_or_bust(sc, pp1, sc->rationalize_symbol, args, sc->type_names[T_REAL], 2)); + } + mpfr_abs(r->error, r->error, MPFR_RNDN); + } + + mpfr_set(r->x0, r->ux, MPFR_RNDN); /* x0 = ux - error */ + mpfr_sub(r->x0, r->x0, r->error, MPFR_RNDN); + mpfr_set(r->x1, r->ux, MPFR_RNDN); /* x1 = ux + error */ + mpfr_add(r->x1, r->x1, r->error, MPFR_RNDN); + mpfr_get_z(r->i, r->x0, MPFR_RNDU); /* i = ceil(x0) */ + + if (mpfr_cmp_ui(r->error, 1) >= 0) /* if (error >= 1.0) */ + { + if (mpfr_cmp_ui(r->x0, 0) < 0) /* if (x0 < 0) */ + { + if (mpfr_cmp_ui(r->x1, 0) < 0) /* if (x1 < 0) */ + mpfr_get_z(r->n, r->x1, MPFR_RNDD); /* num = floor(x1) */ + else mpz_set_ui(r->n, 0); /* else num = 0 */ + } + else mpz_set(r->n, r->i); /* else num = i */ + return(mpz_to_integer(sc, r->n)); + } + + if (mpfr_cmp_z(r->x1, r->i) >= 0) /* if (x1 >= i) */ + { + if (mpz_cmp_ui(r->i, 0) >= 0) /* if (i >= 0) */ + mpz_set(r->n, r->i); /* num = i */ + else mpfr_get_z(r->n, r->x1, MPFR_RNDD); /* else num = floor(x1) */ + return(mpz_to_integer(sc, r->n)); + } + + mpfr_get_z(r->i0, r->x0, MPFR_RNDD); /* i0 = floor(x0) */ + mpfr_get_z(r->i1, r->x1, MPFR_RNDU); /* i1 = ceil(x1) */ + + mpz_set(r->p0, r->i0); /* p0 = i0 */ + mpz_set_ui(r->q0, 1); /* q0 = 1 */ + mpz_set(r->p1, r->i1); /* p1 = i1 */ + mpz_set_ui(r->q1, 1); /* q1 = 1 */ + mpfr_sub_z(r->e0, r->x0, r->i1, MPFR_RNDN); /* e0 = i1 - x0 */ + mpfr_neg(r->e0, r->e0, MPFR_RNDN); + mpfr_sub_z(r->e1, r->x0, r->i0, MPFR_RNDN); /* e1 = x0 - i0 */ + mpfr_sub_z(r->e0p, r->x1, r->i1, MPFR_RNDN); /* e0p = i1 - x1 */ + mpfr_neg(r->e0p, r->e0p, MPFR_RNDN); + mpfr_sub_z(r->e1p, r->x1, r->i0, MPFR_RNDN); /* e1p = x1 - i0 */ + + while (true) + { + mpfr_set_z(r->val, r->p0, MPFR_RNDN); + mpfr_div_z(r->val, r->val, r->q0, MPFR_RNDN); /* val = p0/q0 */ + + if (((mpfr_lessequal_p(r->x0, r->val)) && /* if ((x0 <= val) && (val <= x1)) */ + (mpfr_lessequal_p(r->val, r->x1))) || + (mpfr_cmp_ui(r->e1, 0) == 0) || + (mpfr_cmp_ui(r->e1p, 0) == 0)) + /* these last 2 are probably not needed -- they protect against running out of bits in the non-gmp case above */ + { + mpq_set_num(r->q, r->p0); /* return(p0/q0) */ + mpq_set_den(r->q, r->q0); + return(mpq_to_rational(sc, r->q)); + } + + mpfr_div(r->val, r->e0, r->e1, MPFR_RNDN); + mpfr_get_z(r->r, r->val, MPFR_RNDD); /* r = floor(e0/e1) */ + mpfr_div(r->val, r->e0p, r->e1p, MPFR_RNDN); + mpfr_get_z(r->r1, r->val, MPFR_RNDU); /* r1 = ceil(e0p/e1p) */ + if (mpz_cmp(r->r1, r->r) < 0) /* if (r1 < r) */ + mpz_set(r->r, r->r1); /* r = r1 */ + + mpz_set(r->old_p1, r->p1); /* old_p1 = p1 */ + mpz_set(r->p1, r->p0); /* p1 = p0 */ + mpz_set(r->old_q1, r->q1); /* old_q1 = q1 */ + mpz_set(r->q1, r->q0); /* q1 = q0 */ + + mpfr_set(r->old_e0, r->e0, MPFR_RNDN); /* old_e0 = e0 */ + mpfr_set(r->e0, r->e1p, MPFR_RNDN); /* e0 = e1p */ + mpfr_set(r->old_e0p, r->e0p, MPFR_RNDN); /* old_e0p = e0p */ + mpfr_set(r->e0p, r->e1, MPFR_RNDN); /* e0p = e1 */ + mpfr_set(r->old_e1, r->e1, MPFR_RNDN); /* old_e1 = e1 */ + + mpz_mul(r->p0, r->p0, r->r); /* p0 = old_p1 + r * p0 */ + mpz_add(r->p0, r->p0, r->old_p1); + + mpz_mul(r->q0, r->q0, r->r); /* q0 = old_q1 + r * q0 */ + mpz_add(r->q0, r->q0, r->old_q1); + + mpfr_mul_z(r->e1, r->e1p, r->r, MPFR_RNDN); /* e1 = old_e0p - r * e1p */ + mpfr_sub(r->e1, r->old_e0p, r->e1, MPFR_RNDN); + + mpfr_mul_z(r->e1p, r->old_e1, r->r, MPFR_RNDN);/* e1p = old_e0 - r * old_e1 */ + mpfr_sub(r->e1p, r->old_e0, r->e1p, MPFR_RNDN); + } +} +#endif + +static s7_pointer g_rationalize(s7_scheme *sc, s7_pointer args) +{ + #define H_rationalize "(rationalize x err) returns the ratio with smallest denominator within err of x" + #define Q_rationalize s7_make_signature(sc, 3, sc->is_rational_symbol, sc->is_real_symbol, sc->is_real_symbol) + /* I can't find a case where this returns a non-rational result */ + + s7_double err; + s7_pointer x = car(args); + +#if WITH_GMP + if (is_big_number(x)) + return(big_rationalize(sc, args)); +#endif + if (!is_real(x)) + return(method_or_bust(sc, x, sc->rationalize_symbol, args, sc->type_names[T_REAL], 1)); + + if (is_null(cdr(args))) + err = sc->default_rationalize_error; + else + { + s7_pointer ex = cadr(args); +#if WITH_GMP + if (is_big_number(ex)) + return(big_rationalize(sc, args)); +#endif + if (!is_real(ex)) + return(method_or_bust(sc, ex, sc->rationalize_symbol, args, sc->type_names[T_REAL], 2)); + err = real_to_double(sc, ex, "rationalize"); + if (is_NaN(err)) + out_of_range_error_nr(sc, sc->rationalize_symbol, int_two, ex, it_is_nan_string); + if (err < 0.0) err = -err; + } + + switch (type(x)) + { + case T_INTEGER: + { + s7_int a, b, pa; + if (err < 1.0) return(x); + a = integer(x); + pa = (a < 0) ? -a : a; + if (err >= pa) return(int_zero); + b = (s7_int)err; + pa -= b; + return(make_integer(sc, (a < 0) ? -pa : pa)); + } + + case T_RATIO: + if (err == 0.0) + return(x); + + case T_REAL: + { + s7_double rat = s7_real(x); /* possible fall through from above */ + s7_int numer = 0, denom = 1; + + if ((is_NaN(rat)) || (is_inf(rat))) + out_of_range_error_nr(sc, sc->rationalize_symbol, int_one, x, a_normal_real_string); + + if (err >= fabs(rat)) + return(int_zero); + +#if WITH_GMP + if (fabs(rat) > RATIONALIZE_LIMIT) + return(big_rationalize(sc, set_plist_2(sc, x, wrap_real(sc, err)))); +#else + if (fabs(rat) > RATIONALIZE_LIMIT) + out_of_range_error_nr(sc, sc->rationalize_symbol, int_one, x, it_is_too_large_string); +#endif + if ((fabs(rat) + fabs(err)) < 1.0e-18) + err = 1.0e-18; + /* (/ 1.0 most-positive-fixnum) is 1.0842021e-19, so if we let err be less than that, + * (rationalize 1e-19 1e-20) hangs, but this only affects the initial ceiling, I believe. + */ + + if (fabs(rat) < fabs(err)) + return(int_zero); + + return((c_rationalize(rat, err, &numer, &denom)) ? make_ratio(sc, numer, denom) : sc->F); + }} + return(sc->F); /* make compiler happy */ +} + +static s7_int rationalize_i_i(s7_int x) {return(x);} +static s7_pointer rationalize_p_i(s7_scheme *sc, s7_int x) {return(make_integer(sc, x));} +static s7_pointer rationalize_p_d(s7_scheme *sc, s7_double x) +{ + if ((is_NaN(x)) || (is_inf(x))) + out_of_range_error_nr(sc, sc->rationalize_symbol, int_one, wrap_real(sc, x), a_normal_real_string); /* was make_real, also below */ + if (fabs(x) > RATIONALIZE_LIMIT) +#if WITH_GMP + return(big_rationalize(sc, set_plist_1(sc, wrap_real(sc, x)))); +#else + out_of_range_error_nr(sc, sc->rationalize_symbol, int_one, wrap_real(sc, x), it_is_too_large_string); +#endif + return(s7_rationalize(sc, x, sc->default_rationalize_error)); +} + + +/* -------------------------------- angle -------------------------------- */ +static s7_pointer g_angle(s7_scheme *sc, s7_pointer args) +{ + #define H_angle "(angle z) returns the angle of z" + #define Q_angle s7_make_signature(sc, 2, sc->is_real_symbol, sc->is_number_symbol) + + s7_pointer x = car(args); /* (angle inf+infi) -> 0.78539816339745 ? I think this should be -pi < ang <= pi */ + switch (type(x)) + { + case T_INTEGER: return((integer(x) < 0) ? real_pi : int_zero); + case T_RATIO: return((numerator(x) < 0) ? real_pi : int_zero); + case T_COMPLEX: return(make_real(sc, atan2(imag_part(x), real_part(x)))); + + case T_REAL: + if (is_NaN(real(x))) return(x); + return((real(x) < 0.0) ? real_pi : real_zero); +#if WITH_GMP + case T_BIG_INTEGER: return((mpz_cmp_ui(big_integer(x), 0) >= 0) ? int_zero : big_pi(sc)); + case T_BIG_RATIO: return((mpq_cmp_ui(big_ratio(x), 0, 1) >= 0) ? int_zero : big_pi(sc)); + case T_BIG_REAL: + if (mpfr_nan_p(big_real(x))) return(x); + return((mpfr_cmp_d(big_real(x), 0.0) >= 0) ? real_zero : big_pi(sc)); + case T_BIG_COMPLEX: + { + s7_pointer z; + new_cell(sc, z, T_BIG_REAL); + big_real_bgf(z) = alloc_bigflt(sc); + add_big_real(sc, z); + mpc_arg(big_real(z), big_complex(x), MPFR_RNDN); + return(z); + } +#endif + default: + return(method_or_bust_p(sc, x, sc->angle_symbol, a_number_string)); + } +} + + +/* -------------------------------- complex -------------------------------- */ + +static s7_pointer complex_p_pp(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ +#if WITH_GMP + if ((is_big_number(x)) || (is_big_number(y))) + { + s7_pointer p0 = x, p1 = y, p = NULL; + + if (!is_real(p0)) + return(method_or_bust(sc, p0, sc->complex_symbol, set_plist_2(sc, x, y), sc->type_names[T_REAL], 1)); + if (!is_real(p1)) + return(method_or_bust(sc, p1, sc->complex_symbol, set_plist_2(sc, x, y), sc->type_names[T_REAL], 2)); + + switch (type(p1)) + { + case T_INTEGER: case T_RATIO: case T_REAL: + { + s7_double iz = s7_real(p1); + if (iz == 0.0) /* imag-part is 0.0 */ + return(p0); + new_cell(sc, p, T_BIG_COMPLEX); + big_complex_bgc(p) = alloc_bigcmp(sc); + mpfr_set_d(mpc_imagref(big_complex(p)), iz, MPFR_RNDN); + } + break; + case T_BIG_REAL: + if (mpfr_zero_p(big_real(p1))) return(p0); + new_cell(sc, p, T_BIG_COMPLEX); + big_complex_bgc(p) = alloc_bigcmp(sc); + mpfr_set(mpc_imagref(big_complex(p)), big_real(p1), MPFR_RNDN); + break; + case T_BIG_RATIO: + new_cell(sc, p, T_BIG_COMPLEX); + big_complex_bgc(p) = alloc_bigcmp(sc); + mpfr_set_q(mpc_imagref(big_complex(p)), big_ratio(p1), MPFR_RNDN); + break; + case T_BIG_INTEGER: + if (mpz_cmp_ui(big_integer(p1), 0) == 0) return(p0); + new_cell(sc, p, T_BIG_COMPLEX); + big_complex_bgc(p) = alloc_bigcmp(sc); + mpfr_set_z(mpc_imagref(big_complex(p)), big_integer(p1), MPFR_RNDN); + break; + } + switch (type(p0)) + { + case T_INTEGER: case T_RATIO: case T_REAL: + mpfr_set_d(mpc_realref(big_complex(p)), s7_real(p0), MPFR_RNDN); + break; + case T_BIG_REAL: + mpfr_set(mpc_realref(big_complex(p)), big_real(p0), MPFR_RNDN); + break; + case T_BIG_RATIO: + mpfr_set_q(mpc_realref(big_complex(p)), big_ratio(p0), MPFR_RNDN); + break; + case T_BIG_INTEGER: + mpfr_set_z(mpc_realref(big_complex(p)), big_integer(p0), MPFR_RNDN); + break; + } + add_big_complex(sc, p); + return(p); + } +#endif + if ((is_t_real(x)) && (is_t_real(y))) return((real(y) == 0.0) ? x : make_complex_not_0i(sc, real(x), real(y))); + switch (type(y)) + { + case T_INTEGER: + switch (type(x)) + { + case T_INTEGER: return((integer(y) == 0) ? x : make_complex_not_0i(sc, (s7_double)integer(x), (s7_double)integer(y))); + /* these int->dbl's are problematic: + * (complex 9223372036854775807 9007199254740995): 9223372036854776000.0+9007199254740996.0i + * should we raise an error? + */ + case T_RATIO: return((integer(y) == 0) ? x : make_complex_not_0i(sc, (s7_double)fraction(x), (s7_double)integer(y))); + case T_REAL: return((integer(y) == 0) ? x : make_complex_not_0i(sc, real(x), (s7_double)integer(y))); + default: return(method_or_bust(sc, x, sc->complex_symbol, set_plist_2(sc, x, y), sc->type_names[T_REAL], 1)); + } + case T_RATIO: + switch (type(x)) + { + case T_INTEGER: return(s7_make_complex(sc, (s7_double)integer(x), (s7_double)fraction(y))); /* can fraction be 0.0? */ + case T_RATIO: return(s7_make_complex(sc, (s7_double)fraction(x), (s7_double)fraction(y))); + case T_REAL: return(s7_make_complex(sc, real(x), (s7_double)fraction(y))); + default: return(method_or_bust(sc, x, sc->complex_symbol, set_plist_2(sc, x, y), sc->type_names[T_REAL], 1)); + } + case T_REAL: + switch (type(x)) + { + case T_INTEGER: return((real(y) == 0.0) ? x : make_complex_not_0i(sc, (s7_double)integer(x), real(y))); + case T_RATIO: return((real(y) == 0.0) ? x : make_complex_not_0i(sc, (s7_double)fraction(x), real(y))); + case T_REAL: return((real(y) == 0.0) ? x : make_complex_not_0i(sc, real(x), real(y))); + default: return(method_or_bust(sc, x, sc->complex_symbol, set_plist_2(sc, x, y), sc->type_names[T_REAL], 1)); + } + default: + return(method_or_bust(sc, (is_let(x)) ? x : y, sc->complex_symbol, set_plist_2(sc, x, y), sc->type_names[T_REAL], 2)); + } +} + +static s7_pointer g_complex(s7_scheme *sc, s7_pointer args) +{ + #define H_complex "(complex x1 x2) returns a complex number with real-part x1 and imaginary-part x2" + #define Q_complex s7_make_signature(sc, 3, sc->is_number_symbol, sc->is_real_symbol, sc->is_real_symbol) + return(complex_p_pp(sc, car(args), cadr(args))); +} + +static s7_pointer complex_p_ii(s7_scheme *sc, s7_int x, s7_int y) +{ + return((y == 0.0) ? make_integer(sc, x) : make_complex_not_0i(sc, (s7_double)x, (s7_double)y)); +} + +static s7_pointer complex_p_dd(s7_scheme *sc, s7_double x, s7_double y) +{ + return((y == 0.0) ? make_real(sc, x) : make_complex_not_0i(sc, x, y)); +} + + +/* -------------------------------- bignum -------------------------------- */ +static s7_pointer g_bignum(s7_scheme *sc, s7_pointer args) +{ + #define H_bignum "(bignum val (radix 10)) returns a multiprecision version of the string 'val'. If the argument is a number \ +bignum returns that number as a bignum" +#if WITH_GMP + #define Q_bignum s7_make_signature(sc, 3, sc->is_bignum_symbol, s7_make_signature(sc, 2, sc->is_number_symbol, sc->is_string_symbol), sc->is_integer_symbol) +#else + #define Q_bignum s7_make_signature(sc, 3, \ + s7_make_signature(sc, 2, sc->is_number_symbol, sc->not_symbol), \ + s7_make_signature(sc, 2, sc->is_number_symbol, sc->is_string_symbol), \ + sc->is_integer_symbol) +#endif + + s7_pointer p = car(args); + if (is_number(p)) + { + if (!is_null(cdr(args))) + error_nr(sc, make_symbol(sc, "bignum-error", 12), + set_elist_2(sc, wrap_string(sc, "bignum of a number takes only one argument: ~S", 46), args)); +#if WITH_GMP + switch (type(p)) + { + case T_INTEGER: return(s7_int_to_big_integer(sc, integer(p))); + case T_RATIO: return(s7_int_to_big_ratio(sc, numerator(p), denominator(p))); + case T_REAL: return(s7_double_to_big_real(sc, real(p))); + case T_COMPLEX: return(s7_double_to_big_complex(sc, real_part(p), imag_part(p))); + default: return(p); + } +#else + return(p); +#endif + } + p = g_string_to_number_1(sc, args, sc->bignum_symbol); + if (is_false(sc, p)) /* (bignum "1/3.0") */ + error_nr(sc, make_symbol(sc, "bignum-error", 12), + set_elist_2(sc, wrap_string(sc, "bignum string argument does not represent a number: ~S", 54), car(args))); +#if WITH_GMP + switch (type(p)) + { + case T_INTEGER: return(s7_int_to_big_integer(sc, integer(p))); + case T_RATIO: return(s7_int_to_big_ratio(sc, numerator(p), denominator(p))); + case T_COMPLEX: return(s7_number_to_big_complex(sc, p)); + case T_REAL: + if (is_NaN(real(p))) return(p); + return(s7_double_to_big_real(sc, real(p))); + /* 9Sep21: this was return(string_to_big_real(sc, string_value(car(args)), (is_pair(cdr(args))) ? s7_integer_clamped_if_gmp(sc, cadr(args)) : 10)); */ + default: + return(p); + } +#else + return(p); +#endif +} + + +/* -------------------------------- exp -------------------------------- */ +#if (!HAVE_COMPLEX_NUMBERS) + static s7_pointer no_complex_numbers_string; +#endif + +#define EXP_LIMIT 100.0 + +#if WITH_GMP +static s7_pointer exp_1(s7_scheme *sc, s7_double x) +{ + mpfr_set_d(sc->mpfr_1, x, MPFR_RNDN); + mpfr_exp(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); +} + +static s7_pointer exp_2(s7_scheme *sc, s7_double x, s7_double y) +{ + mpc_set_d_d(sc->mpc_1, x, y, MPC_RNDNN); + mpc_exp(sc->mpc_1, sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); +} +#endif + +static s7_pointer exp_p_p(s7_scheme *sc, s7_pointer x) +{ + s7_double z; + switch (type(x)) + { + case T_INTEGER: + if (integer(x) == 0) return(int_one); /* (exp 0) -> 1 */ + z = (s7_double)integer(x); +#if WITH_GMP + if (fabs(z) > EXP_LIMIT) + return(exp_1(sc, z)); +#endif + return(make_real(sc, exp(z))); + + case T_RATIO: + z = (s7_double)fraction(x); +#if WITH_GMP + if (fabs(z) > EXP_LIMIT) + return(exp_1(sc, z)); +#endif + return(make_real(sc, exp(z))); + + case T_REAL: +#if WITH_GMP + if (fabs(real(x)) > EXP_LIMIT) + return(exp_1(sc, real(x))); +#endif + return(make_real(sc, exp(real(x)))); + + case T_COMPLEX: +#if HAVE_COMPLEX_NUMBERS +#if WITH_GMP + if ((fabs(real_part(x)) > EXP_LIMIT) || + (fabs(imag_part(x)) > EXP_LIMIT)) + return(exp_2(sc, real_part(x), imag_part(x))); +#endif + return(c_complex_to_s7(sc, cexp(to_c_complex(x)))); + /* this is inaccurate for large arguments: + * (exp 0+1e20i) -> -0.66491178990701-0.74692189125949i, not 7.639704044417283004001468027378811228331E-1-6.45251285265780844205811711312523007406E-1i + */ +#else + out_of_range_error_nr(sc, sc->exp_symbol, int_one, x, no_complex_numbers_string); +#endif + +#if WITH_GMP + case T_BIG_INTEGER: + mpfr_set_z(sc->mpfr_1, big_integer(x), MPFR_RNDN); + mpfr_exp(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_RATIO: + mpfr_set_q(sc->mpfr_1, big_ratio(x), MPFR_RNDN); + mpfr_exp(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_REAL: + mpfr_exp(sc->mpfr_1, big_real(x), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + mpc_exp(sc->mpc_1, big_complex(x), MPC_RNDNN); + if (mpfr_zero_p(mpc_imagref(sc->mpc_1))) + return(mpfr_to_big_real(sc, mpc_realref(sc->mpc_1))); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_p(sc, x, sc->exp_symbol, a_number_string)); + } +} + +static s7_pointer g_exp(s7_scheme *sc, s7_pointer args) +{ + #define H_exp "(exp z) returns e^z, (exp 1) is 2.718281828459" + #define Q_exp sc->pl_nn + return(exp_p_p(sc, car(args))); +} + +static s7_double exp_d_d(s7_double x) {return(exp(x));} +static s7_pointer exp_p_d(s7_scheme *sc, s7_double x) {return(make_real(sc, exp(x)));} + + +/* -------------------------------- log -------------------------------- */ +#if __cplusplus +#define LOG_2 1.4426950408889634074 +#else +#define LOG_2 1.4426950408889634073599246810018921L /* (/ (log 2.0)) */ +#endif + +#if WITH_GMP +static s7_pointer big_log(s7_scheme *sc, s7_pointer args) +{ + s7_pointer p0 = car(args), p1 = NULL, res; + + if (!is_number(p0)) + return(method_or_bust(sc, p0, sc->log_symbol, args, a_number_string, 1)); + + if (is_pair(cdr(args))) + { + p1 = cadr(args); + if (!is_number(p1)) + return(method_or_bust(sc, p1, sc->log_symbol, args, a_number_string, 2)); + } + + if (is_real(p0)) + { + res = any_real_to_mpfr(sc, p0, sc->mpfr_1); + if (res == real_NaN) return(res); + if ((is_positive(sc, p0)) && + ((!p1) || + ((is_real(p1)) && (is_positive(sc, p1))))) + { + if (res) return(res); + mpfr_log(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + if (p1) + { + res = any_real_to_mpfr(sc, p1, sc->mpfr_2); + if (res) + return((res == real_infinity) ? real_zero : res); + if (mpfr_zero_p(sc->mpfr_2)) + out_of_range_error_nr(sc, sc->log_symbol, int_two, p1, wrap_string(sc, "can't be zero", 13)); + mpfr_log(sc->mpfr_2, sc->mpfr_2, MPFR_RNDN); + mpfr_div(sc->mpfr_1, sc->mpfr_1, sc->mpfr_2, MPFR_RNDN); + } + if ((mpfr_integer_p(sc->mpfr_1)) && ((is_rational(p0)) && ((!p1) || (is_rational(p1))))) + return(mpfr_to_integer(sc, sc->mpfr_1)); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + }} + if (p1) + { + res = any_number_to_mpc(sc, p1, sc->mpc_2); + if (res) + return((res == real_infinity) ? real_zero : complex_NaN); + if (mpc_zero_p(sc->mpc_2)) + out_of_range_error_nr(sc, sc->log_symbol, int_two, p1, wrap_string(sc, "can't be zero", 13)); + } + res = any_number_to_mpc(sc, p0, sc->mpc_1); + if (res) + { + if ((res == real_infinity) && (p1) && ((is_negative(sc, p0)))) + return(make_complex_not_0i(sc, INFINITY, -NAN)); + return((res == real_NaN) ? complex_NaN : res); + } + mpc_log(sc->mpc_1, sc->mpc_1, MPC_RNDNN); + if (p1) + { + mpc_log(sc->mpc_2, sc->mpc_2, MPC_RNDNN); + mpc_div(sc->mpc_1, sc->mpc_1, sc->mpc_2, MPC_RNDNN); + } + if (mpfr_zero_p(mpc_imagref(sc->mpc_1))) + return(mpfr_to_big_real(sc, mpc_realref(sc->mpc_1))); + return(mpc_to_number(sc, sc->mpc_1)); +} +#endif + +static s7_pointer g_int_log2(s7_scheme *sc, s7_pointer args) +{ + s7_int ix = integer(car(args)); + s7_double fx = log2((double)ix); + return(((ix & (ix - 1)) == 0) ? make_integer(sc, (s7_int)s7_round(fx)) : make_real(sc, fx)); +} + +static s7_pointer g_log(s7_scheme *sc, s7_pointer args) +{ + #define H_log "(log z1 (z2 e)) returns log(z1) / log(z2) where z2 (the base) defaults to e: (log 8 2) = 3" + #define Q_log sc->pcl_n + + s7_pointer x = car(args); + +#if WITH_GMP + if (is_big_number(x)) return(big_log(sc, args)); +#endif + + if (!is_number(x)) + return(method_or_bust(sc, x, sc->log_symbol, args, a_number_string, 1)); + + if (is_pair(cdr(args))) + { + s7_pointer y = cadr(args); + if (!(is_number(y))) + return(method_or_bust(sc, y, sc->log_symbol, args, a_number_string, 2)); + +#if WITH_GMP + if (is_big_number(y)) return(big_log(sc, args)); +#endif + if ((is_t_integer(y)) && (integer(y) == 2)) + { + /* (define (2^n? x) (and (not (zero? x)) (zero? (logand x (- x 1))))) */ + if (is_t_integer(x)) + { + s7_int ix = integer(x); + if (ix > 0) + { + s7_double fx; +#if (__ANDROID__) || (MS_WINDOWS) || (((__GNUC__) && ((__GNUC__ < 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ <= 4)))) && (!defined(__clang__))) + /* just a guess -- log2 gets a warning in gcc 4.3.2, but not in 4.4.4 */ + fx = log((double)ix) * LOG_2; +#else + fx = log2((double)ix); +#endif + /* (s7_int)fx rounds (log 8 2) to 2 in FreeBSD! */ +#if (((__GNUC__) && ((__GNUC__ < 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ < 4)))) && (!defined(__clang__))) + return(make_real(sc, fx)); +#else + return(((ix & (ix - 1)) == 0) ? make_integer(sc, (s7_int)s7_round(fx)) : make_real(sc, fx)); +#endif + }} + if ((is_real(x)) && + (is_positive(sc, x))) + return(make_real(sc, log(s7_real(x)) * LOG_2)); + return(c_complex_to_s7(sc, clog(s7_to_c_complex(x)) * LOG_2)); + } + + if ((is_t_integer(x)) && (integer(x) == 1) && (is_t_integer(y)) && (integer(y) == 1)) /* (log 1 1) -> 0 (this is NaN in the bignum case) */ + return(int_zero); + + /* (log 1 0) must be 0 since everyone says (expt 0 0) is 1 */ + if (is_zero(y)) + { + if ((is_t_integer(y)) && (is_t_integer(x)) && (integer(x) == 1)) + return(y); + out_of_range_error_nr(sc, sc->log_symbol, int_two, y, wrap_string(sc, "can't be zero", 13)); + } + + if ((is_t_real(x)) && (is_NaN(real(x)))) + return(x); + if (is_one(y)) /* this used to raise an error, but the bignum case is simpler if we return inf */ + return((is_one(x)) ? real_zero : real_infinity); /* but (log 1.0 1.0) -> 0.0, currently (log 1/0 1) is inf? */ + + if ((is_real(x)) && + (is_real(y)) && + (is_positive(sc, x)) && + (is_positive(sc, y))) + { + if ((is_rational(x)) && + (is_rational(y))) + { + s7_double res = log(rational_to_double(sc, x)) / log(rational_to_double(sc, y)); + s7_int ires = (s7_int)res; + if (res - ires == 0.0) + return(make_integer(sc, ires)); /* (log 8 2) -> 3 or (log 1/8 2) -> -3 */ + /* since x and y are rational here, it seems reasonable to try to rationalize the result, but not go overboard? + * what about (expt 16 3/2) -> 64? also 2 as base is handled above and always returns a float. + */ + if (fabs(res) < RATIONALIZE_LIMIT) + { + s7_int num, den; + if ((c_rationalize(res, sc->default_rationalize_error, &num, &den)) && + (s7_int_abs(num) < 100) && (s7_int_abs(den) < 100)) + return(make_simple_ratio(sc, num, den)); + } + return(make_real(sc, res)); + } + return(make_real(sc, log(s7_real(x)) / log(s7_real(y)))); + } + if ((is_t_real(x)) && (is_NaN(real(x)))) + return(x); + if ((is_t_complex(y)) && ((is_NaN(real_part(y))) || (is_NaN(imag_part(y))))) + return(y); + return(c_complex_to_s7(sc, clog(s7_to_c_complex(x)) / clog(s7_to_c_complex(y)))); + } + + if (!is_real(x)) + return(c_complex_to_s7(sc, clog(s7_to_c_complex(x)))); + if (is_positive(sc, x)) + return(make_real(sc, log(s7_real(x)))); + return(make_complex_not_0i(sc, log(-s7_real(x)), M_PI)); +} + +static s7_pointer log_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool ops) +{ +#if (!WITH_GMP) + if (args == 2) + { + s7_pointer x = cadr(expr), y = caddr(expr); + if ((is_t_integer(y)) && (integer(y) == 2) && (is_t_integer(x)) && (integer(x) > 0)) + return(sc->int_log2); + } +#endif + return(f); +} + +/* -------------------------------- sin -------------------------------- */ +#define SIN_LIMIT 1.0e16 +#define SINH_LIMIT 20.0 +/* (- (sinh (bignum 30.0)) (sinh 30.0)): -3.718172657214174140191915872003397016115E-4 + * (- (sinh (bignum 20.0)) (sinh 20.0)): -7.865629467297586346406367346575835463792E-10, slightly worse (e-8) if imag-part + */ + +static s7_pointer sin_p_p(s7_scheme *sc, s7_pointer x) +{ +#if (!WITH_GMP) + if (is_t_real(x)) return(make_real(sc, sin(real(x)))); /* range check in gmp case */ +#endif + switch (type(x)) + { + case T_INTEGER: + if (integer(x) == 0) return(int_zero); /* (sin 0) -> 0 */ +#if WITH_GMP + if (integer(x) > SIN_LIMIT) + { + mpz_set_si(sc->mpz_1, integer(x)); + mpfr_set_z(sc->mpfr_1, sc->mpz_1, MPFR_RNDN); + mpfr_sin(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + } +#endif + return(make_real(sc, sin((s7_double)(integer(x))))); /* bogus for very large integers, but so is the equivalent real (see SIN_LIMIT) */ + + case T_RATIO: + return(make_real(sc, sin((s7_double)(fraction(x))))); + + case T_REAL: + { + s7_double y = real(x); +#if WITH_GMP + if (fabs(y) > SIN_LIMIT) + { + mpfr_set_d(sc->mpfr_1, y, MPFR_RNDN); + mpfr_sin(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + } +#endif + return(make_real(sc, sin(y))); + } + + case T_COMPLEX: +#if WITH_GMP + if ((fabs(real_part(x)) > SIN_LIMIT) || (fabs(imag_part(x)) > SINH_LIMIT)) + { + mpc_set_d_d(sc->mpc_1, real_part(x), imag_part(x), MPC_RNDNN); + mpc_sin(sc->mpc_1, sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + } +#endif +#if HAVE_COMPLEX_NUMBERS + return(c_complex_to_s7(sc, csin(to_c_complex(x)))); +#else + out_of_range_error_nr(sc, sc->sin_symbol, int_one, x, no_complex_numbers_string); +#endif + +#if WITH_GMP + case T_BIG_INTEGER: + mpfr_set_z(sc->mpfr_1, big_integer(x), MPFR_RNDN); + mpfr_sin(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_RATIO: + mpfr_set_q(sc->mpfr_1, big_ratio(x), MPFR_RNDN); + mpfr_sin(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_REAL: + mpfr_sin(sc->mpfr_1, big_real(x), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + mpc_sin(sc->mpc_1, big_complex(x), MPC_RNDNN); + if (mpfr_zero_p(mpc_imagref(sc->mpc_1))) + return(mpfr_to_big_real(sc, mpc_realref(sc->mpc_1))); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_p(sc, x, sc->sin_symbol, a_number_string)); + } + /* sin is inaccurate over about 1e30. There's a way to get true results, but it involves fancy "range reduction" techniques. + * (sin 1e32): 0.5852334864823946 + * but it should be 3.901970254333630491697613212893425767786E-1 + * (remainder 1e22 (* 2 pi)) -> 1.0057952155665e+22 !! (it's now a range error) + * it should be 5.263007914620499494429139986095833592117E0 + * before comparing imag-part to 0, we need to look for NaN and inf, else: + * (sinh 0+0/0i) -> 0.0 and (sinh (log 0.0)) -> inf.0 + */ +} + +static s7_pointer g_sin(s7_scheme *sc, s7_pointer args) +{ + #define H_sin "(sin z) returns sin(z)" + #define Q_sin sc->pl_nn + return(sin_p_p(sc, car(args))); +} + +#if WITH_GMP +static s7_pointer sin_p_d(s7_scheme *sc, s7_double x) +{ + if (fabs(x) <= SIN_LIMIT) + return(make_real(sc, sin(x))); + mpfr_set_d(sc->mpfr_1, x, MPFR_RNDN); + mpfr_sin(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); +} +#else +static s7_pointer sin_p_d(s7_scheme *sc, s7_double x) {return(make_real(sc, sin(x)));} +#endif + +static s7_double sin_d_d(s7_double x) {return(sin(x));} + + +/* -------------------------------- cos -------------------------------- */ +static s7_pointer cos_p_p(s7_scheme *sc, s7_pointer x) +{ +#if (!WITH_GMP) + if (is_t_real(x)) return(make_real(sc, cos(real(x)))); /* range check in gmp case */ +#endif + switch (type(x)) + { + case T_INTEGER: + if (integer(x) == 0) return(int_one); /* (cos 0) -> 1 */ +#if WITH_GMP + if (integer(x) > SIN_LIMIT) + { + mpz_set_si(sc->mpz_1, integer(x)); + mpfr_set_z(sc->mpfr_1, sc->mpz_1, MPFR_RNDN); + mpfr_cos(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + } +#endif + return(make_real(sc, cos((s7_double)(integer(x))))); + + case T_RATIO: + return(make_real(sc, cos((s7_double)(fraction(x))))); + + case T_REAL: /* if with_gmp */ + { + s7_double y = real(x); +#if WITH_GMP + if (fabs(y) > SIN_LIMIT) + { + mpfr_set_d(sc->mpfr_1, y, MPFR_RNDN); + mpfr_cos(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + } +#endif + return(make_real(sc, cos(y))); + } + + case T_COMPLEX: +#if WITH_GMP + if ((fabs(real_part(x)) > SIN_LIMIT) || (fabs(imag_part(x)) > SINH_LIMIT)) + { + mpc_set_d_d(sc->mpc_1, real_part(x), imag_part(x), MPC_RNDNN); + mpc_cos(sc->mpc_1, sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + } +#endif +#if HAVE_COMPLEX_NUMBERS + return(c_complex_to_s7(sc, ccos(to_c_complex(x)))); +#else + out_of_range_error_nr(sc, sc->cos_symbol, int_one, x, no_complex_numbers_string); +#endif + +#if WITH_GMP + case T_BIG_INTEGER: + mpfr_set_z(sc->mpfr_1, big_integer(x), MPFR_RNDN); + mpfr_cos(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_RATIO: + mpfr_set_q(sc->mpfr_1, big_ratio(x), MPFR_RNDN); + mpfr_cos(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_REAL: + mpfr_cos(sc->mpfr_1, big_real(x), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + mpc_cos(sc->mpc_1, big_complex(x), MPC_RNDNN); + if (mpfr_zero_p(mpc_imagref(sc->mpc_1))) + return(mpfr_to_big_real(sc, mpc_realref(sc->mpc_1))); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_p(sc, x, sc->cos_symbol, a_number_string)); + } +} + +static s7_pointer g_cos(s7_scheme *sc, s7_pointer args) +{ + #define H_cos "(cos z) returns cos(z)" + #define Q_cos sc->pl_nn + return(cos_p_p(sc, car(args))); +} + +#if WITH_GMP +static s7_pointer cos_p_d(s7_scheme *sc, s7_double x) +{ + if (fabs(x) <= SIN_LIMIT) + return(make_real(sc, cos(x))); + mpfr_set_d(sc->mpfr_1, x, MPFR_RNDN); + mpfr_cos(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); +} +#else +static s7_pointer cos_p_d(s7_scheme *sc, s7_double x) {return(make_real(sc, cos(x)));} +#endif + +static s7_double cos_d_d(s7_double x) {return(cos(x));} + + +/* -------------------------------- tan -------------------------------- */ +#define TAN_LIMIT 1.0e18 + +static s7_pointer tan_p_p(s7_scheme *sc, s7_pointer x) +{ +#if (!WITH_GMP) + if (is_t_real(x)) return(make_real(sc, tan(real(x)))); +#endif + switch (type(x)) + { + case T_INTEGER: + if (integer(x) == 0) return(int_zero); /* (tan 0) -> 0 */ +#if WITH_GMP + if (integer(x) > TAN_LIMIT) + { + mpz_set_si(sc->mpz_1, integer(x)); + mpfr_set_z(sc->mpfr_1, sc->mpz_1, MPFR_RNDN); + mpfr_tan(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + } +#endif + return(make_real(sc, tan((s7_double)(integer(x))))); + + case T_RATIO: + return(make_real(sc, tan((s7_double)(fraction(x))))); + +#if WITH_GMP + case T_REAL: + if (fabs(real(x)) > TAN_LIMIT) + { + mpfr_set_d(sc->mpfr_1, real(x), MPFR_RNDN); + mpfr_tan(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + } +#endif + return(make_real(sc, tan(real(x)))); + + case T_COMPLEX: +#if HAVE_COMPLEX_NUMBERS + if (imag_part(x) > 350.0) + return(make_complex_not_0i(sc, 0.0, 1.0)); + return((imag_part(x) < -350.0) ? s7_make_complex(sc, 0.0, -1.0) : c_complex_to_s7(sc, ctan(to_c_complex(x)))); +#else + out_of_range_error_nr(sc, sc->tan_symbol, int_one, x, no_complex_numbers_string); +#endif + +#if WITH_GMP + case T_BIG_INTEGER: + mpfr_set_z(sc->mpfr_1, big_integer(x), MPFR_RNDN); + mpfr_tan(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_RATIO: + mpfr_set_q(sc->mpfr_1, big_ratio(x), MPFR_RNDN); + mpfr_tan(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_REAL: + mpfr_tan(sc->mpfr_1, big_real(x), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + if ((MPC_INEX_IM(mpc_cmp_si_si(big_complex(x), 1, 350))) > 0) + return(make_complex_not_0i(sc, 0.0, 1.0)); + if ((MPC_INEX_IM(mpc_cmp_si_si(big_complex(x), 1, -350))) < 0) + return(make_complex_not_0i(sc, 0.0, -1.0)); + mpc_tan(sc->mpc_1, big_complex(x), MPC_RNDNN); + if (mpfr_zero_p(mpc_imagref(sc->mpc_1))) + return(mpfr_to_big_real(sc, mpc_realref(sc->mpc_1))); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_p(sc, x, sc->tan_symbol, a_number_string)); + } +} + +static s7_pointer g_tan(s7_scheme *sc, s7_pointer args) +{ + #define H_tan "(tan z) returns tan(z)" + #define Q_tan sc->pl_nn + return(tan_p_p(sc, car(args))); +} + +static s7_double tan_d_d(s7_double x) {return(tan(x));} + + +/* -------------------------------- asin -------------------------------- */ +static s7_pointer c_asin(s7_scheme *sc, s7_double x) +{ + s7_double absx = fabs(x), recip; + s7_complex result; + + if (absx <= 1.0) + return(make_real(sc, asin(x))); + + /* otherwise use maxima code: */ + recip = 1.0 / absx; + result = (M_PI / 2.0) - (s7_complex_i * clog(absx * (1.0 + (sqrt(1.0 + recip) * csqrt(1.0 - recip))))); + return((x < 0.0) ? c_complex_to_s7(sc, -result) : c_complex_to_s7(sc, result)); +} + +static s7_pointer asin_p_p(s7_scheme *sc, s7_pointer p) +{ + if (is_t_real(p)) return(c_asin(sc, real(p))); + switch (type(p)) + { + case T_INTEGER: + if (integer(p) == 0) return(int_zero); /* (asin 0) -> 0 */ + /* in netBSD, (asin 2) returns 0.25383842987008+0.25383842987008i according to Peter Bex */ + return(c_asin(sc, (s7_double)integer(p))); + + case T_RATIO: + return(c_asin(sc, fraction(p))); + + case T_COMPLEX: +#if HAVE_COMPLEX_NUMBERS + /* if either real or imag part is very large, use explicit formula, not casin */ + /* this code taken from sbcl's src/code/irrat.lisp; break is around x+70000000i */ + if ((fabs(real_part(p)) > 1.0e7) || + (fabs(imag_part(p)) > 1.0e7)) + { + s7_complex sq1mz, sq1pz, z = to_c_complex(p); + sq1mz = csqrt(1.0 - z); + sq1pz = csqrt(1.0 + z); + return(s7_make_complex(sc, atan(real_part(p) / creal(sq1mz * sq1pz)), asinh(cimag(sq1pz * conj(sq1mz))))); + } + return(c_complex_to_s7(sc, casin(to_c_complex(p)))); +#else + out_of_range_error_nr(sc, sc->asin_symbol, int_one, p, no_complex_numbers_string); +#endif + +#if WITH_GMP + case T_BIG_INTEGER: + mpfr_set_z(sc->mpfr_1, big_integer(p), MPFR_RNDN); + goto ASIN_BIG_REAL; + case T_BIG_RATIO: + mpfr_set_q(sc->mpfr_1, big_ratio(p), MPFR_RNDN); + goto ASIN_BIG_REAL; + case T_BIG_REAL: + if (mpfr_inf_p(big_real(p))) + { + if (mpfr_cmp_ui(big_real(p), 0) < 0) + return(make_complex_not_0i(sc, NAN, INFINITY)); /* match non-bignum choice */ + return(make_complex_not_0i(sc, NAN, -INFINITY)); + } + mpfr_set(sc->mpfr_1, big_real(p), MPFR_RNDN); + ASIN_BIG_REAL: + mpfr_set_ui(sc->mpfr_2, 1, MPFR_RNDN); + if (mpfr_cmpabs(sc->mpfr_1, sc->mpfr_2) <= 0) + { + mpfr_asin(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + } + mpc_set_fr(sc->mpc_1, sc->mpfr_1, MPC_RNDNN); + mpc_asin(sc->mpc_1, sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_COMPLEX: + mpc_asin(sc->mpc_1, big_complex(p), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_p(sc, p, sc->asin_symbol, a_number_string)); + } +} + +static s7_pointer g_asin(s7_scheme *sc, s7_pointer args) +{ + #define H_asin "(asin z) returns asin(z); (sin (asin x)) = x" + #define Q_asin sc->pl_nn + return(asin_p_p(sc, car(args))); +} + + +/* -------------------------------- acos -------------------------------- */ +static s7_pointer c_acos(s7_scheme *sc, s7_double x) +{ + s7_double absx = fabs(x), recip; + s7_complex result; + + if (absx <= 1.0) + return(make_real(sc, acos(x))); + + /* else follow maxima again: */ + recip = 1.0 / absx; + if (x > 0.0) + result = s7_complex_i * clog(absx * (1.0 + (sqrt(1.0 + recip) * csqrt(1.0 - recip)))); + else result = M_PI - s7_complex_i * clog(absx * (1.0 + (sqrt(1.0 + recip) * csqrt(1.0 - recip)))); + return(c_complex_to_s7(sc, result)); +} + +static s7_pointer acos_p_p(s7_scheme *sc, s7_pointer p) +{ + if (is_t_real(p)) return(c_acos(sc, real(p))); + switch (type(p)) + { + case T_INTEGER: + return((integer(p) == 1) ? int_zero : c_acos(sc, (s7_double)integer(p))); + + case T_RATIO: + return(c_acos(sc, fraction(p))); + + case T_COMPLEX: +#if HAVE_COMPLEX_NUMBERS + /* if either real or imag part is very large, use explicit formula, not cacos */ + /* this code taken from sbcl's src/code/irrat.lisp */ + + if ((fabs(real_part(p)) > 1.0e7) || + (fabs(imag_part(p)) > 1.0e7)) + { + s7_complex sq1mz, sq1pz, z = to_c_complex(p); + sq1mz = csqrt(1.0 - z); + sq1pz = csqrt(1.0 + z); /* creal(sq1pz) can be 0.0 */ + if (creal(sq1pz) == 0.0) /* so the atan arg will be inf, so the real part will be pi/2(?) */ + return(s7_make_complex(sc, M_PI / 2.0, asinh(cimag(sq1mz * conj(sq1pz))))); + return(s7_make_complex(sc, 2.0 * atan(creal(sq1mz) / creal(sq1pz)), asinh(cimag(sq1mz * conj(sq1pz))))); + } + return(c_complex_to_s7(sc, cacos(s7_to_c_complex(p)))); +#else + out_of_range_error_nr(sc, sc->acos_symbol, int_one, p, no_complex_numbers_string); +#endif + +#if WITH_GMP + case T_BIG_INTEGER: + mpfr_set_z(sc->mpfr_1, big_integer(p), MPFR_RNDN); + goto ACOS_BIG_REAL; + case T_BIG_RATIO: + mpfr_set_q(sc->mpfr_1, big_ratio(p), MPFR_RNDN); + goto ACOS_BIG_REAL; + case T_BIG_REAL: + if (mpfr_inf_p(big_real(p))) + { + if (mpfr_cmp_ui(big_real(p), 0) < 0) + return(make_complex_not_0i(sc, -NAN, -INFINITY)); /* match non-bignum choice */ + return(make_complex_not_0i(sc, -NAN, INFINITY)); + } + mpfr_set(sc->mpfr_1, big_real(p), MPFR_RNDN); + ACOS_BIG_REAL: + mpfr_set_ui(sc->mpfr_2, 1, MPFR_RNDN); + if (mpfr_cmpabs(sc->mpfr_1, sc->mpfr_2) <= 0) + { + mpfr_acos(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + } + mpc_set_fr(sc->mpc_1, sc->mpfr_1, MPC_RNDNN); + mpc_acos(sc->mpc_1, sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_COMPLEX: + mpc_acos(sc->mpc_1, big_complex(p), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_p(sc, p, sc->acos_symbol, a_number_string)); + } +} + +static s7_pointer g_acos(s7_scheme *sc, s7_pointer args) +{ + #define H_acos "(acos z) returns acos(z); (cos (acos 1)) = 1" + #define Q_acos sc->pl_nn + return(acos_p_p(sc, car(args))); +} + + +/* -------------------------------- atan -------------------------------- */ +static s7_pointer g_atan(s7_scheme *sc, s7_pointer args) +{ + #define H_atan "(atan z) returns atan(z), (atan y x) returns atan(y/x)" + #define Q_atan s7_make_signature(sc, 3, sc->is_number_symbol, sc->is_number_symbol, sc->is_real_symbol) + /* actually if there are two args, both should be real, but how to express that in the signature? */ + + s7_pointer x = car(args), y; + /* currently (atan inf.0 inf.0) -> 0.78539816339745, and (atan inf.0 -inf.0) -> 2.3561944901923 (etc) */ + + if (!is_pair(cdr(args))) + { + switch (type(x)) + { + case T_INTEGER: return((integer(x) == 0) ? int_zero : make_real(sc, atan((double)integer(x)))); + case T_RATIO: return(make_real(sc, atan(fraction(x)))); + case T_REAL: return(make_real(sc, atan(real(x)))); + + case T_COMPLEX: +#if HAVE_COMPLEX_NUMBERS + return(c_complex_to_s7(sc, catan(to_c_complex(x)))); +#else + out_of_range_error_nr(sc, sc->atan_symbol, int_one, x, no_complex_numbers_string); +#endif + +#if WITH_GMP + case T_BIG_INTEGER: + mpfr_set_z(sc->mpfr_1, big_integer(x), MPFR_RNDN); + mpfr_atan(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_RATIO: + mpfr_set_q(sc->mpfr_1, big_ratio(x), MPFR_RNDN); + mpfr_atan(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_REAL: + mpfr_atan(sc->mpfr_1, big_real(x), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + mpc_atan(sc->mpc_1, big_complex(x), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_p(sc, x, sc->atan_symbol, a_number_string)); + }} + + y = cadr(args); + switch (type(x)) + { + case T_INTEGER: case T_RATIO: case T_REAL: + if (is_small_real(y)) + return(make_real(sc, atan2(s7_real(x), s7_real(y)))); +#if WITH_GMP + if (!is_real(y)) + return(method_or_bust(sc, y, sc->atan_symbol, args, sc->type_names[T_REAL], 2)); + mpfr_set_d(sc->mpfr_1, s7_real(x), MPFR_RNDN); + goto ATAN2_BIG_REAL; + case T_BIG_INTEGER: + mpfr_set_z(sc->mpfr_1, big_integer(x), MPFR_RNDN); + goto ATAN2_BIG_REAL; + case T_BIG_RATIO: + mpfr_set_q(sc->mpfr_1, big_ratio(x), MPFR_RNDN); + goto ATAN2_BIG_REAL; + case T_BIG_REAL: + mpfr_set(sc->mpfr_1, big_real(x), MPFR_RNDN); + goto ATAN2_BIG_REAL; +#endif + default: + return(method_or_bust(sc, x, sc->atan_symbol, args, sc->type_names[T_REAL], 1)); + } +#if WITH_GMP + ATAN2_BIG_REAL: + if (is_small_real(y)) + mpfr_set_d(sc->mpfr_2, s7_real(y), MPFR_RNDN); + else + if (is_t_big_real(y)) + mpfr_set(sc->mpfr_2, big_real(y), MPFR_RNDN); + else + if (is_t_big_integer(y)) + mpfr_set_z(sc->mpfr_2, big_integer(y), MPFR_RNDN); + else + if (is_t_big_ratio(y)) + mpfr_set_q(sc->mpfr_2, big_ratio(y), MPFR_RNDN); + else return(method_or_bust(sc, y, sc->atan_symbol, args, sc->type_names[T_REAL], 2)); + mpfr_atan2(sc->mpfr_1, sc->mpfr_1, sc->mpfr_2, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); +#endif +} + +static s7_double atan_d_dd(s7_double x, s7_double y) {return(atan2(x, y));} + + +/* -------------------------------- sinh -------------------------------- */ +static s7_pointer sinh_p_p(s7_scheme *sc, s7_pointer x) +{ + switch (type(x)) + { + case T_INTEGER: + if (integer(x) == 0) return(int_zero); /* (sinh 0) -> 0 */ + + case T_REAL: + case T_RATIO: + { + s7_double y = s7_real(x); +#if WITH_GMP + if (fabs(y) > SINH_LIMIT) + { + mpfr_set_d(sc->mpfr_1, y, MPFR_RNDN); + mpfr_sinh(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + } +#endif + return(make_real(sc, sinh(y))); + } + + case T_COMPLEX: +#if WITH_GMP + if ((fabs(real_part(x)) > SINH_LIMIT) || (fabs(imag_part(x)) > SINH_LIMIT)) + { + mpc_set_d_d(sc->mpc_1, real_part(x), imag_part(x), MPC_RNDNN); + mpc_sinh(sc->mpc_1, sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + } +#endif +#if HAVE_COMPLEX_NUMBERS + return(c_complex_to_s7(sc, csinh(to_c_complex(x)))); +#else + out_of_range_error_nr(sc, sc->sinh_symbol, int_one, x, no_complex_numbers_string); +#endif + +#if WITH_GMP + case T_BIG_INTEGER: + mpfr_set_z(sc->mpfr_1, big_integer(x), MPFR_RNDN); + mpfr_sinh(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_RATIO: + mpfr_set_q(sc->mpfr_1, big_ratio(x), MPFR_RNDN); + mpfr_sinh(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_REAL: + mpfr_sinh(sc->mpfr_1, big_real(x), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + mpc_sinh(sc->mpc_1, big_complex(x), MPC_RNDNN); + if (mpfr_zero_p(mpc_imagref(sc->mpc_1))) + return(mpfr_to_big_real(sc, mpc_realref(sc->mpc_1))); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_p(sc, x, sc->sinh_symbol, a_number_string)); + } +} + +static s7_pointer g_sinh(s7_scheme *sc, s7_pointer args) +{ + #define H_sinh "(sinh z) returns sinh(z)" + #define Q_sinh sc->pl_nn + return(sinh_p_p(sc, car(args))); +} + +static s7_double sinh_d_d(s7_double x) {return(sinh(x));} +static s7_pointer sinh_p_d(s7_scheme *sc, s7_double x) {return(make_real(sc, sinh(x)));} + /* so sinh in a do-loop with 0 arg may return 0.0 because sinh_p_d does not check if x=0 */ + + +/* -------------------------------- cosh -------------------------------- */ +static s7_pointer cosh_p_p(s7_scheme *sc, s7_pointer x) +{ + switch (type(x)) + { + case T_INTEGER: + if (integer(x) == 0) return(int_one); /* (cosh 0) -> 1 */ + + case T_REAL: + case T_RATIO: + { + s7_double y = s7_real(x); +#if WITH_GMP + if (fabs(y) > SINH_LIMIT) + { + mpfr_set_d(sc->mpfr_1, y, MPFR_RNDN); + mpfr_cosh(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + } +#endif + return(make_real(sc, cosh(y))); + } + + case T_COMPLEX: +#if WITH_GMP + if ((fabs(real_part(x)) > SINH_LIMIT) || (fabs(imag_part(x)) > SINH_LIMIT)) + { + mpc_set_d_d(sc->mpc_1, real_part(x), imag_part(x), MPC_RNDNN); + mpc_cosh(sc->mpc_1, sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + } +#endif +#if HAVE_COMPLEX_NUMBERS + return(c_complex_to_s7(sc, ccosh(to_c_complex(x)))); +#else + out_of_range_error_nr(sc, sc->cosh_symbol, int_one, x, no_complex_numbers_string); +#endif + +#if WITH_GMP + case T_BIG_INTEGER: + mpfr_set_z(sc->mpfr_1, big_integer(x), MPFR_RNDN); + mpfr_cosh(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_RATIO: + mpfr_set_q(sc->mpfr_1, big_ratio(x), MPFR_RNDN); + mpfr_cosh(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_REAL: + mpfr_cosh(sc->mpfr_1, big_real(x), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + mpc_cosh(sc->mpc_1, big_complex(x), MPC_RNDNN); + if (mpfr_zero_p(mpc_imagref(sc->mpc_1))) + return(mpfr_to_big_real(sc, mpc_realref(sc->mpc_1))); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_p(sc, x, sc->cosh_symbol, a_number_string)); + } +} + +static s7_pointer g_cosh(s7_scheme *sc, s7_pointer args) +{ + #define H_cosh "(cosh z) returns cosh(z)" + #define Q_cosh sc->pl_nn + return(cosh_p_p(sc, car(args))); +} + +static s7_double cosh_d_d(s7_double x) {return(cosh(x));} +static s7_pointer cosh_p_d(s7_scheme *sc, s7_double x) {return(make_real(sc, cosh(x)));} + + +/* -------------------------------- tanh -------------------------------- */ +#define TANH_LIMIT 350.0 +static s7_pointer tanh_p_p(s7_scheme *sc, s7_pointer x) +{ + switch (type(x)) + { + case T_INTEGER: return((integer(x) == 0) ? int_zero : make_real(sc, tanh((s7_double)integer(x)))); + case T_RATIO: return(make_real(sc, tanh(fraction(x)))); + case T_REAL: return(make_real(sc, tanh(real(x)))); + + case T_COMPLEX: +#if HAVE_COMPLEX_NUMBERS + if (real_part(x) > TANH_LIMIT) + return(real_one); /* closer than 0.0 which is what ctanh is about to return! */ + if (real_part(x) < -TANH_LIMIT) + return(make_real(sc, -1.0)); /* closer than ctanh's -0.0 */ + return(c_complex_to_s7(sc, ctanh(to_c_complex(x)))); +#else + out_of_range_error_nr(sc, sc->tanh_symbol, int_one, x, no_complex_numbers_string); +#endif + +#if WITH_GMP + case T_BIG_INTEGER: + mpfr_set_z(sc->mpfr_1, big_integer(x), MPFR_RNDN); + goto BIG_REAL_TANH; + case T_BIG_RATIO: + mpfr_set_q(sc->mpfr_1, big_ratio(x), MPFR_RNDN); + goto BIG_REAL_TANH; + case T_BIG_REAL: + if (mpfr_nan_p(big_real(x))) return(x); + mpfr_set(sc->mpfr_1, big_real(x), MPFR_RNDN); + BIG_REAL_TANH: + if (mpfr_cmp_d(sc->mpfr_1, TANH_LIMIT) > 0) return(real_one); + if (mpfr_cmp_d(sc->mpfr_1, -TANH_LIMIT) < 0) return(make_real(sc, -1.0)); + mpfr_tanh(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + if ((MPC_INEX_RE(mpc_cmp_si_si(big_complex(x), TANH_LIMIT, 1))) > 0) + return(real_one); + if ((MPC_INEX_RE(mpc_cmp_si_si(big_complex(x), -TANH_LIMIT, 1))) < 0) + return(make_real(sc, -1.0)); + if ((mpfr_nan_p(mpc_imagref(big_complex(x)))) || + (mpfr_inf_p(mpc_imagref(big_complex(x))))) + { + if (mpfr_cmp_ui(mpc_realref(big_complex(x)), 0) == 0) + return(make_complex_not_0i(sc, 0.0, NAN)); /* match non-bignum choice */ + return(complex_NaN); + } + mpc_tanh(sc->mpc_1, big_complex(x), MPC_RNDNN); + if (mpfr_zero_p(mpc_imagref(sc->mpc_1))) + return(mpfr_to_big_real(sc, mpc_realref(sc->mpc_1))); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_p(sc, x, sc->tanh_symbol, a_number_string)); + } +} + +static s7_pointer g_tanh(s7_scheme *sc, s7_pointer args) +{ + #define H_tanh "(tanh z) returns tanh(z)" + #define Q_tanh sc->pl_nn + return(tanh_p_p(sc, car(args))); +} + +static s7_double tanh_d_d(s7_double x) {return(tanh(x));} + + +/* -------------------------------- asinh -------------------------------- */ +static s7_pointer asinh_p_p(s7_scheme *sc, s7_pointer x) +{ + switch (type(x)) + { + case T_INTEGER: return((integer(x) == 0) ? int_zero : make_real(sc, asinh((s7_double)integer(x)))); + case T_RATIO: return(make_real(sc, asinh(fraction(x)))); + case T_REAL: return(make_real(sc, asinh(real(x)))); + case T_COMPLEX: +#if HAVE_COMPLEX_NUMBERS + #if (defined(__OpenBSD__)) || (defined(__NetBSD__)) + return(c_complex_to_s7(sc, casinh_1(to_c_complex(x)))); + #else + return(c_complex_to_s7(sc, casinh(to_c_complex(x)))); + #endif +#else + out_of_range_error_nr(sc, sc->asinh_symbol, int_one, x, no_complex_numbers_string); +#endif + +#if WITH_GMP + case T_BIG_INTEGER: + mpfr_set_z(sc->mpfr_1, big_integer(x), MPFR_RNDN); + mpfr_asinh(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_RATIO: + mpfr_set_q(sc->mpfr_1, big_ratio(x), MPFR_RNDN); + mpfr_asinh(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_REAL: + mpfr_asinh(sc->mpfr_1, big_real(x), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + mpc_asinh(sc->mpc_1, big_complex(x), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_p(sc, x, sc->asinh_symbol, a_number_string)); + } +} + +static s7_pointer g_asinh(s7_scheme *sc, s7_pointer args) +{ + #define H_asinh "(asinh z) returns asinh(z)" + #define Q_asinh sc->pl_nn + return(asinh_p_p(sc, car(args))); +} + + +/* -------------------------------- acosh -------------------------------- */ +static s7_pointer acosh_p_p(s7_scheme *sc, s7_pointer x) +{ + switch (type(x)) + { + case T_INTEGER: + if (integer(x) == 1) return(int_zero); + case T_REAL: + case T_RATIO: + { + s7_double x1 = s7_real(x); + if (x1 >= 1.0) + return(make_real(sc, acosh(x1))); + } + case T_COMPLEX: +#if HAVE_COMPLEX_NUMBERS + #ifdef __OpenBSD__ + return(c_complex_to_s7(sc, cacosh_1(s7_to_c_complex(x)))); + #else + return(c_complex_to_s7(sc, cacosh(s7_to_c_complex(x)))); /* not to_c_complex because x might not be complex */ + #endif +#else + /* since we can fall through to this branch, we need a better error message than "must be a number, not 0.0" */ + out_of_range_error_nr(sc, sc->acosh_symbol, int_one, x, no_complex_numbers_string); +#endif + +#if WITH_GMP + case T_BIG_INTEGER: + mpc_set_z(sc->mpc_1, big_integer(x), MPC_RNDNN); + mpc_acosh(sc->mpc_1, sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_RATIO: + mpc_set_q(sc->mpc_1, big_ratio(x), MPC_RNDNN); + mpc_acosh(sc->mpc_1, sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_REAL: + mpc_set_fr(sc->mpc_1, big_real(x), MPC_RNDNN); + mpc_acosh(sc->mpc_1, sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_COMPLEX: + mpc_acosh(sc->mpc_1, big_complex(x), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_p(sc, x, sc->acosh_symbol, a_number_string)); + } +} + +static s7_pointer g_acosh(s7_scheme *sc, s7_pointer args) +{ + #define H_acosh "(acosh z) returns acosh(z)" + #define Q_acosh sc->pl_nn + return(acosh_p_p(sc, car(args))); +} + + +/* -------------------------------- atanh -------------------------------- */ +static s7_pointer atanh_p_p(s7_scheme *sc, s7_pointer x) +{ + switch (type(x)) + { + case T_INTEGER: + if (integer(x) == 0) return(int_zero); /* (atanh 0) -> 0 */ + case T_REAL: + case T_RATIO: + { + s7_double x1 = s7_real(x); + if (fabs(x1) < 1.0) + return(make_real(sc, atanh(x1))); + } + /* if we can't distinguish x from 1.0 even with long_doubles, we'll get inf.0: + * (atanh 9223372036854775/9223372036854776) -> 18.714973875119 + * (atanh 92233720368547758/92233720368547757) -> inf.0 + * (atanh (bignum 92233720368547758/92233720368547757)) -> 1.987812468492420421418925013176932317086E1+1.570796326794896619231321691639751442098E0i + * but the imaginary part is unnecessary + */ + case T_COMPLEX: +#if HAVE_COMPLEX_NUMBERS + #if (defined(__OpenBSD__)) || (defined(__NetBSD__)) + return(c_complex_to_s7(sc, catanh_1(s7_to_c_complex(x)))); + #else + return(c_complex_to_s7(sc, catanh(s7_to_c_complex(x)))); + #endif +#else + out_of_range_error_nr(sc, sc->atanh_symbol, int_one, x, no_complex_numbers_string); +#endif + +#if WITH_GMP + case T_BIG_INTEGER: + mpfr_set_z(sc->mpfr_2, big_integer(x), MPFR_RNDN); + goto ATANH_BIG_REAL; + case T_BIG_RATIO: + mpfr_set_q(sc->mpfr_2, big_ratio(x), MPFR_RNDN); + goto ATANH_BIG_REAL; + case T_BIG_REAL: + mpfr_set(sc->mpfr_2, big_real(x), MPFR_RNDN); + ATANH_BIG_REAL: + mpfr_set_ui(sc->mpfr_1, 1, MPFR_RNDN); + if (mpfr_cmpabs(sc->mpfr_2, sc->mpfr_1) < 0) + { + mpfr_atanh(sc->mpfr_2, sc->mpfr_2, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_2)); + } + mpc_set_fr(sc->mpc_1, sc->mpfr_2, MPC_RNDNN); + mpc_atanh(sc->mpc_1, sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_COMPLEX: + mpc_atanh(sc->mpc_1, big_complex(x), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_p(sc, x, sc->atanh_symbol, a_number_string)); + } +} + +static s7_pointer g_atanh(s7_scheme *sc, s7_pointer args) +{ + #define H_atanh "(atanh z) returns atanh(z)" + #define Q_atanh sc->pl_nn + return(atanh_p_p(sc, car(args))); +} + + +/* -------------------------------- sqrt -------------------------------- */ +static s7_pointer sqrt_p_p(s7_scheme *sc, s7_pointer p) +{ + switch (type(p)) + { + case T_INTEGER: + { + s7_double sqx; + if (integer(p) >= 0) + { + s7_int ix; +#if WITH_GMP + mpz_set_si(sc->mpz_1, integer(p)); + mpz_sqrtrem(sc->mpz_1, sc->mpz_2, sc->mpz_1); + if (mpz_cmp_ui(sc->mpz_2, 0) == 0) + return(make_integer(sc, mpz_get_si(sc->mpz_1))); + mpfr_set_si(sc->mpfr_1, integer(p), MPFR_RNDN); + mpfr_sqrt(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); +#endif + sqx = sqrt((s7_double)integer(p)); + ix = (s7_int)sqx; + return(((ix * ix) == integer(p)) ? make_integer(sc, ix) : make_real(sc, sqx)); + /* Mark Weaver notes that (zero? (- (sqrt 9007199136250226) 94906265.0)) -> #t + * but (* 94906265 94906265) -> 9007199136250225 -- oops + * if we use bigfloats, we're ok: + * (* (sqrt 9007199136250226.0) (sqrt 9007199136250226.0)) -> 9.007199136250226000000000000000000000026E15 + * at least we return a real here, not an incorrect integer and (sqrt 9007199136250225) -> 94906265 + */ + } +#if HAVE_COMPLEX_NUMBERS +#if WITH_GMP + mpc_set_si(sc->mpc_1, integer(p), MPC_RNDNN); + mpc_sqrt(sc->mpc_1, sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + sqx = (s7_double)integer(p); /* we're trying to protect against (sqrt -9223372036854775808) where we can't negate the integer argument */ + return(s7_make_complex(sc, 0.0, sqrt((s7_double)(-sqx)))); +#else + out_of_range_error_nr(sc, sc->sqrt_symbol, int_one, p, no_complex_numbers_string); +#endif + } + + case T_RATIO: + if (numerator(p) > 0) /* else it's complex, so it can't be a ratio */ + { + s7_int nm = (s7_int)sqrt(numerator(p)); + if (nm * nm == numerator(p)) + { + s7_int dn = (s7_int)sqrt(denominator(p)); + if (dn * dn == denominator(p)) + return(make_ratio(sc, nm, dn)); + } + return(make_real(sc, sqrt((s7_double)fraction(p)))); + } +#if HAVE_COMPLEX_NUMBERS + return(s7_make_complex(sc, 0.0, sqrt((s7_double)(-fraction(p))))); +#else + out_of_range_error_nr(sc, sc->sqrt_symbol, int_one, p, no_complex_numbers_string); +#endif + + case T_REAL: + if (is_NaN(real(p))) return(p); + if (real(p) >= 0.0) + return(make_real(sc, sqrt(real(p)))); + return(make_complex_not_0i(sc, 0.0, sqrt(-real(p)))); + + case T_COMPLEX: /* (* inf.0 (sqrt -1)) -> -nan+infi, but (sqrt -inf.0) -> 0+infi */ +#if HAVE_COMPLEX_NUMBERS + return(c_complex_to_s7(sc, csqrt(to_c_complex(p)))); /* sqrt(+inf.0+1.0i) -> +inf.0 */ +#else + out_of_range_error_nr(sc, sc->sqrt_symbol, int_one, p, no_complex_numbers_string); +#endif + +#if WITH_GMP + case T_BIG_INTEGER: + if (mpz_cmp_ui(big_integer(p), 0) >= 0) + { + mpz_sqrtrem(sc->mpz_1, sc->mpz_2, big_integer(p)); + if (mpz_cmp_ui(sc->mpz_2, 0) == 0) + return(mpz_to_integer(sc, sc->mpz_1)); + mpfr_set_z(sc->mpfr_1, big_integer(p), MPFR_RNDN); + mpfr_sqrt(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + } + mpc_set_z(sc->mpc_1, big_integer(p), MPC_RNDNN); + mpc_sqrt(sc->mpc_1, sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + + case T_BIG_RATIO: /* if big ratio, check both num and den for squares */ + if (mpq_cmp_ui(big_ratio(p), 0, 1) < 0) + { + mpc_set_q(sc->mpc_1, big_ratio(p), MPC_RNDNN); + mpc_sqrt(sc->mpc_1, sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + } + mpz_sqrtrem(sc->mpz_1, sc->mpz_2, mpq_numref(big_ratio(p))); + if (mpz_cmp_ui(sc->mpz_2, 0) == 0) + { + mpz_sqrtrem(sc->mpz_3, sc->mpz_2, mpq_denref(big_ratio(p))); + if (mpz_cmp_ui(sc->mpz_2, 0) == 0) + { + mpq_set_num(sc->mpq_1, sc->mpz_1); + mpq_set_den(sc->mpq_1, sc->mpz_3); + return(mpq_to_canonicalized_rational(sc, sc->mpq_1)); + }} + mpfr_set_q(sc->mpfr_1, big_ratio(p), MPFR_RNDN); + mpfr_sqrt(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + + case T_BIG_REAL: + if (mpfr_cmp_ui(big_real(p), 0) < 0) + { + mpc_set_fr(sc->mpc_1, big_real(p), MPC_RNDNN); + mpc_sqrt(sc->mpc_1, sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + } + mpfr_sqrt(sc->mpfr_1, big_real(p), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + + case T_BIG_COMPLEX: + mpc_sqrt(sc->mpc_1, big_complex(p), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_p(sc, p, sc->sqrt_symbol, a_number_string)); + } +} + +static s7_pointer g_sqrt(s7_scheme *sc, s7_pointer args) +{ + #define H_sqrt "(sqrt z) returns the square root of z" + #define Q_sqrt sc->pl_nn + return(sqrt_p_p(sc, car(args))); +} + + +/* -------------------------------- expt -------------------------------- */ +static s7_int int_to_int(s7_int x, s7_int n) +{ + /* from GSL */ + s7_int value = 1; + do { + if (n & 1) value *= x; + n >>= 1; +#if HAVE_OVERFLOW_CHECKS + if (multiply_overflow(x, x, &x)) + break; +#else + x *= x; +#endif + } while (n); + return(value); +} + +static const int64_t nth_roots[63] = { + S7_INT64_MAX, S7_INT64_MAX, 3037000499LL, 2097151, 55108, 6208, 1448, 511, 234, 127, 78, 52, 38, 28, 22, + 18, 15, 13, 11, 9, 8, 7, 7, 6, 6, 5, 5, 5, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}; + +static bool int_pow_ok(s7_int x, s7_int y) +{ + return((y < S7_INT_BITS) && (nth_roots[y] >= s7_int_abs(x))); +} + +#if WITH_GMP +static s7_pointer real_part_p_p(s7_scheme *sc, s7_pointer p); +static bool lt_b_pi(s7_scheme *sc, s7_pointer p1, s7_int p2); + +static s7_pointer big_expt(s7_scheme *sc, s7_pointer args) +{ + s7_pointer x = car(args), y = cadr(args), res; + if (!is_number(x)) + return(method_or_bust(sc, x, sc->expt_symbol, args, a_number_string, 1)); + if (!is_number(y)) + return(method_or_bust(sc, y, sc->expt_symbol, args, a_number_string, 2)); + + if (is_zero(x)) + { + if ((s7_is_integer(x)) && + (s7_is_integer(y)) && + (is_zero(y))) + return(int_one); + + if (is_real(y)) + { + if (is_negative(sc, y)) + division_by_zero_error_2_nr(sc, sc->expt_symbol, x, y); + } + else + if (is_negative(sc, real_part_p_p(sc, y))) /* handle big_complex as well as complex */ + division_by_zero_error_2_nr(sc, sc->expt_symbol, x, y); + + if ((is_rational(x)) && + (is_rational(y))) + return(int_zero); + return(real_zero); + } + + if (s7_is_integer(y)) + { + s7_int yval = s7_integer_clamped_if_gmp(sc, y); + if (yval == 0) + return((is_rational(x)) ? int_one : real_one); + + if (yval == 1) + return(x); + + if ((!is_big_number(x)) && + ((is_one(x)) || (is_zero(x)))) + return(x); + + if ((yval < S7_INT32_MAX) && + (yval > S7_INT32_MIN)) + { + /* (protect against gmp exception if for example (expt 1/9223372036854775807 -9223372036854775807) */ + if (s7_is_integer(x)) + { + if (is_t_big_integer(x)) + mpz_set(sc->mpz_2, big_integer(x)); + else mpz_set_si(sc->mpz_2, integer(x)); + if (yval >= 0) + { + mpz_pow_ui(sc->mpz_2, sc->mpz_2, (uint32_t)yval); + return(mpz_to_integer(sc, sc->mpz_2)); + } + mpz_pow_ui(sc->mpz_2, sc->mpz_2, (uint32_t)(-yval)); + mpq_set_z(sc->mpq_1, sc->mpz_2); + mpq_inv(sc->mpq_1, sc->mpq_1); + if (mpz_cmp_ui(mpq_denref(sc->mpq_1), 1) == 0) + return(mpz_to_integer(sc, mpq_numref(sc->mpq_1))); + return(mpq_to_big_ratio(sc, sc->mpq_1)); + } + + if (s7_is_ratio(x)) /* here y is an integer */ + { + if (is_t_big_ratio(x)) + { + mpz_set(sc->mpz_1, mpq_numref(big_ratio(x))); + mpz_set(sc->mpz_2, mpq_denref(big_ratio(x))); + } + else + { + mpz_set_si(sc->mpz_1, numerator(x)); + mpz_set_si(sc->mpz_2, denominator(x)); + } + if (yval >= 0) + { + mpz_pow_ui(sc->mpz_1, sc->mpz_1, (uint32_t)yval); + mpz_pow_ui(sc->mpz_2, sc->mpz_2, (uint32_t)yval); + mpq_set_num(sc->mpq_1, sc->mpz_1); + mpq_set_den(sc->mpq_1, sc->mpz_2); + } + else + { + yval = -yval; + mpz_pow_ui(sc->mpz_1, sc->mpz_1, (uint32_t)yval); + mpz_pow_ui(sc->mpz_2, sc->mpz_2, (uint32_t)yval); + mpq_set_num(sc->mpq_1, sc->mpz_2); + mpq_set_den(sc->mpq_1, sc->mpz_1); + mpq_canonicalize(sc->mpq_1); + } + if (mpz_cmp_ui(mpq_denref(sc->mpq_1), 1) == 0) + return(mpz_to_integer(sc, mpq_numref(sc->mpq_1))); + return(mpq_to_big_ratio(sc, sc->mpq_1)); + } + + if (is_real(x)) + { + if (is_t_big_real(x)) + mpfr_set(sc->mpfr_1, big_real(x), MPFR_RNDN); + else mpfr_set_d(sc->mpfr_1, real(x), MPFR_RNDN); + mpfr_pow_si(sc->mpfr_1, sc->mpfr_1, yval, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + }}} + + if ((is_t_ratio(y)) && /* not s7_is_ratio which accepts bignums */ + (numerator(y) == 1)) + { + if (denominator(y) == 2) + return(sqrt_p_p(sc, x)); + + if ((is_real(x)) && + (denominator(y) == 3)) + { + any_real_to_mpfr(sc, x, sc->mpfr_1); + mpfr_cbrt(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + }} + + res = any_number_to_mpc(sc, y, sc->mpc_2); + if (res == real_infinity) + { + if (is_one(x)) return(int_one); + if (!is_real(x)) return((is_negative(sc, y)) ? real_zero : complex_NaN); + if (is_zero(x)) + { + if (is_negative(sc, y)) division_by_zero_error_2_nr(sc, sc->expt_symbol, x, y); + return(real_zero); + } + if (lt_b_pi(sc, x, 0)) + { + if (lt_b_pi(sc, x, -1)) + return((is_positive(sc, y)) ? real_infinity : real_zero); + return((is_positive(sc, y)) ? real_zero : real_infinity); + } + if (lt_b_pi(sc, x, 1)) + return((is_positive(sc, y)) ? real_zero : real_infinity); + return((is_positive(sc, y)) ? real_infinity : real_zero); + } + if (res) return(complex_NaN); + + if ((is_real(x)) && + (is_real(y)) && + (is_positive(sc, x))) + { + res = any_real_to_mpfr(sc, x, sc->mpfr_1); + if (res) + { + if (res == real_infinity) + { + if (is_negative(sc, y)) return(real_zero); + return((is_zero(y)) ? real_one : real_infinity); + } + return(complex_NaN); + } + mpfr_pow(sc->mpfr_1, sc->mpfr_1, mpc_realref(sc->mpc_2), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + } + + res = any_number_to_mpc(sc, x, sc->mpc_1); + if (res) + { + if ((res == real_infinity) && (is_real(y))) + { + if (is_negative(sc, y)) return(real_zero); + return((is_zero(y)) ? real_one : real_infinity); + } + return(complex_NaN); + } + if (mpc_cmp_si_si(sc->mpc_1, 0, 0) == 0) + return(int_zero); + if (mpc_cmp_si_si(sc->mpc_1, 1, 0) == 0) + return(int_one); + + mpc_pow(sc->mpc_1, sc->mpc_1, sc->mpc_2, MPC_RNDNN); + + if ((!mpfr_nan_p(mpc_imagref(sc->mpc_1))) && (mpfr_cmp_ui(mpc_imagref(sc->mpc_1), 0) == 0)) /* (expt -inf.0 1/3) -> +inf.0+nan.0i in mpc */ + { + if ((is_rational(car(args))) && + (is_rational(cadr(args))) && + (mpfr_integer_p(mpc_realref(sc->mpc_1)) != 0)) + { + /* mpfr_integer_p can be confused: (expt 2718/1000 (bignum 617/5)) returns an int32_t if precision=128, float if 512 */ + /* so first make sure we're within (say) 31 bits */ + mpfr_set_ui(sc->mpfr_1, S7_INT32_MAX, MPFR_RNDN); + if (mpfr_cmpabs(mpc_realref(sc->mpc_1), sc->mpfr_1) < 0) + { + mpfr_get_z(sc->mpz_1, mpc_realref(sc->mpc_1), MPFR_RNDN); + return(mpz_to_integer(sc, sc->mpz_1)); + }} + mpfr_set(sc->mpfr_1, mpc_realref(sc->mpc_1), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + } + return(mpc_to_number(sc, sc->mpc_1)); +} +#endif + +static s7_pointer expt_p_pp(s7_scheme *sc, s7_pointer n, s7_pointer pw) +{ + if (!is_number(n)) + return(method_or_bust_pp(sc, n, sc->expt_symbol, n, pw, a_number_string, 1)); + if (!is_number(pw)) + return(method_or_bust_pp(sc, pw, sc->expt_symbol, n, pw, a_number_string, 2)); + + if (is_zero(n)) + { + if (is_zero(pw)) + { + if ((s7_is_integer(n)) && (s7_is_integer(pw))) /* (expt 0 0) -> 1 */ + return(int_one); + return(real_zero); /* (expt 0.0 0) -> 0.0 */ + } + if (is_real(pw)) + { + if (is_negative(sc, pw)) /* (expt 0 -1) */ + division_by_zero_error_2_nr(sc, sc->expt_symbol, n, pw); + /* (Clisp gives divide-by-zero error here, Guile returns inf.0) */ + + if (is_NaN(s7_real(pw))) /* (expt 0 +nan.0) */ + return(pw); + } + else + { /* (expt 0 a+bi) */ + if (real_part(pw) < 0.0) /* (expt 0 -1+i) */ + division_by_zero_error_2_nr(sc, sc->expt_symbol, n, pw); + if ((is_NaN(real_part(pw))) || /* (expt 0 0+1/0i) */ + (is_NaN(imag_part(pw)))) + return(pw); + } + if ((s7_is_integer(n)) && (s7_is_integer(pw))) /* pw != 0, (expt 0 2312) */ + return(int_zero); + return(real_zero); /* (expt 0.0 123123) */ + } + + if (is_one(pw)) + { + if (s7_is_integer(pw)) /* (expt x 1) */ + return(n); + if (is_rational(n)) /* (expt ratio 1.0) */ + return(make_real(sc, rational_to_double(sc, n))); + return(n); + } + if (is_t_integer(pw)) + { + s7_int y = integer(pw); + if (y == 0) + { + if (is_rational(n)) /* (expt 3 0) */ + return(int_one); + if ((is_NaN(s7_real_part(n))) || /* (expt 1/0 0) -> NaN */ + (is_NaN(s7_imag_part(n)))) /* (expt (complex 0 1/0) 0) -> NaN */ + return(n); + return(real_one); /* (expt 3.0 0) */ + } + switch (type(n)) + { + case T_INTEGER: + { + s7_int x = integer(n); + if (x == 1) /* (expt 1 y) */ + return(n); + + if (x == -1) + { + if (y == S7_INT64_MIN) /* (expt -1 most-negative-fixnum) */ + return(int_one); + if (s7_int_abs(y) & 1) /* (expt -1 odd-int) */ + return(n); + return(int_one); /* (expt -1 even-int) */ + } + + if (y == S7_INT64_MIN) /* (expt x most-negative-fixnum) */ + return(int_zero); + if (x == S7_INT64_MIN) /* (expt most-negative-fixnum y) */ + return(make_real(sc, pow((double)x, (double)y))); + + if (int_pow_ok(x, s7_int_abs(y))) + { + if (y > 0) + return(make_integer(sc, int_to_int(x, y))); + return(make_ratio(sc, 1, int_to_int(x, -y))); + }} + break; + + case T_RATIO: + { + s7_int nm = numerator(n), dn = denominator(n); + if (y == S7_INT64_MIN) + { + if (s7_int_abs(nm) > dn) + return(int_zero); /* (expt 4/3 most-negative-fixnum) -> 0? */ + return(real_infinity); /* (expt 3/4 most-negative-fixnum) -> inf? */ + } + if ((int_pow_ok(nm, s7_int_abs(y))) && + (int_pow_ok(dn, s7_int_abs(y)))) + { + if (y > 0) + return(make_ratio(sc, int_to_int(nm, y), int_to_int(dn, y))); + return(make_ratio_with_div_check(sc, sc->expt_symbol, int_to_int(dn, -y), int_to_int(nm, -y))); + }} + break; + /* occasionally int^rat can be int32_t but it happens so infrequently it's probably not worth checking + * one possibly easy case: (expt 1 1/2) -> 1 (-1?) etc + */ + + case T_REAL: + /* (expt -1.0 most-positive-fixnum) should be -1.0 + * (expt -1.0 (+ (expt 2 53) 1)) -> -1.0 + * (expt -1.0 (- 1 (expt 2 54))) -> -1.0 + */ + if (real(n) == -1.0) + { + if (y == S7_INT64_MIN) + return(real_one); + return((s7_int_abs(y) & 1) ? n : real_one); + } + break; + + case T_COMPLEX: +#if HAVE_COMPLEX_NUMBERS + if ((s7_real_part(n) == 0.0) && + ((s7_imag_part(n) == 1.0) || + (s7_imag_part(n) == -1.0))) + { + bool yp = (y > 0), np = (s7_imag_part(n) > 0.0); + switch (s7_int_abs(y) % 4) + { + case 0: return(real_one); + case 1: return(make_complex_not_0i(sc, 0.0, (yp == np) ? 1.0 : -1.0)); + case 2: return(make_real(sc, -1.0)); + case 3: return(make_complex_not_0i(sc, 0.0, (yp == np) ? -1.0 : 1.0)); + }} +#else + out_of_range_error_nr(sc, sc->expt_symbol, int_two, n, no_complex_numbers_string); +#endif + break; + }} + + if ((is_real(n)) && + (is_real(pw))) + { + s7_double x, y; + + if ((is_t_ratio(pw)) && + (numerator(pw) == 1)) + { + if (denominator(pw) == 2) + return(sqrt_p_p(sc, n)); + if (denominator(pw) == 3) + return(make_real(sc, cbrt(s7_real(n)))); /* (expt 27 1/3) should be 3, not 3.0... */ + /* but: (expt 512/729 1/3) -> 0.88888888888889, and 4 -> sqrt(sqrt...) etc? */ + } + + x = s7_real(n); + y = s7_real(pw); + if (is_NaN(x)) return(n); + if (is_NaN(y)) return(pw); + if (y == 0.0) return(real_one); + /* I think pow(rl, inf) is ok */ + if (x > 0.0) + return(make_real(sc, pow(x, y))); /* tricky cases abound here: (expt -1 1/9223372036854775807) */ + } + + /* (expt 0+i 1e+16) = 0.98156860153485-0.19111012657867i ? + * (expt 0+i 1+1/0i) = 0.0 ?? + */ + return(c_complex_to_s7(sc, cpow(s7_to_c_complex(n), s7_to_c_complex(pw)))); +} + +static s7_pointer g_expt(s7_scheme *sc, s7_pointer args) +{ + #define H_expt "(expt z1 z2) returns z1^z2" + #define Q_expt sc->pcl_n +#if WITH_GMP + return(big_expt(sc, args)); + /* big_expt sometimes chooses a different value: g_expt (expt -1 1/3) is -1, but big_expt (expt -1 (bignum 1/3)) is (complex 1/2 (/ (sqrt 3) 2)) */ +#endif + return(expt_p_pp(sc, car(args), cadr(args))); +} + + +/* -------------------------------- lcm -------------------------------- */ +#if WITH_GMP +static s7_pointer big_lcm(s7_scheme *sc, s7_int num, s7_int den, s7_pointer args) +{ + mpz_set_si(sc->mpz_3, num); + mpz_set_si(sc->mpz_4, den); + + for (s7_pointer x = args; is_pair(x); x = cdr(x)) + { + s7_pointer rat = car(x); + switch (type(rat)) + { + case T_INTEGER: + mpz_set_si(sc->mpz_1, integer(rat)); + mpz_lcm(sc->mpz_3, sc->mpz_3, sc->mpz_1); + mpz_set_si(sc->mpz_4, 1); + break; + case T_RATIO: + mpz_set_si(sc->mpz_1, numerator(rat)); + mpz_set_si(sc->mpz_2, denominator(rat)); + mpz_lcm(sc->mpz_3, sc->mpz_3, sc->mpz_1); + mpz_gcd(sc->mpz_4, sc->mpz_4, sc->mpz_2); + break; + case T_BIG_INTEGER: + mpz_lcm(sc->mpz_3, sc->mpz_3, big_integer(rat)); + mpz_set_si(sc->mpz_4, 1); + break; + case T_BIG_RATIO: + mpz_lcm(sc->mpz_3, sc->mpz_3, mpq_numref(big_ratio(rat))); + mpz_gcd(sc->mpz_4, sc->mpz_4, mpq_denref(big_ratio(rat))); + break; + case T_REAL: case T_BIG_REAL: case T_COMPLEX: case T_BIG_COMPLEX: + wrong_type_error_nr(sc, sc->lcm_symbol, position_of(x, args), rat, a_rational_string); + default: + return(method_or_bust(sc, rat, sc->lcm_symbol, + set_ulist_1(sc, mpz_to_rational(sc, sc->mpz_3, sc->mpz_4), x), + a_rational_string, position_of(x, args))); + }} + return(mpz_to_rational(sc, sc->mpz_3, sc->mpz_4)); +} +#endif + +static s7_pointer g_lcm(s7_scheme *sc, s7_pointer args) +{ + /* (/ (* m n) (gcd m n)), (lcm a b c) -> (lcm a (lcm b c)) */ + #define H_lcm "(lcm ...) returns the least common multiple of its rational arguments" + #define Q_lcm sc->pcl_f + + s7_int n = 1, d = 0; + + if (!is_pair(args)) + return(int_one); + + if (!is_pair(cdr(args))) + { + if (!is_rational(car(args))) + return(method_or_bust(sc, car(args), sc->lcm_symbol, args, a_rational_string, 1)); + return(g_abs(sc, args)); + } + + for (s7_pointer p = args; is_pair(p); p = cdr(p)) + { + s7_pointer x = car(p); + s7_int b; +#if HAVE_OVERFLOW_CHECKS + s7_int n1; +#endif + switch (type(x)) + { + case T_INTEGER: + d = 1; + if (integer(x) == 0) /* return 0 unless there's a wrong-type-arg (geez what a mess) */ + { + for (p = cdr(p); is_pair(p); p = cdr(p)) + { + s7_pointer x1 = car(p); + if (is_number(x1)) + { + if (!is_rational(x1)) + wrong_type_error_nr(sc, sc->lcm_symbol, position_of(p, args), x1, a_rational_string); + } + else + if (has_active_methods(sc, x1)) + { + s7_pointer f = find_method_with_let(sc, x1, sc->is_rational_symbol); + if ((f == sc->undefined) || + (is_false(sc, s7_apply_function(sc, f, set_plist_1(sc, x1))))) + wrong_type_error_nr(sc, sc->lcm_symbol, position_of(p, args), x1, a_rational_string); + } + else wrong_type_error_nr(sc, sc->lcm_symbol, position_of(p, args), x1, a_rational_string); + } + return(int_zero); + } + b = integer(x); + if (b < 0) + { + if (b == S7_INT64_MIN) +#if WITH_GMP + return(big_lcm(sc, n, d, p)); +#else + sole_arg_out_of_range_error_nr(sc, sc->lcm_symbol, args, it_is_too_large_string); +#endif + b = -b; + } +#if HAVE_OVERFLOW_CHECKS + if (multiply_overflow(n / c_gcd(n, b), b, &n1)) +#if WITH_GMP + return(big_lcm(sc, n, d, p)); +#else + sole_arg_out_of_range_error_nr(sc, sc->lcm_symbol, args, result_is_too_large_string); +#endif + n = n1; +#else + n = (n / c_gcd(n, b)) * b; +#endif + break; + + case T_RATIO: + b = numerator(x); + if (b < 0) + { + if (b == S7_INT64_MIN) +#if WITH_GMP + return(big_lcm(sc, n, d, p)); +#else + sole_arg_out_of_range_error_nr(sc, sc->lcm_symbol, args, it_is_too_large_string); +#endif + b = -b; + } +#if HAVE_OVERFLOW_CHECKS + if (multiply_overflow(n / c_gcd(n, b), b, &n1)) /* (lcm 92233720368547758/3 3005/2) */ +#if WITH_GMP + return(big_lcm(sc, n, d, p)); +#else + sole_arg_out_of_range_error_nr(sc, sc->lcm_symbol, args, intermediate_too_large_string); +#endif + n = n1; +#else + n = (n / c_gcd(n, b)) * b; +#endif + if (d == 0) + d = (p == args) ? denominator(x) : 1; + else d = c_gcd(d, denominator(x)); + break; + +#if WITH_GMP + case T_BIG_INTEGER: + d = 1; + case T_BIG_RATIO: + return(big_lcm(sc, n, d, p)); +#endif + case T_REAL: case T_BIG_REAL: case T_COMPLEX: case T_BIG_COMPLEX: + wrong_type_error_nr(sc, sc->lcm_symbol, position_of(p, args), x, a_rational_string); + + default: + return(method_or_bust(sc, x, sc->lcm_symbol, + set_ulist_1(sc, (d <= 1) ? make_integer(sc, n) : make_ratio_with_div_check(sc, sc->lcm_symbol, n, d), p), + a_rational_string, position_of(p, args))); + }} + return((d <= 1) ? make_integer(sc, n) : make_simple_ratio(sc, n, d)); +} + + +/* -------------------------------- gcd -------------------------------- */ +#if WITH_GMP +static s7_pointer big_gcd(s7_scheme *sc, s7_int num, s7_int den, s7_pointer args) +{ + mpz_set_si(sc->mpz_3, num); + mpz_set_si(sc->mpz_4, den); + + for (s7_pointer x = args; is_pair(x); x = cdr(x)) + { + s7_pointer rat = car(x); + switch (type(rat)) + { + case T_INTEGER: + mpz_set_si(sc->mpz_1, integer(rat)); + mpz_gcd(sc->mpz_3, sc->mpz_3, sc->mpz_1); + break; + case T_RATIO: + mpz_set_si(sc->mpz_1, numerator(rat)); + mpz_set_si(sc->mpz_2, denominator(rat)); + mpz_gcd(sc->mpz_3, sc->mpz_3, sc->mpz_1); + mpz_lcm(sc->mpz_4, sc->mpz_4, sc->mpz_2); + break; + case T_BIG_INTEGER: + mpz_gcd(sc->mpz_3, sc->mpz_3, big_integer(rat)); + break; + case T_BIG_RATIO: + mpz_gcd(sc->mpz_3, sc->mpz_3, mpq_numref(big_ratio(rat))); + mpz_lcm(sc->mpz_4, sc->mpz_4, mpq_denref(big_ratio(rat))); + break; + case T_REAL: case T_BIG_REAL: case T_COMPLEX: case T_BIG_COMPLEX: + wrong_type_error_nr(sc, sc->gcd_symbol, position_of(x, args), rat, a_rational_string); + default: + return(method_or_bust(sc, rat, sc->gcd_symbol, + set_ulist_1(sc, mpz_to_rational(sc, sc->mpz_3, sc->mpz_4), x), + a_rational_string, position_of(x, args))); + }} + return(mpz_to_rational(sc, sc->mpz_3, sc->mpz_4)); +} +#endif + +static s7_pointer g_gcd(s7_scheme *sc, s7_pointer args) +{ + #define H_gcd "(gcd ...) returns the greatest common divisor of its rational arguments" + #define Q_gcd sc->pcl_f + + s7_int n = 0, d = 1; + + if (!is_pair(args)) /* (gcd) */ + return(int_zero); + + if (!is_pair(cdr(args))) /* (gcd 3/4) */ + { + if (!is_rational(car(args))) + return(method_or_bust(sc, car(args), sc->gcd_symbol, args, a_rational_string, 1)); + return(abs_p_p(sc, car(args))); + } + + for (s7_pointer p = args; is_pair(p); p = cdr(p)) + { + s7_pointer x = car(p); + switch (type(x)) + { + case T_INTEGER: + if (integer(x) == S7_INT64_MIN) +#if WITH_GMP + return(big_gcd(sc, n, d, p)); +#else + sole_arg_out_of_range_error_nr(sc, sc->lcm_symbol, args, it_is_too_large_string); +#endif + n = c_gcd(n, integer(x)); + break; + + case T_RATIO: + { +#if HAVE_OVERFLOW_CHECKS + s7_int dn; +#endif + n = c_gcd(n, numerator(x)); + if (d == 1) + d = denominator(x); + else + { + s7_int b = denominator(x); +#if HAVE_OVERFLOW_CHECKS + if (multiply_overflow(d / c_gcd(d, b), b, &dn)) /* (gcd 1/92233720368547758 1/3005) */ +#if WITH_GMP + return(big_gcd(sc, n, d, x)); +#else + sole_arg_out_of_range_error_nr(sc, sc->gcd_symbol, args, intermediate_too_large_string); +#endif + d = dn; +#else + d = (d / c_gcd(d, b)) * b; +#endif + }} + break; + +#if WITH_GMP + case T_BIG_INTEGER: + case T_BIG_RATIO: + return(big_gcd(sc, n, d, p)); +#endif + + case T_REAL: case T_BIG_REAL: case T_COMPLEX: case T_BIG_COMPLEX: + wrong_type_error_nr(sc, sc->gcd_symbol, position_of(p, args), x, a_rational_string); + + default: + return(method_or_bust(sc, x, sc->gcd_symbol, + set_ulist_1(sc, (d <= 1) ? make_integer(sc, n) : make_ratio_with_div_check(sc, sc->gcd_symbol, n, d), p), + a_rational_string, position_of(p, args))); + }} + return((d <= 1) ? make_integer(sc, n) : make_simple_ratio(sc, n, d)); +} + + +/* -------------------------------- floor -------------------------------- */ +static s7_pointer floor_p_p(s7_scheme *sc, s7_pointer x) +{ + switch (type(x)) + { + case T_INTEGER: + return(x); + case T_RATIO: + { + s7_int val = numerator(x) / denominator(x); + /* C "/" truncates? -- C spec says "truncation toward 0" */ + /* we're avoiding "floor" here because the int->double conversion introduces inaccuracies for big numbers + * but it's used by opt_i_d_c (via s7_number_to_real) so floor_i_7d below can return different results: + * (let () (define (func) (do ((i 0 (+ i 1))) ((= i 1)) (display (floor 3441313796169221281/1720656898084610641)) (newline))) (func)): 1 + * (let () (define (func) (do ((i 0 (+ i 1))) ((= i 1)) (display (/ (floor 3441313796169221281/1720656898084610641))) (newline))) (func)): 1/2 + */ + return(make_integer(sc, (numerator(x) < 0) ? (val - 1) : val)); /* not "val" because it might be truncated to 0 */ + } + case T_REAL: + { + s7_double z = real(x); + if (is_NaN(z)) + sole_arg_out_of_range_error_nr(sc, sc->floor_symbol, x, it_is_nan_string); + if (is_inf(z)) + sole_arg_out_of_range_error_nr(sc, sc->floor_symbol, x, it_is_infinite_string); +#if WITH_GMP + if (fabs(z) > DOUBLE_TO_INT64_LIMIT) + { + mpfr_set_d(sc->mpfr_1, z, MPFR_RNDN); + mpfr_get_z(sc->mpz_1, sc->mpfr_1, MPFR_RNDD); + return(mpz_to_integer(sc, sc->mpz_1)); + } +#else + if (fabs(z) > DOUBLE_TO_INT64_LIMIT) + sole_arg_out_of_range_error_nr(sc, sc->floor_symbol, x, it_is_too_large_string); +#endif + return(make_integer(sc, (s7_int)floor(z))); + /* floor here rounds down, whereas a straight int<=real coercion apparently rounds towards 0 */ + } +#if WITH_GMP + case T_BIG_INTEGER: + return(x); + case T_BIG_RATIO: + mpz_fdiv_q(sc->mpz_1, mpq_numref(big_ratio(x)), mpq_denref(big_ratio(x))); + return(mpz_to_integer(sc, sc->mpz_1)); + case T_BIG_REAL: + if (mpfr_nan_p(big_real(x))) + sole_arg_out_of_range_error_nr(sc, sc->floor_symbol, x, it_is_nan_string); + if (mpfr_inf_p(big_real(x))) + sole_arg_out_of_range_error_nr(sc, sc->floor_symbol, x, it_is_infinite_string); + mpfr_get_z(sc->mpz_1, big_real(x), MPFR_RNDD); + return(mpz_to_integer(sc, sc->mpz_1)); + case T_BIG_COMPLEX: +#endif + case T_COMPLEX: + sole_arg_wrong_type_error_nr(sc, sc->floor_symbol, x, sc->type_names[T_REAL]); + default: + return(method_or_bust_p(sc, x, sc->floor_symbol, sc->type_names[T_REAL])); + } +} + +static s7_pointer g_floor(s7_scheme *sc, s7_pointer args) +{ + #define H_floor "(floor x) returns the integer closest to x toward -inf" + #define Q_floor s7_make_signature(sc, 2, sc->is_integer_symbol, sc->is_real_symbol) + return(floor_p_p(sc, car(args))); +} + +static s7_int floor_i_i(s7_int i) {return(i);} + +#if (!WITH_GMP) +static s7_int floor_i_7d(s7_scheme *sc, s7_double x) +{ + if (is_NaN(x)) + sole_arg_out_of_range_error_nr(sc, sc->floor_symbol, real_NaN, it_is_nan_string); + if (fabs(x) > DOUBLE_TO_INT64_LIMIT) + sole_arg_out_of_range_error_nr(sc, sc->floor_symbol, wrap_real(sc, x), it_is_too_large_string); + return((s7_int)floor(x)); +} + +static s7_int floor_i_7p(s7_scheme *sc, s7_pointer p) +{ + if (is_t_integer(p)) return(integer(p)); + if (is_t_real(p)) return(floor_i_7d(sc, real(p))); + if (is_t_ratio(p)) /* for consistency with floor_p_p, don't use floor(fraction(p)) */ + { + s7_int val = numerator(p) / denominator(p); + return((numerator(p) < 0) ? val - 1 : val); + } + return(s7_integer(method_or_bust_p(sc, p, sc->floor_symbol, sc->type_names[T_REAL]))); +} + +static s7_pointer floor_p_d(s7_scheme *sc, s7_double x) {return(make_integer(sc,floor_i_7d(sc, x)));} +#endif + + +/* -------------------------------- ceiling -------------------------------- */ +static s7_pointer ceiling_p_p(s7_scheme *sc, s7_pointer x) +{ + switch (type(x)) + { + case T_INTEGER: + return(x); + case T_RATIO: + { + s7_int val = numerator(x) / denominator(x); + return(make_integer(sc, (numerator(x) < 0) ? val : (val + 1))); + } + case T_REAL: + { + s7_double z = real(x); + if (is_NaN(z)) + sole_arg_out_of_range_error_nr(sc, sc->ceiling_symbol, x, it_is_nan_string); + if (is_inf(z)) + sole_arg_out_of_range_error_nr(sc, sc->ceiling_symbol, x, it_is_infinite_string); +#if WITH_GMP + if (fabs(z) > DOUBLE_TO_INT64_LIMIT) + { + mpfr_set_d(sc->mpfr_1, z, MPFR_RNDN); + mpfr_get_z(sc->mpz_1, sc->mpfr_1, MPFR_RNDU); + return(mpz_to_integer(sc, sc->mpz_1)); + } +#else + if (fabs(z) > DOUBLE_TO_INT64_LIMIT) + sole_arg_out_of_range_error_nr(sc, sc->ceiling_symbol, x, it_is_too_large_string); +#endif + return(make_integer(sc, (s7_int)ceil(real(x)))); + } +#if WITH_GMP + case T_BIG_INTEGER: + return(x); + case T_BIG_RATIO: + mpz_cdiv_q(sc->mpz_1, mpq_numref(big_ratio(x)), mpq_denref(big_ratio(x))); + return(mpz_to_integer(sc, sc->mpz_1)); + case T_BIG_REAL: + if (mpfr_nan_p(big_real(x))) + sole_arg_out_of_range_error_nr(sc, sc->ceiling_symbol, x, it_is_nan_string); + if (mpfr_inf_p(big_real(x))) + sole_arg_out_of_range_error_nr(sc, sc->ceiling_symbol, x, it_is_infinite_string); + mpfr_get_z(sc->mpz_1, big_real(x), MPFR_RNDU); + return(mpz_to_integer(sc, sc->mpz_1)); + case T_BIG_COMPLEX: +#endif + case T_COMPLEX: + sole_arg_wrong_type_error_nr(sc, sc->ceiling_symbol, x, sc->type_names[T_REAL]); + default: + return(method_or_bust_p(sc, x, sc->ceiling_symbol, sc->type_names[T_REAL])); + } +} + +static s7_pointer g_ceiling(s7_scheme *sc, s7_pointer args) +{ + #define H_ceiling "(ceiling x) returns the integer closest to x toward inf" + #define Q_ceiling s7_make_signature(sc, 2, sc->is_integer_symbol, sc->is_real_symbol) + return(ceiling_p_p(sc, car(args))); +} + +static s7_int ceiling_i_i(s7_int i) {return(i);} + +#if (!WITH_GMP) +static s7_int ceiling_i_7d(s7_scheme *sc, s7_double x) +{ + if (is_NaN(x)) + sole_arg_out_of_range_error_nr(sc, sc->ceiling_symbol, real_NaN, it_is_nan_string); + if ((is_inf(x)) || + (x > DOUBLE_TO_INT64_LIMIT) || (x < -DOUBLE_TO_INT64_LIMIT)) + sole_arg_out_of_range_error_nr(sc, sc->ceiling_symbol, wrap_real(sc, x), it_is_too_large_string); + return((s7_int)ceil(x)); +} + +static s7_int ceiling_i_7p(s7_scheme *sc, s7_pointer p) +{ + if (is_t_integer(p)) return(integer(p)); + if (is_t_real(p)) return(ceiling_i_7d(sc, real(p))); + if (is_t_ratio(p)) return((s7_int)(ceil(fraction(p)))); + return(s7_integer(method_or_bust_p(sc, p, sc->ceiling_symbol, sc->type_names[T_REAL]))); +} + +static s7_pointer ceiling_p_d(s7_scheme *sc, s7_double x) {return(make_integer(sc, ceiling_i_7d(sc, x)));} +#endif + + +/* -------------------------------- truncate -------------------------------- */ +static s7_pointer truncate_p_p(s7_scheme *sc, s7_pointer x) +{ + switch (type(x)) + { + case T_INTEGER: + return(x); + case T_RATIO: + return(make_integer(sc, (s7_int)(numerator(x) / denominator(x)))); /* C "/" already truncates (but this divide is not accurate over e13) */ + case T_REAL: + { + s7_double z = real(x); + if (is_NaN(z)) + sole_arg_out_of_range_error_nr(sc, sc->truncate_symbol, x, it_is_nan_string); + if (is_inf(z)) + sole_arg_out_of_range_error_nr(sc, sc->truncate_symbol, x, it_is_infinite_string); +#if WITH_GMP + if (fabs(z) > DOUBLE_TO_INT64_LIMIT) + { + mpfr_set_d(sc->mpfr_1, real(x), MPFR_RNDN); + mpfr_get_z(sc->mpz_1, sc->mpfr_1, MPFR_RNDZ); + return(mpz_to_integer(sc, sc->mpz_1)); + } +#else + if (fabs(z) > DOUBLE_TO_INT64_LIMIT) + sole_arg_out_of_range_error_nr(sc, sc->truncate_symbol, x, it_is_too_large_string); +#endif + return(make_integer(sc, (z > 0.0) ? (s7_int)floor(z) : (s7_int)ceil(z))); + } +#if WITH_GMP + case T_BIG_INTEGER: + return(x); + case T_BIG_RATIO: + mpz_tdiv_q(sc->mpz_1, mpq_numref(big_ratio(x)), mpq_denref(big_ratio(x))); + return(mpz_to_integer(sc, sc->mpz_1)); + case T_BIG_REAL: + if (mpfr_nan_p(big_real(x))) + sole_arg_out_of_range_error_nr(sc, sc->truncate_symbol, x, it_is_nan_string); + if (mpfr_inf_p(big_real(x))) + sole_arg_out_of_range_error_nr(sc, sc->truncate_symbol, x, it_is_infinite_string); + mpfr_get_z(sc->mpz_1, big_real(x), MPFR_RNDZ); + return(mpz_to_integer(sc, sc->mpz_1)); + case T_BIG_COMPLEX: +#endif + case T_COMPLEX: + sole_arg_wrong_type_error_nr(sc, sc->truncate_symbol, x, sc->type_names[T_REAL]); + default: + return(method_or_bust_p(sc, x, sc->truncate_symbol, sc->type_names[T_REAL])); + } +} + +static s7_pointer g_truncate(s7_scheme *sc, s7_pointer args) +{ + #define H_truncate "(truncate x) returns the integer closest to x toward 0" + #define Q_truncate s7_make_signature(sc, 2, sc->is_integer_symbol, sc->is_real_symbol) + return(truncate_p_p(sc, car(args))); +} + +static s7_int truncate_i_i(s7_int i) {return(i);} + +#if (!WITH_GMP) +static s7_int truncate_i_7d(s7_scheme *sc, s7_double x) +{ + if (is_NaN(x)) + sole_arg_out_of_range_error_nr(sc, sc->truncate_symbol, real_NaN, it_is_nan_string); + if (is_inf(x)) + sole_arg_out_of_range_error_nr(sc, sc->truncate_symbol, wrap_real(sc, x), it_is_infinite_string); + if (fabs(x) > DOUBLE_TO_INT64_LIMIT) + sole_arg_out_of_range_error_nr(sc, sc->truncate_symbol, wrap_real(sc, x), it_is_too_large_string); + return((x > 0.0) ? (s7_int)floor(x) : (s7_int)ceil(x)); +} + +static s7_pointer truncate_p_d(s7_scheme *sc, s7_double x) {return(make_integer(sc, truncate_i_7d(sc, x)));} +#endif + + +/* -------------------------------- round -------------------------------- */ +static s7_double r5rs_round(s7_double x) +{ + s7_double fl = floor(x), ce = ceil(x); + s7_double dfl = x - fl; + s7_double dce = ce - x; + if (dfl > dce) return(ce); + if (dfl < dce) return(fl); + return((fmod(fl, 2.0) == 0.0) ? fl : ce); +} + +static s7_pointer round_p_p(s7_scheme *sc, s7_pointer x) +{ + switch (type(x)) + { + case T_INTEGER: + return(x); + case T_RATIO: + { + s7_int truncated = numerator(x) / denominator(x), remains = numerator(x) % denominator(x); + long_double frac = s7_fabsl((long_double)remains / (long_double)denominator(x)); + if ((frac > 0.5) || + ((frac == 0.5) && + (truncated % 2 != 0))) + return(make_integer(sc, (numerator(x) < 0) ? (truncated - 1) : (truncated + 1))); + return(make_integer(sc, truncated)); + } + case T_REAL: + { + s7_double z = real(x); + if (is_NaN(z)) + sole_arg_out_of_range_error_nr(sc, sc->round_symbol, x, it_is_nan_string); + if (is_inf(z)) + sole_arg_out_of_range_error_nr(sc, sc->round_symbol, x, it_is_infinite_string); +#if WITH_GMP + if (fabs(z) > DOUBLE_TO_INT64_LIMIT) + { + mpfr_set_d(sc->mpfr_1, z, MPFR_RNDN); + mpfr_rint(sc->mpfr_2, sc->mpfr_1, MPFR_RNDN); /* mpfr_roundeven in mpfr 4.0.0 */ + mpfr_get_z(sc->mpz_3, sc->mpfr_2, MPFR_RNDN); + return(mpz_to_integer(sc, sc->mpz_3)); + } +#else + if (fabs(z) > DOUBLE_TO_INT64_LIMIT) + sole_arg_out_of_range_error_nr(sc, sc->round_symbol, x, it_is_too_large_string); +#endif + return(make_integer(sc, (s7_int)r5rs_round(z))); + } +#if WITH_GMP + case T_BIG_INTEGER: + return(x); + case T_BIG_RATIO: + { + int32_t rnd; + mpz_fdiv_qr(sc->mpz_1, sc->mpz_2, mpq_numref(big_ratio(x)), mpq_denref(big_ratio(x))); + mpz_mul_ui(sc->mpz_2, sc->mpz_2, 2); + rnd = mpz_cmpabs(sc->mpz_2, mpq_denref(big_ratio(x))); + mpz_fdiv_q(sc->mpz_2, sc->mpz_2, mpq_denref(big_ratio(x))); + if (rnd > 0) + mpz_add(sc->mpz_1, sc->mpz_1, sc->mpz_2); + else + if ((rnd == 0) && + (mpz_odd_p(sc->mpz_1))) + mpz_add_ui(sc->mpz_1, sc->mpz_1, 1); + return(mpz_to_integer(sc, sc->mpz_1)); + } + case T_BIG_REAL: + if (mpfr_nan_p(big_real(x))) + sole_arg_out_of_range_error_nr(sc, sc->round_symbol, x, it_is_nan_string); + if (mpfr_inf_p(big_real(x))) + sole_arg_out_of_range_error_nr(sc, sc->round_symbol, x, it_is_infinite_string); + mpfr_set(sc->mpfr_1, big_real(x), MPFR_RNDN); + mpfr_rint(sc->mpfr_2, sc->mpfr_1, MPFR_RNDN); + mpfr_get_z(sc->mpz_3, sc->mpfr_2, MPFR_RNDN); + return(mpz_to_integer(sc, sc->mpz_3)); + case T_BIG_COMPLEX: +#endif + case T_COMPLEX: + sole_arg_wrong_type_error_nr(sc, sc->round_symbol, x, sc->type_names[T_REAL]); + default: + return(method_or_bust_p(sc, x, sc->round_symbol, sc->type_names[T_REAL])); + } +} + +static s7_pointer g_round(s7_scheme *sc, s7_pointer args) +{ + #define H_round "(round x) returns the integer closest to x" + #define Q_round s7_make_signature(sc, 2, sc->is_integer_symbol, sc->is_real_symbol) + return(round_p_p(sc, car(args))); +} + +static s7_int round_i_i(s7_int i) {return(i);} + +#if (!WITH_GMP) +static s7_int round_i_7d(s7_scheme *sc, s7_double z) +{ + if (is_NaN(z)) + sole_arg_out_of_range_error_nr(sc, sc->round_symbol, real_NaN, it_is_nan_string); + if ((is_inf(z)) || + (z > DOUBLE_TO_INT64_LIMIT) || (z < -DOUBLE_TO_INT64_LIMIT)) + sole_arg_out_of_range_error_nr(sc, sc->round_symbol, wrap_real(sc, z), it_is_too_large_string); + return((s7_int)r5rs_round(z)); +} + +static s7_pointer round_p_d(s7_scheme *sc, s7_double x) {return(make_integer(sc,round_i_7d(sc, x)));} +#endif + + +/* ---------------------------------------- add ---------------------------------------- */ +static inline s7_pointer add_if_overflow_to_real_or_big_integer(s7_scheme *sc, s7_int x, s7_int y) +{ +#if HAVE_OVERFLOW_CHECKS + s7_int val; + if (add_overflow(x, y, &val)) +#if WITH_GMP + { + mpz_set_si(sc->mpz_1, x); + mpz_set_si(sc->mpz_2, y); + mpz_add(sc->mpz_1, sc->mpz_1, sc->mpz_2); + return(mpz_to_big_integer(sc, sc->mpz_1)); + } +#else + { + if (WITH_WARNINGS) s7_warn(sc, 128, "integer add overflow: (+ %" ld64 " %" ld64 ")\n", x, y); + return(make_real(sc, (long_double)x + (long_double)y)); + } +#endif + return(make_integer(sc, val)); +#else + return(make_integer(sc, x + y)); +#endif +} + +static s7_pointer integer_ratio_add_if_overflow_to_real_or_rational(s7_scheme *sc, s7_pointer x, s7_pointer y) /* x: int, y:ratio */ +{ +#if HAVE_OVERFLOW_CHECKS + s7_int z; + if ((multiply_overflow(integer(x), denominator(y), &z)) || + (add_overflow(z, numerator(y), &z))) +#if WITH_GMP + { + mpz_set_si(sc->mpz_1, integer(x)); + mpz_mul_si(sc->mpz_1, sc->mpz_1, denominator(y)); + mpz_set_si(sc->mpz_2, numerator(y)); + mpz_add(mpq_numref(sc->mpq_1), sc->mpz_2, sc->mpz_1); + mpz_set_si(mpq_denref(sc->mpq_1), denominator(y)); + return(mpq_to_rational(sc, sc->mpq_1)); + } +#else + { + if (WITH_WARNINGS) s7_warn(sc, 128, "integer + ratio overflow: (+ %" ld64 " %" ld64 "/%" ld64 ")\n", integer(x), numerator(y), denominator(y)); + return(make_real(sc, (long_double)integer(x) + fraction(y))); + } +#endif + return(make_ratio(sc, z, denominator(y))); +#else + return(make_ratio(sc, integer(x) * denominator(y) + numerator(y), denominator(y))); +#endif +} + +#define parcel_out_fractions(X, Y) do {d1 = denominator(x); n1 = numerator(x); d2 = denominator(y); n2 = numerator(y);} while (0) +/* add_out_x|y here (as in lt_out_x|y) gives a small speed-up, say 3-7 callgrind units, about 2% */ + +static s7_pointer add_p_pp(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + /* an experiment: try to avoid the switch statement */ + /* this wins in most s7 cases, not so much elsewhere? parallel subtract/multiply code is slower */ + if (is_t_integer(x)) + { + if (is_t_integer(y)) + return(add_if_overflow_to_real_or_big_integer(sc, integer(x), integer(y))); + } + else + if (is_t_real(x)) + { + if (is_t_real(y)) + return(make_real(sc, real(x) + real(y))); + } + else + if ((is_t_complex(x)) && (is_t_complex(y))) + return(make_complex(sc, real_part(x) + real_part(y), imag_part(x) + imag_part(y))); + + switch (type(x)) + { + case T_INTEGER: + switch (type(y)) + { + case T_INTEGER: + return(add_if_overflow_to_real_or_big_integer(sc, integer(x), integer(y))); + case T_RATIO: + return(integer_ratio_add_if_overflow_to_real_or_rational(sc, x, y)); + case T_REAL: +#if WITH_GMP + if (s7_int_abs(integer(x)) >= INT64_TO_DOUBLE_LIMIT) /* (+ 9223372036854775807 .1), >= needed for (+ 9007199254740992 1.0) */ + { + mpfr_set_si(sc->mpfr_1, integer(x), MPFR_RNDN); + mpfr_add_d(sc->mpfr_1, sc->mpfr_1, real(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + } +#endif + return(make_real(sc, (long_double)integer(x) + real(y))); + case T_COMPLEX: + return(make_complex_not_0i(sc, (long_double)integer(x) + (long_double)real_part(y), imag_part(y))); +#if WITH_GMP + case T_BIG_INTEGER: + mpz_set_si(sc->mpz_1, integer(x)); + mpz_add(sc->mpz_1, sc->mpz_1, big_integer(y)); + return(mpz_to_integer(sc, sc->mpz_1)); + case T_BIG_RATIO: + mpq_set_si(sc->mpq_1, integer(x), 1); + mpq_add(sc->mpq_1, sc->mpq_1, big_ratio(y)); + return(mpq_to_rational(sc, sc->mpq_1)); + case T_BIG_REAL: + mpfr_add_si(sc->mpfr_1, big_real(y), integer(x), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + mpc_set_si(sc->mpc_1, integer(x), MPC_RNDNN); + mpc_add(sc->mpc_1, sc->mpc_1, big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->add_symbol, x, y, a_number_string, 2)); + } + + case T_RATIO: + switch (type(y)) + { + case T_INTEGER: + return(integer_ratio_add_if_overflow_to_real_or_rational(sc, y, x)); + case T_RATIO: + { + s7_int d1, d2, n1, n2; + parcel_out_fractions(x, y); + if (d1 == d2) + { +#if HAVE_OVERFLOW_CHECKS + s7_int q; + if (add_overflow(n1, n2, &q)) +#if WITH_GMP + { + mpq_set_si(sc->mpq_1, n1, d1); + mpq_set_si(sc->mpq_2, n2, d2); + mpq_add(sc->mpq_1, sc->mpq_1, sc->mpq_2); + return(mpq_to_rational(sc, sc->mpq_1)); + } +#else + { + if (WITH_WARNINGS) s7_warn(sc, 128, "ratio + ratio overflow: (/ (+ %" ld64 " %" ld64 ") %" ld64 ")\n", n1, n2, d1); + return(make_real(sc, ((long_double)n1 + (long_double)n2) / (long_double)d1)); + } +#endif + return(make_ratio_with_div_check(sc, sc->add_symbol, q, d1)); +#else + return(make_ratio_with_div_check(sc, sc->add_symbol, n1 + n2, d1)); +#endif + } + +#if HAVE_OVERFLOW_CHECKS + { + s7_int n1d2, n2d1, d1d2, q; + if ((multiply_overflow(d1, d2, &d1d2)) || + (multiply_overflow(n1, d2, &n1d2)) || + (multiply_overflow(n2, d1, &n2d1)) || + (add_overflow(n1d2, n2d1, &q))) +#if WITH_GMP + { + mpq_set_si(sc->mpq_1, n1, d1); + mpq_set_si(sc->mpq_2, n2, d2); + mpq_add(sc->mpq_1, sc->mpq_1, sc->mpq_2); + return(mpq_to_rational(sc, sc->mpq_1)); + } +#else + { + if (WITH_WARNINGS) s7_warn(sc, 128, "ratio + ratio overflow: (+ %" ld64 "/%" ld64 " %" ld64 "/%" ld64 ")\n", n1, d1, n2, d2); + return(make_real(sc, ((long_double)n1 / (long_double)d1) + ((long_double)n2 / (long_double)d2))); + } +#endif + return(make_ratio_with_div_check(sc, sc->add_symbol, q, d1d2)); + } +#else + return(make_ratio_with_div_check(sc, sc->add_symbol, n1 * d2 + n2 * d1, d1 * d2)); +#endif + } + case T_REAL: + return(make_real(sc, fraction(x) + real(y))); + case T_COMPLEX: + return(make_complex_not_0i(sc, fraction(x) + real_part(y), imag_part(y))); +#if WITH_GMP + case T_BIG_INTEGER: + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + mpq_set_z(sc->mpq_2, big_integer(y)); + mpq_add(sc->mpq_1, sc->mpq_1, sc->mpq_2); + return(mpq_to_rational(sc, sc->mpq_1)); + case T_BIG_RATIO: + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + mpq_add(sc->mpq_1, sc->mpq_1, big_ratio(y)); + return(mpq_to_rational(sc, sc->mpq_1)); + case T_BIG_REAL: + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + mpfr_add_q(sc->mpfr_1, big_real(y), sc->mpq_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + mpc_set_q(sc->mpc_1, sc->mpq_1, MPC_RNDNN); + mpc_add(sc->mpc_1, sc->mpc_1, big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->add_symbol, x, y, a_number_string, 2)); + } + + case T_REAL: + switch (type(y)) + { + case T_INTEGER: +#if WITH_GMP + if (s7_int_abs(integer(y)) >= INT64_TO_DOUBLE_LIMIT) /* (+ .1 9223372036854775807) */ + { + mpfr_set_si(sc->mpfr_1, integer(y), MPFR_RNDN); + mpfr_add_d(sc->mpfr_1, sc->mpfr_1, real(x), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + } +#endif + return(make_real(sc, real(x) + (long_double)integer(y))); + case T_RATIO: + return(make_real(sc, real(x) + fraction(y))); + case T_REAL: + return(make_real(sc, real(x) + real(y))); + case T_COMPLEX: + return(make_complex_not_0i(sc, real(x) + real_part(y), imag_part(y))); +#if WITH_GMP + case T_BIG_INTEGER: + mpfr_set_d(sc->mpfr_1, real(x), MPFR_RNDN); + mpfr_add_z(sc->mpfr_1, sc->mpfr_1, big_integer(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_RATIO: + mpfr_set_d(sc->mpfr_1, real(x), MPFR_RNDN); + mpfr_add_q(sc->mpfr_1, sc->mpfr_1, big_ratio(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_REAL: + mpfr_add_d(sc->mpfr_1, big_real(y), real(x), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + mpc_set_d_d(sc->mpc_1, real(x), 0.0, MPC_RNDNN); + mpc_add(sc->mpc_1, sc->mpc_1, big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->add_symbol, x, y, a_number_string, 2)); + } + + case T_COMPLEX: + switch (type(y)) + { + case T_INTEGER: + return(make_complex_not_0i(sc, real_part(x) + integer(y), imag_part(x))); + case T_RATIO: + return(make_complex_not_0i(sc, real_part(x) + fraction(y), imag_part(x))); + case T_REAL: + return(make_complex_not_0i(sc, real_part(x) + real(y), imag_part(x))); + case T_COMPLEX: + return(make_complex(sc, real_part(x) + real_part(y), imag_part(x) + imag_part(y))); +#if WITH_GMP + case T_BIG_INTEGER: + mpc_set_d_d(sc->mpc_1, real_part(x), imag_part(x), MPC_RNDNN); + mpc_set_z(sc->mpc_2, big_integer(y), MPC_RNDNN); + mpc_add(sc->mpc_1, sc->mpc_1, sc->mpc_2, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_RATIO: + mpc_set_d_d(sc->mpc_1, real_part(x), imag_part(x), MPC_RNDNN); + mpc_set_q(sc->mpc_2, big_ratio(y), MPC_RNDNN); + mpc_add(sc->mpc_1, sc->mpc_1, sc->mpc_2, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_REAL: + mpc_set_d_d(sc->mpc_1, real_part(x), imag_part(x), MPC_RNDNN); + mpc_add_fr(sc->mpc_1, sc->mpc_1, big_real(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_COMPLEX: + mpc_set_d_d(sc->mpc_1, real_part(x), imag_part(x), MPC_RNDNN); + mpc_add(sc->mpc_1, sc->mpc_1, big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->add_symbol, x, y, a_number_string, 2)); + } + +#if WITH_GMP + case T_BIG_INTEGER: + switch (type(y)) + { + case T_INTEGER: + mpz_set_si(sc->mpz_1, integer(y)); + mpz_add(sc->mpz_1, big_integer(x), sc->mpz_1); + return(mpz_to_integer(sc, sc->mpz_1)); + case T_RATIO: + mpq_set_z(sc->mpq_2, big_integer(x)); + mpq_set_si(sc->mpq_1, numerator(y), denominator(y)); + mpq_add(sc->mpq_1, sc->mpq_2, sc->mpq_1); + return(mpq_to_canonicalized_rational(sc, sc->mpq_1)); + case T_REAL: + if (is_NaN(real(y))) return(y); + mpfr_set_d(sc->mpfr_1, real(y), MPFR_RNDN); + mpfr_add_z(sc->mpfr_1, sc->mpfr_1, big_integer(x), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_COMPLEX: + mpc_set_d_d(sc->mpc_1, real_part(y), imag_part(y), MPC_RNDNN); + mpc_set_z(sc->mpc_2, big_integer(x), MPC_RNDNN); + mpc_add(sc->mpc_1, sc->mpc_2, sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_INTEGER: + mpz_add(sc->mpz_1, big_integer(x), big_integer(y)); + return(mpz_to_integer(sc, sc->mpz_1)); + case T_BIG_RATIO: + mpq_set_z(sc->mpq_1, big_integer(x)); + mpq_add(sc->mpq_1, sc->mpq_1, big_ratio(y)); + return(mpq_to_rational(sc, sc->mpq_1)); + case T_BIG_REAL: + mpfr_add_z(sc->mpfr_1, big_real(y), big_integer(x), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + mpc_set_z(sc->mpc_1, big_integer(x), MPC_RNDNN); + mpc_add(sc->mpc_1, sc->mpc_1, big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->add_symbol, x, y, a_number_string, 2)); + } + + case T_BIG_RATIO: + switch (type(y)) + { + case T_INTEGER: + mpq_set_si(sc->mpq_1, integer(y), 1); + mpq_add(sc->mpq_1, big_ratio(x), sc->mpq_1); + return(mpq_to_rational(sc, sc->mpq_1)); + case T_RATIO: + mpq_set_si(sc->mpq_1, numerator(y), denominator(y)); + mpq_add(sc->mpq_1, big_ratio(x), sc->mpq_1); + return(mpq_to_rational(sc, sc->mpq_1)); + case T_REAL: + if (is_NaN(real(y))) return(y); + mpfr_set_d(sc->mpfr_1, real(y), MPFR_RNDN); + mpfr_add_q(sc->mpfr_1, sc->mpfr_1, big_ratio(x), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_COMPLEX: + mpc_set_q(sc->mpc_1, big_ratio(x), MPC_RNDNN); + mpc_set_d_d(sc->mpc_2, real_part(y), imag_part(y), MPC_RNDNN); + mpc_add(sc->mpc_1, sc->mpc_1, sc->mpc_2, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_INTEGER: + mpq_set_z(sc->mpq_1, big_integer(y)); + mpq_add(sc->mpq_1, big_ratio(x), sc->mpq_1); + return(mpq_to_canonicalized_rational(sc, sc->mpq_1)); + case T_BIG_RATIO: + mpq_add(sc->mpq_1, big_ratio(x), big_ratio(y)); + return(mpq_to_canonicalized_rational(sc, sc->mpq_1)); + case T_BIG_REAL: + mpfr_add_q(sc->mpfr_1, big_real(y), big_ratio(x), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + mpc_set_q(sc->mpc_1, big_ratio(x), MPC_RNDNN); + mpc_add(sc->mpc_1, sc->mpc_1, big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->add_symbol, x, y, a_number_string, 2)); + } + + case T_BIG_REAL: + switch (type(y)) + { + case T_INTEGER: + mpfr_add_si(sc->mpfr_1, big_real(x), integer(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_RATIO: + mpq_set_si(sc->mpq_1, numerator(y), denominator(y)); + mpfr_add_q(sc->mpfr_1, big_real(x), sc->mpq_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_REAL: + if (is_NaN(real(y))) return(y); + mpfr_add_d(sc->mpfr_1, big_real(x), real(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_COMPLEX: + mpc_set_d_d(sc->mpc_1, real_part(y), imag_part(y), MPC_RNDNN); + mpc_add_fr(sc->mpc_1, sc->mpc_1, big_real(x), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_INTEGER: + mpfr_add_z(sc->mpfr_1, big_real(x), big_integer(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_RATIO: + mpfr_add_q(sc->mpfr_1, big_real(x), big_ratio(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_REAL: + mpfr_add(sc->mpfr_1, big_real(x), big_real(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + mpc_add_fr(sc->mpc_1, big_complex(y), big_real(x), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->add_symbol, x, y, a_number_string, 2)); + } + case T_BIG_COMPLEX: + switch (type(y)) + { + case T_INTEGER: + mpc_set_si(sc->mpc_1, integer(y), MPC_RNDNN); + mpc_add(sc->mpc_1, big_complex(x), sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_RATIO: + mpq_set_si(sc->mpq_1, numerator(y), denominator(y)); + mpc_set_q(sc->mpc_1, sc->mpq_1, MPC_RNDNN); + mpc_add(sc->mpc_1, big_complex(x), sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_REAL: + /* if (is_NaN(real(y))) return(y); */ + mpc_set_d_d(sc->mpc_1, real(y), 0.0, MPC_RNDNN); + mpc_add(sc->mpc_1, big_complex(x), sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_COMPLEX: + mpc_set_d_d(sc->mpc_1, real_part(y), imag_part(y), MPC_RNDNN); + mpc_add(sc->mpc_1, big_complex(x), sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_INTEGER: + mpc_set_z(sc->mpc_1, big_integer(y), MPC_RNDNN); + mpc_add(sc->mpc_1, big_complex(x), sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_RATIO: + mpc_set_q(sc->mpc_1, big_ratio(y), MPC_RNDNN); + mpc_add(sc->mpc_1, big_complex(x), sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_REAL: + mpc_add_fr(sc->mpc_1, big_complex(x), big_real(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_COMPLEX: + mpc_add(sc->mpc_1, big_complex(x), big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->add_symbol, x, y, a_number_string, 2)); + } +#endif + default: + return(method_or_bust_pp(sc, x, sc->add_symbol, x, y, a_number_string, 1)); + } +} + +static s7_pointer add_p_ppp(s7_scheme *sc, s7_pointer p0, s7_pointer p1, s7_pointer p2) +{ + if ((is_t_integer(p0)) && (is_t_integer(p1)) && (is_t_integer(p2))) + { +#if HAVE_OVERFLOW_CHECKS + s7_int val; + if ((!add_overflow(integer(p0), integer(p1), &val)) && + (!add_overflow(val, integer(p2), &val))) + return(make_integer(sc, val)); +#if WITH_GMP + mpz_set_si(sc->mpz_1, integer(p0)); + mpz_set_si(sc->mpz_2, integer(p1)); + mpz_add(sc->mpz_1, sc->mpz_1, sc->mpz_2); + mpz_set_si(sc->mpz_2, integer(p2)); + mpz_add(sc->mpz_1, sc->mpz_1, sc->mpz_2); + return(mpz_to_integer(sc, sc->mpz_1)); +#else + if (WITH_WARNINGS) s7_warn(sc, 128, "integer add overflow: (+ %" ld64 " %" ld64 " %" ld64 ")\n", integer(p0), integer(p1), integer(p2)); + return(make_real(sc, (long_double)integer(p0) + (long_double)integer(p1) + (long_double)integer(p2))); +#endif +#else + return(make_integer(sc, integer(p0) + integer(p1) + integer(p2))); +#endif + } + if ((is_t_real(p0)) && (is_t_real(p1)) && (is_t_real(p2))) + return(make_real(sc, real(p0) + real(p1) + real(p2))); + { + s7_pointer p = add_p_pp(sc, p0, p1); + sc->error_argnum = 1; + p = add_p_pp(sc, p, p2); + sc->error_argnum = 0; + return(p); + } +} + +static s7_pointer g_add(s7_scheme *sc, s7_pointer args) +{ + #define H_add "(+ ...) adds its arguments" + #define Q_add sc->pcl_n + + s7_pointer x, p; + if (is_null(args)) + return(int_zero); + x = car(args); + p = cdr(args); + if (is_null(p)) + { + if (!is_number(x)) + return(method_or_bust_p(sc, x, sc->add_symbol, a_number_string)); + return(x); + } + if (is_null(cdr(p))) + return(add_p_pp(sc, x, car(p))); + for (sc->error_argnum = 0; is_pair(p); p = cdr(p), sc->error_argnum++) + x = add_p_pp(sc, x, car(p)); + sc->error_argnum = 0; + return(x); +} + +static s7_pointer g_add_2(s7_scheme *sc, s7_pointer args) {return(add_p_pp(sc, car(args), cadr(args)));} +static s7_pointer g_add_3(s7_scheme *sc, s7_pointer args) {return(add_p_ppp(sc, car(args), cadr(args), caddr(args)));} + +static s7_pointer g_add_x1_1(s7_scheme *sc, s7_pointer x, int32_t pos) +{ + if (is_t_integer(x)) + return(add_if_overflow_to_real_or_big_integer(sc, integer(x), 1)); + + switch (type(x)) + { + case T_RATIO: return(integer_ratio_add_if_overflow_to_real_or_rational(sc, int_one, x)); /* return(add_p_pp(sc, x, int_one)) */ + case T_REAL: return(make_real(sc, real(x) + 1.0)); + case T_COMPLEX: return(make_complex_not_0i(sc, real_part(x) + 1.0, imag_part(x))); +#if WITH_GMP + case T_BIG_INTEGER: + mpz_set_si(sc->mpz_1, 1); + mpz_add(sc->mpz_1, big_integer(x), sc->mpz_1); + return(mpz_to_integer(sc, sc->mpz_1)); + case T_BIG_RATIO: + case T_BIG_REAL: + case T_BIG_COMPLEX: + return(add_p_pp(sc, x, int_one)); +#endif + default: + return(method_or_bust(sc, x, sc->add_symbol, + (pos == 1) ? set_plist_2(sc, x, int_one) : set_plist_2(sc, int_one, x), + a_number_string, pos)); + } + return(x); +} + +#if WITH_GMP +static s7_pointer g_add_x1(s7_scheme *sc, s7_pointer args) {return(g_add_x1_1(sc, car(args), 1));} +#else +static s7_pointer g_add_x1(s7_scheme *sc, s7_pointer args) +{ + s7_pointer x = car(args); + if (is_t_integer(x)) return(add_if_overflow_to_real_or_big_integer(sc, integer(x), 1)); /* return(make_integer(sc, integer(x) + 1)); */ + if (is_t_real(x)) return(make_real(sc, real(x) + 1.0)); + if (is_t_complex(x)) return(make_complex_not_0i(sc, real_part(x) + 1.0, imag_part(x))); + return(add_p_pp(sc, x, int_one)); +} +#endif +static s7_pointer g_add_1x(s7_scheme *sc, s7_pointer args) {return(g_add_x1_1(sc, cadr(args), 2));} + +static s7_pointer g_add_xi(s7_scheme *sc, s7_pointer x, s7_int y, int32_t loc) +{ + if (is_t_integer(x)) + return(add_if_overflow_to_real_or_big_integer(sc, integer(x), y)); + + switch (type(x)) + { + case T_RATIO: return(add_p_pp(sc, x, wrap_integer(sc, y))); + case T_REAL: return(make_real(sc, real(x) + y)); + case T_COMPLEX: return(make_complex_not_0i(sc, real_part(x) + y, imag_part(x))); +#if WITH_GMP + case T_BIG_INTEGER: + mpz_set_si(sc->mpz_1, y); + mpz_add(sc->mpz_1, big_integer(x), sc->mpz_1); + return(mpz_to_integer(sc, sc->mpz_1)); + case T_BIG_RATIO: + case T_BIG_REAL: + case T_BIG_COMPLEX: + return(add_p_pp(sc, x, wrap_integer(sc, y))); +#endif + default: return(method_or_bust_with_type_pi(sc, x, sc->add_symbol, x, y, a_number_string, loc)); + } + return(x); +} + +static s7_pointer add_p_pi(s7_scheme *sc, s7_pointer p1, s7_int i1) {return(g_add_xi(sc, p1, i1, 1));} + +static s7_pointer g_add_xf(s7_scheme *sc, s7_pointer x, s7_double y, int32_t loc) +{ + if (is_t_real(x)) return(make_real(sc, real(x) + y)); + switch (type(x)) + { + case T_INTEGER: return(make_real(sc, integer(x) + y)); + case T_RATIO: return(make_real(sc, fraction(x) + y)); + case T_COMPLEX: return(make_complex_not_0i(sc, real_part(x) + y, imag_part(x))); +#if WITH_GMP + case T_BIG_INTEGER: case T_BIG_RATIO: case T_BIG_REAL: case T_BIG_COMPLEX: + return(add_p_pp(sc, x, wrap_real(sc, y))); +#endif + default: return(method_or_bust_with_type_pf(sc, x, sc->add_symbol, x, y, a_number_string, loc)); + } + return(x); +} + +static s7_pointer g_add_2_ff(s7_scheme *sc, s7_pointer args) +{ +#if WITH_GMP + if ((is_t_real(car(args))) && (is_t_real(cadr(args)))) + return(make_real(sc, real(car(args)) + real(cadr(args)))); + return(add_p_pp(sc, car(args), cadr(args))); +#else + return(make_real(sc, real(car(args)) + real(cadr(args)))); +#endif +} + +static s7_pointer g_add_2_ii(s7_scheme *sc, s7_pointer args) +{ +#if WITH_GMP + if ((is_t_integer(car(args))) && (is_t_integer(cadr(args)))) +#endif + return(add_if_overflow_to_real_or_big_integer(sc, integer(car(args)), integer(cadr(args)))); +#if WITH_GMP + return(g_add(sc, args)); /* possibly bigint? */ +#endif +} + +#if WITH_GMP +static s7_pointer add_2_if(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + if ((is_t_integer(x)) && (is_t_real(y))) + { + if (s7_int_abs(integer(x)) >= INT64_TO_DOUBLE_LIMIT) + { + mpfr_set_si(sc->mpfr_1, integer(x), MPFR_RNDN); + mpfr_add_d(sc->mpfr_1, sc->mpfr_1, real(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + } + return(make_real(sc, integer(x) + real(y))); + } + return(add_p_pp(sc, x, y)); +} + +static s7_pointer g_add_2_if(s7_scheme *sc, s7_pointer args) {return(add_2_if(sc, car(args), cadr(args)));} +static s7_pointer g_add_2_fi(s7_scheme *sc, s7_pointer args) {return(add_2_if(sc, cadr(args), car(args)));} + +static s7_pointer g_add_2_xi(s7_scheme *sc, s7_pointer args) {if (is_t_integer(cadr(args))) return(g_add_xi(sc, car(args), integer(cadr(args)), 1)); return(g_add(sc, args));} +static s7_pointer g_add_2_ix(s7_scheme *sc, s7_pointer args) {if (is_t_integer(car(args))) return(g_add_xi(sc, cadr(args), integer(car(args)), 2)); return(g_add(sc, args));} +static s7_pointer g_add_2_xf(s7_scheme *sc, s7_pointer args) {if (is_t_real(cadr(args))) return(g_add_xf(sc, car(args), real(cadr(args)), 1)); return(g_add(sc, args));} +static s7_pointer g_add_2_fx(s7_scheme *sc, s7_pointer args) {if (is_t_real(car(args))) return(g_add_xf(sc, cadr(args), real(car(args)), 2)); return(g_add(sc, args));} + +#else + +static s7_pointer g_add_2_if(s7_scheme *sc, s7_pointer args) {return(make_real(sc, integer(car(args)) + real(cadr(args))));} +static s7_pointer g_add_2_fi(s7_scheme *sc, s7_pointer args) {return(make_real(sc, real(car(args)) + integer(cadr(args))));} +static s7_pointer g_add_2_xi(s7_scheme *sc, s7_pointer args) {return(g_add_xi(sc, car(args), integer(cadr(args)), 1));} +static s7_pointer g_add_2_ix(s7_scheme *sc, s7_pointer args) {return(g_add_xi(sc, cadr(args), integer(car(args)), 2));} +static s7_pointer g_add_2_xf(s7_scheme *sc, s7_pointer args) {return(g_add_xf(sc, car(args), real(cadr(args)), 1));} +static s7_pointer g_add_2_fx(s7_scheme *sc, s7_pointer args) {return(g_add_xf(sc, cadr(args), real(car(args)), 2));} +#endif + +static s7_pointer add_p_dd(s7_scheme *sc, s7_double x1, s7_double x2) {return(make_real(sc, x1 + x2));} +static s7_pointer add_p_ii(s7_scheme *sc, s7_int x1, s7_int x2) {return(make_integer(sc, x1 + x2));} + +/* add_p_ii and add_d_id unhittable apparently -- this (d_id) is due to the order of d_dd_ok and d_id_ok in float_optimize, + * but d_dd is much more often hit, and the int arg (if constant) is turned into a float in d_dd + */ +static s7_double add_d_d(s7_double x) {return(x);} +static s7_double add_d_dd(s7_double x1, s7_double x2) {return(x1 + x2);} +static s7_double add_d_ddd(s7_double x1, s7_double x2, s7_double x3) {return(x1 + x2 + x3);} +static s7_double add_d_dddd(s7_double x1, s7_double x2, s7_double x3, s7_double x4) {return(x1 + x2 + x3 + x4);} + +static s7_int add_i_ii(s7_int i1, s7_int i2) {return(i1 + i2);} +static s7_int add_i_iii(s7_int i1, s7_int i2, s7_int i3) {return(i1 + i2 + i3);} + +static s7_pointer argument_type(s7_scheme *sc, s7_pointer arg1) +{ + if (is_pair(arg1)) + { + if (car(arg1) == sc->quote_symbol) + return((is_pair(cdr(arg1))) ? s7_type_of(sc, cadr(arg1)) : NULL); /* arg1 = (quote) */ + + if ((is_h_optimized(arg1)) && + (is_safe_c_op(optimize_op(arg1))) && + (is_c_function(opt1_cfunc(arg1)))) + { + s7_pointer sig = c_function_signature(opt1_cfunc(arg1)); + if ((sig) && + (is_pair(sig)) && + (is_symbol(car(sig)))) + return(car(sig)); + } + /* perhaps add closure sig if we can depend on it (immutable func etc) */ + } + else + if (!is_symbol(arg1)) + return(s7_type_of(sc, arg1)); + return(NULL); +} + +static s7_pointer chooser_check_arg_types(s7_scheme *sc, s7_pointer arg1, s7_pointer arg2, s7_pointer fallback, + s7_pointer f_2_ff, s7_pointer f_2_ii, s7_pointer f_2_if, s7_pointer f_2_fi, + s7_pointer f_2_xi, s7_pointer f_2_ix, s7_pointer f_2_fx, s7_pointer f_2_xf) +{ + const s7_pointer arg1_type = argument_type(sc, arg1); + const s7_pointer arg2_type = argument_type(sc, arg2); + if ((arg1_type) || (arg2_type)) + { + if (arg1_type == sc->is_float_symbol) + { + if (arg2_type == sc->is_float_symbol) + return(f_2_ff); + return((arg2_type == sc->is_integer_symbol) ? f_2_fi : f_2_fx); + } + if (arg1_type == sc->is_integer_symbol) + { + if (arg2_type == sc->is_float_symbol) + return(f_2_if); + return((arg2_type == sc->is_integer_symbol) ? f_2_ii : f_2_ix); + } + if (arg2_type == sc->is_float_symbol) + return(f_2_xf); + if (arg2_type == sc->is_integer_symbol) + return(f_2_xi); + } + return(fallback); +} + +static s7_pointer g_random_i(s7_scheme *sc, s7_pointer args); + +static s7_pointer add_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool ops) +{ + /* (+ s f) (+ (* s s) s) (+ s s) (+ s (* s s)) */ + if (args != 2) return((args == 3) ? sc->add_3 : f); + if (ops) + { + s7_pointer arg1 = cadr(expr), arg2 = caddr(expr); + if (arg2 == int_one) /* (+ ... 1) */ + return(sc->add_x1); + if ((is_t_integer(arg1)) && ((is_pair(arg2)) && (is_optimized(arg2)) && (is_h_safe_c_nc(arg2)) && (fn_proc(arg2) == g_random_i))) + { + set_opt3_int(cdr(expr), integer(cadr(arg2))); + set_safe_optimize_op(expr, HOP_SAFE_C_NC); /* op if r op? */ + return(sc->add_i_random); + } + if (arg1 == int_one) + return(sc->add_1x); + return(chooser_check_arg_types(sc, arg1, arg2, sc->add_2, + sc->add_2_ff, sc->add_2_ii, sc->add_2_if, sc->add_2_fi, + sc->add_2_xi, sc->add_2_ix, sc->add_2_fx, sc->add_2_xf)); + } + return(sc->add_2); +} + +/* ---------------------------------------- subtract ---------------------------------------- */ +static s7_pointer negate_p_p(s7_scheme *sc, s7_pointer p) /* can't use "negate" because it confuses C++! */ +{ + switch (type(p)) + { + case T_INTEGER: + if (integer(p) == S7_INT64_MIN) +#if WITH_GMP + { + mpz_set_si(sc->mpz_1, S7_INT64_MIN); + mpz_neg(sc->mpz_1, sc->mpz_1); + return(mpz_to_big_integer(sc, sc->mpz_1)); + } +#else + sole_arg_out_of_range_error_nr(sc, sc->subtract_symbol, p, wrap_string(sc, "most-negative-fixnum can't be negated", 37)); +#endif + return(make_integer(sc, -integer(p))); + + case T_RATIO: return(make_simple_ratio(sc, -numerator(p), denominator(p))); + case T_REAL: return(make_real(sc, -real(p))); + case T_COMPLEX: return(make_complex_not_0i(sc, -real_part(p), -imag_part(p))); + +#if WITH_GMP + case T_BIG_INTEGER: + mpz_neg(sc->mpz_1, big_integer(p)); + return(mpz_to_integer(sc, sc->mpz_1)); + case T_BIG_RATIO: + mpq_neg(sc->mpq_1, big_ratio(p)); + return(mpq_to_canonicalized_rational(sc, sc->mpq_1)); + case T_BIG_REAL: + mpfr_neg(sc->mpfr_1, big_real(p), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + mpc_neg(sc->mpc_1, big_complex(p), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_p(sc, p, sc->subtract_symbol, a_number_string)); + } +} + +static inline s7_pointer subtract_if_overflow_to_real_or_big_integer(s7_scheme *sc, s7_int x, s7_int y) +{ +#if HAVE_OVERFLOW_CHECKS + s7_int val; + if (subtract_overflow(x, y, &val)) +#if WITH_GMP + { + mpz_set_si(sc->mpz_1, x); + mpz_set_si(sc->mpz_2, y); + mpz_sub(sc->mpz_1, sc->mpz_1, sc->mpz_2); + return(mpz_to_big_integer(sc, sc->mpz_1)); + } +#else + { + if (WITH_WARNINGS) s7_warn(sc, 128, "integer subtract overflow: (- %" ld64 " %" ld64 ")\n", x, y); + return(make_real(sc, (long_double)x - (long_double)y)); + } +#endif + return(make_integer(sc, val)); +#else + return(make_integer(sc, x - y)); +#endif +} + +static s7_pointer subtract_p_pp(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + switch (type(x)) + { + case T_INTEGER: + if (integer(x) == 0) + return(negate_p_p(sc, y)); + switch (type(y)) + { + case T_INTEGER: + return(subtract_if_overflow_to_real_or_big_integer(sc, integer(x), integer(y))); + + case T_RATIO: + { +#if HAVE_OVERFLOW_CHECKS + s7_int z; + if ((multiply_overflow(integer(x), denominator(y), &z)) || + (subtract_overflow(z, numerator(y), &z))) +#if WITH_GMP + { + mpz_set_si(sc->mpz_1, integer(x)); + mpz_mul_si(sc->mpz_1, sc->mpz_1, denominator(y)); + mpz_set_si(sc->mpz_2, numerator(y)); + mpz_sub(mpq_numref(sc->mpq_1), sc->mpz_1, sc->mpz_2); + mpz_set_si(mpq_denref(sc->mpq_1), denominator(y)); + return(mpq_to_rational(sc, sc->mpq_1)); + } +#else + { + if (WITH_WARNINGS) s7_warn(sc, 128, "integer - ratio overflow: (- %" ld64 " %" ld64 "/%" ld64 ")\n", integer(x), numerator(y), denominator(y)); + return(make_real(sc, (long_double)integer(x) - fraction(y))); + } +#endif + return(make_ratio(sc, z, denominator(y))); +#else + return(make_ratio(sc, integer(x) * denominator(y) - numerator(y), denominator(y))); +#endif + } + case T_REAL: +#if WITH_GMP + if (s7_int_abs(integer(x)) >= INT64_TO_DOUBLE_LIMIT) /* (- 9223372036854775807 .1) */ + { + mpfr_set_si(sc->mpfr_1, integer(x), MPFR_RNDN); + mpfr_sub_d(sc->mpfr_1, sc->mpfr_1, real(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + } +#endif + return(make_real(sc, (long_double)integer(x) - real(y))); + case T_COMPLEX: + return(make_complex_not_0i(sc, (long_double)integer(x) - real_part(y), -imag_part(y))); +#if WITH_GMP + case T_BIG_INTEGER: + mpz_set_si(sc->mpz_1, integer(x)); + mpz_sub(sc->mpz_1, sc->mpz_1, big_integer(y)); + return(mpz_to_integer(sc, sc->mpz_1)); + case T_BIG_RATIO: + mpq_set_si(sc->mpq_1, integer(x), 1); + mpq_sub(sc->mpq_1, sc->mpq_1, big_ratio(y)); + return(mpq_to_rational(sc, sc->mpq_1)); + case T_BIG_REAL: + mpfr_si_sub(sc->mpfr_1, integer(x), big_real(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + mpc_set_si(sc->mpc_1, integer(x), MPC_RNDNN); + mpc_sub(sc->mpc_1, sc->mpc_1, big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->subtract_symbol, x, y, a_number_string, 2)); + } + + case T_RATIO: + switch (type(y)) + { + case T_INTEGER: + { +#if HAVE_OVERFLOW_CHECKS + s7_int z; + if ((multiply_overflow(integer(y), denominator(x), &z)) || + (subtract_overflow(numerator(x), z, &z))) +#if WITH_GMP + { + mpz_set_si(sc->mpz_1, integer(y)); + mpz_mul_si(sc->mpz_1, sc->mpz_1, denominator(x)); + mpz_set_si(sc->mpz_2, numerator(x)); + mpz_sub(mpq_numref(sc->mpq_1), sc->mpz_2, sc->mpz_1); + mpz_set_si(mpq_denref(sc->mpq_1), denominator(x)); + return(mpq_to_rational(sc, sc->mpq_1)); + } +#else + { + if (WITH_WARNINGS) s7_warn(sc, 128, "ratio - integer overflow: (- %" ld64 "/%" ld64 " %" ld64 ")\n", numerator(x), denominator(x), integer(y)); + return(make_real(sc, fraction(x) - (long_double)integer(y))); + } +#endif + return(make_ratio(sc, z, denominator(x))); +#else + return(make_ratio(sc, numerator(x) - (integer(y) * denominator(x)), denominator(x))); +#endif + } + case T_RATIO: + { + s7_int d1, d2, n1, n2; + parcel_out_fractions(x, y); + if (d1 == d2) + { +#if HAVE_OVERFLOW_CHECKS + s7_int q; + if (subtract_overflow(n1, n2, &q)) +#if WITH_GMP + { + mpq_set_si(sc->mpq_1, n1, d1); + mpq_set_si(sc->mpq_2, n2, d2); + mpq_sub(sc->mpq_1, sc->mpq_1, sc->mpq_2); + return(mpq_to_rational(sc, sc->mpq_1)); + } +#else + { + if (WITH_WARNINGS) s7_warn(sc, 128, "ratio - ratio overflow: (- %" ld64 "/%" ld64 " %" ld64 "/%" ld64 ")\n", n1, d1, n2, d2); + return(make_real(sc, ((long_double)n1 - (long_double)n2) / (long_double)d1)); + } +#endif + return(make_ratio_with_div_check(sc, sc->subtract_symbol, q, d1)); +#else + return(make_ratio(sc, numerator(x) - numerator(y), denominator(x))); +#endif + } + +#if HAVE_OVERFLOW_CHECKS + { + s7_int n1d2, n2d1, d1d2, q; + if ((multiply_overflow(d1, d2, &d1d2)) || + (multiply_overflow(n1, d2, &n1d2)) || + (multiply_overflow(n2, d1, &n2d1)) || + (subtract_overflow(n1d2, n2d1, &q))) +#if WITH_GMP + { + mpq_set_si(sc->mpq_1, n1, d1); + mpq_set_si(sc->mpq_2, n2, d2); + mpq_sub(sc->mpq_1, sc->mpq_1, sc->mpq_2); + return(mpq_to_rational(sc, sc->mpq_1)); + } +#else + { + if (WITH_WARNINGS) s7_warn(sc, 128, "ratio - ratio overflow: (- %" ld64 "/%" ld64 " %" ld64 "/%" ld64 ")\n", n1, d1, n2, d2); + return(make_real(sc, ((long_double)n1 / (long_double)d1) - ((long_double)n2 / (long_double)d2))); + } +#endif + return(make_ratio_with_div_check(sc, sc->subtract_symbol, q, d1d2)); + } +#else + return(make_ratio_with_div_check(sc, sc->subtract_symbol, n1 * d2 - n2 * d1, d1 * d2)); +#endif + } + case T_REAL: + return(make_real(sc, fraction(x) - real(y))); + case T_COMPLEX: + return(make_complex_not_0i(sc, fraction(x) - real_part(y), -imag_part(y))); +#if WITH_GMP + case T_BIG_INTEGER: + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + mpq_set_z(sc->mpq_2, big_integer(y)); + mpq_sub(sc->mpq_1, sc->mpq_1, sc->mpq_2); + return(mpq_to_rational(sc, sc->mpq_1)); + case T_BIG_RATIO: + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + mpq_sub(sc->mpq_1, sc->mpq_1, big_ratio(y)); + return(mpq_to_rational(sc, sc->mpq_1)); + case T_BIG_REAL: + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + mpfr_set_q(sc->mpfr_1, sc->mpq_1, MPFR_RNDN); + mpfr_sub(sc->mpfr_1, sc->mpfr_1, big_real(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + mpc_set_q(sc->mpc_1, sc->mpq_1, MPC_RNDNN); + mpc_sub(sc->mpc_1, sc->mpc_1, big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->subtract_symbol, x, y, a_number_string, 2)); + } + + case T_REAL: + switch (type(y)) + { + case T_INTEGER: +#if WITH_GMP + if (s7_int_abs(integer(y)) >= INT64_TO_DOUBLE_LIMIT) /* (- .1 92233720368547758071) */ + { + mpfr_set_d(sc->mpfr_1, real(x), MPFR_RNDN); + mpfr_sub_si(sc->mpfr_1, sc->mpfr_1, integer(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + } +#endif + return(make_real(sc, real(x) - (long_double)integer(y))); /* long_double saves (- 9007199254740996.0 9007199254740995): 1.0 */ + case T_RATIO: + return(make_real(sc, real(x) - fraction(y))); + case T_REAL: + return(make_real(sc, real(x) - real(y))); + case T_COMPLEX: + return(make_complex_not_0i(sc, real(x) - real_part(y), -imag_part(y))); +#if WITH_GMP + case T_BIG_INTEGER: + mpfr_set_d(sc->mpfr_1, real(x), MPFR_RNDN); + mpfr_sub_z(sc->mpfr_1, sc->mpfr_1, big_integer(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_RATIO: + mpfr_set_d(sc->mpfr_1, real(x), MPFR_RNDN); + mpfr_sub_q(sc->mpfr_1, sc->mpfr_1, big_ratio(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_REAL: + mpfr_d_sub(sc->mpfr_1, real(x), big_real(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + mpc_set_d_d(sc->mpc_1, real(x), 0.0, MPC_RNDNN); + mpc_sub(sc->mpc_1, sc->mpc_1, big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->subtract_symbol, x, y, a_number_string, 2)); + } + + case T_COMPLEX: + switch (type(y)) + { + case T_INTEGER: + return(make_complex_not_0i(sc, real_part(x) - integer(y), imag_part(x))); + case T_RATIO: + return(make_complex_not_0i(sc, real_part(x) - fraction(y), imag_part(x))); + case T_REAL: + return(make_complex_not_0i(sc, real_part(x) - real(y), imag_part(x))); + case T_COMPLEX: + return(make_complex(sc, real_part(x) - real_part(y), imag_part(x) - imag_part(y))); +#if WITH_GMP + case T_BIG_INTEGER: + mpc_set_d_d(sc->mpc_1, real_part(x), imag_part(x), MPC_RNDNN); + mpc_set_z(sc->mpc_2, big_integer(y), MPC_RNDNN); + mpc_sub(sc->mpc_1, sc->mpc_1, sc->mpc_2, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_RATIO: + mpc_set_d_d(sc->mpc_1, real_part(x), imag_part(x), MPC_RNDNN); + mpc_set_q(sc->mpc_2, big_ratio(y), MPC_RNDNN); + mpc_sub(sc->mpc_1, sc->mpc_1, sc->mpc_2, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_REAL: + mpc_set_d_d(sc->mpc_1, real_part(x), imag_part(x), MPC_RNDNN); + mpc_sub_fr(sc->mpc_1, sc->mpc_1, big_real(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_COMPLEX: + mpc_set_d_d(sc->mpc_1, real_part(x), imag_part(x), MPC_RNDNN); + mpc_sub(sc->mpc_1, sc->mpc_1, big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->subtract_symbol, x, y, a_number_string, 2)); + } + +#if WITH_GMP + case T_BIG_INTEGER: + switch (type(y)) + { + case T_INTEGER: + mpz_set_si(sc->mpz_1, integer(y)); + mpz_sub(sc->mpz_1, big_integer(x), sc->mpz_1); + return(mpz_to_integer(sc, sc->mpz_1)); + case T_RATIO: + mpq_set_z(sc->mpq_2, big_integer(x)); + mpq_set_si(sc->mpq_1, numerator(y), denominator(y)); + mpq_sub(sc->mpq_1, sc->mpq_2, sc->mpq_1); + return(mpq_to_canonicalized_rational(sc, sc->mpq_1)); + case T_REAL: + if (is_NaN(real(y))) return(y); + mpfr_set_z(sc->mpfr_1, big_integer(x), MPFR_RNDN); + mpfr_sub_d(sc->mpfr_1, sc->mpfr_1, real(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_COMPLEX: + mpc_set_d_d(sc->mpc_1, real_part(y), imag_part(y), MPC_RNDNN); + mpc_set_z(sc->mpc_2, big_integer(x), MPC_RNDNN); + mpc_sub(sc->mpc_1, sc->mpc_2, sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_INTEGER: + mpz_sub(sc->mpz_1, big_integer(x), big_integer(y)); + return(mpz_to_integer(sc, sc->mpz_1)); + case T_BIG_RATIO: + mpq_set_z(sc->mpq_1, big_integer(x)); + mpq_sub(sc->mpq_1, sc->mpq_1, big_ratio(y)); + return(mpq_to_rational(sc, sc->mpq_1)); + case T_BIG_REAL: + mpfr_set_z(sc->mpfr_1, big_integer(x), MPFR_RNDN); + mpfr_sub(sc->mpfr_1, sc->mpfr_1, big_real(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + mpc_set_z(sc->mpc_1, big_integer(x), MPC_RNDNN); + mpc_sub(sc->mpc_1, sc->mpc_1, big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->subtract_symbol, x, y, a_number_string, 2)); + } + + case T_BIG_RATIO: + switch (type(y)) + { + case T_INTEGER: + mpq_set_si(sc->mpq_1, integer(y), 1); + mpq_sub(sc->mpq_1, big_ratio(x), sc->mpq_1); + return(mpq_to_rational(sc, sc->mpq_1)); + case T_RATIO: + mpq_set_si(sc->mpq_1, numerator(y), denominator(y)); + mpq_sub(sc->mpq_1, big_ratio(x), sc->mpq_1); + return(mpq_to_rational(sc, sc->mpq_1)); + case T_REAL: + if (is_NaN(real(y))) return(y); + mpfr_set_q(sc->mpfr_1, big_ratio(x), MPFR_RNDN); + mpfr_sub_d(sc->mpfr_1, sc->mpfr_1, real(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_COMPLEX: + mpc_set_q(sc->mpc_1, big_ratio(x), MPC_RNDNN); + mpc_set_d_d(sc->mpc_2, real_part(y), imag_part(y), MPC_RNDNN); + mpc_sub(sc->mpc_1, sc->mpc_1, sc->mpc_2, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_INTEGER: + mpq_set_z(sc->mpq_1, big_integer(y)); + mpq_sub(sc->mpq_1, big_ratio(x), sc->mpq_1); + return(mpq_to_canonicalized_rational(sc, sc->mpq_1)); + case T_BIG_RATIO: + mpq_sub(sc->mpq_1, big_ratio(x), big_ratio(y)); + return(mpq_to_canonicalized_rational(sc, sc->mpq_1)); + case T_BIG_REAL: + mpfr_set_q(sc->mpfr_1, big_ratio(x), MPFR_RNDN); + mpfr_sub(sc->mpfr_1, sc->mpfr_1, big_real(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + mpc_set_q(sc->mpc_1, big_ratio(x), MPC_RNDNN); + mpc_sub(sc->mpc_1, sc->mpc_1, big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->subtract_symbol, x, y, a_number_string, 2)); + } + + case T_BIG_REAL: + switch (type(y)) + { + case T_INTEGER: + mpfr_sub_si(sc->mpfr_1, big_real(x), integer(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_RATIO: + mpq_set_si(sc->mpq_1, numerator(y), denominator(y)); + mpfr_sub_q(sc->mpfr_1, big_real(x), sc->mpq_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_REAL: + if (is_NaN(real(y))) return(y); + mpfr_sub_d(sc->mpfr_1, big_real(x), real(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_COMPLEX: + mpc_set_d_d(sc->mpc_1, real_part(y), imag_part(y), MPC_RNDNN); + mpc_fr_sub(sc->mpc_1, big_real(x), sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_INTEGER: + mpfr_sub_z(sc->mpfr_1, big_real(x), big_integer(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_RATIO: + mpfr_sub_q(sc->mpfr_1, big_real(x), big_ratio(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_REAL: + mpfr_sub(sc->mpfr_1, big_real(x), big_real(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + mpc_fr_sub(sc->mpc_1, big_real(x), big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->subtract_symbol, x, y, a_number_string, 2)); + } + case T_BIG_COMPLEX: + switch (type(y)) + { + case T_INTEGER: + mpc_set_si(sc->mpc_2, integer(y), MPC_RNDNN); + mpc_sub(sc->mpc_1, big_complex(x), sc->mpc_2, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_RATIO: + mpq_set_si(sc->mpq_1, numerator(y), denominator(y)); + mpc_set_q(sc->mpc_1, sc->mpq_1, MPC_RNDNN); + mpc_sub(sc->mpc_1, big_complex(x), sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_REAL: + /* if (is_NaN(real(y))) return(y); */ + mpc_set_d_d(sc->mpc_1, real(y), 0.0, MPC_RNDNN); + mpc_sub(sc->mpc_1, big_complex(x), sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_COMPLEX: + mpc_set_d_d(sc->mpc_1, real_part(y), imag_part(y), MPC_RNDNN); + mpc_sub(sc->mpc_1, big_complex(x), sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_INTEGER: + mpc_set_z(sc->mpc_1, big_integer(y), MPC_RNDNN); + mpc_sub(sc->mpc_1, big_complex(x), sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_RATIO: + mpc_set_q(sc->mpc_1, big_ratio(y), MPC_RNDNN); + mpc_sub(sc->mpc_1, big_complex(x), sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_REAL: + mpc_sub_fr(sc->mpc_1, big_complex(x), big_real(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_COMPLEX: + mpc_sub(sc->mpc_1, big_complex(x), big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->subtract_symbol, x, y, a_number_string, 2)); + } +#endif + default: + return(method_or_bust_pp(sc, x, sc->subtract_symbol, x, y, a_number_string, 1)); + } +} + +static s7_pointer g_subtract(s7_scheme *sc, s7_pointer args) +{ + #define H_subtract "(- x1 ...) subtracts its trailing arguments from the first, or negates the first if only one it is given" + #define Q_subtract sc->pcl_n + + s7_pointer x = car(args), p = cdr(args); + if (is_null(p)) + return(negate_p_p(sc, x)); + for (sc->error_argnum = 0; is_pair(p); p = cdr(p), sc->error_argnum++) + x = subtract_p_pp(sc, x, car(p)); + sc->error_argnum = 0; + return(x); +} + +static s7_pointer g_subtract_1(s7_scheme *sc, s7_pointer args) {return(negate_p_p(sc, car(args)));} +static s7_pointer g_subtract_2(s7_scheme *sc, s7_pointer args) {return(subtract_p_pp(sc, car(args), cadr(args)));} + +static s7_pointer g_subtract_3(s7_scheme *sc, s7_pointer args) +{ + s7_pointer x = car(args); + x = subtract_p_pp(sc, x, cadr(args)); + sc->error_argnum = 1; + x = subtract_p_pp(sc, x, caddr(args)); + sc->error_argnum = 0; + return(x); +} + +static s7_pointer minus_c1(s7_scheme *sc, s7_pointer x) +{ + switch (type(x)) + { + case T_INTEGER: return(subtract_if_overflow_to_real_or_big_integer(sc, integer(x), 1)); + case T_RATIO: return(subtract_p_pp(sc, x, int_one)); + case T_REAL: return(make_real(sc, real(x) - 1.0)); + case T_COMPLEX: return(make_complex_not_0i(sc, real_part(x) - 1.0, imag_part(x))); +#if WITH_GMP + case T_BIG_INTEGER: case T_BIG_RATIO: case T_BIG_REAL: case T_BIG_COMPLEX: + return(subtract_p_pp(sc, x, int_one)); +#endif + default: + return(method_or_bust_pp(sc, x, sc->subtract_symbol, x, int_one, a_number_string, 1)); + } + return(x); +} + +static s7_pointer g_subtract_x1(s7_scheme *sc, s7_pointer args) +{ + s7_pointer p = car(args); +#if WITH_GMP + return(subtract_p_pp(sc, p, int_one)); +#endif + /* return((is_t_integer(p)) ? make_integer(sc, integer(p) - 1) : minus_c1(sc, p)); */ + return((is_t_integer(p)) ? subtract_if_overflow_to_real_or_big_integer(sc, integer(p), 1) : minus_c1(sc, p)); +} + +static s7_pointer g_subtract_2f(s7_scheme *sc, s7_pointer args) /* (- x f) */ +{ + s7_pointer x = car(args); + s7_double n = real(cadr(args)); /* checked below is_t_real */ + if (is_t_real(x)) return(make_real(sc, real(x) - n)); + switch (type(x)) + { + case T_INTEGER: return(make_real(sc, integer(x) - n)); + case T_RATIO: return(make_real(sc, fraction(x) - n)); + case T_COMPLEX: return(make_complex_not_0i(sc, real_part(x) - n, imag_part(x))); +#if WITH_GMP + case T_BIG_INTEGER: case T_BIG_RATIO: case T_BIG_REAL: case T_BIG_COMPLEX: + return(subtract_p_pp(sc, x, cadr(args))); +#endif + default: + return(method_or_bust(sc, x, sc->subtract_symbol, args, a_number_string, 1)); + } + return(x); +} + +static s7_pointer g_subtract_f2(s7_scheme *sc, s7_pointer args) /* (- f x) */ +{ + s7_pointer x = cadr(args); + s7_double n = real(car(args)); /* checked below is_t_real */ + + if (is_t_real(x)) return(make_real(sc, n - real(x))); + switch (type(x)) + { + case T_INTEGER: return(make_real(sc, n - integer(x))); + case T_RATIO: return(make_real(sc, n - fraction(x))); + case T_COMPLEX: return(make_complex_not_0i(sc, n - real_part(x), -imag_part(x))); +#if WITH_GMP + case T_BIG_INTEGER: case T_BIG_RATIO: case T_BIG_REAL: case T_BIG_COMPLEX: + return(subtract_p_pp(sc, car(args), x)); +#endif + default: + return(method_or_bust(sc, x, sc->subtract_symbol, args, a_number_string, 1)); + } + return(x); +} + +static s7_int subtract_i_ii(s7_int i1, s7_int i2) {return(i1 - i2);} +static s7_int subtract_i_i(s7_int x) {return(-x);} +static s7_int subtract_i_iii(s7_int i1, s7_int i2, s7_int i3) {return(i1 - i2 - i3);} + +static s7_double subtract_d_d(s7_double x) {return(-x);} +static s7_double subtract_d_dd(s7_double x1, s7_double x2) {return(x1 - x2);} +static s7_double subtract_d_ddd(s7_double x1, s7_double x2, s7_double x3) {return(x1 - x2 - x3);} +static s7_double subtract_d_dddd(s7_double x1, s7_double x2, s7_double x3, s7_double x4) {return(x1 - x2 - x3 - x4);} + +static s7_pointer subtract_p_dd(s7_scheme *sc, s7_double x1, s7_double x2) {return(make_real(sc, x1 - x2));} +static s7_pointer subtract_p_ii(s7_scheme *sc, s7_int i1, s7_int i2) {return(make_integer(sc, i1 - i2));} + +static s7_pointer g_sub_xi(s7_scheme *sc, s7_pointer x, s7_int y) +{ + if (is_t_integer(x)) + return(subtract_if_overflow_to_real_or_big_integer(sc, integer(x), y)); + + switch (type(x)) + { + case T_RATIO: return(make_ratio(sc, numerator(x) - (y * denominator(x)), denominator(x))); + case T_REAL: return(make_real(sc, real(x) - y)); + case T_COMPLEX: return(make_complex_not_0i(sc, real_part(x) - y, imag_part(x))); +#if WITH_GMP + case T_BIG_INTEGER: + mpz_set_si(sc->mpz_1, y); + mpz_sub(sc->mpz_1, big_integer(x), sc->mpz_1); + return(mpz_to_integer(sc, sc->mpz_1)); + case T_BIG_RATIO: + case T_BIG_REAL: + case T_BIG_COMPLEX: + return(subtract_p_pp(sc, x, wrap_integer(sc, y))); +#endif + default: return(method_or_bust_with_type_pi(sc, x, sc->subtract_symbol, x, y, a_number_string, 1)); + } + return(x); +} + +static s7_pointer subtract_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool ops) +{ + if (args == 1) return(sc->subtract_1); + if (args != 2) return((args == 3) ? sc->subtract_3 : f); + if (ops) + { + s7_pointer arg1 = cadr(expr), arg2 = caddr(expr); + if (arg2 == int_one) return(sc->subtract_x1); + if (is_t_real(arg1)) return(sc->subtract_f2); + if (is_t_real(arg2)) return(sc->subtract_2f); + } + return(sc->subtract_2); +} + + +/* ---------------------------------------- multiply ---------------------------------------- */ +#define QUOTIENT_FLOAT_LIMIT 1e13 +#define QUOTIENT_INT_LIMIT 10000000000000 +/* fraction(x) is not accurate enough if it involves numbers over e18 even when done with long_doubles */ + +static inline s7_pointer multiply_if_overflow_to_real_or_big_integer(s7_scheme *sc, s7_int x, s7_int y) +{ +#if HAVE_OVERFLOW_CHECKS + s7_int val; + if (multiply_overflow(x, y, &val)) +#if WITH_GMP + { + mpz_set_si(sc->mpz_1, x); + mpz_mul_si(sc->mpz_1, sc->mpz_1, y); + return(mpz_to_big_integer(sc, sc->mpz_1)); + } +#else + { + if (WITH_WARNINGS) s7_warn(sc, 128, "integer multiply overflow: (* %" ld64 " %" ld64 ")\n", x, y); + return(make_real(sc, (s7_double)x * (s7_double)y)); + } +#endif + return(make_integer(sc, val)); +#else + return(make_integer(sc, x * y)); +#endif +} + +static s7_pointer integer_ratio_multiply_if_overflow_to_real_or_ratio(s7_scheme *sc, s7_int x, s7_pointer y) +{ +#if HAVE_OVERFLOW_CHECKS + s7_int z; + if (multiply_overflow(x, numerator(y), &z)) +#if WITH_GMP + { + mpz_set_si(sc->mpz_1, x); + mpz_mul_si(sc->mpz_1, sc->mpz_1, numerator(y)); + mpq_set_si(sc->mpq_1, 1, denominator(y)); + mpq_set_num(sc->mpq_1, sc->mpz_1); + return(mpq_to_canonicalized_rational(sc, sc->mpq_1)); + } +#else + { + if (WITH_WARNINGS) s7_warn(sc, 128, "integer * ratio overflow: (* %" ld64 " %" ld64 "/%" ld64 ")\n", x, numerator(y), denominator(y)); + return(make_real(sc, (s7_double)x * fraction(y))); + } +#endif + return(make_ratio(sc, z, denominator(y))); +#else + return(make_ratio(sc, x * numerator(y), denominator(y))); +#endif +} + +static s7_pointer multiply_p_pp(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + switch (type(x)) + { + case T_INTEGER: + switch (type(y)) + { + case T_INTEGER: + return(multiply_if_overflow_to_real_or_big_integer(sc, integer(x), integer(y))); + case T_RATIO: + return(integer_ratio_multiply_if_overflow_to_real_or_ratio(sc, integer(x), y)); + case T_REAL: +#if WITH_GMP + if (s7_int_abs(integer(x)) > QUOTIENT_INT_LIMIT) + { + mpfr_set_si(sc->mpfr_1, integer(x), MPFR_RNDN); + mpfr_set_d(sc->mpfr_2, real(y), MPFR_RNDN); + mpfr_mul(sc->mpfr_1, sc->mpfr_1, sc->mpfr_2, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + } +#endif + return(make_real(sc, (long_double)integer(x) * real(y))); + case T_COMPLEX: + return(s7_make_complex(sc, (long_double)integer(x) * real_part(y), (long_double)integer(x) * imag_part(y))); +#if WITH_GMP + case T_BIG_INTEGER: + mpz_mul_si(sc->mpz_1, big_integer(y), integer(x)); + return(mpz_to_integer(sc, sc->mpz_1)); + case T_BIG_RATIO: + mpq_set_si(sc->mpq_1, integer(x), 1); + mpq_mul(sc->mpq_1, sc->mpq_1, big_ratio(y)); + return(mpq_to_rational(sc, sc->mpq_1)); + case T_BIG_REAL: + mpfr_mul_si(sc->mpfr_1, big_real(y), integer(x), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + mpc_mul_si(sc->mpc_1, big_complex(y), integer(x), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); /* x might be 0 */ +#endif + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->multiply_symbol, x, y, a_number_string, 2)); + } + + case T_RATIO: + switch (type(y)) + { + case T_INTEGER: + return(integer_ratio_multiply_if_overflow_to_real_or_ratio(sc, integer(y), x)); + case T_RATIO: + { + s7_int d1, d2, n1, n2; + parcel_out_fractions(x, y); +#if HAVE_OVERFLOW_CHECKS + { + s7_int n1n2, d1d2; + if ((multiply_overflow(d1, d2, &d1d2)) || + (multiply_overflow(n1, n2, &n1n2))) +#if WITH_GMP + { + mpq_set_si(sc->mpq_1, n1, d1); + mpq_set_si(sc->mpq_2, n2, d2); + mpq_mul(sc->mpq_1, sc->mpq_1, sc->mpq_2); + return(mpq_to_canonicalized_rational(sc, sc->mpq_1)); + } +#else + { + if (WITH_WARNINGS) s7_warn(sc, 128, "ratio * ratio overflow: (* %" ld64 "/%" ld64 " %" ld64 "/%" ld64 ")\n", n1, d1, n2, d2); + return(make_real(sc, fraction(x) * fraction(y))); + } +#endif + return(make_ratio_with_div_check(sc, sc->multiply_symbol, n1n2, d1d2)); + } +#else + return(make_ratio_with_div_check(sc, sc->multiply_symbol, n1 * n2, d1 * d2)); +#endif + } + case T_REAL: +#if WITH_GMP + if (numerator(x) > QUOTIENT_INT_LIMIT) + { + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + mpfr_set_q(sc->mpfr_1, sc->mpq_1, MPFR_RNDN); + mpfr_mul_d(sc->mpfr_1, sc->mpfr_1, real(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + } +#endif + return(make_real(sc, fraction(x) * real(y))); + case T_COMPLEX: + return(s7_make_complex(sc, fraction(x) * real_part(y), fraction(x) * imag_part(y))); +#if WITH_GMP + case T_BIG_INTEGER: + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + mpq_set_z(sc->mpq_2, big_integer(y)); + mpq_mul(sc->mpq_1, sc->mpq_1, sc->mpq_2); + return(mpq_to_canonicalized_rational(sc, sc->mpq_1)); + case T_BIG_RATIO: + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + mpq_mul(sc->mpq_1, sc->mpq_1, big_ratio(y)); + return(mpq_to_canonicalized_rational(sc, sc->mpq_1)); + case T_BIG_REAL: + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + mpfr_mul_q(sc->mpfr_1, big_real(y), sc->mpq_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + mpc_set_q(sc->mpc_1, sc->mpq_1, MPC_RNDNN); + mpc_mul(sc->mpc_1, sc->mpc_1, big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->multiply_symbol, x, y, a_number_string, 2)); + } + + case T_REAL: + switch (type(y)) + { + case T_INTEGER: +#if WITH_GMP + if (s7_int_abs(integer(y)) > QUOTIENT_INT_LIMIT) + { + mpfr_set_si(sc->mpfr_1, integer(y), MPFR_RNDN); + mpfr_set_d(sc->mpfr_2, real(x), MPFR_RNDN); + mpfr_mul(sc->mpfr_1, sc->mpfr_1, sc->mpfr_2, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + } +#endif + return(make_real(sc, real(x) * (long_double)integer(y))); + case T_RATIO: +#if WITH_GMP + if (numerator(y) > QUOTIENT_INT_LIMIT) + { + mpq_set_si(sc->mpq_1, numerator(y), denominator(y)); + mpfr_set_q(sc->mpfr_1, sc->mpq_1, MPFR_RNDN); + mpfr_mul_d(sc->mpfr_1, sc->mpfr_1, real(x), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + } +#endif + return(make_real(sc, fraction(y) * real(x))); + case T_REAL: + return(make_real(sc, real(x) * real(y))); + case T_COMPLEX: + return(make_complex(sc, real(x) * real_part(y), real(x) * imag_part(y))); +#if WITH_GMP + case T_BIG_INTEGER: + mpfr_set_d(sc->mpfr_1, real(x), MPFR_RNDN); + mpfr_mul_z(sc->mpfr_1, sc->mpfr_1, big_integer(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_RATIO: + mpfr_set_d(sc->mpfr_1, real(x), MPFR_RNDN); + mpfr_mul_q(sc->mpfr_1, sc->mpfr_1, big_ratio(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_REAL: + mpfr_mul_d(sc->mpfr_1, big_real(y), real(x), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + mpc_set_d_d(sc->mpc_1, real(x), 0.0, MPC_RNDNN); + mpc_mul(sc->mpc_1, sc->mpc_1, big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); /* x might = 0.0 */ +#endif + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->multiply_symbol, x, y, a_number_string, 2)); + } + + case T_COMPLEX: + switch (type(y)) + { + case T_INTEGER: + return(make_complex(sc, real_part(x) * integer(y), imag_part(x) * integer(y))); + case T_RATIO: + return(s7_make_complex(sc, real_part(x) * fraction(y), imag_part(x) * fraction(y))); + case T_REAL: + return(make_complex(sc, real_part(x) * real(y), imag_part(x) * real(y))); + case T_COMPLEX: + { + s7_double r1 = real_part(x), r2 = real_part(y), i1 = imag_part(x), i2 = imag_part(y); + return(make_complex(sc, r1 * r2 - i1 * i2, r1 * i2 + r2 * i1)); + } +#if WITH_GMP + case T_BIG_INTEGER: + mpc_set_d_d(sc->mpc_1, real_part(x), imag_part(x), MPC_RNDNN); + mpc_set_z(sc->mpc_2, big_integer(y), MPC_RNDNN); + mpc_mul(sc->mpc_1, sc->mpc_1, sc->mpc_2, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_RATIO: + mpc_set_d_d(sc->mpc_1, real_part(x), imag_part(x), MPC_RNDNN); + mpc_set_q(sc->mpc_2, big_ratio(y), MPC_RNDNN); + mpc_mul(sc->mpc_1, sc->mpc_1, sc->mpc_2, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_REAL: + mpc_set_d_d(sc->mpc_1, real_part(x), imag_part(x), MPC_RNDNN); + mpc_mul_fr(sc->mpc_1, sc->mpc_1, big_real(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_COMPLEX: + mpc_set_d_d(sc->mpc_1, real_part(x), imag_part(x), MPC_RNDNN); + mpc_mul(sc->mpc_1, sc->mpc_1, big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->multiply_symbol, x, y, a_number_string, 2)); + } + +#if WITH_GMP + case T_BIG_INTEGER: + switch (type(y)) + { + case T_INTEGER: + mpz_mul_si(sc->mpz_1, big_integer(x), integer(y)); + return(mpz_to_integer(sc, sc->mpz_1)); + case T_RATIO: + mpq_set_z(sc->mpq_2, big_integer(x)); + mpq_set_si(sc->mpq_1, numerator(y), denominator(y)); + mpq_mul(sc->mpq_1, sc->mpq_2, sc->mpq_1); + return(mpq_to_canonicalized_rational(sc, sc->mpq_1)); + case T_REAL: + if (is_NaN(real(y))) return(y); + mpfr_set_d(sc->mpfr_1, real(y), MPFR_RNDN); + mpfr_mul_z(sc->mpfr_1, sc->mpfr_1, big_integer(x), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_COMPLEX: + mpc_set_d_d(sc->mpc_1, real_part(y), imag_part(y), MPC_RNDNN); + mpc_set_z(sc->mpc_2, big_integer(x), MPC_RNDNN); + mpc_mul(sc->mpc_1, sc->mpc_2, sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_INTEGER: + mpz_mul(sc->mpz_1, big_integer(x), big_integer(y)); + return(mpz_to_integer(sc, sc->mpz_1)); + case T_BIG_RATIO: + mpq_set_z(sc->mpq_1, big_integer(x)); + mpq_mul(sc->mpq_1, sc->mpq_1, big_ratio(y)); + return(mpq_to_rational(sc, sc->mpq_1)); + case T_BIG_REAL: + mpfr_mul_z(sc->mpfr_1, big_real(y), big_integer(x), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + mpc_set_z(sc->mpc_1, big_integer(x), MPC_RNDNN); + mpc_mul(sc->mpc_1, sc->mpc_1, big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->multiply_symbol, x, y, a_number_string, 2)); + } + + case T_BIG_RATIO: + switch (type(y)) + { + case T_INTEGER: + mpq_set_si(sc->mpq_1, integer(y), 1); + mpq_mul(sc->mpq_1, big_ratio(x), sc->mpq_1); + return(mpq_to_rational(sc, sc->mpq_1)); + case T_RATIO: + mpq_set_si(sc->mpq_1, numerator(y), denominator(y)); + mpq_mul(sc->mpq_1, big_ratio(x), sc->mpq_1); + return(mpq_to_rational(sc, sc->mpq_1)); + case T_REAL: + if (is_NaN(real(y))) return(y); + mpfr_set_d(sc->mpfr_1, real(y), MPFR_RNDN); + mpfr_mul_q(sc->mpfr_1, sc->mpfr_1, big_ratio(x), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_COMPLEX: + mpc_set_q(sc->mpc_1, big_ratio(x), MPC_RNDNN); + mpc_set_d_d(sc->mpc_2, real_part(y), imag_part(y), MPC_RNDNN); + mpc_mul(sc->mpc_1, sc->mpc_1, sc->mpc_2, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_INTEGER: + mpq_set_z(sc->mpq_1, big_integer(y)); + mpq_mul(sc->mpq_1, big_ratio(x), sc->mpq_1); + return(mpq_to_canonicalized_rational(sc, sc->mpq_1)); + case T_BIG_RATIO: + mpq_mul(sc->mpq_1, big_ratio(x), big_ratio(y)); + return(mpq_to_canonicalized_rational(sc, sc->mpq_1)); + case T_BIG_REAL: + mpfr_mul_q(sc->mpfr_1, big_real(y), big_ratio(x), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + mpc_set_q(sc->mpc_1, big_ratio(x), MPC_RNDNN); + mpc_mul(sc->mpc_1, sc->mpc_1, big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->multiply_symbol, x, y, a_number_string, 2)); + } + + case T_BIG_REAL: + switch (type(y)) + { + case T_INTEGER: + mpfr_mul_si(sc->mpfr_1, big_real(x), integer(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_RATIO: + mpq_set_si(sc->mpq_1, numerator(y), denominator(y)); + mpfr_mul_q(sc->mpfr_1, big_real(x), sc->mpq_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_REAL: + if (is_NaN(real(y))) return(y); + mpfr_mul_d(sc->mpfr_1, big_real(x), real(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_COMPLEX: + mpc_set_d_d(sc->mpc_1, real_part(y), imag_part(y), MPC_RNDNN); + mpc_mul_fr(sc->mpc_1, sc->mpc_1, big_real(x), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_INTEGER: + mpfr_mul_z(sc->mpfr_1, big_real(x), big_integer(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_RATIO: + mpfr_mul_q(sc->mpfr_1, big_real(x), big_ratio(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_REAL: + mpfr_mul(sc->mpfr_1, big_real(x), big_real(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + mpc_mul_fr(sc->mpc_1, big_complex(y), big_real(x), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); /* 0.0? */ + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->multiply_symbol, x, y, a_number_string, 2)); + } + case T_BIG_COMPLEX: + switch (type(y)) + { + case T_INTEGER: + mpc_mul_si(sc->mpc_1, big_complex(x), integer(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_RATIO: + mpq_set_si(sc->mpq_1, numerator(y), denominator(y)); + mpc_set_q(sc->mpc_1, sc->mpq_1, MPC_RNDNN); + mpc_mul(sc->mpc_1, big_complex(x), sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_REAL: + /* if (is_NaN(real(y))) return(y); */ + mpc_set_d_d(sc->mpc_1, real(y), 0.0, MPC_RNDNN); + mpc_mul(sc->mpc_1, big_complex(x), sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_COMPLEX: + mpc_set_d_d(sc->mpc_1, real_part(y), imag_part(y), MPC_RNDNN); + mpc_mul(sc->mpc_1, big_complex(x), sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_INTEGER: + mpc_set_z(sc->mpc_1, big_integer(y), MPC_RNDNN); + mpc_mul(sc->mpc_1, big_complex(x), sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_RATIO: + mpc_set_q(sc->mpc_1, big_ratio(y), MPC_RNDNN); + mpc_mul(sc->mpc_1, big_complex(x), sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_REAL: + mpc_mul_fr(sc->mpc_1, big_complex(x), big_real(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_COMPLEX: + mpc_mul(sc->mpc_1, big_complex(x), big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->multiply_symbol, x, y, a_number_string, 2)); + } +#endif + default: + return(method_or_bust_pp(sc, x, sc->multiply_symbol, x, y, a_number_string, 1)); + } +} + +static s7_pointer multiply_p_ppp(s7_scheme *sc, s7_pointer x, s7_pointer y, s7_pointer z) +{ + x = multiply_p_pp(sc, x, y); + sc->error_argnum = 1; + x = multiply_p_pp(sc, x, z); + sc->error_argnum = 0; + return(x); +} + +static s7_pointer multiply_method_or_bust(s7_scheme *sc, s7_pointer obj, s7_pointer args, s7_pointer typ, int32_t num) +{ + if (has_active_methods(sc, obj)) + return(find_and_apply_method(sc, obj, sc->multiply_symbol, args)); + if (num == 0) + sole_arg_wrong_type_error_nr(sc, sc->multiply_symbol, obj, typ); + wrong_type_error_nr(sc, sc->multiply_symbol, num, obj, typ); + return(NULL); +} + +static s7_pointer g_multiply(s7_scheme *sc, s7_pointer args) +{ + #define H_multiply "(* ...) multiplies its arguments" + #define Q_multiply sc->pcl_n + + s7_pointer x, p; + if (is_null(args)) + return(int_one); + x = car(args); + p = cdr(args); + if (is_null(p)) + { + if (!is_number(x)) + return(multiply_method_or_bust(sc, x, args, a_number_string, 0)); + return(x); + } + for (sc->error_argnum = 0; is_pair(p); p = cdr(p), sc->error_argnum++) + x = multiply_p_pp(sc, x, car(p)); + sc->error_argnum = 0; + return(x); +} + +static s7_pointer g_multiply_2(s7_scheme *sc, s7_pointer args) {return(multiply_p_pp(sc, car(args), cadr(args)));} + +static s7_pointer g_mul_xi(s7_scheme *sc, s7_pointer x, s7_int n, int32_t loc) +{ + switch (type(x)) + { + case T_INTEGER: return(multiply_if_overflow_to_real_or_big_integer(sc, integer(x), n)); + case T_RATIO: return(integer_ratio_multiply_if_overflow_to_real_or_ratio(sc, n, x)); + case T_REAL: return(make_real(sc, real(x) * n)); + case T_COMPLEX: return(s7_make_complex(sc, real_part(x) * n, imag_part(x) * n)); +#if WITH_GMP + case T_BIG_INTEGER: + mpz_mul_si(sc->mpz_1, big_integer(x), n); + return(mpz_to_integer(sc, sc->mpz_1)); + case T_BIG_RATIO: + case T_BIG_REAL: + case T_BIG_COMPLEX: + return(multiply_p_pp(sc, x, wrap_integer(sc, n))); +#endif + default: + /* we can get here from mul_2_xi for example so the non-integer argument might not be a symbol */ + return(method_or_bust_with_type_pi(sc, x, sc->multiply_symbol, x, n, a_number_string, loc)); + } + return(x); +} + +static s7_pointer multiply_p_pi(s7_scheme *sc, s7_pointer p1, s7_int i1) {return(g_mul_xi(sc, p1, i1, 1));} + +static s7_pointer g_mul_xf(s7_scheme *sc, s7_pointer x, s7_double y, int32_t num) +{ + /* it's possible to return different argument NaNs depending on the expression or how it is wrapped: + * (* (bignum +nan.0) +nan.123) -> nan.123 + * (let () (define (func) (* (bignum +nan.0) +nan.123)) (func) (func)) -> nan.0 + * latter call is fx_c_aaa->fx_c_ac->g_mul_xf (if +nan.122 instead of +nan.0, we get +nan.122 so we always get one of the NaNs) + */ + switch (type(x)) + { + case T_INTEGER: return(make_real(sc, integer(x) * y)); + case T_RATIO: return(make_real(sc, numerator(x) * y / denominator(x))); + case T_REAL: return(make_real(sc, real(x) * y)); + case T_COMPLEX: return(s7_make_complex(sc, real_part(x) * y, imag_part(x) * y)); +#if WITH_GMP + case T_BIG_INTEGER: + mpfr_set_d(sc->mpfr_1, y, MPFR_RNDN); + mpfr_mul_z(sc->mpfr_1, sc->mpfr_1, big_integer(x), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_RATIO: + mpfr_set_d(sc->mpfr_1, y, MPFR_RNDN); + mpfr_mul_q(sc->mpfr_1, sc->mpfr_1, big_ratio(x), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_REAL: + mpfr_mul_d(sc->mpfr_1, big_real(x), y, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + mpfr_set_d(sc->mpfr_1, y, MPFR_RNDN); + mpc_mul_fr(sc->mpc_1, big_complex(x), sc->mpfr_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: return(method_or_bust_with_type_pf(sc, x, sc->multiply_symbol, x, y, a_number_string, num)); + } + return(x); +} + +#if WITH_GMP +static s7_pointer g_mul_2_if(s7_scheme *sc, s7_pointer args) { + if ((is_t_integer(car(args))) && (is_t_real(cadr(args)))) + return(make_real(sc, integer(car(args)) * real(cadr(args)))); + return(multiply_p_pp(sc, car(args), cadr(args))); +} + +static s7_pointer g_mul_2_fi(s7_scheme *sc, s7_pointer args) +{ + if ((is_t_integer(cadr(args))) && (is_t_real(car(args)))) + return(make_real(sc, real(car(args)) * integer(cadr(args)))); + return(multiply_p_pp(sc, car(args), cadr(args))); +} + +static s7_pointer g_mul_2_xi(s7_scheme *sc, s7_pointer args) {if (is_t_integer(cadr(args))) return(g_mul_xi(sc, car(args), integer(cadr(args)), 1)); return(g_multiply(sc, args));} +static s7_pointer g_mul_2_ix(s7_scheme *sc, s7_pointer args) {if (is_t_integer(car(args))) return(g_mul_xi(sc, cadr(args), integer(car(args)), 2)); return(g_multiply(sc, args));} +static s7_pointer g_mul_2_xf(s7_scheme *sc, s7_pointer args) {if (is_t_real(cadr(args))) return(g_mul_xf(sc, car(args), real(cadr(args)), 1)); return(g_multiply(sc, args));} +static s7_pointer g_mul_2_fx(s7_scheme *sc, s7_pointer args) {if (is_t_real(car(args))) return(g_mul_xf(sc, cadr(args), real(car(args)), 2)); return(g_multiply(sc, args));} +static s7_pointer g_mul_2_ff(s7_scheme *sc, s7_pointer args) {return(multiply_p_pp(sc, car(args), cadr(args)));} +static s7_pointer g_mul_2_ii(s7_scheme *sc, s7_pointer args) {return(multiply_p_pp(sc, car(args), cadr(args)));} +#else +static s7_pointer g_mul_2_if(s7_scheme *sc, s7_pointer args) {return(make_real(sc, integer(car(args)) * real(cadr(args))));} +static s7_pointer g_mul_2_fi(s7_scheme *sc, s7_pointer args) {return(make_real(sc, real(car(args)) * integer(cadr(args))));} +static s7_pointer g_mul_2_xi(s7_scheme *sc, s7_pointer args) {return(g_mul_xi(sc, car(args), integer(cadr(args)), 1));} +static s7_pointer g_mul_2_ix(s7_scheme *sc, s7_pointer args) {return(g_mul_xi(sc, cadr(args), integer(car(args)), 2));} +static s7_pointer g_mul_2_xf(s7_scheme *sc, s7_pointer args) {return(g_mul_xf(sc, car(args), real(cadr(args)), 1));} /* split out t_real is slower */ +static s7_pointer g_mul_2_fx(s7_scheme *sc, s7_pointer args) {return(g_mul_xf(sc, cadr(args), real(car(args)), 2));} +static s7_pointer g_mul_2_ff(s7_scheme *sc, s7_pointer args) {return(make_real(sc, real(car(args)) * real(cadr(args))));} + +static s7_pointer g_mul_2_ii(s7_scheme *sc, s7_pointer args) +{ +#if HAVE_OVERFLOW_CHECKS + s7_int val, x = integer(car(args)), y = integer(cadr(args)); + if (multiply_overflow(x, y, &val)) + { + if (WITH_WARNINGS) s7_warn(sc, 128, "integer multiply overflow: (* %" ld64 " %" ld64 ")\n", x, y); + return(make_real(sc, (s7_double)x * (s7_double)y)); + } + return(make_integer(sc, val)); +#else + return(make_integer(sc, integer(car(args)) * integer(cadr(args)))); +#endif +} +#endif + +static s7_int multiply_i_ii(s7_int i1, s7_int i2) +{ +#if HAVE_OVERFLOW_CHECKS + s7_int val; + if (multiply_overflow(i1, i2, &val)) + { +#if WITH_WARNINGS + s7_warn(cur_sc, 64, "integer multiply overflow: (* %" ld64 " %" ld64 ")\n", i1, i2); +#endif + return(S7_INT64_MAX); /* this is inconsistent with other unopt cases where an overflow -> double result */ + } + /* (let () (define (func) (do ((i 0 (+ i 1))) ((= i 1)) (do ((j 0 (+ j 1))) ((= j 1)) (even? (* (ash 1 43) (ash 1 43)))))) (define (hi) (func)) (hi)) */ + return(val); +#else + return(i1 * i2); +#endif +} + +static s7_int multiply_i_iii(s7_int i1, s7_int i2, s7_int i3) +{ +#if HAVE_OVERFLOW_CHECKS + s7_int val1, val2; + if ((multiply_overflow(i1, i2, &val1)) || + (multiply_overflow(val1, i3, &val2))) + { +#if WITH_WARNINGS + s7_warn(cur_sc, 64, "integer multiply overflow: (* %" ld64 " %" ld64 " %" ld64 ")\n", i1, i2, i3); +#endif + return(S7_INT64_MAX); + } + return(val2); +#else + return(i1 * i2 * i3); +#endif +} + +static s7_double multiply_d_d(s7_double x) {return(x);} +static s7_double multiply_d_dd(s7_double x1, s7_double x2) {return(x1 * x2);} +static s7_double multiply_d_ddd(s7_double x1, s7_double x2, s7_double x3) {return(x1 * x2 * x3);} +static s7_double multiply_d_dddd(s7_double x1, s7_double x2, s7_double x3, s7_double x4) {return(x1 * x2 * x3 * x4);} +static s7_pointer mul_p_dd(s7_scheme *sc, s7_double x1, s7_double x2) {return(make_real(sc, x1 * x2));} + +static s7_pointer multiply_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool ops) +{ + if (args != 2) return(f); + if (ops) + return(chooser_check_arg_types(sc, cadr(expr), caddr(expr), sc->multiply_2, + sc->mul_2_ff, sc->mul_2_ii, sc->mul_2_if, sc->mul_2_fi, + sc->mul_2_xi, sc->mul_2_ix, sc->mul_2_fx, sc->mul_2_xf)); + return(sc->multiply_2); +} + + +/* ---------------------------------------- divide ---------------------------------------- */ +static s7_pointer complex_invert(s7_scheme *sc, s7_pointer p) +{ + s7_double r2 = real_part(p), i2 = imag_part(p); + s7_double den = (r2 * r2 + i2 * i2); + /* here if p is, for example, -inf.0+i, den is +inf.0 so -i2/den is -0.0 (in gcc anyway), so the imag part is 0.0 */ + return(s7_make_complex(sc, r2 / den, -i2 / den)); +} + +static s7_pointer invert_p_p(s7_scheme *sc, s7_pointer p) +{ +#if WITH_GMP + s7_pointer x; +#endif + switch (type(p)) + { + case T_INTEGER: +#if WITH_GMP && (!POINTER_32) + if (integer(p) == S7_INT64_MIN) /* (/ 1 (*s7* 'most-negative-fixnum)) -> -1/9223372036854775808 */ + { + new_cell(sc, x, T_BIG_RATIO); + big_ratio_bgr(x) = alloc_bigrat(sc); + add_big_ratio(sc, x); + mpz_set_si(sc->mpz_1, S7_INT64_MAX); + mpz_set_si(sc->mpz_2, 1); + mpz_add(sc->mpz_1, sc->mpz_1, sc->mpz_2); + mpq_set_si(big_ratio(x), -1, 1); + mpq_set_den(big_ratio(x), sc->mpz_1); /* geez... */ + return(x); + } +#endif + if (integer(p) == 0) + division_by_zero_error_1_nr(sc, sc->divide_symbol, p); + return(make_simple_ratio(sc, 1, integer(p))); /* this checks for int */ + case T_RATIO: + return(make_simple_ratio(sc, denominator(p), numerator(p))); + case T_REAL: + if (real(p) == 0.0) + division_by_zero_error_1_nr(sc, sc->divide_symbol, p); + return(make_real(sc, 1.0 / real(p))); + case T_COMPLEX: + return(complex_invert(sc, p)); + +#if WITH_GMP + case T_BIG_INTEGER: + if (mpz_cmp_ui(big_integer(p), 0) == 0) + division_by_zero_error_1_nr(sc, sc->divide_symbol, p); + if ((mpz_cmp_ui(big_integer(p), 1) == 0) || (mpz_cmp_si(big_integer(p), -1) == 0)) + return(p); + new_cell(sc, x, T_BIG_RATIO); + big_ratio_bgr(x) = alloc_bigrat(sc); + add_big_ratio(sc, x); + mpq_set_si(big_ratio(x), 1, 1); + mpq_set_den(big_ratio(x), big_integer(p)); + mpq_canonicalize(big_ratio(x)); + return(x); + + case T_BIG_RATIO: + if (mpz_cmp_ui(mpq_numref(big_ratio(p)), 1) == 0) + return(mpz_to_integer(sc, mpq_denref(big_ratio(p)))); + if (mpz_cmp_si(mpq_numref(big_ratio(p)), -1) == 0) + { + mpz_neg(sc->mpz_1, mpq_denref(big_ratio(p))); + return(mpz_to_integer(sc, sc->mpz_1)); + } + new_cell(sc, x, T_BIG_RATIO); + big_ratio_bgr(x) = alloc_bigrat(sc); + add_big_ratio(sc, x); + mpq_inv(big_ratio(x), big_ratio(p)); + mpq_canonicalize(big_ratio(x)); + return(x); + + case T_BIG_REAL: + if (mpfr_zero_p(big_real(p))) + division_by_zero_error_1_nr(sc, sc->divide_symbol, p); + x = mpfr_to_big_real(sc, big_real(p)); + mpfr_ui_div(big_real(x), 1, big_real(x), MPFR_RNDN); + return(x); + + case T_BIG_COMPLEX: + if ((!mpfr_number_p(mpc_realref(big_complex(p)))) || (!mpfr_number_p(mpc_imagref(big_complex(p))))) + return(complex_NaN); + mpc_ui_div(sc->mpc_1, 1, big_complex(p), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); /* x might be 0+0i if real-part is inf? */ +#endif + default: + check_method(sc, p, sc->divide_symbol, set_plist_1(sc, p)); + wrong_type_error_nr(sc, sc->divide_symbol, 1, p, a_number_string); + } + return(NULL); +} + +static s7_pointer divide_p_pp(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + /* splitting out real/real here saves very little */ + switch (type(x)) + { + case T_INTEGER: + switch (type(y)) + { + /* -------- integer x -------- */ + case T_INTEGER: + if (integer(y) == 0) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + if (integer(x) == 1) /* mainly to handle (/ 1 -9223372036854775808) correctly! */ + return(invert_p_p(sc, y)); + return(make_ratio(sc, integer(x), integer(y))); + + case T_RATIO: +#if HAVE_OVERFLOW_CHECKS + { + s7_int dn; + if (multiply_overflow(integer(x), denominator(y), &dn)) +#if WITH_GMP + { + mpq_set_si(sc->mpq_1, integer(x), 1); + mpq_set_si(sc->mpq_2, numerator(y), denominator(y)); + mpq_div(sc->mpq_1, sc->mpq_1, sc->mpq_2); + return(mpq_to_canonicalized_rational(sc, sc->mpq_1)); + } +#else + { + if (WITH_WARNINGS) s7_warn(sc, 128, "integer / ratio overflow: (/ %" ld64 " %" ld64 "/%" ld64 ")\n", integer(x), numerator(y), denominator(y)); + return(make_real(sc, integer(x) * inverted_fraction(y))); + } +#endif + return(make_ratio_with_div_check(sc, sc->divide_symbol, dn, numerator(y))); + } +#else + return(make_ratio_with_div_check(sc, sc->divide_symbol, integer(x) * denominator(y), numerator(y))); +#endif + + case T_REAL: + if (is_NaN(real(y))) return(y); + if (is_inf(real(y))) return(real_zero); + if (real(y) == 0.0) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); +#if WITH_GMP + if ((s7_int_abs(integer(x))) > QUOTIENT_INT_LIMIT) + { + mpfr_set_si(sc->mpfr_1, integer(x), MPFR_RNDN); + mpfr_set_d(sc->mpfr_2, real(y), MPFR_RNDN); + mpfr_div(sc->mpfr_1, sc->mpfr_1, sc->mpfr_2, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + } +#endif + return(make_real(sc, (s7_double)(integer(x)) / real(y))); + + case T_COMPLEX: + { + s7_double den, r1 = (s7_double)integer(x), r2 = real_part(y), i2 = imag_part(y); + den = 1.0 / (r2 * r2 + i2 * i2); + /* we could avoid the squaring (see Knuth II p613 16), not a big deal: (/ 1.0e308+1.0e308i 2.0e308+2.0e308i) => nan, (gmp case is ok here) */ + return(s7_make_complex(sc, r1 * r2 * den, -(r1 * i2 * den))); + } + +#if WITH_GMP + case T_BIG_INTEGER: + if (mpz_cmp_ui(big_integer(y), 0) == 0) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + mpq_set_si(sc->mpq_1, integer(x), 1); + mpq_set_den(sc->mpq_1, big_integer(y)); + return(mpq_to_canonicalized_rational(sc, sc->mpq_1)); + case T_BIG_RATIO: + mpq_set_si(sc->mpq_1, integer(x), 1); + mpq_div(sc->mpq_1, sc->mpq_1, big_ratio(y)); + return(mpq_to_canonicalized_rational(sc, sc->mpq_1)); + case T_BIG_REAL: + if (mpfr_zero_p(big_real(y))) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + mpfr_si_div(sc->mpfr_1, integer(x), big_real(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + if ((!mpfr_number_p(mpc_realref(big_complex(y)))) || (!mpfr_number_p(mpc_imagref(big_complex(y))))) + return(complex_NaN); + mpc_set_si(sc->mpc_1, integer(x), MPC_RNDNN); + mpc_div(sc->mpc_1, sc->mpc_1, big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); /* x might be 0? */ +#endif + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->divide_symbol, x, y, a_number_string, 2)); + } + break; + + /* -------- ratio x -------- */ + case T_RATIO: + switch (type(y)) + { + case T_INTEGER: + if (integer(y) == 0) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); +#if HAVE_OVERFLOW_CHECKS + { + s7_int dn; + if (multiply_overflow(denominator(x), integer(y), &dn)) +#if WITH_GMP + { + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + mpq_set_si(sc->mpq_2, integer(y), 1); + mpq_div(sc->mpq_1, sc->mpq_1, sc->mpq_2); + return(mpq_to_rational(sc, sc->mpq_1)); + } +#else + { + if (WITH_WARNINGS) + s7_warn(sc, 128, "ratio / integer overflow: (/ %" ld64 "/%" ld64 " %" ld64 ")\n", numerator(x), denominator(x), integer(y)); + return(make_real(sc, (long_double)numerator(x) / ((long_double)denominator(x) * (long_double)integer(y)))); + } +#endif + return(make_ratio_with_div_check(sc, sc->divide_symbol, numerator(x), dn)); + } +#else + return(make_ratio_with_div_check(sc, sc->divide_symbol, numerator(x), denominator(x) * integer(y))); +#endif + + case T_RATIO: + { + s7_int d1, d2, n1, n2; + parcel_out_fractions(x, y); + if (d1 == d2) + return(make_ratio_with_div_check(sc, sc->divide_symbol, n1, n2)); +#if HAVE_OVERFLOW_CHECKS + if ((multiply_overflow(n1, d2, &n1)) || + (multiply_overflow(n2, d1, &d1))) + { +#if WITH_GMP + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); /* not n1 and d1! they are garbage here */ + mpq_set_si(sc->mpq_2, n2, d2); + mpq_div(sc->mpq_1, sc->mpq_1, sc->mpq_2); + return(mpq_to_rational(sc, sc->mpq_1)); +#else + s7_double r1, r2; + if (WITH_WARNINGS) + s7_warn(sc, 128, "ratio / ratio overflow: (/ %" ld64 "/%" ld64 " %" ld64 "/%" ld64 ")\n", numerator(x), denominator(x), numerator(y), denominator(y)); + r1 = fraction(x); + r2 = inverted_fraction(y); + return(make_real(sc, r1 * r2)); +#endif + } + return(make_ratio_with_div_check(sc, sc->divide_symbol, n1, d1)); +#else + return(make_ratio_with_div_check(sc, sc->divide_symbol, n1 * d2, n2 * d1)); +#endif + } + + case T_REAL: + if (real(y) == 0.0) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + return(make_real(sc, fraction(x) / real(y))); + + case T_COMPLEX: + { + s7_double rx = fraction(x), r2 = real_part(y), i2 = imag_part(y); + s7_double den = 1.0 / (r2 * r2 + i2 * i2); + return(s7_make_complex(sc, rx * r2 * den, -rx * i2 * den)); /* not unchecked: (/ 3/4 -inf.0+i) */ + } + +#if WITH_GMP + case T_BIG_INTEGER: + if (mpz_cmp_ui(big_integer(y), 0) == 0) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + mpq_set_z(sc->mpq_1, big_integer(y)); + mpq_set_si(sc->mpq_2, numerator(x), denominator(x)); + mpq_div(sc->mpq_1, sc->mpq_2, sc->mpq_1); + return(mpq_to_rational(sc, sc->mpq_1)); + case T_BIG_RATIO: + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + mpq_div(sc->mpq_1, sc->mpq_1, big_ratio(y)); + return(mpq_to_rational(sc, sc->mpq_1)); + case T_BIG_REAL: + if (mpfr_zero_p(big_real(y))) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + mpfr_set_q(sc->mpfr_1, sc->mpq_1, MPFR_RNDN); + mpfr_div(sc->mpfr_1, sc->mpfr_1, big_real(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + if ((!mpfr_number_p(mpc_realref(big_complex(y)))) || (!mpfr_number_p(mpc_imagref(big_complex(y))))) + return(complex_NaN); + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + mpc_set_q(sc->mpc_1, sc->mpq_1, MPC_RNDNN); + mpc_div(sc->mpc_1, sc->mpc_1, big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->divide_symbol, x, y, a_number_string, 2)); + } + + /* -------- real x -------- */ + case T_REAL: + switch (type(y)) + { + case T_INTEGER: + if (integer(y) == 0) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + if (is_NaN(real(x))) return(x); /* what is (/ +nan.0 0)? */ + if (is_inf(real(x))) + return((real(x) > 0.0) ? ((integer(y) > 0) ? real_infinity : real_minus_infinity) : ((integer(y) > 0) ? real_minus_infinity : real_infinity)); + return(make_real(sc, (long_double)real(x) / (long_double)integer(y))); + + case T_RATIO: + if (is_NaN(real(x))) return(x); + if (is_inf(real(x))) + return((real(x) > 0) ? ((numerator(y) > 0) ? real_infinity : real_minus_infinity) : ((numerator(y) > 0) ? real_minus_infinity : real_infinity)); + return(make_real(sc, real(x) * inverted_fraction(y))); + + case T_REAL: + if (is_NaN(real(y))) return(y); + if (real(y) == 0.0) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + if (is_NaN(real(x))) return(x); + if (is_inf(real(y))) + return((is_inf(real(x))) ? real_NaN : real_zero); + return(make_real(sc, real(x) / real(y))); + + case T_COMPLEX: + { + s7_double den, r2, i2; + if (is_NaN(real(x))) return(complex_NaN); + r2 = real_part(y); + i2 = imag_part(y); + if ((is_NaN(r2)) || (is_inf(r2))) return(complex_NaN); + if ((is_NaN(i2)) || (is_inf(i2))) return(complex_NaN); + den = 1.0 / (r2 * r2 + i2 * i2); + return(s7_make_complex(sc, real(x) * r2 * den, -real(x) * i2 * den)); + } + +#if WITH_GMP + case T_BIG_INTEGER: + if (mpz_cmp_ui(big_integer(y), 0) == 0) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + mpfr_set_z(sc->mpfr_1, big_integer(y), MPFR_RNDN); + mpfr_d_div(sc->mpfr_1, real(x), sc->mpfr_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_RATIO: + mpfr_set_d(sc->mpfr_1, real(x), MPFR_RNDN); + mpfr_div_q(sc->mpfr_1, sc->mpfr_1, big_ratio(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_REAL: + if (mpfr_zero_p(big_real(y))) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + mpfr_d_div(sc->mpfr_1, real(x), big_real(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + if ((is_NaN(real(x))) || (!mpfr_number_p(mpc_realref(big_complex(y)))) || (!mpfr_number_p(mpc_imagref(big_complex(y))))) + return(complex_NaN); + mpc_set_d_d(sc->mpc_1, real(x), 0.0, MPC_RNDNN); + mpc_div(sc->mpc_1, sc->mpc_1, big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->divide_symbol, x, y, a_number_string, 2)); + } + + /* -------- complex x -------- */ + case T_COMPLEX: + switch (type(y)) + { + case T_INTEGER: + { + s7_double r1; + if (integer(y) == 0) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + r1 = (long_double)1.0 / (long_double)integer(y); + return(s7_make_complex(sc, real_part(x) * r1, imag_part(x) * r1)); + } + + case T_RATIO: + { + s7_double frac = inverted_fraction(y); + return(make_complex(sc, real_part(x) * frac, imag_part(x) * frac)); + } + + case T_REAL: + { + s7_double r1; + if (real(y) == 0.0) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + r1 = 1.0 / real(y); + return(make_complex(sc, real_part(x) * r1, imag_part(x) * r1)); /* (/ 0.0+1.0i +inf.0) */ + } + + case T_COMPLEX: + { + s7_double r1 = real_part(x), r2, i1, i2, den; + if (is_NaN(r1)) return(x); + i1 = imag_part(x); + if (is_NaN(i1)) return(x); + r2 = real_part(y); + if (is_NaN(r2)) return(y); + if (is_inf(r2)) return(complex_NaN); + i2 = imag_part(y); + if (is_NaN(i2)) return(y); + den = 1.0 / (r2 * r2 + i2 * i2); + return(s7_make_complex(sc, (r1 * r2 + i1 * i2) * den, (r2 * i1 - r1 * i2) * den)); + } + +#if WITH_GMP + case T_BIG_INTEGER: + if (mpz_cmp_ui(big_integer(y), 0) == 0) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + mpc_set_d_d(sc->mpc_1, real_part(x), imag_part(x), MPC_RNDNN); + mpc_set_z(sc->mpc_2, big_integer(y), MPC_RNDNN); + mpc_div(sc->mpc_1, sc->mpc_1, sc->mpc_2, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_RATIO: + mpc_set_d_d(sc->mpc_1, real_part(x), imag_part(x), MPC_RNDNN); + mpc_set_q(sc->mpc_2, big_ratio(y), MPC_RNDNN); + mpc_div(sc->mpc_1, sc->mpc_1, sc->mpc_2, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_REAL: + if (mpfr_zero_p(big_real(y))) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + mpc_set_d_d(sc->mpc_1, real_part(x), imag_part(x), MPC_RNDNN); + mpc_div_fr(sc->mpc_1, sc->mpc_1, big_real(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_COMPLEX: + if ((!mpfr_number_p(mpc_realref(big_complex(y)))) || (!mpfr_number_p(mpc_imagref(big_complex(y))))) + return(complex_NaN); + mpc_set_d_d(sc->mpc_1, real_part(x), imag_part(x), MPC_RNDNN); + mpc_div(sc->mpc_1, sc->mpc_1, big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->divide_symbol, x, y, a_number_string, 2)); + } + +#if WITH_GMP + case T_BIG_INTEGER: + switch (type(y)) + { + case T_INTEGER: + if (integer(y) == 0) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + mpz_set_si(sc->mpz_1, integer(y)); + mpq_set_num(sc->mpq_1, big_integer(x)); + mpq_set_den(sc->mpq_1, sc->mpz_1); + return(mpq_to_canonicalized_rational(sc, sc->mpq_1)); + case T_RATIO: + mpq_set_z(sc->mpq_2, big_integer(x)); + mpq_set_si(sc->mpq_1, numerator(y), denominator(y)); /* can't invert here, mpq den=unsigned */ + mpq_div(sc->mpq_1, sc->mpq_2, sc->mpq_1); + return(mpq_to_canonicalized_rational(sc, sc->mpq_1)); + case T_REAL: + if (is_NaN(real(y))) return(y); + if (real(y) == 0.0) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + mpfr_set_z(sc->mpfr_1, big_integer(x), MPFR_RNDN); + mpfr_div_d(sc->mpfr_1, sc->mpfr_1, real(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_COMPLEX: + if ((is_NaN(real_part(y))) || (is_NaN(imag_part(y))) || + (is_inf(real_part(y))) || (is_inf(imag_part(y)))) + return(complex_NaN); + mpc_set_d_d(sc->mpc_1, real_part(y), imag_part(y), MPC_RNDNN); + mpc_set_z(sc->mpc_2, big_integer(x), MPC_RNDNN); + mpc_div(sc->mpc_1, sc->mpc_2, sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_INTEGER: + if (mpz_cmp_ui(big_integer(y), 0) == 0) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + mpq_set_num(sc->mpq_1, big_integer(x)); + mpq_set_den(sc->mpq_1, big_integer(y)); + return(mpq_to_canonicalized_rational(sc, sc->mpq_1)); + case T_BIG_RATIO: + mpq_set_si(sc->mpq_1, 0, 1); + mpq_set_num(sc->mpq_1, big_integer(x)); + mpq_div(sc->mpq_1, sc->mpq_1, big_ratio(y)); + return(mpq_to_rational(sc, sc->mpq_1)); + case T_BIG_REAL: + if (mpfr_zero_p(big_real(y))) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + mpfr_set_z(sc->mpfr_1, big_integer(x), MPFR_RNDN); + mpfr_div(sc->mpfr_1, sc->mpfr_1, big_real(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + if ((!mpfr_number_p(mpc_realref(big_complex(y)))) || (!mpfr_number_p(mpc_imagref(big_complex(y))))) + return(complex_NaN); + mpc_set_z(sc->mpc_1, big_integer(x), MPC_RNDNN); + mpc_div(sc->mpc_1, sc->mpc_1, big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->divide_symbol, x, y, a_number_string, 2)); + } + case T_BIG_RATIO: + switch (type(y)) + { + case T_INTEGER: + if (integer(y) == 0) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + mpq_set_si(sc->mpq_1, integer(y), 1); + mpq_div(sc->mpq_1, big_ratio(x), sc->mpq_1); + return(mpq_to_rational(sc, sc->mpq_1)); + case T_RATIO: + mpq_set_si(sc->mpq_1, numerator(y), denominator(y)); + mpq_div(sc->mpq_1, big_ratio(x), sc->mpq_1); + return(mpq_to_rational(sc, sc->mpq_1)); + case T_REAL: + if (is_NaN(real(y))) return(y); + if (real(y) == 0.0) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + mpfr_set_q(sc->mpfr_1, big_ratio(x), MPFR_RNDN); + mpfr_div_d(sc->mpfr_1, sc->mpfr_1, real(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_COMPLEX: + if ((is_NaN(real_part(y))) || (is_NaN(imag_part(y))) || + (is_inf(real_part(y))) || (is_inf(imag_part(y)))) + return(complex_NaN); + mpc_set_q(sc->mpc_1, big_ratio(x), MPC_RNDNN); + mpc_set_d_d(sc->mpc_2, real_part(y), imag_part(y), MPC_RNDNN); + mpc_div(sc->mpc_1, sc->mpc_1, sc->mpc_2, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_INTEGER: + if (mpz_cmp_ui(big_integer(y), 0) == 0) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + mpq_set_z(sc->mpq_1, big_integer(y)); + mpq_div(sc->mpq_1, big_ratio(x), sc->mpq_1); + return(mpq_to_canonicalized_rational(sc, sc->mpq_1)); + case T_BIG_RATIO: + mpq_div(sc->mpq_1, big_ratio(x), big_ratio(y)); + return(mpq_to_canonicalized_rational(sc, sc->mpq_1)); + case T_BIG_REAL: + if (mpfr_zero_p(big_real(y))) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + mpfr_set_q(sc->mpfr_1, big_ratio(x), MPFR_RNDN); + mpfr_div(sc->mpfr_1, sc->mpfr_1, big_real(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + if ((!mpfr_number_p(mpc_realref(big_complex(y)))) || (!mpfr_number_p(mpc_imagref(big_complex(y))))) + return(complex_NaN); + mpc_set_q(sc->mpc_1, big_ratio(x), MPC_RNDNN); + mpc_div(sc->mpc_1, sc->mpc_1, big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->divide_symbol, x, y, a_number_string, 2)); + } + case T_BIG_REAL: + switch (type(y)) + { + case T_INTEGER: + if (integer(y) == 0) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + mpfr_div_si(sc->mpfr_1, big_real(x), integer(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_RATIO: + mpq_set_si(sc->mpq_1, numerator(y), denominator(y)); + mpfr_div_q(sc->mpfr_1, big_real(x), sc->mpq_1, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_REAL: + if (is_NaN(real(y))) return(y); + if (real(y) == 0.0) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + mpfr_div_d(sc->mpfr_1, big_real(x), real(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_COMPLEX: + if ((is_NaN(real_part(y))) || (is_NaN(imag_part(y))) || + (is_inf(real_part(y))) || (is_inf(imag_part(y)))) + return(complex_NaN); + mpc_set_d_d(sc->mpc_1, real_part(y), imag_part(y), MPC_RNDNN); + mpc_fr_div(sc->mpc_1, big_real(x), sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_INTEGER: + if (mpz_cmp_ui(big_integer(y), 0) == 0) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + mpfr_div_z(sc->mpfr_1, big_real(x), big_integer(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_RATIO: + mpfr_div_q(sc->mpfr_1, big_real(x), big_ratio(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_REAL: + if (mpfr_zero_p(big_real(y))) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + mpfr_div(sc->mpfr_1, big_real(x), big_real(y), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + if ((!mpfr_number_p(mpc_realref(big_complex(y)))) || (!mpfr_number_p(mpc_imagref(big_complex(y))))) + return(complex_NaN); + mpc_fr_div(sc->mpc_1, big_real(x), big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->divide_symbol, x, y, a_number_string, 2)); + } + case T_BIG_COMPLEX: + switch (type(y)) + { + case T_INTEGER: + if (integer(y) == 0) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + mpc_set_si(sc->mpc_1, integer(y), MPC_RNDNN); + mpc_div(sc->mpc_1, big_complex(x), sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_RATIO: + mpq_set_si(sc->mpq_1, numerator(y), denominator(y)); + mpc_set_q(sc->mpc_1, sc->mpq_1, MPC_RNDNN); + mpc_div(sc->mpc_1, big_complex(x), sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_REAL: + /* if (is_NaN(real(y))) return(y); */ + if (real(y) == 0.0) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + mpc_set_d_d(sc->mpc_1, real(y), 0.0, MPC_RNDNN); + mpc_div(sc->mpc_1, big_complex(x), sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_COMPLEX: + if ((is_NaN(real_part(y))) || (is_NaN(imag_part(y))) || + (is_inf(real_part(y))) || (is_inf(imag_part(y)))) + return(complex_NaN); + mpc_set_d_d(sc->mpc_1, real_part(y), imag_part(y), MPC_RNDNN); + mpc_div(sc->mpc_1, big_complex(x), sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_INTEGER: + if (mpz_cmp_ui(big_integer(y), 0) == 0) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + mpc_set_z(sc->mpc_1, big_integer(y), MPC_RNDNN); + mpc_div(sc->mpc_1, big_complex(x), sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_RATIO: + mpc_set_q(sc->mpc_1, big_ratio(y), MPC_RNDNN); + mpc_div(sc->mpc_1, big_complex(x), sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_REAL: + if (mpfr_zero_p(big_real(y))) + division_by_zero_error_2_nr(sc, sc->divide_symbol, x, y); + mpc_div_fr(sc->mpc_1, big_complex(x), big_real(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + case T_BIG_COMPLEX: + if ((!mpfr_number_p(mpc_realref(big_complex(y)))) || (!mpfr_number_p(mpc_imagref(big_complex(y))))) + return(complex_NaN); + mpc_div(sc->mpc_1, big_complex(x), big_complex(y), MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); + default: + return(method_or_bust_with_type_and_loc_pp(sc, y, sc->divide_symbol, x, y, a_number_string, 2)); + } +#endif + + default: /* x is not a built-in number */ + return(method_or_bust_pp(sc, x, sc->divide_symbol, x, y, a_number_string, 1)); /* not args here! y = apply * to cdr(args) */ + } + return(NULL); /* make the compiler happy */ +} + +static s7_pointer g_divide(s7_scheme *sc, s7_pointer args) +{ + #define H_divide "(/ x1 ...) divides its first argument by the rest, or inverts the first if there is only one argument" + #define Q_divide sc->pcl_n + + s7_pointer x = car(args), p = cdr(args); + if (is_null(p)) /* (/ x) */ + { + if (!is_number(x)) + return(method_or_bust_p(sc, x, sc->divide_symbol, a_number_string)); + return(invert_p_p(sc, x)); + } + for (sc->error_argnum = 0; is_pair(p); p = cdr(p), sc->error_argnum++) + x = divide_p_pp(sc, x, car(p)); + sc->error_argnum = 0; + return(x); +} + +static s7_pointer g_invert_1(s7_scheme *sc, s7_pointer args) {return(invert_p_p(sc, car(args)));} +static s7_pointer g_divide_2(s7_scheme *sc, s7_pointer args) {return(divide_p_pp(sc, car(args), cadr(args)));} + +static s7_pointer g_divide_by_2(s7_scheme *sc, s7_pointer args) +{ + /* (/ x 2) */ + s7_pointer num = car(args); + if (is_t_integer(num)) + { + s7_int i = integer(num); + if (i & 1) + { + s7_pointer x; + new_cell(sc, x, T_RATIO); + numerator(x) = i; + denominator(x) = 2; + return(x); + } + return(make_integer(sc, i >> 1)); + } + switch (type(num)) + { + case T_RATIO: +#if HAVE_OVERFLOW_CHECKS + { + s7_int dn; + if (multiply_overflow(denominator(num), 2, &dn)) + { + if ((numerator(num) & 1) == 1) +#if WITH_GMP + { + mpq_set_si(sc->mpq_1, numerator(num), denominator(num)); + mpq_set_si(sc->mpq_2, 1, 2); + mpq_mul(sc->mpq_1, sc->mpq_1, sc->mpq_2); + return(mpq_to_rational(sc, sc->mpq_1)); + } +#else + { + if (WITH_WARNINGS) s7_warn(sc, 128, "ratio / 2 overflow: (/ %" ld64 "/%" ld64 " 2)\n", numerator(num), denominator(num)); + return(make_real(sc, ((long_double)numerator(num) * 0.5) / (long_double)denominator(num))); + } +#endif + return(make_ratio(sc, numerator(num) / 2, denominator(num))); + } + return(make_ratio_with_div_check(sc, sc->divide_symbol, numerator(num), dn)); + } +#else + return(make_ratio(sc, numerator(num), denominator(num) * 2)); +#endif + + case T_REAL: return(make_real(sc, real(num) * 0.5)); + case T_COMPLEX: return(make_complex_not_0i(sc, real_part(num) * 0.5, imag_part(num) * 0.5)); + +#if WITH_GMP + case T_BIG_INTEGER: + mpq_set_z(sc->mpq_1, big_integer(num)); + mpz_mul_ui(mpq_denref(sc->mpq_1), mpq_denref(sc->mpq_1), 2); + return(mpq_to_canonicalized_rational(sc, sc->mpq_1)); + case T_BIG_RATIO: + mpq_set_si(sc->mpq_1, 2, 1); + mpq_div(sc->mpq_1, big_ratio(num), sc->mpq_1); + return(mpq_to_canonicalized_rational(sc, sc->mpq_1)); + case T_BIG_REAL: + mpfr_div_si(sc->mpfr_1, big_real(num), 2, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_BIG_COMPLEX: + mpc_set_si(sc->mpc_1, 2, MPC_RNDNN); + mpc_div(sc->mpc_1, big_complex(num), sc->mpc_1, MPC_RNDNN); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust_pp(sc, num, sc->divide_symbol, num, int_two, a_number_string, 1)); + } +} + +static s7_pointer g_invert_x(s7_scheme *sc, s7_pointer args) +{ + /* (/ 1.0 x) */ + s7_pointer x = cadr(args); + if (is_t_real(x)) + { + s7_double rl = real(x); + if (rl == 0.0) + division_by_zero_error_2_nr(sc, sc->divide_symbol, car(args), x); + return((is_NaN(rl)) ? x : make_real(sc, 1.0 / rl)); + } + return(g_divide(sc, args)); +} + +static s7_double divide_d_7d(s7_scheme *sc, s7_double x) +{ + if (x == 0.0) division_by_zero_error_1_nr(sc, sc->divide_symbol, real_zero); + return(1.0 / x); +} + +static s7_double divide_d_7dd(s7_scheme *sc, s7_double x1, s7_double x2) +{ + if (x2 == 0.0) division_by_zero_error_1_nr(sc, sc->divide_symbol, real_zero); + return(x1 / x2); +} + +static s7_pointer divide_p_ii(s7_scheme *sc, s7_int x, s7_int y) {return(make_ratio_with_div_check(sc, sc->divide_symbol, x, y));} +static s7_pointer divide_p_i(s7_scheme *sc, s7_int x) {return(make_ratio_with_div_check(sc, sc->divide_symbol, 1, x));} + +static s7_pointer divide_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool ops) +{ + if (args == 1) + return(sc->invert_1); + if ((ops) && (args == 2)) + { + s7_pointer arg1 = cadr(expr); + if ((is_t_real(arg1)) && (real(arg1) == 1.0)) + return(sc->invert_x); + return(((is_t_integer(caddr(expr))) && (integer(caddr(expr)) == 2)) ? sc->divide_by_2 : sc->divide_2); + } + return(f); +} + + +/* -------------------------------- quotient -------------------------------- */ +static inline s7_int quotient_i_7ii(s7_scheme *sc, s7_int x, s7_int y) +{ + if ((y > 0) || (y < -1)) return(x / y); + if (y == 0) + division_by_zero_error_2_nr(sc, sc->quotient_symbol, wrap_integer(sc, x), int_zero); + if (x == S7_INT64_MIN) /* (quotient most-negative-fixnum -1) */ + sole_arg_out_of_range_error_nr(sc, sc->quotient_symbol, set_elist_2(sc, leastfix, minus_one), it_is_too_large_string); + return(-x); /* (quotient x -1) */ +} + +#if (!WITH_GMP) +static s7_pointer s7_truncate(s7_scheme *sc, s7_pointer caller, s7_double xf) /* can't use "truncate" -- it's in unistd.h */ +{ + if (fabs(xf) > QUOTIENT_FLOAT_LIMIT) + sole_arg_out_of_range_error_nr(sc, caller, wrap_real(sc, xf), it_is_too_large_string); + return(make_integer(sc, (xf > 0.0) ? (s7_int)floor(xf) : (s7_int)ceil(xf))); +} + +static s7_int c_quo_dbl(s7_scheme *sc, s7_double x, s7_double y) +{ + s7_double xf; + if (y == 0.0) + division_by_zero_error_2_nr(sc, sc->quotient_symbol, wrap_real(sc, x), real_zero); + if ((is_inf(y)) || (is_NaN(y))) /* here we can't return NAN so I guess we should signal an error */ + wrong_type_error_nr(sc, sc->quotient_symbol, 2, wrap_real(sc, y), a_normal_real_string); + xf = x / y; + if (fabs(xf) > QUOTIENT_FLOAT_LIMIT) + sole_arg_out_of_range_error_nr(sc, sc->quotient_symbol, wrap_real(sc, xf), it_is_too_large_string); + return((xf > 0.0) ? (s7_int)floor(xf) : (s7_int)ceil(xf)); +} +#endif + +static s7_int quotient_i_ii_unchecked(s7_int i1, s7_int i2) {return(i1 / i2);} /* i2 > 0 */ + +static s7_pointer quotient_p_pp(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ +#if WITH_GMP + if ((is_real(x)) && (is_real(y))) + { + if (is_zero(y)) + division_by_zero_error_2_nr(sc, sc->quotient_symbol, x, y); + if ((s7_is_integer(x)) && (s7_is_integer(y))) + { + if (is_t_integer(x)) mpz_set_si(sc->mpz_1, integer(x)); else mpz_set(sc->mpz_1, big_integer(x)); + if (is_t_integer(y)) mpz_set_si(sc->mpz_2, integer(y)); else mpz_set(sc->mpz_2, big_integer(y)); + mpz_tdiv_q(sc->mpz_1, sc->mpz_1, sc->mpz_2); + } + else + if ((!is_rational(x)) || (!is_rational(y))) + { + if (any_real_to_mpfr(sc, x, sc->mpfr_1)) return(real_NaN); + if (any_real_to_mpfr(sc, y, sc->mpfr_2)) return(real_NaN); + mpfr_div(sc->mpfr_3, sc->mpfr_1, sc->mpfr_2, MPFR_RNDN); + mpfr_get_z(sc->mpz_1, sc->mpfr_3, MPFR_RNDZ); + } + else + { + any_rational_to_mpq(sc, x, sc->mpq_1); + any_rational_to_mpq(sc, y, sc->mpq_2); + mpq_div(sc->mpq_3, sc->mpq_1, sc->mpq_2); + mpz_tdiv_q(sc->mpz_1, mpq_numref(sc->mpq_3), mpq_denref(sc->mpq_3)); + } + return(mpz_to_integer(sc, sc->mpz_1)); + } + return(method_or_bust_pp(sc, (is_real(x)) ? y : x, sc->quotient_symbol, x, y, sc->type_names[T_REAL], (is_real(x)) ? 2 : 1)); +#else + + s7_int d1, d2, n1, n2; + if ((is_t_integer(x)) && (is_t_integer(y))) + return(make_integer(sc, quotient_i_7ii(sc, integer(x), integer(y)))); + + switch (type(x)) + { + case T_INTEGER: + switch (type(y)) + { + case T_INTEGER: + return(make_integer(sc, quotient_i_7ii(sc, integer(x), integer(y)))); + + case T_RATIO: + n1 = integer(x); + d1 = 1; + n2 = numerator(y); + d2 = denominator(y); + /* (quotient -9223372036854775808 -1/9223372036854775807): arithmetic exception in the no-overflow-checks case */ + goto RATIO_QUO_RATIO; + + case T_REAL: + if (real(y) == 0.0) + division_by_zero_error_2_nr(sc, sc->quotient_symbol, x, y); + if (is_inf(real(y))) return(real_NaN); + if (is_NaN(real(y))) return(y); + return(s7_truncate(sc, sc->quotient_symbol, (s7_double)integer(x) / real(y))); /* s7_truncate returns an integer */ + + default: + return(method_or_bust_pp(sc, y, sc->quotient_symbol, x, y, sc->type_names[T_REAL], 2)); + } + + case T_RATIO: + switch (type(y)) + { + case T_INTEGER: + if (integer(y) == 0) + division_by_zero_error_2_nr(sc, sc->quotient_symbol, x, y); + n1 = numerator(x); + d1 = denominator(x); + n2 = integer(y); + d2 = 1; + goto RATIO_QUO_RATIO; + /* this can lose: + * (quotient 1 2305843009213693952/4611686018427387903) -> 2, not 1 + * (quotient 21053343141/6701487259 3587785776203/1142027682075) -> 1, not 0 + */ + + case T_RATIO: + parcel_out_fractions(x, y); + RATIO_QUO_RATIO: + if (d1 == d2) + return(make_integer(sc, n1 / n2)); /* (quotient 3/9223372036854775807 1/9223372036854775807) */ + if (n1 == n2) + return(make_integer(sc, d2 / d1)); /* (quotient 9223372036854775807/2 9223372036854775807/8) */ +#if HAVE_OVERFLOW_CHECKS + { + s7_int n1d2, n2d1; + if ((multiply_overflow(n1, d2, &n1d2)) || + (multiply_overflow(n2, d1, &n2d1))) + return(s7_truncate(sc, sc->quotient_symbol, ((long_double)n1 / (long_double)n2) * ((long_double)d2 / (long_double)d1))); + return(make_integer(sc, n1d2 / n2d1)); + } +#else + return(make_integer(sc, (n1 * d2) / (n2 * d1))); +#endif + + case T_REAL: + if (real(y) == 0.0) + division_by_zero_error_2_nr(sc, sc->quotient_symbol, x, y); + if (is_inf(real(y))) return(real_NaN); + if (is_NaN(real(y))) return(y); + return(s7_truncate(sc, sc->quotient_symbol, (s7_double)fraction(x) / real(y))); + + default: + return(method_or_bust_pp(sc, y, sc->quotient_symbol, x, y, sc->type_names[T_REAL], 2)); + } + + case T_REAL: + if (((is_inf(real(x))) || (is_NaN(real(x)))) && (is_real(y))) + return(real_NaN); + /* if infs allowed we need to return infs/nans, else: + * (quotient inf.0 1e-309) -> -9223372036854775808 + * (quotient inf.0 inf.0) -> -9223372036854775808 + */ + + switch (type(y)) + { + case T_INTEGER: + if (integer(y) == 0) + division_by_zero_error_2_nr(sc, sc->quotient_symbol, x, y); + return(s7_truncate(sc, sc->quotient_symbol, (long_double)real(x) / (long_double)integer(y))); + + case T_RATIO: return(s7_truncate(sc, sc->quotient_symbol, real(x) / (s7_double)fraction(y))); + case T_REAL: return(make_integer(sc, c_quo_dbl(sc, real(x), real(y)))); /* c_quo_dbl returns an integer */ + default: return(method_or_bust_pp(sc, y, sc->quotient_symbol, x, y, sc->type_names[T_REAL], 2)); + } + + default: + return(method_or_bust_pp(sc, x, sc->quotient_symbol, x, y, sc->type_names[T_REAL], 2)); + } +#endif +} + +static s7_pointer quotient_p_pi(s7_scheme *sc, s7_pointer x, s7_int y) +{ + if ((is_t_integer(x)) && ((y > 0) || (y < -1))) return(make_integer(sc, integer(x) / y)); + return(quotient_p_pp(sc, x, wrap_integer(sc, y))); +} + +static s7_pointer g_quotient(s7_scheme *sc, s7_pointer args) +{ + #define H_quotient "(quotient x1 x2) returns the integer quotient of x1 and x2; (quotient 4 3) = 1" + #define Q_quotient sc->pcl_r + /* sig was '(integer? ...) but quotient can return NaN */ + /* (define (quo x1 x2) (truncate (/ x1 x2))) ; slib */ + return(quotient_p_pp(sc, car(args), cadr(args))); +} + + +/* -------------------------------- remainder -------------------------------- */ +#if WITH_GMP +static s7_pointer big_mod_or_rem(s7_scheme *sc, s7_pointer x, s7_pointer y, bool use_floor) +{ + if ((is_real(x)) && (is_real(y))) + { + if ((s7_is_integer(x)) && (s7_is_integer(y))) + { + if (is_t_integer(x)) mpz_set_si(sc->mpz_1, integer(x)); else mpz_set(sc->mpz_1, big_integer(x)); + if (is_t_integer(y)) mpz_set_si(sc->mpz_2, integer(y)); else mpz_set(sc->mpz_2, big_integer(y)); + if (use_floor) + mpz_fdiv_q(sc->mpz_3, sc->mpz_1, sc->mpz_2); + else mpz_tdiv_q(sc->mpz_3, sc->mpz_1, sc->mpz_2); + mpz_mul(sc->mpz_3, sc->mpz_3, sc->mpz_2); + mpz_sub(sc->mpz_1, sc->mpz_1, sc->mpz_3); + return(mpz_to_integer(sc, sc->mpz_1)); + } + if ((!is_rational(x)) || (!is_rational(y))) + { + any_real_to_mpfr(sc, x, sc->mpfr_1); + any_real_to_mpfr(sc, y, sc->mpfr_2); + mpfr_div(sc->mpfr_3, sc->mpfr_1, sc->mpfr_2, MPFR_RNDN); + if (use_floor) + mpfr_get_z(sc->mpz_1, sc->mpfr_3, MPFR_RNDD); + else mpfr_get_z(sc->mpz_1, sc->mpfr_3, MPFR_RNDZ); + mpfr_mul_z(sc->mpfr_2, sc->mpfr_2, sc->mpz_1, MPFR_RNDN); + mpfr_sub(sc->mpfr_1, sc->mpfr_1, sc->mpfr_2, MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + } + any_rational_to_mpq(sc, x, sc->mpq_1); + any_rational_to_mpq(sc, y, sc->mpq_2); + mpq_div(sc->mpq_3, sc->mpq_1, sc->mpq_2); + if (use_floor) + mpz_fdiv_q(sc->mpz_1, mpq_numref(sc->mpq_3), mpq_denref(sc->mpq_3)); + else mpz_tdiv_q(sc->mpz_1, mpq_numref(sc->mpq_3), mpq_denref(sc->mpq_3)); + mpz_mul(mpq_numref(sc->mpq_2), sc->mpz_1, mpq_numref(sc->mpq_2)); + mpq_sub(sc->mpq_1, sc->mpq_1, sc->mpq_2); + return(mpq_to_canonicalized_rational(sc, sc->mpq_1)); + } + return(method_or_bust_pp(sc, (is_real(x)) ? y : x, (use_floor) ? sc->modulo_symbol : sc->remainder_symbol, x, y, sc->type_names[T_REAL], (is_real(x)) ? 2 : 1)); +} +#endif + +#define REMAINDER_FLOAT_LIMIT 1e13 + +static inline s7_int remainder_i_7ii(s7_scheme *sc, s7_int x, s7_int y) +{ + if ((y > 1) || (y < -1)) return(x % y); /* avoid floating exception if (remainder -9223372036854775808 -1)! */ + if (y == 0) + division_by_zero_error_2_nr(sc, sc->remainder_symbol, wrap_integer(sc, x), int_zero); + return(0); +} + +static s7_double c_rem_dbl(s7_scheme *sc, s7_double x, s7_double y) +{ + s7_int quo; + s7_double pre_quo; + if ((is_inf(y)) || (is_NaN(y))) + return(NAN); + pre_quo = x / y; + if (fabs(pre_quo) > REMAINDER_FLOAT_LIMIT) + sole_arg_out_of_range_error_nr(sc, sc->remainder_symbol, set_elist_2(sc, wrap_real(sc, x), wrap_real(sc, y)), it_is_too_large_string); + quo = (pre_quo > 0.0) ? (s7_int)floor(pre_quo) : (s7_int)ceil(pre_quo); + return(x - (y * quo)); +} + +static s7_int remainder_i_ii_unchecked(s7_int i1, s7_int i2) {return(i1 % i2);} /* i2 > 1 */ +static s7_double remainder_d_7dd(s7_scheme *sc, s7_double x1, s7_double x2) +{ + if (x2 == 0.0) + division_by_zero_error_2_nr(sc, sc->remainder_symbol, wrap_real(sc, x1), real_zero); + if ((is_inf(x1)) || (is_NaN(x1))) /* match remainder_p_pp */ + return(NAN); + return(c_rem_dbl(sc, x1, x2)); +} + +static s7_pointer remainder_p_pp(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ +#if WITH_GMP + if (is_zero(y)) + division_by_zero_error_2_nr(sc, sc->remainder_symbol, x, y); + return(big_mod_or_rem(sc, x, y, false)); +#else + s7_int quo, d1, d2, n1, n2; + s7_double pre_quo; + + if ((is_t_integer(x)) && (is_t_integer(y))) + return(make_integer(sc, remainder_i_7ii(sc, integer(x), integer(y)))); + + switch (type(x)) + { + case T_INTEGER: + switch (type(y)) + { + case T_INTEGER: + return(make_integer(sc, remainder_i_7ii(sc, integer(x), integer(y)))); + + case T_RATIO: + n1 = integer(x); + d1 = 1; + n2 = numerator(y); + d2 = denominator(y); + goto RATIO_REM_RATIO; + + case T_REAL: + if (real(y) == 0.0) + division_by_zero_error_2_nr(sc, sc->remainder_symbol, x, y); + if (is_inf(real(y))) return(real_NaN); + if (is_NaN(real(y))) return(y); + pre_quo = (long_double)integer(x) / (long_double)real(y); + if (fabs(pre_quo) > REMAINDER_FLOAT_LIMIT) + sole_arg_out_of_range_error_nr(sc, sc->remainder_symbol, set_elist_2(sc, x, y), it_is_too_large_string); + quo = (pre_quo > 0.0) ? (s7_int)floor(pre_quo) : (s7_int)ceil(pre_quo); + return(make_real(sc, integer(x) - real(y) * quo)); + + default: + return(method_or_bust_pp(sc, y, sc->remainder_symbol, x, y, sc->type_names[T_REAL], 2)); + } + + case T_RATIO: + switch (type(y)) + { + case T_INTEGER: + n2 = integer(y); + if (n2 == 0) + division_by_zero_error_2_nr(sc, sc->remainder_symbol, x, y); + n1 = numerator(x); + d1 = denominator(x); + d2 = 1; + goto RATIO_REM_RATIO; + + case T_RATIO: + parcel_out_fractions(x, y); + RATIO_REM_RATIO: + if (d1 == d2) + quo = (s7_int)(n1 / n2); + else + { + if (n1 == n2) + quo = (s7_int)(d2 / d1); + else + { +#if HAVE_OVERFLOW_CHECKS + s7_int n1d2, n2d1; + if ((multiply_overflow(n1, d2, &n1d2)) || + (multiply_overflow(n2, d1, &n2d1))) + { + pre_quo = ((long_double)n1 / (long_double)n2) * ((long_double)d2 / (long_double)d1); + if (fabs(pre_quo) > REMAINDER_FLOAT_LIMIT) + sole_arg_out_of_range_error_nr(sc, sc->remainder_symbol, set_elist_2(sc, x, y), it_is_too_large_string); + quo = (pre_quo > 0.0) ? (s7_int)floor(pre_quo) : (s7_int)ceil(pre_quo); + } + else quo = n1d2 / n2d1; +#else + quo = (n1 * d2) / (n2 * d1); +#endif + }} + if (quo == 0) + return(x); + +#if HAVE_OVERFLOW_CHECKS + { + s7_int dn, nq; + if (!multiply_overflow(n2, quo, &nq)) + { + if ((d1 == d2) && + (!subtract_overflow(n1, nq, &dn))) + return(make_ratio_with_div_check(sc, sc->remainder_symbol, dn, d1)); + + if ((!multiply_overflow(n1, d2, &dn)) && + (!multiply_overflow(nq, d1, &nq)) && + (!subtract_overflow(dn, nq, &nq)) && + (!multiply_overflow(d1, d2, &d1))) + return(make_ratio_with_div_check(sc, sc->remainder_symbol, nq, d1)); + }} +#else + if (d1 == d2) + return(make_ratio_with_div_check(sc, sc->remainder_symbol, n1 - n2 * quo, d1)); + + return(make_ratio_with_div_check(sc, sc->remainder_symbol, n1 * d2 - n2 * d1 * quo, d1 * d2)); +#endif + sole_arg_out_of_range_error_nr(sc, sc->remainder_symbol, set_elist_2(sc, x, y), intermediate_too_large_string); + + case T_REAL: + { + s7_double frac; + if (real(y) == 0.0) + division_by_zero_error_2_nr(sc, sc->remainder_symbol, x, y); + if (is_inf(real(y))) return(real_NaN); + if (is_NaN(real(y))) return(y); + if (s7_int_abs(numerator(x)) > QUOTIENT_INT_LIMIT) + return(subtract_p_pp(sc, x, multiply_p_pp(sc, y, quotient_p_pp(sc, x, y)))); + frac = (s7_double)fraction(x); + pre_quo = frac / real(y); + if (fabs(pre_quo) > REMAINDER_FLOAT_LIMIT) + sole_arg_out_of_range_error_nr(sc, sc->remainder_symbol, set_elist_2(sc, x, y), it_is_too_large_string); + quo = (pre_quo > 0.0) ? (s7_int)floor(pre_quo) : (s7_int)ceil(pre_quo); + return(make_real(sc, frac - real(y) * quo)); + } + + default: + return(method_or_bust_pp(sc, y, sc->remainder_symbol, x, y, sc->type_names[T_REAL], 2)); + } + + case T_REAL: + if (((is_inf(real(x))) || (is_NaN(real(x)))) && (is_real(y))) + { + if (is_zero(y)) + division_by_zero_error_2_nr(sc, sc->remainder_symbol, x, y); + return(real_NaN); + } + switch (type(y)) + { + case T_INTEGER: + if (integer(y) == 0) + division_by_zero_error_2_nr(sc, sc->remainder_symbol, x, y); + /* actually here (and elsewhere) if y > INT64_TO_DOUBLE_LIMIT, the result is probably wrong */ + pre_quo = (long_double)real(x) / (long_double)integer(y); + if (fabs(pre_quo) > REMAINDER_FLOAT_LIMIT) + sole_arg_out_of_range_error_nr(sc, sc->remainder_symbol, set_elist_2(sc, x, y), it_is_too_large_string); + quo = (pre_quo > 0.0) ? (s7_int)floor(pre_quo) : (s7_int)ceil(pre_quo); + return(make_real(sc, real(x) - integer(y) * quo)); + /* but... (remainder 1e+18 9223372036854775807) -> 1e+18 */ + + case T_RATIO: + if (s7_int_abs(numerator(y)) > QUOTIENT_INT_LIMIT) + return(subtract_p_pp(sc, x, multiply_p_pp(sc, y, quotient_p_pp(sc, x, y)))); + { + s7_double frac = (s7_double)fraction(y); + pre_quo = real(x) / frac; + if (fabs(pre_quo) > REMAINDER_FLOAT_LIMIT) + sole_arg_out_of_range_error_nr(sc, sc->remainder_symbol, set_elist_2(sc, x, y), it_is_too_large_string); + quo = (pre_quo > 0.0) ? (s7_int)floor(pre_quo) : (s7_int)ceil(pre_quo); + return(make_real(sc, real(x) - frac * quo)); + } + + case T_REAL: + if (real(y) == 0.0) + division_by_zero_error_2_nr(sc, sc->remainder_symbol, x, y); + return(make_real(sc, c_rem_dbl(sc, real(x), real(y)))); + /* see under sin -- this calculation is completely bogus if "a" is large + * (quotient 1e22 (* 2 pi)) -> -9223372036854775808 but it should be 1591549430918953357688, + * (remainder 1e22 (* 2 pi)) -> 1.0057952155665e+22 -- the "remainder" is greater than the original argument! + * Clisp gives 0.0 here, as does sbcl, currently s7 throws an error (out-of-range). + */ + + default: + return(method_or_bust_pp(sc, y, sc->remainder_symbol, x, y, sc->type_names[T_REAL], 2)); + } + + default: + return(method_or_bust_pp(sc, x, sc->remainder_symbol, x, y, sc->type_names[T_REAL], 1)); + } +#endif +} + +static s7_pointer remainder_p_pi(s7_scheme *sc, s7_pointer x, s7_int y) +{ + if ((is_t_integer(x)) && ((y > 1) || (y < -1))) return(make_integer(sc, integer(x) % y)); + return(remainder_p_pp(sc, x, wrap_integer(sc, y))); +} + +static s7_pointer g_remainder(s7_scheme *sc, s7_pointer args) +{ + #define H_remainder "(remainder x1 x2) returns the remainder of x1/x2; (remainder 10 3) = 1" + #define Q_remainder sc->pcl_r + /* (define (rem x1 x2) (- x1 (* x2 (quo x1 x2)))) ; slib, if x2 is an integer (- x1 (truncate x1 x2)), fractional part: (remainder x 1) */ + + s7_pointer x = car(args), y = cadr(args); + if ((is_t_integer(x)) && (is_t_integer(y))) + return(make_integer(sc, remainder_i_7ii(sc, integer(x), integer(y)))); + return(remainder_p_pp(sc, x, y)); +} + + +/* -------------------------------- modulo -------------------------------- */ +static s7_int modulo_i_ii(s7_int x, s7_int y) +{ + s7_int z; + if (y > 1) + { + z = x % y; + return((z >= 0) ? z : z + y); + } + if (y < -1) + { + z = x % y; + return((z > 0) ? z + y : z); + } + if (y == 0) return(x); /* else arithmetic exception */ + return(0); +} + +static s7_int modulo_i_ii_unchecked(s7_int i1, s7_int i2) /* here we know i2 > 1 */ +{ + s7_int z = i1 % i2; + return((z < 0) ? (z + i2) : z); +} + +static s7_double modulo_d_7dd(s7_scheme *sc, s7_double x1, s7_double x2) +{ + s7_double c; + if ((is_NaN(x1)) || (is_NaN(x2)) || (is_inf(x1)) || (is_inf(x2))) return(NAN); + if (x2 == 0.0) return(x1); + if (fabs(x1) > 1e17) + out_of_range_error_nr(sc, sc->modulo_symbol, int_one, wrap_real(sc, x1), it_is_too_large_string); + c = x1 / x2; + if ((c > 1e19) || (c < -1e19)) + sole_arg_out_of_range_error_nr(sc, sc->modulo_symbol, + set_elist_3(sc, sc->divide_symbol, wrap_real(sc, x1), wrap_real(sc, x2)), + intermediate_too_large_string); + return(x1 - x2 * (s7_int)floor(c)); +} + +static s7_pointer modulo_p_pp(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ +#if WITH_GMP + /* as tricky as expt, so just use bignums; mpz_mod|_ui = mpz_fdiv_r_ui, but sign ignored -- probably not worth the code + * originally subtract_p_pp(sc, x, multiply_p_pp(sc, y, floor_p_p(sc, divide_p_pp(sc, x, y)))) + * quotient is truncate_p_p(sc, divide_p_pp(sc, x, y)) + * remainder is subtract_p_pp(sc, x, multiply_p_pp(sc, y, quotient_p_pp(sc, x, y))) + */ + if (!is_zero(y)) return(big_mod_or_rem(sc, x, y, true)); + if (is_real(x)) return(x); + return(method_or_bust_pp(sc, x, sc->modulo_symbol, x, y, sc->type_names[T_REAL], 1)); +#else + s7_double a, b; + s7_int n1, n2, d1, d2; + + switch (type(x)) + { + case T_INTEGER: + switch (type(y)) + { + case T_INTEGER: + return(make_integer(sc, modulo_i_ii(integer(x), integer(y)))); + + case T_RATIO: + n1 = integer(x); + d1 = 1; + n2 = numerator(y); + d2 = denominator(y); + if ((n1 == n2) && (d1 > d2)) return(x); /* signs match so this should be ok */ + goto RATIO_MOD_RATIO; + + case T_REAL: + if ((integer(x) == S7_INT64_MIN) || (s7_int_abs(integer(x)) > QUOTIENT_INT_LIMIT)) + out_of_range_error_nr(sc, sc->modulo_symbol, int_one, x, it_is_too_large_string); + b = real(y); + if (b == 0.0) return(x); + if (is_NaN(b)) return(y); + if (is_inf(b)) return(real_NaN); + a = (s7_double)integer(x); + goto REAL_MOD; + + default: + return(method_or_bust_pp(sc, y, sc->modulo_symbol, x, y, sc->type_names[T_REAL], 2)); + } + + case T_RATIO: + switch (type(y)) + { + case T_INTEGER: + if (integer(y) == 0) return(x); + n1 = numerator(x); + d1 = denominator(x); + n2 = integer(y); + + if ((n2 > 0) && (n1 > 0) && (n2 > n1)) return(x); + if ((n2 < 0) && (n1 < 0) && (n2 < n1)) return(x); + if (n2 == S7_INT64_MIN) + sole_arg_out_of_range_error_nr(sc, sc->modulo_symbol, + set_elist_3(sc, sc->divide_symbol, x, y), + intermediate_too_large_string); + /* the problem here is that (modulo 3/2 most-negative-fixnum) + * will segfault with signal SIGFPE, Arithmetic exception, so try to trap it. + */ + if ((n1 == n2) && (d1 > 1)) return(x); + d2 = 1; + goto RATIO_MOD_RATIO; + + case T_RATIO: + parcel_out_fractions(x, y); + if (d1 == d2) + return(make_ratio_with_div_check(sc, sc->modulo_symbol, modulo_i_ii(n1, n2), d1)); + if ((n1 == n2) && (d1 > d2)) return(x); + + RATIO_MOD_RATIO: +#if HAVE_OVERFLOW_CHECKS + { + s7_int n2d1, n1d2, d1d2, fl; + if (!multiply_overflow(n2, d1, &n2d1)) + { + if ((n2d1 == 1) || (n2d1 == -1)) /* (modulo 100 -1/2) */ + return(int_zero); + + if (!multiply_overflow(n1, d2, &n1d2)) + { + fl = (s7_int)(n1d2 / n2d1); + if (((n1 < 0) && (n2 > 0)) || + ((n1 > 0) && (n2 < 0))) + fl -= 1; + if (fl == 0) + return(x); + + if ((!multiply_overflow(d1, d2, &d1d2)) && + (!multiply_overflow(fl, n2d1, &fl)) && + (!subtract_overflow(n1d2, fl, &fl))) + return(make_ratio_with_div_check(sc, sc->modulo_symbol, fl, d1d2)); + }}} +#else + { + s7_int fl; + s7_int n1d2 = n1 * d2; + s7_int n2d1 = n2 * d1; + + if (n2d1 == 1) + return(int_zero); + + /* can't use "floor" here (float->int ruins everything) */ + fl = (s7_int)(n1d2 / n2d1); + if (((n1 < 0) && (n2 > 0)) || + ((n1 > 0) && (n2 < 0))) + fl -= 1; + + if (fl == 0) + return(x); + + return(make_ratio_with_div_check(sc, sc->modulo_symbol, n1d2 - (n2d1 * fl), d1 * d2)); + } +#endif + sole_arg_out_of_range_error_nr(sc, sc->modulo_symbol, + set_elist_3(sc, sc->divide_symbol, x, y), + intermediate_too_large_string); + case T_REAL: + b = real(y); + if (is_inf(b)) return(real_NaN); + if (fabs(b) > 1e17) + out_of_range_error_nr(sc, sc->modulo_symbol, int_two, y, it_is_too_large_string); + if (b == 0.0) return(x); + if (is_NaN(b)) return(y); + a = fraction(x); + return(make_real(sc, a - b * (s7_int)floor(a / b))); + + default: + return(method_or_bust_pp(sc, y, sc->modulo_symbol, x, y, sc->type_names[T_REAL], 2)); + } + + case T_REAL: + { + s7_double c; + a = real(x); + if (!is_real(y)) + return(method_or_bust_pp(sc, y, sc->modulo_symbol, x, y, sc->type_names[T_REAL], 2)); + if (is_NaN(a)) return(x); + if (is_inf(a)) return(real_NaN); /* not b */ + if (fabs(a) > 1e17) + out_of_range_error_nr(sc, sc->modulo_symbol, int_one, x, it_is_too_large_string); + + switch (type(y)) + { + case T_INTEGER: + if (integer(y) == 0) return(x); + if ((integer(y) == S7_INT64_MIN) || (s7_int_abs(integer(y)) > QUOTIENT_INT_LIMIT)) + out_of_range_error_nr(sc, sc->modulo_symbol, int_two, y, it_is_too_large_string); + b = (s7_double)integer(y); + goto REAL_MOD; + + case T_RATIO: + b = fraction(y); + goto REAL_MOD; + + case T_REAL: + b = real(y); + if (b == 0.0) return(x); + if (is_NaN(b)) return(y); + if (is_inf(b)) return(real_NaN); + REAL_MOD: + c = a / b; + if (fabs(c) > 1e19) + sole_arg_out_of_range_error_nr(sc, sc->modulo_symbol, + set_elist_3(sc, sc->divide_symbol, x, y), + intermediate_too_large_string); + return(make_real(sc, a - b * (s7_int)floor(c))); + + default: + return(method_or_bust_pp(sc, y, sc->modulo_symbol, x, y, sc->type_names[T_REAL], 2)); + }} + + default: + return(method_or_bust_pp(sc, x, sc->modulo_symbol, x, y, sc->type_names[T_REAL], 1)); + } +#endif +} + +static s7_pointer modulo_p_pi(s7_scheme *sc, s7_pointer x, s7_int y) +{ + if (is_t_integer(x)) return(make_integer(sc, modulo_i_ii(integer(x), y))); + return(modulo_p_pp(sc, x, wrap_integer(sc, y))); +} + +static s7_pointer g_modulo(s7_scheme *sc, s7_pointer args) +{ + #define H_modulo "(modulo x1 x2) returns x1 mod x2; (modulo 4 3) = 1. The arguments can be real numbers." + #define Q_modulo sc->pcl_r + /* (define (mod x1 x2) (- x1 (* x2 (floor (/ x1 x2))))) from slib + * (mod x 0) = x according to "Concrete Mathematics" + */ + return(modulo_p_pp(sc, car(args), cadr(args))); +} + + +/* ---------------------------------------- max ---------------------------------------- */ +static bool is_real_via_method_1(s7_scheme *sc, s7_pointer p) +{ + s7_pointer f = find_method_with_let(sc, p, sc->is_real_symbol); + if (f != sc->undefined) + return(is_true(sc, s7_apply_function(sc, f, set_plist_1(sc, p)))); + return(false); +} + +#define is_real_via_method(sc, p) ((is_real(p)) || ((has_active_methods(sc, p)) && (is_real_via_method_1(sc, p)))) + +#define max_out_x(Sc, X, Y) method_or_bust_pp(Sc, X, Sc->max_symbol, X, Y, sc->type_names[T_REAL], 1) +#define max_out_y(Sc, X, Y) method_or_bust_pp(Sc, Y, Sc->max_symbol, X, Y, sc->type_names[T_REAL], 2) + +static s7_pointer max_p_pp(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + /* same basic code as lt_b_7_pp (or any relop) but max returns NaN if NaN encountered, and methods for < and max return + * different results, so it seems simpler to repeat the other code. + */ + if (type(x) == type(y)) + { + if (is_t_integer(x)) + return((integer(x) < integer(y)) ? y : x); + if (is_t_real(x)) + return(((is_NaN(real(x))) || (real(x) >= real(y))) ? x : y); + if (is_t_ratio(x)) + return((fraction(x) < fraction(y)) ? y : x); +#if WITH_GMP + if (is_t_big_integer(x)) + return((mpz_cmp(big_integer(x), big_integer(y)) < 0) ? y : x); + if (is_t_big_ratio(x)) + return((mpq_cmp(big_ratio(x), big_ratio(y)) < 0) ? y : x); + if (is_t_big_real(x)) + return(((mpfr_nan_p(big_real(x)) != 0) || (mpfr_greaterequal_p(big_real(x), big_real(y)))) ? x : y); /* ?? */ +#endif + } + switch (type(x)) + { + case T_INTEGER: + switch (type(y)) + { + case T_RATIO: + return((integer(x) < fraction(y)) ? y : x); + case T_REAL: + if (is_NaN(real(y))) return(y); + return((integer(x) < real(y)) ? y : x); +#if WITH_GMP + case T_BIG_INTEGER: + return((mpz_cmp_si(big_integer(y), integer(x)) < 0) ? x : y); + case T_BIG_RATIO: + return((mpq_cmp_si(big_ratio(y), integer(x), 1) < 0) ? x : y); + case T_BIG_REAL: + if (mpfr_nan_p(big_real(y))) return(y); + return((mpfr_cmp_si(big_real(y), integer(x)) < 0) ? x : y); +#endif + default: + return(max_out_y(sc, x, y)); + } + break; + + case T_RATIO: + switch (type(y)) + { + case T_INTEGER: + return((fraction(x) < integer(y)) ? y : x); + case T_REAL: + if (is_NaN(real(y))) return(y); + return((fraction(x) < real(y)) ? y : x); +#if WITH_GMP + case T_BIG_INTEGER: + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + return((mpq_cmp_z(sc->mpq_1, big_integer(y)) < 0) ? y : x); + case T_BIG_RATIO: + return((mpq_cmp_si(big_ratio(y), numerator(x), denominator(x)) < 0) ? x : y); + case T_BIG_REAL: + if (mpfr_nan_p(big_real(y))) return(y); + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + return((mpfr_cmp_q(big_real(y), sc->mpq_1) < 0) ? x : y); +#endif + default: + return(max_out_y(sc, x, y)); + } + + case T_REAL: + switch (type(y)) + { + case T_INTEGER: + if (is_NaN(real(x))) return(x); + return((real(x) < integer(y)) ? y : x); + case T_RATIO: + return((real(x) < fraction(y)) ? y : x); +#if WITH_GMP + case T_BIG_INTEGER: + if (is_NaN(real(x))) return(x); + mpfr_set_d(sc->mpfr_1, real(x), MPFR_RNDN); + return((mpfr_cmp_z(sc->mpfr_1, big_integer(y)) < 0) ? y : x); + case T_BIG_RATIO: + if (is_NaN(real(x))) return(x); + mpfr_set_d(sc->mpfr_1, real(x), MPFR_RNDN); + return((mpfr_cmp_q(sc->mpfr_1, big_ratio(y)) < 0) ? y : x); + case T_BIG_REAL: + if (is_NaN(real(x))) return(x); + if (mpfr_nan_p(big_real(y))) return(y); + return((mpfr_cmp_d(big_real(y), real(x)) < 0) ? x : y); +#endif + default: + return(max_out_y(sc, x, y)); + } + break; + +#if WITH_GMP + case T_BIG_INTEGER: + switch (type(y)) + { + case T_INTEGER: + return((mpz_cmp_si(big_integer(x), integer(y)) < 0) ? y : x); + case T_RATIO: + mpq_set_si(sc->mpq_1, numerator(y), denominator(y)); + return((mpq_cmp_z(sc->mpq_1, big_integer(x)) < 0) ? x : y); + case T_REAL: + if (is_NaN(real(y))) return(y); + mpfr_set_z(sc->mpfr_1, big_integer(x), MPFR_RNDN); + return((mpfr_cmp_d(sc->mpfr_1, real(y)) < 0) ? y : x); + case T_BIG_RATIO: + return((mpq_cmp_z(big_ratio(y), big_integer(x)) < 0) ? x : y); + case T_BIG_REAL: + if (mpfr_nan_p(big_real(y))) return(y); + return((mpfr_cmp_z(big_real(y), big_integer(x)) < 0) ? x : y); + default: + return(max_out_y(sc, x, y)); + } + case T_BIG_RATIO: + switch (type(y)) + { + case T_INTEGER: + return((mpq_cmp_si(big_ratio(x), integer(y), 1) < 0) ? y : x); + case T_RATIO: + return((mpq_cmp_si(big_ratio(x), numerator(y), denominator(y)) < 0) ? y : x); + case T_REAL: + if (is_NaN(real(y))) return(y); + mpfr_set_q(sc->mpfr_1, big_ratio(x), MPFR_RNDN); + return((mpfr_cmp_d(sc->mpfr_1, real(y)) < 0) ? y : x); + case T_BIG_INTEGER: + return((mpq_cmp_z(big_ratio(x), big_integer(y)) < 0) ? y : x); + case T_BIG_REAL: + if (mpfr_nan_p(big_real(y))) return(y); + return((mpfr_cmp_q(big_real(y), big_ratio(x)) < 0) ? x : y); + default: + return(max_out_y(sc, x, y)); + } + + case T_BIG_REAL: + switch (type(y)) + { + case T_INTEGER: + if (mpfr_nan_p(big_real(x))) return(x); + return((mpfr_cmp_si(big_real(x), integer(y)) < 0) ? y : x); + case T_RATIO: + if (mpfr_nan_p(big_real(x))) return(x); + mpq_set_si(sc->mpq_1, numerator(y), denominator(y)); + return((mpfr_cmp_q(big_real(x), sc->mpq_1) < 0) ? y : x); + case T_REAL: + if (mpfr_nan_p(big_real(x))) return(x); + if (is_NaN(real(y))) return(y); + return((mpfr_cmp_d(big_real(x), real(y)) < 0) ? y : x); + case T_BIG_INTEGER: + if (mpfr_nan_p(big_real(x))) return(x); + return((mpfr_cmp_z(big_real(x), big_integer(y)) < 0) ? y : x); + case T_BIG_RATIO: + if (mpfr_nan_p(big_real(x))) return(x); + return((mpfr_cmp_q(big_real(x), big_ratio(y)) < 0) ? y : x); + default: + return(max_out_y(sc, x, y)); + } +#endif + default: + return(max_out_x(sc, x, y)); + } + return(x); +} + +static s7_pointer g_max(s7_scheme *sc, s7_pointer args) +{ + #define H_max "(max ...) returns the maximum of its arguments" + #define Q_max sc->pcl_r + + s7_pointer x = car(args); + if (is_null(cdr(args))) + { + if (is_real(x)) return(x); + return(method_or_bust_p(sc, x, sc->max_symbol, sc->type_names[T_REAL])); + } + for (s7_pointer p = cdr(args); is_pair(p); p = cdr(p)) + x = max_p_pp(sc, x, car(p)); + return(x); +} + +static s7_pointer g_max_2(s7_scheme *sc, s7_pointer args) {return(max_p_pp(sc, car(args), cadr(args)));} +static s7_pointer g_max_3(s7_scheme *sc, s7_pointer args) {return(max_p_pp(sc, max_p_pp(sc, car(args), cadr(args)), caddr(args)));} + +static s7_pointer max_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer unused_expr, bool unused_ops) +{ + return((args == 2) ? sc->max_2 : ((args == 3) ? sc->max_3 : f)); +} + +static s7_int max_i_ii(s7_int i1, s7_int i2) {return((i1 > i2) ? i1 : i2);} +static s7_int max_i_iii(s7_int i1, s7_int i2, s7_int i3) {return((i1 > i2) ? ((i1 > i3) ? i1 : i3) : ((i2 > i3) ? i2 : i3));} +static s7_double max_d_dd(s7_double x1, s7_double x2) {if (is_NaN(x1)) return(x1); return((x1 > x2) ? x1 : x2);} +static s7_double max_d_ddd(s7_double x1, s7_double x2, s7_double x3) {return(max_d_dd(x1, max_d_dd(x2, x3)));} +static s7_double max_d_dddd(s7_double x1, s7_double x2, s7_double x3, s7_double x4) {return(max_d_dd(x1, max_d_ddd(x2, x3, x4)));} + + +/* ---------------------------------------- min ---------------------------------------- */ +#define min_out_x(Sc, X, Y) method_or_bust_pp(Sc, X, Sc->min_symbol, X, Y, sc->type_names[T_REAL], 1) +#define min_out_y(Sc, X, Y) method_or_bust_pp(Sc, Y, Sc->min_symbol, X, Y, sc->type_names[T_REAL], 2) + +static s7_pointer min_p_pp(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + if (type(x) == type(y)) + { + if (is_t_integer(x)) + return((integer(x) > integer(y)) ? y : x); + if (is_t_real(x)) + return(((is_NaN(real(x))) || (real(x) <= real(y))) ? x : y); + if (is_t_ratio(x)) + return((fraction(x) > fraction(y)) ? y : x); +#if WITH_GMP + if (is_t_big_integer(x)) + return((mpz_cmp(big_integer(x), big_integer(y)) > 0) ? y : x); + if (is_t_big_ratio(x)) + return((mpq_cmp(big_ratio(x), big_ratio(y)) > 0) ? y : x); + if (is_t_big_real(x)) + return(((mpfr_nan_p(big_real(x)) != 0) || (mpfr_lessequal_p(big_real(x), big_real(y)))) ? x : y); /* ?? */ +#endif + } + switch (type(x)) + { + case T_INTEGER: + switch (type(y)) + { + case T_RATIO: return((integer(x) > fraction(y)) ? y : x); + case T_REAL: + if (is_NaN(real(y))) return(y); + return((integer(x) > real(y)) ? y : x); +#if WITH_GMP + case T_BIG_INTEGER: return((mpz_cmp_si(big_integer(y), integer(x)) > 0) ? x : y); + case T_BIG_RATIO: return((mpq_cmp_si(big_ratio(y), integer(x), 1) > 0) ? x : y); + case T_BIG_REAL: + if (mpfr_nan_p(big_real(y))) return(y); + return((mpfr_cmp_si(big_real(y), integer(x)) > 0) ? x : y); +#endif + default: + return(min_out_y(sc, x, y)); + } + break; + + case T_RATIO: + switch (type(y)) + { + case T_INTEGER: + return((fraction(x) > integer(y)) ? y : x); + case T_REAL: + if (is_NaN(real(y))) return(y); + return((fraction(x) > real(y)) ? y : x); +#if WITH_GMP + case T_BIG_INTEGER: + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + return((mpq_cmp_z(sc->mpq_1, big_integer(y)) > 0) ? y : x); + case T_BIG_RATIO: + return((mpq_cmp_si(big_ratio(y), numerator(x), denominator(x)) > 0) ? x : y); + case T_BIG_REAL: + if (mpfr_nan_p(big_real(y))) return(y); + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + return((mpfr_cmp_q(big_real(y), sc->mpq_1) > 0) ? x : y); +#endif + default: + return(min_out_y(sc, x, y)); + } + + case T_REAL: + switch (type(y)) + { + case T_INTEGER: + if (is_NaN(real(x))) return(x); + return((real(x) > integer(y)) ? y : x); + case T_RATIO: + return((real(x) > fraction(y)) ? y : x); +#if WITH_GMP + case T_BIG_INTEGER: + if (is_NaN(real(x))) return(x); + mpfr_set_d(sc->mpfr_1, real(x), MPFR_RNDN); + return((mpfr_cmp_z(sc->mpfr_1, big_integer(y)) > 0) ? y : x); + + case T_BIG_RATIO: + if (is_NaN(real(x))) return(x); + mpfr_set_d(sc->mpfr_1, real(x), MPFR_RNDN); + return((mpfr_cmp_q(sc->mpfr_1, big_ratio(y)) > 0) ? y : x); + + case T_BIG_REAL: + if (is_NaN(real(x))) return(x); + if (mpfr_nan_p(big_real(y))) return(y); + return((mpfr_cmp_d(big_real(y), real(x)) > 0) ? x : y); +#endif + default: + return(min_out_y(sc, x, y)); + } + break; + +#if WITH_GMP + case T_BIG_INTEGER: + switch (type(y)) + { + case T_INTEGER: + return((mpz_cmp_si(big_integer(x), integer(y)) > 0) ? y : x); + case T_RATIO: + mpq_set_z(sc->mpq_1, big_integer(x)); + return((mpq_cmp_si(sc->mpq_1, numerator(y), denominator(y)) > 0) ? y : x); + case T_REAL: + if (is_NaN(real(y))) return(y); + mpfr_set_z(sc->mpfr_1, big_integer(x), MPFR_RNDN); + return((mpfr_cmp_d(sc->mpfr_1, real(y)) > 0) ? y : x); + case T_BIG_RATIO: + return((mpq_cmp_z(big_ratio(y), big_integer(x)) > 0) ? x : y); + case T_BIG_REAL: + if (mpfr_nan_p(big_real(y))) return(y); + return((mpfr_cmp_z(big_real(y), big_integer(x)) > 0) ? x : y); + default: + return(min_out_y(sc, x, y)); + } + case T_BIG_RATIO: + switch (type(y)) + { + case T_INTEGER: + return((mpq_cmp_si(big_ratio(x), integer(y), 1) > 0) ? y : x); + case T_RATIO: + return((mpq_cmp_si(big_ratio(x), numerator(y), denominator(y)) > 0) ? y : x); + case T_REAL: + if (is_NaN(real(y))) return(y); + mpfr_set_q(sc->mpfr_1, big_ratio(x), MPFR_RNDN); + return((mpfr_cmp_d(sc->mpfr_1, real(y)) > 0) ? y : x); + case T_BIG_INTEGER: + return((mpq_cmp_z(big_ratio(x), big_integer(y)) > 0) ? y : x); + case T_BIG_REAL: + if (mpfr_nan_p(big_real(y))) return(y); + return((mpfr_cmp_q(big_real(y), big_ratio(x)) > 0) ? x : y); + default: + return(min_out_y(sc, x, y)); + } + + case T_BIG_REAL: + switch (type(y)) + { + case T_INTEGER: + if (mpfr_nan_p(big_real(x))) return(x); + return((mpfr_cmp_si(big_real(x), integer(y)) > 0) ? y : x); + case T_RATIO: + if (mpfr_nan_p(big_real(x))) return(x); + mpq_set_si(sc->mpq_1, numerator(y), denominator(y)); + return((mpfr_cmp_q(big_real(x), sc->mpq_1) > 0) ? y : x); + case T_REAL: + if (mpfr_nan_p(big_real(x))) return(x); + if (is_NaN(real(y))) return(y); + return((mpfr_cmp_d(big_real(x), real(y)) > 0) ? y : x); + case T_BIG_INTEGER: + if (mpfr_nan_p(big_real(x))) return(x); + return((mpfr_cmp_z(big_real(x), big_integer(y)) > 0) ? y : x); + case T_BIG_RATIO: + if (mpfr_nan_p(big_real(x))) return(x); + return((mpfr_cmp_q(big_real(x), big_ratio(y)) > 0) ? y : x); + default: + return(min_out_y(sc, x, y)); + } +#endif + default: + return(min_out_x(sc, x, y)); + } + return(x); +} + +static s7_pointer g_min(s7_scheme *sc, s7_pointer args) +{ + #define H_min "(min ...) returns the minimum of its arguments" + #define Q_min sc->pcl_r + + s7_pointer x = car(args); + if (is_null(cdr(args))) + { + if (is_real(x)) return(x); + return(method_or_bust_p(sc, x, sc->min_symbol, sc->type_names[T_REAL])); + } + for (s7_pointer p = cdr(args); is_pair(p); p = cdr(p)) + x = min_p_pp(sc, x, car(p)); + return(x); +} + +static s7_pointer g_min_2(s7_scheme *sc, s7_pointer args) {return(min_p_pp(sc, car(args), cadr(args)));} +static s7_pointer g_min_3(s7_scheme *sc, s7_pointer args) {return(min_p_pp(sc, min_p_pp(sc, car(args), cadr(args)), caddr(args)));} + +static s7_pointer min_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer unused_expr, bool unused_ops) +{ + return((args == 2) ? sc->min_2 : ((args == 3) ? sc->min_3 : f)); +} + +static s7_int min_i_ii(s7_int i1, s7_int i2) {return((i1 < i2) ? i1 : i2);} +static s7_int min_i_iii(s7_int i1, s7_int i2, s7_int i3) {return((i1 < i2) ? ((i1 < i3) ? i1 : i3) : ((i2 < i3) ? i2 : i3));} +static s7_double min_d_dd(s7_double x1, s7_double x2) {if (is_NaN(x1)) return(x1); return((x1 < x2) ? x1 : x2);} +static s7_double min_d_ddd(s7_double x1, s7_double x2, s7_double x3) {return(min_d_dd(x1, min_d_dd(x2, x3)));} +static s7_double min_d_dddd(s7_double x1, s7_double x2, s7_double x3, s7_double x4) {return(min_d_dd(x1, min_d_ddd(x2, x3, x4)));} + + +/* ---------------------------------------- = ---------------------------------------- */ +static bool eq_out_x(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + if (has_active_methods(sc, x)) + return(find_and_apply_method(sc, x, sc->num_eq_symbol, set_plist_2(sc, x, y)) != sc->F); + wrong_type_error_nr(sc, sc->num_eq_symbol, 1, x, a_number_string); + return(false); +} + +static bool eq_out_y(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + if (has_active_methods(sc, y)) + return(find_and_apply_method(sc, y, sc->num_eq_symbol, set_plist_2(sc, x, y)) != sc->F); + wrong_type_error_nr(sc, sc->num_eq_symbol, 2, y, a_number_string); + return(false); +} + +static bool num_eq_b_7pp(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + if (type(x) == type(y)) + { + if (is_t_integer(x)) + return(integer(x) == integer(y)); + if (is_t_real(x)) + return(real(x) == real(y)); + if (is_t_complex(x)) + return((real_part(x) == real_part(y)) && (imag_part(x) == imag_part(y))); + if (is_t_ratio(x)) + return((numerator(x) == numerator(y)) && (denominator(x) == denominator(y))); +#if WITH_GMP + if (is_t_big_integer(x)) + return(mpz_cmp(big_integer(x), big_integer(y)) == 0); + if (is_t_big_ratio(x)) + return(mpq_equal(big_ratio(x), big_ratio(y))); + if (is_t_big_real(x)) + return(mpfr_equal_p(big_real(x), big_real(y))); + if (is_t_big_complex(x)) /* mpc_cmp can't handle NaN */ + { + if ((mpfr_nan_p(mpc_realref(big_complex(x)))) || (mpfr_nan_p(mpc_imagref(big_complex(x)))) || + (mpfr_nan_p(mpc_realref(big_complex(y)))) || (mpfr_nan_p(mpc_imagref(big_complex(y))))) + return(false); + return(mpc_cmp(big_complex(x), big_complex(y)) == 0); + } +#endif + } + + switch (type(x)) + { + case T_INTEGER: + switch (type(y)) + { + case T_RATIO: + return(false); + case T_REAL: +#if WITH_GMP + if (s7_int_abs(integer(x)) >= INT64_TO_DOUBLE_LIMIT) + { + if (is_NaN(real(y))) return(false); + mpfr_set_d(sc->mpfr_1, real(y), MPFR_RNDN); + return(mpfr_cmp_si(sc->mpfr_1, integer(x)) == 0); + } +#endif + return(integer(x) == real(y)); + case T_COMPLEX: + return(false); +#if WITH_GMP + case T_BIG_INTEGER: + return((mpz_fits_slong_p(big_integer(y))) && (integer(x) == mpz_get_si(big_integer(y)))); + case T_BIG_RATIO: + return(false); + case T_BIG_REAL: + return((!mpfr_nan_p(big_real(y))) && (mpfr_cmp_si(big_real(y), integer(x)) == 0)); + case T_BIG_COMPLEX: + return(false); +#endif + default: return(eq_out_y(sc, x, y)); + } + break; + + case T_RATIO: + switch (type(y)) + { + case T_INTEGER: return(false); + case T_REAL: return(fraction(x) == real(y)); + case T_COMPLEX: return(false); +#if WITH_GMP + case T_BIG_INTEGER: + return(false); + case T_BIG_RATIO: + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + return(mpq_equal(sc->mpq_1, big_ratio(y))); + case T_BIG_REAL: + if (mpfr_nan_p(big_real(y))) return(false); + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + return(mpfr_cmp_q(big_real(y), sc->mpq_1) == 0); + case T_BIG_COMPLEX: + return(false); +#endif + default: return(eq_out_y(sc, x, y)); + } + break; + + case T_REAL: + switch (type(y)) + { + case T_INTEGER: + return(real(x) == integer(y)); + case T_RATIO: + return(real(x) == fraction(y)); + case T_COMPLEX: + return(false); +#if WITH_GMP + case T_BIG_INTEGER: + if (is_NaN(real(x))) return(false); + mpfr_set_d(sc->mpfr_1, real(x), MPFR_RNDN); + return(mpfr_cmp_z(sc->mpfr_1, big_integer(y)) == 0); + case T_BIG_RATIO: + if (is_NaN(real(x))) return(false); + mpfr_set_d(sc->mpfr_1, real(x), MPFR_RNDN); + return(mpfr_cmp_q(sc->mpfr_1, big_ratio(y)) == 0); + case T_BIG_REAL: + if (is_NaN(real(x))) return(false); + return((!mpfr_nan_p(big_real(y))) && (mpfr_cmp_d(big_real(y), real(x)) == 0)); + case T_BIG_COMPLEX: + return(false); +#endif + default: return(eq_out_y(sc, x, y)); + } + break; + + case T_COMPLEX: + if (is_real(y)) return(false); +#if WITH_GMP + if (is_t_big_complex(y)) + { + if ((is_NaN(real_part(x))) || (is_NaN(imag_part(x))) || + (mpfr_nan_p(mpc_realref(big_complex(y)))) || (mpfr_nan_p(mpc_imagref(big_complex(y))))) + return(false); + mpc_set_d_d(sc->mpc_1, real_part(x), imag_part(x), MPC_RNDNN); + return(mpc_cmp(big_complex(y), sc->mpc_1) == 0); + } +#endif + return(eq_out_y(sc, x, y)); + +#if WITH_GMP + case T_BIG_INTEGER: + switch (type(y)) + { + case T_INTEGER: + return((mpz_fits_slong_p(big_integer(x))) && (integer(y) == mpz_get_si(big_integer(x)))); + case T_REAL: + if (is_NaN(real(y))) return(false); + mpfr_set_d(sc->mpfr_1, real(y), MPFR_RNDN); + return(mpfr_cmp_z(sc->mpfr_1, big_integer(x)) == 0); + case T_RATIO: case T_COMPLEX: case T_BIG_RATIO: case T_BIG_COMPLEX: + return(false); + case T_BIG_REAL: + return((!mpfr_nan_p(big_real(y))) && (mpfr_cmp_z(big_real(y), big_integer(x)) == 0)); + default: return(eq_out_y(sc, x, y)); + } + case T_BIG_RATIO: + switch (type(y)) + { + case T_RATIO: + mpq_set_si(sc->mpq_1, numerator(y), denominator(y)); + return(mpq_equal(sc->mpq_1, big_ratio(x))); + case T_REAL: + if (is_NaN(real(y))) return(false); + mpfr_set_d(sc->mpfr_1, real(y), MPFR_RNDN); + return(mpfr_cmp_q(sc->mpfr_1, big_ratio(x)) == 0); + case T_INTEGER: case T_BIG_INTEGER: case T_COMPLEX: case T_BIG_COMPLEX: + return(false); + case T_BIG_REAL: + return((!mpfr_nan_p(big_real(y))) && (mpfr_cmp_q(big_real(y), big_ratio(x)) == 0)); + default: return(eq_out_y(sc, x, y)); + } + + case T_BIG_REAL: + if ((is_number(y)) && (mpfr_nan_p(big_real(x)))) return(false); + switch (type(y)) + { + case T_INTEGER: + return(mpfr_cmp_si(big_real(x), integer(y)) == 0); + case T_RATIO: + mpq_set_si(sc->mpq_1, numerator(y), denominator(y)); + return(mpfr_cmp_q(big_real(x), sc->mpq_1) == 0); + case T_REAL: + return((!is_NaN(real(y))) && (mpfr_cmp_d(big_real(x), real(y)) == 0)); + case T_BIG_INTEGER: + return(mpfr_cmp_z(big_real(x), big_integer(y)) == 0); + case T_BIG_RATIO: + return(mpfr_cmp_q(big_real(x), big_ratio(y)) == 0); + case T_COMPLEX: case T_BIG_COMPLEX: + return(false); + default: return(eq_out_y(sc, x, y)); + } + + case T_BIG_COMPLEX: + switch (type(y)) + { + case T_RATIO: case T_REAL: case T_INTEGER: case T_BIG_INTEGER: case T_BIG_RATIO: case T_BIG_REAL: + return(false); + case T_COMPLEX: + if ((is_NaN(real_part(y))) || (is_NaN(imag_part(y))) || + (mpfr_nan_p(mpc_realref(big_complex(x)))) || (mpfr_nan_p(mpc_imagref(big_complex(x))))) + return(false); + mpc_set_d_d(sc->mpc_1, real_part(y), imag_part(y), MPC_RNDNN); + return(mpc_cmp(big_complex(x), sc->mpc_1) == 0); /* NaN's not allowed! */ + default: return(eq_out_y(sc, x, y)); + } +#endif + default: return(eq_out_x(sc, x, y)); + } + return(false); +} + +static bool is_number_via_method(s7_scheme *sc, s7_pointer p) +{ + if (is_number(p)) + return(true); + if (has_active_methods(sc, p)) + { + s7_pointer f = find_method_with_let(sc, p, sc->is_number_symbol); + if (f != sc->undefined) + return(is_true(sc, s7_apply_function(sc, f, set_plist_1(sc, p)))); + } + return(false); +} + +static s7_pointer g_num_eq(s7_scheme *sc, s7_pointer args) +{ + #define H_num_eq "(= z1 ...) returns #t if all its arguments are equal" + #define Q_num_eq s7_make_circular_signature(sc, 1, 2, sc->is_boolean_symbol, sc->is_number_symbol) + + s7_pointer x = car(args), p = cdr(args); + if (is_null(cdr(p))) + return(make_boolean(sc, num_eq_b_7pp(sc, x, car(p)))); + + for (; is_pair(p); p = cdr(p)) + if (!num_eq_b_7pp(sc, x, car(p))) + { + for (p = cdr(p); is_pair(p); p = cdr(p)) + if (!is_number_via_method(sc, car(p))) + wrong_type_error_nr(sc, sc->num_eq_symbol, position_of(p, args), car(p), a_number_string); + return(sc->F); + } + return(sc->T); +} + +static bool num_eq_b_ii(s7_int i1, s7_int i2) {return(i1 == i2);} +static bool num_eq_b_dd(s7_double i1, s7_double i2) {return(i1 == i2);} + +static s7_pointer num_eq_p_dd(s7_scheme *sc, s7_double x1, s7_double x2) {return(make_boolean(sc, x1 == x2));} +static s7_pointer num_eq_p_ii(s7_scheme *sc, s7_int x1, s7_int x2) {return(make_boolean(sc, x1 == x2));} +static s7_pointer num_eq_p_pp(s7_scheme *sc, s7_pointer x, s7_pointer y) {return(make_boolean(sc, num_eq_b_7pp(sc, x, y)));} + +static s7_pointer num_eq_p_pi(s7_scheme *sc, s7_pointer p1, s7_int p2) +{ + if (is_t_integer(p1)) + return((integer(p1) == p2) ? sc->T : sc->F); + if (is_t_real(p1)) + return((real(p1) == p2) ? sc->T : sc->F); +#if WITH_GMP + if (is_t_big_integer(p1)) + return(((mpz_fits_slong_p(big_integer(p1))) && (p2 == mpz_get_si(big_integer(p1)))) ? sc->T : sc->F); + if (is_t_big_real(p1)) + return((mpfr_cmp_si(big_real(p1), p2) == 0) ? sc->T : sc->F); +#endif + return((is_number(p1)) ? sc->F : make_boolean(sc, eq_out_x(sc, p1, make_integer(sc, p2)))); +} + +static bool num_eq_b_pi(s7_scheme *sc, s7_pointer x, s7_int y) +{ + if (is_t_integer(x)) + return(integer(x) == y); + if (is_t_real(x)) + return(real(x) == y); +#if WITH_GMP + if (is_t_big_integer(x)) + return((mpz_fits_slong_p(big_integer(x))) && (y == mpz_get_si(big_integer(x)))); + if (is_t_big_real(x)) + return(mpfr_cmp_si(big_real(x), y) == 0); +#endif + if (!is_number(x)) /* complex/ratio */ + sole_arg_wrong_type_error_nr(sc, sc->num_eq_symbol, x, a_number_string); + /* return(eq_out_x(sc, x, make_integer(sc, y))); */ /* much slower? see thash */ + return(false); +} + +static s7_pointer g_num_eq_2(s7_scheme *sc, s7_pointer args) +{ + s7_pointer x = car(args), y = cadr(args); + if ((is_t_integer(x)) && (is_t_integer(y))) /* this is by far the most common case (ratios aren't used much, and = with floats is frowned upon) */ + return(make_boolean(sc, integer(x) == integer(y))); + return(make_boolean(sc, num_eq_b_7pp(sc, x, y))); +} + +static inline s7_pointer num_eq_xx(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + if (is_t_integer(x)) + return(make_boolean(sc, integer(x) == integer(y))); + if (is_t_real(x)) + return(make_boolean(sc, real(x) == integer(y))); + if (!is_number(x)) + return(make_boolean(sc, eq_out_x(sc, x, y))); +#if WITH_GMP + if (is_t_big_integer(x)) + return(make_boolean(sc, mpz_cmp_si(big_integer(x), integer(y)) == 0)); + if (is_t_big_real(x)) + { + if (mpfr_nan_p(big_real(x))) return(sc->F); + return(make_boolean(sc, mpfr_cmp_si(big_real(x), integer(y)) == 0)); + } + if (is_t_big_ratio(x)) + return(make_boolean(sc, mpq_cmp_si(big_ratio(x), integer(y), 1) == 0)); +#endif + return(sc->F); +} + +static s7_pointer g_num_eq_xi(s7_scheme *sc, s7_pointer args) {return(num_eq_xx(sc, car(args), cadr(args)));} +static s7_pointer g_num_eq_ix(s7_scheme *sc, s7_pointer args) {return(num_eq_xx(sc, cadr(args), car(args)));} + +static s7_pointer num_eq_chooser(s7_scheme *sc, s7_pointer ur_f, int32_t args, s7_pointer expr, bool ops) +{ + if (args != 2) return(ur_f); + if ((ops) && (is_t_integer(caddr(expr)))) + return(sc->num_eq_xi); + return(((ops) && (is_t_integer(cadr(expr)))) ? sc->num_eq_ix : sc->num_eq_2); +} + + +/* ---------------------------------------- < ---------------------------------------- */ +static bool lt_out_x(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + if (has_active_methods(sc, x)) + return(find_and_apply_method(sc, x, sc->lt_symbol, list_2(sc, x, y)) != sc->F); /* not plist */ + wrong_type_error_nr(sc, sc->lt_symbol, 1, x, sc->type_names[T_REAL]); + return(false); +} + +static bool lt_out_y(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + if (has_active_methods(sc, y)) + return(find_and_apply_method(sc, y, sc->lt_symbol, list_2(sc, x, y)) != sc->F); + wrong_type_error_nr(sc, sc->lt_symbol, 2, y, sc->type_names[T_REAL]); + return(false); +} + +static bool lt_b_7pp(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + if (type(x) == type(y)) + { + if (is_t_integer(x)) + return(integer(x) < integer(y)); + if (is_t_real(x)) + return(real(x) < real(y)); + if (is_t_ratio(x)) + return(fraction(x) < fraction(y)); +#if WITH_GMP + if (is_t_big_integer(x)) + return(mpz_cmp(big_integer(x), big_integer(y)) < 0); + if (is_t_big_ratio(x)) + return(mpq_cmp(big_ratio(x), big_ratio(y)) < 0); + if (is_t_big_real(x)) + return(mpfr_less_p(big_real(x), big_real(y))); +#endif + } + switch (type(x)) + { + case T_INTEGER: + switch (type(y)) + { + case T_RATIO: return(integer(x) < fraction(y)); /* ?? */ + case T_REAL: return(integer(x) < real(y)); +#if WITH_GMP + case T_BIG_INTEGER: return(mpz_cmp_si(big_integer(y), integer(x)) > 0); + case T_BIG_RATIO: return(mpq_cmp_si(big_ratio(y), integer(x), 1) > 0); + case T_BIG_REAL: return(mpfr_cmp_si(big_real(y), integer(x)) > 0); +#endif + default: return(lt_out_y(sc, x, y)); + } + break; + + case T_RATIO: + switch (type(y)) + { + case T_INTEGER: return(fraction(x) < integer(y)); + case T_REAL: return(fraction(x) < real(y)); +#if WITH_GMP + case T_BIG_INTEGER: + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + return(mpq_cmp_z(sc->mpq_1, big_integer(y)) < 0); + case T_BIG_RATIO: + return(mpq_cmp_si(big_ratio(y), numerator(x), denominator(x)) > 0); + case T_BIG_REAL: + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + return(mpfr_cmp_q(big_real(y), sc->mpq_1) > 0); +#endif + default: return(lt_out_y(sc, x, y)); + } + + case T_REAL: + switch (type(y)) + { + case T_INTEGER: return(real(x) < integer(y)); + case T_RATIO: return(real(x) < fraction(y)); +#if WITH_GMP + case T_BIG_INTEGER: + if (is_NaN(real(x))) return(false); + mpfr_set_d(sc->mpfr_1, real(x), MPFR_RNDN); + return(mpfr_cmp_z(sc->mpfr_1, big_integer(y)) < 0); + case T_BIG_RATIO: + mpfr_set_d(sc->mpfr_1, real(x), MPFR_RNDN); + return(mpfr_cmp_q(sc->mpfr_1, big_ratio(y)) < 0); + case T_BIG_REAL: + return(mpfr_cmp_d(big_real(y), real(x)) > 0); +#endif + default: return(lt_out_y(sc, x, y)); + } + break; + +#if WITH_GMP + case T_BIG_INTEGER: + switch (type(y)) + { + case T_INTEGER: + return(mpz_cmp_si(big_integer(x), integer(y)) < 0); + case T_RATIO: + mpq_set_z(sc->mpq_1, big_integer(x)); + return(mpq_cmp_si(sc->mpq_1, numerator(y), denominator(y)) < 0); + case T_REAL: + if (is_NaN(real(y))) return(false); + mpfr_set_z(sc->mpfr_1, big_integer(x), MPFR_RNDN); + return(mpfr_cmp_d(sc->mpfr_1, real(y)) < 0); + case T_BIG_RATIO: + return(mpq_cmp_z(big_ratio(y), big_integer(x)) > 0); + case T_BIG_REAL: + return(mpfr_cmp_z(big_real(y), big_integer(x)) > 0); + default: return(lt_out_y(sc, x, y)); + } + case T_BIG_RATIO: + switch (type(y)) + { + case T_INTEGER: + return(mpq_cmp_si(big_ratio(x), integer(y), 1) < 0); + case T_RATIO: + return(mpq_cmp_si(big_ratio(x), numerator(y), denominator(y)) < 0); + case T_REAL: + if (is_NaN(real(y))) return(false); + mpfr_set_q(sc->mpfr_1, big_ratio(x), MPFR_RNDN); + return(mpfr_cmp_d(sc->mpfr_1, real(y)) < 0); + case T_BIG_INTEGER: + return(mpq_cmp_z(big_ratio(x), big_integer(y)) < 0); + case T_BIG_REAL: + return(mpfr_cmp_q(big_real(y), big_ratio(x)) > 0); + default: return(lt_out_y(sc, x, y)); + } + + case T_BIG_REAL: + switch (type(y)) + { + case T_INTEGER: + return(mpfr_cmp_si(big_real(x), integer(y)) < 0); + case T_RATIO: + mpq_set_si(sc->mpq_1, numerator(y), denominator(y)); + return(mpfr_cmp_q(big_real(x), sc->mpq_1) < 0); + case T_REAL: + return(mpfr_cmp_d(big_real(x), real(y)) < 0); + case T_BIG_INTEGER: + return(mpfr_cmp_z(big_real(x), big_integer(y)) < 0); + case T_BIG_RATIO: + return(mpfr_cmp_q(big_real(x), big_ratio(y)) < 0); + default: return(lt_out_y(sc, x, y)); + } +#endif + default: return(lt_out_x(sc, x, y)); + } + return(true); +} + +static s7_pointer g_less(s7_scheme *sc, s7_pointer args) +{ + #define H_less "(< x1 ...) returns #t if its arguments are in increasing order" + #define Q_less s7_make_circular_signature(sc, 1, 2, sc->is_boolean_symbol, sc->is_real_symbol) + + s7_pointer x = car(args), p = cdr(args); + if (is_null(cdr(p))) + return(make_boolean(sc, lt_b_7pp(sc, x, car(p)))); + + for (; is_pair(p); p = cdr(p)) + { + if (!lt_b_7pp(sc, x, car(p))) + { + for (p = cdr(p); is_pair(p); p = cdr(p)) + if (!is_real_via_method(sc, car(p))) + wrong_type_error_nr(sc, sc->lt_symbol, position_of(p, args), car(p), sc->type_names[T_REAL]); + return(sc->F); + } + x = car(p); + } + return(sc->T); +} + +static bool ratio_lt_pi(s7_pointer x, s7_int y) +{ + if ((y >= 0) && (numerator(x) < 0)) + return(true); + if ((y <= 0) && (numerator(x) > 0)) + return(false); + if (denominator(x) < S7_INT32_MAX) + return(numerator(x) < (y * denominator(x))); + return(fraction(x) < y); +} + +static s7_pointer g_less_x0(s7_scheme *sc, s7_pointer args) +{ + s7_pointer x = car(args); + if (is_t_integer(x)) + return(make_boolean(sc, integer(x) < 0)); + if (is_small_real(x)) + return(make_boolean(sc, is_negative(sc, x))); +#if WITH_GMP + if (is_t_big_integer(x)) + return(make_boolean(sc, mpz_cmp_si(big_integer(x), 0) < 0)); + if (is_t_big_real(x)) + return(make_boolean(sc, mpfr_cmp_si(big_real(x), 0) < 0)); + if (is_t_big_ratio(x)) + return(make_boolean(sc, mpq_cmp_si(big_ratio(x), 0, 1) < 0)); +#endif + return(method_or_bust(sc, x, sc->lt_symbol, args, sc->type_names[T_REAL], 1)); +} + +static s7_pointer g_less_xi(s7_scheme *sc, s7_pointer args) +{ + s7_int y = integer(cadr(args)); + s7_pointer x = car(args); + + if (is_t_integer(x)) + return(make_boolean(sc, integer(x) < y)); + if (is_t_real(x)) + return(make_boolean(sc, real(x) < y)); + if (is_t_ratio(x)) + return(make_boolean(sc, ratio_lt_pi(x, y))); +#if WITH_GMP + if (is_t_big_integer(x)) + return(make_boolean(sc, mpz_cmp_si(big_integer(x), y) < 0)); + if (is_t_big_real(x)) + return(make_boolean(sc, mpfr_cmp_si(big_real(x), y) < 0)); + if (is_t_big_ratio(x)) + return(make_boolean(sc, mpq_cmp_si(big_ratio(x), y, 1) < 0)); +#endif + return(method_or_bust(sc, x, sc->lt_symbol, args, sc->type_names[T_REAL], 1)); +} + +static s7_pointer g_less_xf(s7_scheme *sc, s7_pointer args) +{ + s7_double y = real(cadr(args)); /* chooser below checks is_t_real(y) */ + s7_pointer x = car(args); + + if (is_t_real(x)) + return(make_boolean(sc, real(x) < y)); + if (is_t_integer(x)) + return(make_boolean(sc, integer(x) < y)); + if (is_t_ratio(x)) + return(make_boolean(sc, fraction(x) < y)); +#if WITH_GMP + if (is_t_big_real(x)) + return(make_boolean(sc, mpfr_cmp_d(big_real(x), y) < 0)); + if (is_t_big_integer(x)) + { + mpfr_set_d(sc->mpfr_1, y, MPFR_RNDN); + return(make_boolean(sc, mpfr_cmp_z(sc->mpfr_1, big_integer(x)) > 0)); + } + if (is_t_big_ratio(x)) + { + mpfr_set_d(sc->mpfr_1, y, MPFR_RNDN); + return(make_boolean(sc, mpfr_cmp_q(sc->mpfr_1, big_ratio(x)) > 0)); + } +#endif + return(method_or_bust(sc, x, sc->lt_symbol, args, sc->type_names[T_REAL], 1)); +} + +static inline s7_pointer lt_p_pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) {return(make_boolean(sc, lt_b_7pp(sc, p1, p2)));} +static bool lt_b_ii(s7_int i1, s7_int i2) {return(i1 < i2);} +static bool lt_b_dd(s7_double i1, s7_double i2) {return(i1 < i2);} +static s7_pointer lt_p_dd(s7_scheme *sc, s7_double x1, s7_double x2) {return(make_boolean(sc, x1 < x2));} +static s7_pointer lt_p_ii(s7_scheme *sc, s7_int x1, s7_int x2) {return(make_boolean(sc, x1 < x2));} + +static bool lt_b_pi(s7_scheme *sc, s7_pointer p1, s7_int p2) +{ + if (is_t_integer(p1)) return(integer(p1) < p2); + if (is_t_real(p1)) return(real(p1) < p2); + if (is_t_ratio(p1)) return(ratio_lt_pi(p1, p2)); +#if WITH_GMP + if (is_t_big_integer(p1)) + return(mpz_cmp_si(big_integer(p1), p2) < 0); + if (is_t_big_real(p1)) + return(mpfr_cmp_si(big_real(p1), p2) < 0); + if (is_t_big_ratio(p1)) + return(mpq_cmp_si(big_ratio(p1), p2, 1) < 0); +#endif + return(lt_out_x(sc, p1, make_integer(sc, p2))); +} + +static s7_pointer g_less_2(s7_scheme *sc, s7_pointer args) {return(lt_p_pp(sc, car(args), cadr(args)));} +static s7_pointer lt_p_pi(s7_scheme *sc, s7_pointer p1, s7_int p2) {return(make_boolean(sc, lt_b_pi(sc, p1, p2)));} + +static s7_pointer less_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool ops) +{ + if (args != 2) return(f); + if (ops) + { + s7_pointer arg2 = caddr(expr); + if (is_t_integer(arg2)) + { + if (integer(arg2) == 0) + return(sc->less_x0); + + if ((integer(arg2) < S7_INT32_MAX) && + (integer(arg2) > S7_INT32_MIN)) + return(sc->less_xi); + } + if (is_t_real(arg2)) + return(sc->less_xf); + } + return(sc->less_2); +} + + +/* ---------------------------------------- <= ---------------------------------------- */ +static bool leq_out_x(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + if (has_active_methods(sc, x)) + return(find_and_apply_method(sc, x, sc->leq_symbol, list_2(sc, x, y)) != sc->F); /* not plist */ + wrong_type_error_nr(sc, sc->leq_symbol, 1, x, sc->type_names[T_REAL]); + return(false); +} + +static bool leq_out_y(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + if (has_active_methods(sc, y)) + return(find_and_apply_method(sc, y, sc->leq_symbol, list_2(sc, x, y)) != sc->F); + wrong_type_error_nr(sc, sc->leq_symbol, 2, y, sc->type_names[T_REAL]); + return(false); +} + +static bool leq_b_7pp(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + if (type(x) == type(y)) + { + if (is_t_integer(x)) + return(integer(x) <= integer(y)); + if (is_t_real(x)) + return(real(x) <= real(y)); + if (is_t_ratio(x)) + return(fraction(x) <= fraction(y)); +#if WITH_GMP + if (is_t_big_integer(x)) + return(mpz_cmp(big_integer(x), big_integer(y)) <= 0); + if (is_t_big_ratio(x)) + return(mpq_cmp(big_ratio(x), big_ratio(y)) <= 0); + if (is_t_big_real(x)) + return(mpfr_lessequal_p(big_real(x), big_real(y))); +#endif + } + switch (type(x)) + { + case T_INTEGER: + switch (type(y)) + { + case T_RATIO: return(integer(x) <= fraction(y)); /* ?? */ + case T_REAL: return(integer(x) <= real(y)); +#if WITH_GMP + case T_BIG_INTEGER: return(mpz_cmp_si(big_integer(y), integer(x)) >= 0); + case T_BIG_RATIO: return(mpq_cmp_si(big_ratio(y), integer(x), 1) >= 0); + case T_BIG_REAL: + return((!mpfr_nan_p(big_real(y))) && (mpfr_cmp_si(big_real(y), integer(x)) >= 0)); +#endif + default: return(leq_out_y(sc, x, y)); + } + break; + + case T_RATIO: + switch (type(y)) + { + case T_INTEGER: return(fraction(x) <= integer(y)); + case T_REAL: return(fraction(x) <= real(y)); +#if WITH_GMP + case T_BIG_INTEGER: + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + return(mpq_cmp_z(sc->mpq_1, big_integer(y)) <= 0); + case T_BIG_RATIO: + return(mpq_cmp_si(big_ratio(y), numerator(x), denominator(x)) >= 0); + case T_BIG_REAL: + if (mpfr_nan_p(big_real(y))) return(false); + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + return(mpfr_cmp_q(big_real(y), sc->mpq_1) >= 0); +#endif + default: return(leq_out_y(sc, x, y)); + } + + case T_REAL: + switch (type(y)) + { + case T_INTEGER: return(real(x) <= integer(y)); + case T_RATIO: return(real(x) <= fraction(y)); +#if WITH_GMP + case T_BIG_INTEGER: + if (is_NaN(real(x))) return(false); + mpfr_set_d(sc->mpfr_1, real(x), MPFR_RNDN); + return(mpfr_cmp_z(sc->mpfr_1, big_integer(y)) <= 0); + case T_BIG_RATIO: + if (is_NaN(real(x))) return(false); + mpfr_set_d(sc->mpfr_1, real(x), MPFR_RNDN); + return(mpfr_cmp_q(sc->mpfr_1, big_ratio(y)) <= 0); + case T_BIG_REAL: + if (is_NaN(real(x))) return(false); + return((!mpfr_nan_p(big_real(y))) && (mpfr_cmp_d(big_real(y), real(x)) >= 0)); +#endif + default: return(leq_out_y(sc, x, y)); + } + break; + +#if WITH_GMP + case T_BIG_INTEGER: + switch (type(y)) + { + case T_INTEGER: + return(mpz_cmp_si(big_integer(x), integer(y)) <= 0); + case T_RATIO: + mpq_set_z(sc->mpq_1, big_integer(x)); + return(mpq_cmp_si(sc->mpq_1, numerator(y), denominator(y)) <= 0); + case T_REAL: + if (is_NaN(real(y))) return(false); + mpfr_set_z(sc->mpfr_1, big_integer(x), MPFR_RNDN); + return(mpfr_cmp_d(sc->mpfr_1, real(y)) <= 0); + case T_BIG_RATIO: + return(mpq_cmp_z(big_ratio(y), big_integer(x)) >= 0); + case T_BIG_REAL: + return((!mpfr_nan_p(big_real(y))) && (mpfr_cmp_z(big_real(y), big_integer(x)) >= 0)); + default: return(leq_out_y(sc, x, y)); + } + case T_BIG_RATIO: + switch (type(y)) + { + case T_INTEGER: + return(mpq_cmp_si(big_ratio(x), integer(y), 1) <= 0); + case T_RATIO: + return(mpq_cmp_si(big_ratio(x), numerator(y), denominator(y)) <= 0); + case T_REAL: + if (is_NaN(real(y))) return(false); + mpfr_set_q(sc->mpfr_1, big_ratio(x), MPFR_RNDN); + return(mpfr_cmp_d(sc->mpfr_1, real(y)) <= 0); + case T_BIG_INTEGER: + return(mpq_cmp_z(big_ratio(x), big_integer(y)) <= 0); + case T_BIG_REAL: + return((!mpfr_nan_p(big_real(y))) && (mpfr_cmp_q(big_real(y), big_ratio(x)) >= 0)); + default: return(leq_out_y(sc, x, y)); + } + + case T_BIG_REAL: + if ((is_real(y)) && (mpfr_nan_p(big_real(x)))) return(false); + switch (type(y)) + { + case T_INTEGER: + return(mpfr_cmp_si(big_real(x), integer(y)) <= 0); + case T_RATIO: + mpq_set_si(sc->mpq_1, numerator(y), denominator(y)); + return(mpfr_cmp_q(big_real(x), sc->mpq_1) <= 0); + case T_REAL: + return((!is_NaN(real(y))) && (mpfr_cmp_d(big_real(x), real(y)) <= 0)); + case T_BIG_INTEGER: + return(mpfr_cmp_z(big_real(x), big_integer(y)) <= 0); + case T_BIG_RATIO: + return(mpfr_cmp_q(big_real(x), big_ratio(y)) <= 0); + default: return(leq_out_y(sc, x, y)); + } +#endif + default: return(leq_out_x(sc, x, y)); + } + return(true); +} + +static s7_pointer g_less_or_equal(s7_scheme *sc, s7_pointer args) +{ + #define H_less_or_equal "(<= x1 ...) returns #t if its arguments are in non-decreasing order" + #define Q_less_or_equal s7_make_circular_signature(sc, 1, 2, sc->is_boolean_symbol, sc->is_real_symbol) + + s7_pointer x = car(args), p = cdr(args); + + if (is_null(cdr(p))) + return(make_boolean(sc, leq_b_7pp(sc, x, car(p)))); + + for (; is_pair(p); x = car(p), p = cdr(p)) + if (!leq_b_7pp(sc, x, car(p))) + { + for (p = cdr(p); is_pair(p); p = cdr(p)) + if (!is_real_via_method(sc, car(p))) + wrong_type_error_nr(sc, sc->leq_symbol, position_of(p, args), car(p), sc->type_names[T_REAL]); + return(sc->F); + } + return(sc->T); +} + +static inline s7_pointer leq_p_pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) {return(make_boolean(sc, leq_b_7pp(sc, p1, p2)));} +static bool leq_b_ii(s7_int i1, s7_int i2) {return(i1 <= i2);} +static bool leq_b_dd(s7_double i1, s7_double i2) {return(i1 <= i2);} +static s7_pointer leq_p_dd(s7_scheme *sc, s7_double x1, s7_double x2) {return(make_boolean(sc, x1 <= x2));} +static s7_pointer leq_p_ii(s7_scheme *sc, s7_int x1, s7_int x2) {return(make_boolean(sc, x1 <= x2));} + +static bool ratio_leq_pi(s7_pointer x, s7_int y) +{ + if ((y >= 0) && (numerator(x) <= 0)) + return(true); + if ((y <= 0) && (numerator(x) > 0)) + return(false); + if (denominator(x) < S7_INT32_MAX) + return(numerator(x) <= (y * denominator(x))); + return(fraction(x) <= y); +} + +static s7_pointer g_leq_xi(s7_scheme *sc, s7_pointer args) +{ + s7_int y = integer(cadr(args)); + s7_pointer x = car(args); + + if (is_t_integer(x)) + return(make_boolean(sc, integer(x) <= y)); + if (is_t_real(x)) + return(make_boolean(sc, real(x) <= y)); + if (is_t_ratio(x)) + return(make_boolean(sc, ratio_leq_pi(x, y))); +#if WITH_GMP + if (is_t_big_integer(x)) + return(make_boolean(sc, mpz_cmp_si(big_integer(x), y) <= 0)); + if (is_t_big_real(x)) + { + if (mpfr_nan_p(big_real(x))) return(sc->F); + return(make_boolean(sc, mpfr_cmp_si(big_real(x), y) <= 0)); + } + if (is_t_big_ratio(x)) + return(make_boolean(sc, mpq_cmp_si(big_ratio(x), y, 1) <= 0)); +#endif + return(method_or_bust(sc, x, sc->leq_symbol, args, sc->type_names[T_REAL], 1)); +} + +static bool leq_b_pi(s7_scheme *sc, s7_pointer p1, s7_int p2) +{ + if (is_t_integer(p1)) return(integer(p1) <= p2); + if (is_t_real(p1)) return(real(p1) <= p2); + if (is_t_ratio(p1)) return(ratio_leq_pi(p1, p2)); +#if WITH_GMP + if (is_t_big_integer(p1)) + return(mpz_cmp_si(big_integer(p1), p2) <= 0); + if (is_t_big_real(p1)) + return(mpfr_cmp_si(big_real(p1), p2) <= 0); + if (is_t_big_ratio(p1)) + return(mpq_cmp_si(big_ratio(p1), p2, 1) <= 0); +#endif + return(leq_out_x(sc, p1, make_integer(sc, p2))); +} + +static s7_pointer leq_p_pi(s7_scheme *sc, s7_pointer p1, s7_int p2) {return(make_boolean(sc, leq_b_pi(sc, p1, p2)));} +static s7_pointer g_leq_2(s7_scheme *sc, s7_pointer args) {return(make_boolean(sc, leq_b_7pp(sc, car(args), cadr(args))));} +static s7_pointer g_leq_ixx(s7_scheme *sc, s7_pointer args) +{ + s7_pointer p = cdr(args); + if (is_t_integer(car(p))) + { + if (integer(car(args)) > integer(car(p))) + { + if (!is_real_via_method(sc, cadr(p))) + wrong_type_error_nr(sc, sc->leq_symbol, 3, cadr(p), sc->type_names[T_REAL]); + return(sc->F); + } + if (is_t_integer(cadr(p))) + return((integer(car(p)) > integer(cadr(p))) ? sc->F : sc->T); + } + return(g_less_or_equal(sc, args)); +} + +static s7_pointer leq_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool ops) +{ + if (args == 2) + { + if (ops) + { + s7_pointer arg2 = caddr(expr); + if ((is_t_integer(arg2)) && + (integer(arg2) < S7_INT32_MAX) && + (integer(arg2) > S7_INT32_MIN)) + return(sc->leq_xi); + } + return(sc->leq_2); + } + if ((args == 3) && (is_t_integer(cadr(expr)))) + return(sc->leq_ixx); + return(f); +} + + +/* ---------------------------------------- > ---------------------------------------- */ +static bool gt_out_x(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + if (has_active_methods(sc, x)) + return(find_and_apply_method(sc, x, sc->gt_symbol, list_2(sc, x, y)) != sc->F); /* not plist */ + wrong_type_error_nr(sc, sc->gt_symbol, 1, x, sc->type_names[T_REAL]); + return(false); +} + +static bool gt_out_y(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + if (has_active_methods(sc, y)) + return(find_and_apply_method(sc, y, sc->gt_symbol, list_2(sc, x, y)) != sc->F); + wrong_type_error_nr(sc, sc->gt_symbol, 2, y, sc->type_names[T_REAL]); + return(false); +} + +static bool gt_b_7pp(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + if (type(x) == type(y)) + { + if (is_t_integer(x)) + return(integer(x) > integer(y)); + if (is_t_real(x)) + return(real(x) > real(y)); + if (is_t_ratio(x)) + return(fraction(x) > fraction(y)); +#if WITH_GMP + if (is_t_big_integer(x)) + return(mpz_cmp(big_integer(x), big_integer(y)) > 0); + if (is_t_big_ratio(x)) + return(mpq_cmp(big_ratio(x), big_ratio(y)) > 0); + if (is_t_big_real(x)) + return(mpfr_greater_p(big_real(x), big_real(y))); +#endif + } + switch (type(x)) + { + case T_INTEGER: + switch (type(y)) + { + case T_RATIO: return(integer(x) > fraction(y)); /* ?? */ + case T_REAL: return(integer(x) > real(y)); +#if WITH_GMP + case T_BIG_INTEGER: return(mpz_cmp_si(big_integer(y), integer(x)) < 0); + case T_BIG_RATIO: return(mpq_cmp_si(big_ratio(y), integer(x), 1) < 0); + case T_BIG_REAL: return(mpfr_cmp_si(big_real(y), integer(x)) < 0); +#endif + default: return(gt_out_y(sc, x, y)); + } + break; + + case T_RATIO: + switch (type(y)) + { + case T_INTEGER: return(fraction(x) > integer(y)); + case T_REAL: return(fraction(x) > real(y)); +#if WITH_GMP + case T_BIG_INTEGER: + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + return(mpq_cmp_z(sc->mpq_1, big_integer(y)) > 0); + case T_BIG_RATIO: + return(mpq_cmp_si(big_ratio(y), numerator(x), denominator(x)) < 0); + case T_BIG_REAL: + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + return(mpfr_cmp_q(big_real(y), sc->mpq_1) < 0); +#endif + default: return(gt_out_y(sc, x, y)); + } + + case T_REAL: + switch (type(y)) + { + case T_INTEGER: return(real(x) > integer(y)); + case T_RATIO: return(real(x) > fraction(y)); +#if WITH_GMP + case T_BIG_INTEGER: + if (is_NaN(real(x))) return(false); + mpfr_set_d(sc->mpfr_1, real(x), MPFR_RNDN); + return(mpfr_cmp_z(sc->mpfr_1, big_integer(y)) > 0); + case T_BIG_RATIO: + mpfr_set_d(sc->mpfr_1, real(x), MPFR_RNDN); + return(mpfr_cmp_q(sc->mpfr_1, big_ratio(y)) > 0); + case T_BIG_REAL: + return(mpfr_cmp_d(big_real(y), real(x)) < 0); +#endif + default: return(gt_out_y(sc, x, y)); + } + break; + +#if WITH_GMP + case T_BIG_INTEGER: + switch (type(y)) + { + case T_INTEGER: + return(mpz_cmp_si(big_integer(x), integer(y)) > 0); + case T_RATIO: + mpq_set_z(sc->mpq_1, big_integer(x)); + return(mpq_cmp_si(sc->mpq_1, numerator(y), denominator(y)) > 0); + case T_REAL: + if (is_NaN(real(y))) return(false); + mpfr_set_z(sc->mpfr_1, big_integer(x), MPFR_RNDN); + return(mpfr_cmp_d(sc->mpfr_1, real(y)) > 0); + case T_BIG_RATIO: + return(mpq_cmp_z(big_ratio(y), big_integer(x)) < 0); + case T_BIG_REAL: + return(mpfr_cmp_z(big_real(y), big_integer(x)) < 0); + default: return(gt_out_y(sc, x, y)); + } + case T_BIG_RATIO: + switch (type(y)) + { + case T_INTEGER: + return(mpq_cmp_si(big_ratio(x), integer(y), 1) > 0); + case T_RATIO: + return(mpq_cmp_si(big_ratio(x), numerator(y), denominator(y)) > 0); + case T_REAL: + if (is_NaN(real(y))) return(false); + mpfr_set_q(sc->mpfr_1, big_ratio(x), MPFR_RNDN); + return(mpfr_cmp_d(sc->mpfr_1, real(y)) > 0); + case T_BIG_INTEGER: + return(mpq_cmp_z(big_ratio(x), big_integer(y)) > 0); + case T_BIG_REAL: + return(mpfr_cmp_q(big_real(y), big_ratio(x)) < 0); + default: return(gt_out_y(sc, x, y)); + } + case T_BIG_REAL: + switch (type(y)) + { + case T_INTEGER: + return(mpfr_cmp_si(big_real(x), integer(y)) > 0); + case T_RATIO: + mpq_set_si(sc->mpq_1, numerator(y), denominator(y)); + return(mpfr_cmp_q(big_real(x), sc->mpq_1) > 0); + case T_REAL: + return(mpfr_cmp_d(big_real(x), real(y)) > 0); + case T_BIG_INTEGER: + return(mpfr_cmp_z(big_real(x), big_integer(y)) > 0); + case T_BIG_RATIO: + return(mpfr_cmp_q(big_real(x), big_ratio(y)) > 0); + default: return(gt_out_y(sc, x, y)); + } +#endif + default: return(gt_out_x(sc, x, y)); + } + return(true); +} + +static s7_pointer g_greater(s7_scheme *sc, s7_pointer args) +{ + #define H_greater "(> x1 ...) returns #t if its arguments are in decreasing order" + #define Q_greater s7_make_circular_signature(sc, 1, 2, sc->is_boolean_symbol, sc->is_real_symbol) + + s7_pointer x = car(args), p = cdr(args); + + if (is_null(cdr(p))) + return(make_boolean(sc, gt_b_7pp(sc, x, car(p)))); + + for (; is_pair(p); x = car(p), p = cdr(p)) + if (!gt_b_7pp(sc, x, car(p))) + { + for (p = cdr(p); is_pair(p); p = cdr(p)) + if (!is_real_via_method(sc, car(p))) + wrong_type_error_nr(sc, sc->gt_symbol, position_of(p, args), car(p), sc->type_names[T_REAL]); + return(sc->F); + } + return(sc->T); +} + +static s7_pointer g_greater_xi(s7_scheme *sc, s7_pointer args) +{ + s7_int y = integer(cadr(args)); + s7_pointer x = car(args); + + if (is_t_integer(x)) + return(make_boolean(sc, integer(x) > y)); + if (is_t_real(x)) + return(make_boolean(sc, real(x) > y)); + if (is_t_ratio(x)) + return(make_boolean(sc, !ratio_leq_pi(x, y))); +#if WITH_GMP + if (is_t_big_integer(x)) + return(make_boolean(sc, mpz_cmp_si(big_integer(x), y) > 0)); + if (is_t_big_real(x)) + return(make_boolean(sc, mpfr_cmp_si(big_real(x), y) > 0)); + if (is_t_big_ratio(x)) + return(make_boolean(sc, mpq_cmp_si(big_ratio(x), y, 1) > 0)); +#endif + return(method_or_bust(sc, x, sc->gt_symbol, args, a_number_string, 1)); +} + +static s7_pointer g_greater_xf(s7_scheme *sc, s7_pointer args) +{ + s7_double y = real(cadr(args)); + s7_pointer x = car(args); + + if (is_t_real(x)) + return(make_boolean(sc, real(x) > y)); + + switch (type(x)) + { + case T_INTEGER: return(make_boolean(sc, integer(x) > y)); + + case T_RATIO: + /* (> 9223372036854775807/9223372036854775806 1.0) */ + if (denominator(x) < S7_INT32_MAX) /* y range check was handled in greater_chooser */ + return(make_boolean(sc, (numerator(x) > (y * denominator(x))))); + return(make_boolean(sc, fraction(x) > y)); + +#if WITH_GMP + case T_BIG_INTEGER: + mpfr_set_d(sc->mpfr_1, y, MPFR_RNDN); + return(make_boolean(sc, mpfr_cmp_z(sc->mpfr_1, big_integer(x)) < 0)); + case T_BIG_RATIO: + mpfr_set_d(sc->mpfr_1, y, MPFR_RNDN); + return(make_boolean(sc, mpfr_cmp_q(sc->mpfr_1, big_ratio(x)) < 0)); + case T_BIG_REAL: + return(make_boolean(sc, mpfr_cmp_d(big_real(x), y) > 0)); +#endif + default: + return(method_or_bust(sc, x, sc->gt_symbol, args, a_number_string, 1)); + } + return(sc->T); +} + +static inline s7_pointer gt_p_pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) {return(make_boolean(sc, gt_b_7pp(sc, p1, p2)));} +static bool gt_b_ii(s7_int i1, s7_int i2) {return(i1 > i2);} +static bool gt_b_dd(s7_double i1, s7_double i2) {return(i1 > i2);} +static s7_pointer gt_p_dd(s7_scheme *sc, s7_double x1, s7_double x2) {return(make_boolean(sc, x1 > x2));} +static s7_pointer gt_p_ii(s7_scheme *sc, s7_int x1, s7_int x2) {return(make_boolean(sc, x1 > x2));} + +static bool gt_b_pi(s7_scheme *sc, s7_pointer p1, s7_int p2) +{ + if (is_t_integer(p1)) return(integer(p1) > p2); + if (is_t_real(p1)) return(real(p1) > p2); + if (is_t_ratio(p1)) return(!ratio_leq_pi(p1, p2)); +#if WITH_GMP + if (is_t_big_integer(p1)) + return(mpz_cmp_si(big_integer(p1), p2) > 0); + if (is_t_big_real(p1)) + return(mpfr_cmp_si(big_real(p1), p2) > 0); + if (is_t_big_ratio(p1)) + return(mpq_cmp_si(big_ratio(p1), p2, 1) > 0); +#endif + return(gt_out_x(sc, p1, make_integer(sc, p2))); +} + +static s7_pointer gt_p_pi(s7_scheme *sc, s7_pointer p1, s7_int p2) {return(make_boolean(sc, gt_b_pi(sc, p1, p2)));} + +static s7_pointer g_greater_2(s7_scheme *sc, s7_pointer args) +{ + /* ridiculous repetition, but overheads are killing this poor thing */ + s7_pointer x = car(args), y = cadr(args); + if (type(x) == type(y)) + { + if (is_t_integer(x)) return(make_boolean(sc, integer(x) > integer(y))); + if (is_t_real(x)) return(make_boolean(sc, real(x) > real(y))); + if (is_t_ratio(x)) return(make_boolean(sc, fraction(x) > fraction(y))); + } + switch (type(x)) + { + case T_INTEGER: + switch (type(y)) + { + case T_RATIO: return(gt_p_pp(sc, x, y)); + case T_REAL: return(make_boolean(sc, integer(x) > real(y))); +#if WITH_GMP + case T_BIG_INTEGER: case T_BIG_RATIO: case T_BIG_REAL: + return(gt_p_pp(sc, x, y)); +#endif + default: return(make_boolean(sc, gt_out_y(sc, x, y))); + } + break; + + case T_RATIO: return(gt_p_pp(sc, x, y)); + + case T_REAL: + switch (type(y)) + { + case T_INTEGER: return(make_boolean(sc, real(x) > integer(y))); + case T_RATIO: return(make_boolean(sc, real(x) > fraction(y))); +#if WITH_GMP + case T_BIG_INTEGER: case T_BIG_RATIO: case T_BIG_REAL: + return(gt_p_pp(sc, x, y)); +#endif + default: return(make_boolean(sc, gt_out_y(sc, x, y))); + } + break; +#if WITH_GMP + case T_BIG_INTEGER: case T_BIG_RATIO: case T_BIG_REAL: + return(gt_p_pp(sc, x, y)); +#endif + + default: return(make_boolean(sc, gt_out_x(sc, x, y))); + } + return(sc->T); +} + +static s7_pointer greater_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool ops) +{ + if (args != 2) return(f); + if (ops) + { + s7_pointer arg2 = caddr(expr); + if ((is_t_integer(arg2)) && + (integer(arg2) < S7_INT32_MAX) && + (integer(arg2) > S7_INT32_MIN)) + return(sc->greater_xi); + if ((is_t_real(arg2)) && + (real(arg2) < S7_INT32_MAX) && + (real(arg2) > S7_INT32_MIN)) + return(sc->greater_xf); + } + return(sc->greater_2); +} + + +/* ---------------------------------------- >= ---------------------------------------- */ +static bool geq_out_x(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + if (!has_active_methods(sc, x)) + wrong_type_error_nr(sc, sc->geq_symbol, 1, x, sc->type_names[T_REAL]); + return(find_and_apply_method(sc, x, sc->geq_symbol, list_2(sc, x, y)) != sc->F); /* not plist */ +} + +static bool geq_out_y(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + if (!has_active_methods(sc, y)) + wrong_type_error_nr(sc, sc->geq_symbol, 2, y, sc->type_names[T_REAL]); + return(find_and_apply_method(sc, y, sc->geq_symbol, list_2(sc, x, y)) != sc->F); /* not plist */ +} + +static bool geq_b_7pp(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + if (type(x) == type(y)) + { + if (is_t_integer(x)) + return(integer(x) >= integer(y)); + if (is_t_real(x)) + return(real(x) >= real(y)); + if (is_t_ratio(x)) + return(fraction(x) >= fraction(y)); +#if WITH_GMP + if (is_t_big_integer(x)) + return(mpz_cmp(big_integer(x), big_integer(y)) >= 0); + if (is_t_big_ratio(x)) + return(mpq_cmp(big_ratio(x), big_ratio(y)) >= 0); + if (is_t_big_real(x)) + return(mpfr_greaterequal_p(big_real(x), big_real(y))); +#endif + } + switch (type(x)) + { + case T_INTEGER: + switch (type(y)) + { + case T_RATIO: return(integer(x) >= fraction(y)); /* ?? */ + case T_REAL: return(integer(x) >= real(y)); +#if WITH_GMP + case T_BIG_INTEGER: return(mpz_cmp_si(big_integer(y), integer(x)) <= 0); + case T_BIG_RATIO: return(mpq_cmp_si(big_ratio(y), integer(x), 1) <= 0); + case T_BIG_REAL: + return((!mpfr_nan_p(big_real(y))) && (mpfr_cmp_si(big_real(y), integer(x)) <= 0)); +#endif + default: return(geq_out_y(sc, x, y)); + } + break; + + case T_RATIO: + switch (type(y)) + { + case T_INTEGER: return(fraction(x) >= integer(y)); + case T_REAL: return(fraction(x) >= real(y)); +#if WITH_GMP + case T_BIG_INTEGER: + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + return(mpq_cmp_z(sc->mpq_1, big_integer(y)) >= 0); + case T_BIG_RATIO: + return(mpq_cmp_si(big_ratio(y), numerator(x), denominator(x)) <= 0); + case T_BIG_REAL: + if (mpfr_nan_p(big_real(y))) return(false); + mpq_set_si(sc->mpq_1, numerator(x), denominator(x)); + return(mpfr_cmp_q(big_real(y), sc->mpq_1) <= 0); +#endif + default: return(geq_out_y(sc, x, y)); + } + + case T_REAL: + switch (type(y)) + { + case T_INTEGER: return(real(x) >= integer(y)); + case T_RATIO: return(real(x) >= fraction(y)); +#if WITH_GMP + case T_BIG_INTEGER: + if (is_NaN(real(x))) return(false); + mpfr_set_d(sc->mpfr_1, real(x), MPFR_RNDN); + return(mpfr_cmp_z(sc->mpfr_1, big_integer(y)) >= 0); + case T_BIG_RATIO: + if (is_NaN(real(x))) return(false); + mpfr_set_d(sc->mpfr_1, real(x), MPFR_RNDN); + return(mpfr_cmp_q(sc->mpfr_1, big_ratio(y)) >= 0); + case T_BIG_REAL: + if (is_NaN(real(x))) return(false); + return((!mpfr_nan_p(big_real(y))) && (mpfr_cmp_d(big_real(y), real(x)) <= 0)); +#endif + default: return(geq_out_y(sc, x, y)); + } + break; + +#if WITH_GMP + case T_BIG_INTEGER: + switch (type(y)) + { + case T_INTEGER: + return(mpz_cmp_si(big_integer(x), integer(y)) >= 0); + case T_RATIO: + mpq_set_z(sc->mpq_1, big_integer(x)); + return(mpq_cmp_si(sc->mpq_1, numerator(y), denominator(y)) >= 0); + case T_REAL: + if (is_NaN(real(y))) return(false); + mpfr_set_z(sc->mpfr_1, big_integer(x), MPFR_RNDN); + return(mpfr_cmp_d(sc->mpfr_1, real(y)) >= 0); + case T_BIG_RATIO: + return(mpq_cmp_z(big_ratio(y), big_integer(x)) <= 0); + case T_BIG_REAL: + return((!mpfr_nan_p(big_real(y))) && (mpfr_cmp_z(big_real(y), big_integer(x)) <= 0)); + default: return(geq_out_y(sc, x, y)); + } + case T_BIG_RATIO: + switch (type(y)) + { + case T_INTEGER: + return(mpq_cmp_si(big_ratio(x), integer(y), 1) >= 0); + case T_RATIO: + return(mpq_cmp_si(big_ratio(x), numerator(y), denominator(y)) >= 0); + case T_REAL: + if (is_NaN(real(y))) return(false); + mpfr_set_q(sc->mpfr_1, big_ratio(x), MPFR_RNDN); + return(mpfr_cmp_d(sc->mpfr_1, real(y)) >= 0); + case T_BIG_INTEGER: + return(mpq_cmp_z(big_ratio(x), big_integer(y)) >= 0); + case T_BIG_REAL: + return((!mpfr_nan_p(big_real(y))) && (mpfr_cmp_q(big_real(y), big_ratio(x)) <= 0)); + default: return(geq_out_y(sc, x, y)); + } + + case T_BIG_REAL: + if ((is_real(y)) && (mpfr_nan_p(big_real(x)))) return(false); + switch (type(y)) + { + case T_INTEGER: + return(mpfr_cmp_si(big_real(x), integer(y)) >= 0); + case T_RATIO: + mpq_set_si(sc->mpq_1, numerator(y), denominator(y)); + return(mpfr_cmp_q(big_real(x), sc->mpq_1) >= 0); + case T_REAL: + return((!is_NaN(real(y))) && (mpfr_cmp_d(big_real(x), real(y)) >= 0)); + case T_BIG_INTEGER: + return(mpfr_cmp_z(big_real(x), big_integer(y)) >= 0); + case T_BIG_RATIO: + return(mpfr_cmp_q(big_real(x), big_ratio(y)) >= 0); + default: return(geq_out_y(sc, x, y)); + } +#endif + default: return(geq_out_x(sc, x, y)); + } + return(true); +} + +static s7_pointer g_greater_or_equal(s7_scheme *sc, s7_pointer args) +{ + #define H_greater_or_equal "(>= x1 ...) returns #t if its arguments are in non-increasing order" + #define Q_greater_or_equal s7_make_circular_signature(sc, 1, 2, sc->is_boolean_symbol, sc->is_real_symbol) + + s7_pointer x = car(args), p = cdr(args); + if (is_null(cdr(p))) + return(make_boolean(sc, geq_b_7pp(sc, x, car(p)))); + + for (; is_pair(p); x = car(p), p = cdr(p)) + if (!geq_b_7pp(sc, x, car(p))) + { + for (p = cdr(p); is_pair(p); p = cdr(p)) + if (!is_real_via_method(sc, car(p))) + wrong_type_error_nr(sc, sc->geq_symbol, position_of(p, args), car(p), sc->type_names[T_REAL]); + return(sc->F); + } + return(sc->T); +} + +static inline s7_pointer geq_p_pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) {return(make_boolean(sc, geq_b_7pp(sc, p1, p2)));} +static bool geq_b_ii(s7_int i1, s7_int i2) {return(i1 >= i2);} +static bool geq_b_dd(s7_double i1, s7_double i2) {return(i1 >= i2);} +static s7_pointer geq_p_dd(s7_scheme *sc, s7_double x1, s7_double x2) {return(make_boolean(sc, x1 >= x2));} +static s7_pointer geq_p_ii(s7_scheme *sc, s7_int x1, s7_int x2) {return(make_boolean(sc, x1 >= x2));} + +static s7_pointer g_geq_2(s7_scheme *sc, s7_pointer args) {return(make_boolean(sc, geq_b_7pp(sc, car(args), cadr(args))));} + +static s7_pointer g_geq_xf(s7_scheme *sc, s7_pointer args) +{ + s7_double y = real(cadr(args)); + s7_pointer x = car(args); + return(make_boolean(sc, ((is_t_real(x)) ? (real(x) >= y) : geq_b_7pp(sc, car(args), cadr(args))))); +} + +static s7_pointer g_geq_xi(s7_scheme *sc, s7_pointer args) +{ + s7_int y = integer(cadr(args)); + s7_pointer x = car(args); + + if (is_t_integer(x)) + return(make_boolean(sc, integer(x) >= y)); + if (is_t_real(x)) + return(make_boolean(sc, real(x) >= y)); + if (is_t_ratio(x)) + return(make_boolean(sc, !ratio_lt_pi(x, y))); +#if WITH_GMP + if (is_t_big_integer(x)) + return(make_boolean(sc, mpz_cmp_si(big_integer(x), y) >= 0)); + if (is_t_big_real(x)) + { + if (mpfr_nan_p(big_real(x))) return(sc->F); + return(make_boolean(sc, mpfr_cmp_si(big_real(x), y) >= 0)); + } + if (is_t_big_ratio(x)) + return(make_boolean(sc, mpq_cmp_si(big_ratio(x), y, 1) >= 0)); +#endif + return(method_or_bust(sc, x, sc->geq_symbol, args, sc->type_names[T_REAL], 1)); +} + +static bool geq_b_pi(s7_scheme *sc, s7_pointer p1, s7_int p2) +{ + if (is_t_integer(p1)) return(integer(p1) >= p2); + if (is_t_real(p1)) return(real(p1) >= p2); + if (is_t_ratio(p1)) return(!ratio_lt_pi(p1, p2)); +#if WITH_GMP + if (is_t_big_integer(p1)) + return(mpz_cmp_si(big_integer(p1), p2) >= 0); + if (is_t_big_real(p1)) + return((!mpfr_nan_p(big_real(p1))) && (mpfr_cmp_si(big_real(p1), p2) >= 0)); + if (is_t_big_ratio(p1)) + return(mpq_cmp_si(big_ratio(p1), p2, 1) >= 0); +#endif + return(geq_out_x(sc, p1, make_integer(sc, p2))); +} + +static s7_pointer geq_p_pi(s7_scheme *sc, s7_pointer p1, s7_int p2) {return(make_boolean(sc, geq_b_pi(sc, p1, p2)));} + +static s7_pointer geq_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool ops) +{ + if (args != 2) return(f); + if (ops) + { + s7_pointer arg2 = caddr(expr); + if ((is_t_integer(arg2)) && + (integer(arg2) < S7_INT32_MAX) && + (integer(arg2) > S7_INT32_MIN)) + return(sc->geq_xi); + if ((is_t_real(arg2)) && + (real(arg2) < S7_INT32_MAX) && + (real(arg2) > S7_INT32_MIN)) + return(sc->geq_xf); + } + return(sc->geq_2); +} + + +/* ---------------------------------------- real-part ---------------------------------------- */ +s7_double s7_real_part(s7_pointer x) +{ + switch(type(x)) + { + case T_INTEGER: return((s7_double)integer(x)); + case T_RATIO: return(fraction(x)); + case T_REAL: return(real(x)); + case T_COMPLEX: return(real_part(x)); +#if WITH_GMP + case T_BIG_INTEGER: return((s7_double)mpz_get_si(big_integer(x))); + case T_BIG_RATIO: return((s7_double)((long_double)mpz_get_si(mpq_numref(big_ratio(x))) / + (long_double)mpz_get_si(mpq_denref(big_ratio(x))))); + case T_BIG_REAL: return((s7_double)mpfr_get_d(big_real(x), MPFR_RNDN)); + case T_BIG_COMPLEX: return((s7_double)mpfr_get_d(mpc_realref(big_complex(x)), MPFR_RNDN)); +#endif + } + return(0.0); +} + +static s7_pointer real_part_p_p(s7_scheme *sc, s7_pointer p) +{ + if (is_t_complex(p)) return(make_real(sc, real_part(p))); + switch (type(p)) + { + case T_INTEGER: case T_RATIO: case T_REAL: + return(p); +#if WITH_GMP + case T_BIG_INTEGER: case T_BIG_RATIO: case T_BIG_REAL: + return(p); + case T_BIG_COMPLEX: + { + s7_pointer x; + new_cell(sc, x, T_BIG_REAL); + big_real_bgf(x) = alloc_bigflt(sc); + add_big_real(sc, x); + mpc_real(big_real(x), big_complex(p), MPFR_RNDN); + return(x); + } +#endif + default: + return(method_or_bust_p(sc, p, sc->real_part_symbol, a_number_string)); + } +} + +static s7_pointer g_real_part(s7_scheme *sc, s7_pointer args) +{ + #define H_real_part "(real-part num) returns the real part of num" + #define Q_real_part s7_make_signature(sc, 2, sc->is_real_symbol, sc->is_number_symbol) + return(real_part_p_p(sc, car(args))); +} + + +/* ---------------------------------------- imag-part ---------------------------------------- */ +s7_double s7_imag_part(s7_pointer x) +{ + if (is_t_complex(x)) + return(imag_part(x)); +#if WITH_GMP + if (is_t_big_complex(x)) + return((s7_double)mpfr_get_d(mpc_imagref(big_complex(x)), MPFR_RNDN)); +#endif + return(0.0); +} + +static s7_pointer imag_part_p_p(s7_scheme *sc, s7_pointer p) +{ + if (is_t_complex(p)) return(make_real(sc, imag_part(p))); + switch (type(p)) + { + case T_INTEGER: case T_RATIO: + return(int_zero); + case T_REAL: + return(real_zero); +#if WITH_GMP + case T_BIG_INTEGER: case T_BIG_RATIO: + return(int_zero); + case T_BIG_REAL: + return(real_zero); + case T_BIG_COMPLEX: + { + s7_pointer x; + new_cell(sc, x, T_BIG_REAL); + big_real_bgf(x) = alloc_bigflt(sc); + add_big_real(sc, x); + mpc_imag(big_real(x), big_complex(p), MPFR_RNDN); + return(x); + } +#endif + default: + return(method_or_bust_p(sc, p, sc->imag_part_symbol, a_number_string)); + } +} + +static s7_pointer g_imag_part(s7_scheme *sc, s7_pointer args) +{ + #define H_imag_part "(imag-part num) returns the imaginary part of num" + #define Q_imag_part s7_make_signature(sc, 2, sc->is_real_symbol, sc->is_number_symbol) + /* currently (imag-part +nan.0) -> 0.0 ? it's true but maybe confusing */ + return(imag_part_p_p(sc, car(args))); +} + + +/* ---------------------------------------- numerator denominator ---------------------------------------- */ +static s7_int numerator_i_7p(s7_scheme *sc, s7_pointer p) +{ + if (is_t_ratio(p)) return(numerator(p)); + if (is_t_integer(p)) return(integer(p)); +#if WITH_GMP + if (is_t_big_ratio(p)) return(mpz_get_si(mpq_numref(big_ratio(p)))); + if (is_t_big_integer(p)) return(mpz_get_si(big_integer(p))); +#endif + return(integer(method_or_bust_p(sc, p, sc->numerator_symbol, a_rational_string))); +} + +static s7_pointer g_numerator(s7_scheme *sc, s7_pointer args) +{ + #define H_numerator "(numerator rat) returns the numerator of the rational number rat" + #define Q_numerator s7_make_signature(sc, 2, sc->is_integer_symbol, sc->is_rational_symbol) + + s7_pointer x = car(args); + switch (type(x)) + { + case T_RATIO: return(make_integer(sc, numerator(x))); + case T_INTEGER: return(x); +#if WITH_GMP + case T_BIG_INTEGER: return(x); + case T_BIG_RATIO: return(mpz_to_integer(sc, mpq_numref(big_ratio(x)))); +#endif + default: return(method_or_bust_p(sc, x, sc->numerator_symbol, a_rational_string)); + } +} + + +static s7_pointer g_denominator(s7_scheme *sc, s7_pointer args) +{ + #define H_denominator "(denominator rat) returns the denominator of the rational number rat" + #define Q_denominator s7_make_signature(sc, 2, sc->is_integer_symbol, sc->is_rational_symbol) + + s7_pointer x = car(args); + switch (type(x)) + { + case T_RATIO: return(make_integer(sc, denominator(x))); + case T_INTEGER: return(int_one); +#if WITH_GMP + case T_BIG_INTEGER: return(int_one); + case T_BIG_RATIO: return(mpz_to_integer(sc, mpq_denref(big_ratio(x)))); +#endif + default: return(method_or_bust_p(sc, x, sc->denominator_symbol, a_rational_string)); + } +} + +static s7_int denominator_i_7p(s7_scheme *sc, s7_pointer p) +{ + if (is_t_ratio(p)) return(denominator(p)); + if (is_t_integer(p)) return(1); +#if WITH_GMP + if (is_t_big_ratio(p)) return(mpz_get_si(mpq_denref(big_ratio(p)))); + if (is_t_big_integer(p)) return(1); +#endif + return(integer(method_or_bust_p(sc, p, sc->denominator_symbol, a_rational_string))); +} + + +/* ---------------------------------------- number? bignum? complex? integer? byte? rational? real? ---------------------------------------- */ +static s7_pointer g_is_number(s7_scheme *sc, s7_pointer args) +{ + #define H_is_number "(number? obj) returns #t if obj is a number" + #define Q_is_number sc->pl_bt + check_boolean_method(sc, is_number, sc->is_number_symbol, args); +} + +bool s7_is_bignum(s7_pointer obj) {return(is_big_number(obj));} + +static s7_pointer g_is_bignum(s7_scheme *sc, s7_pointer args) +{ + #define H_is_bignum "(bignum? obj) returns #t if obj is a multiprecision number." + #define Q_is_bignum sc->pl_bt + return(s7_make_boolean(sc, is_big_number(car(args)))); +} + +static s7_pointer g_is_integer(s7_scheme *sc, s7_pointer args) +{ + #define H_is_integer "(integer? obj) returns #t if obj is an integer" + #define Q_is_integer sc->pl_bt + check_boolean_method(sc, s7_is_integer, sc->is_integer_symbol, args); +} + +static bool is_byte(s7_pointer p) {return((s7_is_integer(p)) && (s7_integer(p) >= 0) && (s7_integer(p) < 256));} +static s7_pointer g_is_byte(s7_scheme *sc, s7_pointer args) +{ + #define H_is_byte "(byte? obj) returns #t if obj is a byte (an integer between 0 and 255)" + #define Q_is_byte sc->pl_bt + check_boolean_method(sc, is_byte, sc->is_byte_symbol, args); +} + +static s7_pointer g_is_real(s7_scheme *sc, s7_pointer args) +{ + #define H_is_real "(real? obj) returns #t if obj is a real number" + #define Q_is_real sc->pl_bt + check_boolean_method(sc, is_real, sc->is_real_symbol, args); +} + +static s7_pointer g_is_complex(s7_scheme *sc, s7_pointer args) +{ + #define H_is_complex "(complex? obj) returns #t if obj is a number" + #define Q_is_complex sc->pl_bt + check_boolean_method(sc, is_number, sc->is_complex_symbol, args); +} + +static s7_pointer g_is_rational(s7_scheme *sc, s7_pointer args) +{ + #define H_is_rational "(rational? obj) returns #t if obj is a rational number (either an integer or a ratio)" + #define Q_is_rational sc->pl_bt + check_boolean_method(sc, is_rational, sc->is_rational_symbol, args); + /* in the non-gmp case, (rational? 455702434782048082459/86885567283849955830) -> #f, not #t, and similarly for exact? etc */ +} + +static s7_pointer g_is_float(s7_scheme *sc, s7_pointer args) +{ + #define H_is_float "(float? x) returns #t is x is real and not rational." + #define Q_is_float sc->pl_bt + s7_pointer p = car(args); +#if WITH_GMP + return(make_boolean(sc, (is_t_real(p)) || (is_t_big_real(p)))); /* (float? pi) */ +#else + return(make_boolean(sc, is_t_real(p))); +#endif +} + +#if WITH_GMP +static bool is_float_b(s7_pointer p) {return((is_t_real(p)) || (is_t_big_real(p)));} +#else +static bool is_float_b(s7_pointer p) {return(is_t_real(p));} +#endif + + +/* ---------------------------------------- nan? ---------------------------------------- */ +static bool is_nan_b_7p(s7_scheme *sc, s7_pointer x) +{ + if (is_t_real(x)) return(is_NaN(real(x))); + switch (type(x)) + { + case T_INTEGER: + case T_RATIO: return(false); + /* case T_REAL: return(is_NaN(real(x))); */ + case T_COMPLEX: return((is_NaN(real_part(x))) || (is_NaN(imag_part(x)))); +#if WITH_GMP + case T_BIG_INTEGER: + case T_BIG_RATIO: return(false); + case T_BIG_REAL: return(mpfr_nan_p(big_real(x)) != 0); + case T_BIG_COMPLEX: return((mpfr_nan_p(mpc_realref(big_complex(x))) != 0) || (mpfr_nan_p(mpc_imagref(big_complex(x))) != 0)); +#endif + default: + if (is_number(x)) + return(method_or_bust_p(sc, x, sc->is_nan_symbol, a_number_string) != sc->F); + } + return(false); +} + +static s7_pointer g_is_nan(s7_scheme *sc, s7_pointer args) +{ + #define H_is_nan "(nan? obj) returns #t if obj is a NaN" + #define Q_is_nan sc->pl_bt + return(make_boolean(sc, is_nan_b_7p(sc, car(args)))); +} + + +/* ---------------------------------------- infinite? ---------------------------------------- */ +static bool is_infinite_b_7p(s7_scheme *sc, s7_pointer x) +{ + switch (type(x)) + { + case T_INTEGER: + case T_RATIO: return(false); + case T_REAL: return(is_inf(real(x))); + case T_COMPLEX: return((is_inf(real_part(x))) || (is_inf(imag_part(x)))); +#if WITH_GMP + case T_BIG_INTEGER: + case T_BIG_RATIO: return(false); + case T_BIG_REAL: return(mpfr_inf_p(big_real(x)) != 0); + case T_BIG_COMPLEX: + return((mpfr_inf_p(mpc_realref(big_complex(x))) != 0) || + (mpfr_inf_p(mpc_imagref(big_complex(x))) != 0)); +#endif + default: + if (is_number(x)) + return(method_or_bust_p(sc, x, sc->is_infinite_symbol, a_number_string) != sc->F); + } + return(false); +} + +static s7_pointer g_is_infinite(s7_scheme *sc, s7_pointer args) +{ + #define H_is_infinite "(infinite? obj) returns #t if obj is an infinite real" + #define Q_is_infinite sc->pl_bt + return(make_boolean(sc, is_infinite_b_7p(sc, car(args)))); +} + + +/* ---------------------------------------- even? odd?---------------------------------------- */ +static bool is_even_b_7p(s7_scheme *sc, s7_pointer p) +{ + if (is_t_integer(p)) + return((integer(p) & 1) == 0); +#if WITH_GMP + if (is_t_big_integer(p)) + return(mpz_even_p(big_integer(p))); +#endif + return(method_or_bust_p(sc, p, sc->is_even_symbol, sc->type_names[T_INTEGER]) != sc->F); +} + +static s7_pointer is_even_p_p(s7_scheme *sc, s7_pointer x) +{ + if (is_t_integer(x)) + return(make_boolean(sc, (integer(x) & 1) == 0)); + return(make_boolean(sc, is_even_b_7p(sc, x))); +} + +static bool is_even_i(s7_int i1) {return((i1 & 1) == 0);} + +static s7_pointer g_is_even(s7_scheme *sc, s7_pointer args) +{ + #define H_is_even "(even? int) returns #t if the integer int32_t is even" + #define Q_is_even s7_make_signature(sc, 2, sc->is_boolean_symbol, sc->is_integer_symbol) + return(make_boolean(sc, is_even_b_7p(sc, car(args)))); +} + + +static bool is_odd_b_7p(s7_scheme *sc, s7_pointer p) +{ + if (is_t_integer(p)) + return((integer(p) & 1) == 1); +#if WITH_GMP + if (is_t_big_integer(p)) + return(mpz_odd_p(big_integer(p))); +#endif + return(method_or_bust_p(sc, p, sc->is_odd_symbol, sc->type_names[T_INTEGER]) != sc->F); +} + +static s7_pointer is_odd_p_p(s7_scheme *sc, s7_pointer x) +{ + if (is_t_integer(x)) + return(make_boolean(sc, (integer(x) & 1) == 1)); + return(make_boolean(sc, is_odd_b_7p(sc, x))); +} + +static bool is_odd_i(s7_int i1) {return((i1 & 1) == 1);} + +static s7_pointer g_is_odd(s7_scheme *sc, s7_pointer args) +{ + #define H_is_odd "(odd? int) returns #t if the integer int32_t is odd" + #define Q_is_odd s7_make_signature(sc, 2, sc->is_boolean_symbol, sc->is_integer_symbol) + return(make_boolean(sc, is_odd_b_7p(sc, car(args)))); +} + + +/* ---------------------------------------- zero? ---------------------------------------- */ +static bool is_zero(s7_pointer x) +{ + switch (type(x)) + { + case T_INTEGER: return(integer(x) == 0); + case T_REAL: return(real(x) == 0.0); +#if WITH_GMP + case T_BIG_INTEGER: return(mpz_cmp_ui(big_integer(x), 0) == 0); + case T_BIG_REAL: return(mpfr_zero_p(big_real(x))); +#endif + default: + return(false); /* ratios and complex numbers here are already collapsed into integers and reals */ + } +} + +static bool is_zero_b_7p(s7_scheme *sc, s7_pointer p) +{ + if (is_t_integer(p)) return(integer(p) == 0); + if (is_t_real(p)) return(real(p) == 0.0); +#if WITH_GMP + if (is_number(p)) return(is_zero(p)); +#else + if (is_number(p)) return(false); +#endif + return(method_or_bust_p(sc, p, sc->is_zero_symbol, a_number_string) != sc->F); +} + +static s7_pointer g_is_zero(s7_scheme *sc, s7_pointer args) +{ + #define H_is_zero "(zero? num) returns #t if the number num is zero" + #define Q_is_zero sc->pl_bn + return(make_boolean(sc, is_zero_b_7p(sc, car(args)))); +} + +static s7_pointer is_zero_p_p(s7_scheme *sc, s7_pointer p) {return(make_boolean(sc, is_zero_b_7p(sc, p)));} +static bool is_zero_i(s7_int p) {return(p == 0);} +static bool is_zero_d(s7_double p) {return(p == 0.0);} + + +/* -------------------------------- positive? -------------------------------- */ +static bool is_positive(s7_scheme *sc, s7_pointer x) +{ + switch (type(x)) + { + case T_INTEGER: return(integer(x) > 0); + case T_RATIO: return(numerator(x) > 0); + case T_REAL: return(real(x) > 0.0); +#if WITH_GMP + case T_BIG_INTEGER: return(mpz_cmp_ui(big_integer(x), 0) > 0); + case T_BIG_RATIO: return(mpq_cmp_ui(big_ratio(x), 0, 1) > 0); + case T_BIG_REAL: return(mpfr_cmp_ui(big_real(x), 0) > 0); +#endif + default: + sole_arg_wrong_type_error_nr(sc, sc->is_positive_symbol, x, sc->type_names[T_REAL]); + } + return(false); +} + +static bool is_positive_b_7p(s7_scheme *sc, s7_pointer p) +{ + if (is_t_integer(p)) return(integer(p) > 0); + if (is_t_real(p)) return(real(p) > 0.0); +#if WITH_GMP + if (is_number(p)) return(is_positive(sc, p)); +#else + if (is_t_ratio(p)) return(numerator(p) > 0); +#endif + return(method_or_bust_p(sc, p, sc->is_positive_symbol, sc->type_names[T_REAL]) != sc->F); +} + +static s7_pointer g_is_positive(s7_scheme *sc, s7_pointer args) +{ + #define H_is_positive "(positive? num) returns #t if the real number num is positive (greater than 0)" + #define Q_is_positive s7_make_signature(sc, 2, sc->is_boolean_symbol, sc->is_real_symbol) + return(make_boolean(sc, is_positive_b_7p(sc, car(args)))); +} + +static s7_pointer is_positive_p_p(s7_scheme *sc, s7_pointer p) {return(make_boolean(sc, is_positive_b_7p(sc, p)));} +static bool is_positive_i(s7_int p) {return(p > 0);} +static bool is_positive_d(s7_double p) {return(p > 0.0);} + + +/* -------------------------------- negative? -------------------------------- */ +static bool is_negative(s7_scheme *sc, s7_pointer x) +{ + switch (type(x)) + { + case T_INTEGER: return(integer(x) < 0); + case T_RATIO: return(numerator(x) < 0); + case T_REAL: return(real(x) < 0.0); +#if WITH_GMP + case T_BIG_INTEGER: return(mpz_cmp_ui(big_integer(x), 0) < 0); + case T_BIG_RATIO: return(mpq_cmp_ui(big_ratio(x), 0, 1) < 0); + case T_BIG_REAL: return(mpfr_cmp_ui(big_real(x), 0) < 0); +#endif + default: + sole_arg_wrong_type_error_nr(sc, sc->is_negative_symbol, x, sc->type_names[T_REAL]); + } + return(false); +} + +static bool is_negative_b_7p(s7_scheme *sc, s7_pointer p) +{ + if (is_t_integer(p)) return(integer(p) < 0); + if (is_t_real(p)) return(real(p) < 0.0); +#if WITH_GMP + if (is_number(p)) return(is_negative(sc, p)); +#else + if (is_t_ratio(p)) return(numerator(p) < 0); +#endif + return(method_or_bust_p(sc, p, sc->is_negative_symbol, sc->type_names[T_REAL]) != sc->F); +} + +static s7_pointer g_is_negative(s7_scheme *sc, s7_pointer args) +{ + #define H_is_negative "(negative? num) returns #t if the real number num is negative (less than 0)" + #define Q_is_negative s7_make_signature(sc, 2, sc->is_boolean_symbol, sc->is_real_symbol) + return(make_boolean(sc, is_negative_b_7p(sc, car(args)))); +} + +static s7_pointer is_negative_p_p(s7_scheme *sc, s7_pointer p) {return(make_boolean(sc, is_negative_b_7p(sc, p)));} +static bool is_negative_i(s7_int p) {return(p < 0);} +static bool is_negative_d(s7_double p) {return(p < 0.0);} + + +#if (!WITH_PURE_S7) +/* ---------------------------------------- exact<->inexact exact? inexact? ---------------------------------------- */ +static s7_pointer exact_to_inexact_p_p(s7_scheme *sc, s7_pointer x) +{ + switch (type(x)) + { + case T_INTEGER: +#if WITH_GMP + if ((integer(x) > INT64_TO_DOUBLE_LIMIT) || (integer(x) < -INT64_TO_DOUBLE_LIMIT)) + return(s7_number_to_big_real(sc, x)); +#endif + return(make_real(sc, (s7_double)(integer(x)))); + + case T_RATIO: +#if WITH_GMP + if ((numerator(x) > INT64_TO_DOUBLE_LIMIT) || (numerator(x) < -INT64_TO_DOUBLE_LIMIT) || + (denominator(x) > INT64_TO_DOUBLE_LIMIT)) /* just a guess */ + return(s7_number_to_big_real(sc, x)); +#endif + return(make_real(sc, (s7_double)(fraction(x)))); + +#if WITH_GMP + case T_BIG_INTEGER: + return(big_integer_to_big_real(sc, x)); + case T_BIG_RATIO: + return(big_ratio_to_big_real(sc, x)); +#endif + case T_REAL: case T_BIG_REAL: + case T_COMPLEX: case T_BIG_COMPLEX: + return(x); /* apparently (exact->inexact 1+i) is not an error */ + default: + return(method_or_bust_p(sc, x, sc->exact_to_inexact_symbol, a_number_string)); + } +} + +static s7_pointer g_exact_to_inexact(s7_scheme *sc, s7_pointer args) +{ + #define H_exact_to_inexact "(exact->inexact num) converts num to an inexact number; (exact->inexact 3/2) = 1.5" + #define Q_exact_to_inexact s7_make_signature(sc, 2, sc->is_number_symbol, sc->is_number_symbol) + /* arg can be complex -> itself! */ + return(exact_to_inexact_p_p(sc, car(args))); +} + +static s7_pointer inexact_to_exact_p_p(s7_scheme *sc, s7_pointer x) +{ + switch (type(x)) + { + case T_INTEGER: case T_BIG_INTEGER: + case T_RATIO: case T_BIG_RATIO: + return(x); + +#if WITH_GMP + case T_BIG_REAL: + return(big_rationalize(sc, set_plist_1(sc, x))); +#endif + + case T_REAL: + { + s7_int numer = 0, denom = 1; + s7_double val = real(x); + if ((is_inf(val)) || (is_NaN(val))) + sole_arg_wrong_type_error_nr(sc, sc->inexact_to_exact_symbol, x, a_normal_real_string); + + if ((val > DOUBLE_TO_INT64_LIMIT) || (val < -(DOUBLE_TO_INT64_LIMIT))) + { +#if WITH_GMP + return(big_rationalize(sc, set_plist_1(sc, x))); /* this can handle t_real as well as t_big_real */ +#else + sole_arg_out_of_range_error_nr(sc, sc->inexact_to_exact_symbol, x, it_is_too_large_string); +#endif + } + /* c_rationalize limit is RATIONALIZE_LIMIT=1e12 currently so this is a tighter limit than DOUBLE_TO_INT64_LIMIT */ + if (c_rationalize(val, sc->default_rationalize_error, &numer, &denom)) + return(make_ratio(sc, numer, denom)); + } + + default: + return(method_or_bust_p(sc, x, sc->inexact_to_exact_symbol, sc->type_names[T_REAL])); + } + return(x); +} + +static s7_pointer g_inexact_to_exact(s7_scheme *sc, s7_pointer args) +{ + #define H_inexact_to_exact "(inexact->exact num) converts num to an exact number; (inexact->exact 1.5) = 3/2" + #define Q_inexact_to_exact s7_make_signature(sc, 2, sc->is_real_symbol, sc->is_real_symbol) + return(inexact_to_exact_p_p(sc, car(args))); +} + +static s7_pointer g_is_exact(s7_scheme *sc, s7_pointer args) +{ + #define H_is_exact "(exact? num) returns #t if num is exact (an integer or a ratio)" + #define Q_is_exact sc->pl_bn + + s7_pointer x = car(args); + switch (type(x)) + { + case T_INTEGER: case T_BIG_INTEGER: + case T_RATIO: case T_BIG_RATIO: + return(sc->T); + case T_REAL: case T_BIG_REAL: + case T_COMPLEX: case T_BIG_COMPLEX: + return(sc->F); + default: + return(method_or_bust_p(sc, x, sc->is_exact_symbol, a_number_string)); + } +} + +static bool is_exact_b_7p(s7_scheme *sc, s7_pointer p) +{ + if (!is_number(p)) + return(method_or_bust_p(sc, p, sc->is_exact_symbol, a_number_string) != sc->F); + return(is_rational(p)); +} + + +static s7_pointer g_is_inexact(s7_scheme *sc, s7_pointer args) +{ + #define H_is_inexact "(inexact? num) returns #t if num is inexact (neither an integer nor a ratio)" + #define Q_is_inexact sc->pl_bn + + s7_pointer x = car(args); + switch (type(x)) + { + case T_INTEGER: case T_BIG_INTEGER: + case T_RATIO: case T_BIG_RATIO: + return(sc->F); + case T_REAL: case T_BIG_REAL: + case T_COMPLEX: case T_BIG_COMPLEX: + return(sc->T); + default: + return(method_or_bust_p(sc, x, sc->is_inexact_symbol, a_number_string)); + } +} + +static bool is_inexact_b_7p(s7_scheme *sc, s7_pointer p) +{ + if (!is_number(p)) + return(method_or_bust_p(sc, p, sc->is_inexact_symbol, a_number_string) != sc->F); + return(!is_rational(p)); +} + + +/* ---------------------------------------- integer-length ---------------------------------------- */ +static int32_t integer_length(s7_int a) +{ + if (a < 0) + { + if (a == S7_INT64_MIN) return(63); + a = -a; + } + if (a < 256LL) return(intlen_bits[a]); /* in gmp, sbcl and clisp (integer-length 0) is 0 */ + if (a < 65536LL) return(8 + intlen_bits[a >> 8]); + if (a < 16777216LL) return(16 + intlen_bits[a >> 16]); + if (a < 4294967296LL) return(24 + intlen_bits[a >> 24]); + if (a < 1099511627776LL) return(32 + intlen_bits[a >> 32]); + if (a < 281474976710656LL) return(40 + intlen_bits[a >> 40]); + if (a < 72057594037927936LL) return(48 + intlen_bits[a >> 48]); + return(56 + intlen_bits[a >> 56]); +} + +static s7_pointer g_integer_length(s7_scheme *sc, s7_pointer args) +{ + #define H_integer_length "(integer-length arg) returns the number of bits required to represent the integer 'arg': \ +(ceiling (log (if (< arg 0) (- arg) (+ arg 1)) 2))" + #define Q_integer_length sc->pcl_i + + s7_pointer p = car(args); + if (is_t_integer(p)) + { + s7_int x = integer(p); + return((x < 0) ? small_int(integer_length(-(x + 1))) : small_int(integer_length(x))); + } +#if WITH_GMP + if (is_t_big_integer(p)) + return(make_integer(sc, mpz_sizeinbase(big_integer(p), 2))); +#endif + return(sole_arg_method_or_bust(sc, p, sc->integer_length_symbol, args, sc->type_names[T_INTEGER])); +} + +static s7_int integer_length_i_i(s7_int x) {return((x < 0) ? integer_length(-(x + 1)) : integer_length(x));} +#endif /* !pure s7 */ + + +/* ---------------------------------------- integer-decode-float ---------------------------------------- */ +static s7_pointer g_integer_decode_float(s7_scheme *sc, s7_pointer args) +{ + #define H_integer_decode_float "(integer-decode-float x) returns a list containing the significand, exponent, and \ +sign of 'x' (1 = positive, -1 = negative). (integer-decode-float 0.0): (0 0 1)" + #define Q_integer_decode_float s7_make_signature(sc, 2, sc->is_pair_symbol, sc->is_float_symbol) + + decode_float_t num; + s7_pointer x = car(args); + if (is_t_real(x)) + { + if (real(x) == 0.0) + return(list_3(sc, int_zero, int_zero, int_one)); + num.fx = (double)real(x); + return(list_3(sc, + make_integer_unchecked(sc, (s7_int)((num.ix & 0xfffffffffffffLL) | 0x10000000000000LL)), + make_integer(sc, (s7_int)(((num.ix & 0x7fffffffffffffffLL) >> 52) - 1023 - 52)), + ((num.ix & 0x8000000000000000LL) != 0) ? minus_one : int_one)); + } +#if WITH_GMP + if (is_t_big_real(x)) + { + mp_exp_t exp_n; + bool neg; + exp_n = mpfr_get_z_exp(sc->mpz_1, big_real(x)); + neg = (mpz_cmp_ui(sc->mpz_1, 0) < 0); + if (neg) mpz_abs(sc->mpz_1, sc->mpz_1); + return(list_3(sc, mpz_to_integer(sc, sc->mpz_1), make_integer(sc, exp_n), (neg) ? minus_one : int_one)); + /* not gmp: (integer-decode-float +nan.0): (6755399441055744 972 1), gmp: (integer-decode-float (bignum +nan.0)): (0 -1073741823 1) */ + } +#endif + return(method_or_bust_p(sc, x, sc->integer_decode_float_symbol, wrap_string(sc, "a non-rational real", 19))); +} + + +/* -------------------------------- logior -------------------------------- */ +#if WITH_GMP +static s7_pointer big_logior(s7_scheme *sc, s7_int start, s7_pointer args) +{ + mpz_set_si(sc->mpz_1, start); + for (s7_pointer x = args; is_not_null(x); x = cdr(x)) + { + s7_pointer i = car(x); + switch (type(i)) + { + case T_BIG_INTEGER: + mpz_ior(sc->mpz_1, sc->mpz_1, big_integer(i)); + break; + case T_INTEGER: + mpz_set_si(sc->mpz_2, integer(i)); + mpz_ior(sc->mpz_1, sc->mpz_1, sc->mpz_2); + break; + default: + if (!is_integer_via_method(sc, i)) + wrong_type_error_nr(sc, sc->logior_symbol, position_of(x, args), i, sc->type_names[T_INTEGER]); + return(method_or_bust(sc, i, sc->logior_symbol, + set_ulist_1(sc, mpz_to_integer(sc, sc->mpz_1), x), + sc->type_names[T_INTEGER], position_of(x, args))); + }} + return(mpz_to_integer(sc, sc->mpz_1)); +} +#endif + +static s7_pointer g_logior(s7_scheme *sc, s7_pointer args) +{ + #define H_logior "(logior int32_t ...) returns the OR of its integer arguments (the bits that are on in any of the arguments)" + #define Q_logior sc->pcl_i + + s7_int result = 0; + for (s7_pointer x = args; is_not_null(x); x = cdr(x)) + { +#if WITH_GMP + if (is_t_big_integer(car(x))) + return(big_logior(sc, result, x)); +#endif + if (!is_t_integer(car(x))) + return(method_or_bust(sc, car(x), sc->logior_symbol, + (result == 0) ? x : set_ulist_1(sc, make_integer(sc, result), x), + sc->type_names[T_INTEGER], position_of(x, args))); + result |= integer(car(x)); + } + return(make_integer(sc, result)); +} + +static s7_int logior_i_ii(s7_int i1, s7_int i2) {return(i1 | i2);} +static s7_int logior_i_iii(s7_int i1, s7_int i2, s7_int i3) {return(i1 | i2 | i3);} + + +/* -------------------------------- logxor -------------------------------- */ +#if WITH_GMP +static s7_pointer big_logxor(s7_scheme *sc, s7_int start, s7_pointer args) +{ + mpz_set_si(sc->mpz_1, start); + for (s7_pointer x = args; is_not_null(x); x = cdr(x)) + { + s7_pointer i = car(x); + switch (type(i)) + { + case T_BIG_INTEGER: + mpz_xor(sc->mpz_1, sc->mpz_1, big_integer(i)); + break; + case T_INTEGER: + mpz_set_si(sc->mpz_2, integer(i)); + mpz_xor(sc->mpz_1, sc->mpz_1, sc->mpz_2); + break; + default: + if (!is_integer_via_method(sc, i)) + wrong_type_error_nr(sc, sc->logxor_symbol, position_of(x, args), i, sc->type_names[T_INTEGER]); + return(method_or_bust(sc, i, sc->logxor_symbol, + set_ulist_1(sc, mpz_to_integer(sc, sc->mpz_1), x), + sc->type_names[T_INTEGER], position_of(x, args))); + }} + return(mpz_to_integer(sc, sc->mpz_1)); +} +#endif + +static s7_pointer g_logxor(s7_scheme *sc, s7_pointer args) +{ + #define H_logxor "(logxor int32_t ...) returns the XOR of its integer arguments (the bits that are on in an odd number of the arguments)" + #define Q_logxor sc->pcl_i + + s7_int result = 0; + for (s7_pointer x = args; is_not_null(x); x = cdr(x)) + { +#if WITH_GMP + if (is_t_big_integer(car(x))) + return(big_logxor(sc, result, x)); +#endif + if (!is_t_integer(car(x))) + return(method_or_bust(sc, car(x), sc->logxor_symbol, + (result == 0) ? x : set_ulist_1(sc, make_integer(sc, result), x), + sc->type_names[T_INTEGER], position_of(x, args))); + result ^= integer(car(x)); + } + return(make_integer(sc, result)); +} + +static s7_int logxor_i_ii(s7_int i1, s7_int i2) {return(i1 ^ i2);} +static s7_int logxor_i_iii(s7_int i1, s7_int i2, s7_int i3) {return(i1 ^ i2 ^ i3);} + + +/* -------------------------------- logand -------------------------------- */ +#if WITH_GMP +static s7_pointer big_logand(s7_scheme *sc, s7_int start, s7_pointer args) +{ + mpz_set_si(sc->mpz_1, start); + for (s7_pointer x = args; is_not_null(x); x = cdr(x)) + { + s7_pointer i = car(x); + switch (type(i)) + { + case T_BIG_INTEGER: + mpz_and(sc->mpz_1, sc->mpz_1, big_integer(i)); + break; + case T_INTEGER: + mpz_set_si(sc->mpz_2, integer(i)); + mpz_and(sc->mpz_1, sc->mpz_1, sc->mpz_2); + break; + default: + if (!is_integer_via_method(sc, i)) + wrong_type_error_nr(sc, sc->logand_symbol, position_of(x, args), i, sc->type_names[T_INTEGER]); + return(method_or_bust(sc, i, sc->logand_symbol, + set_ulist_1(sc, mpz_to_integer(sc, sc->mpz_1), x), + sc->type_names[T_INTEGER], position_of(x, args))); + }} + return(mpz_to_integer(sc, sc->mpz_1)); +} +#endif + +static s7_pointer g_logand(s7_scheme *sc, s7_pointer args) +{ + #define H_logand "(logand int32_t ...) returns the AND of its integer arguments (the bits that are on in every argument)" + #define Q_logand sc->pcl_i + + s7_int result = -1; + for (s7_pointer x = args; is_not_null(x); x = cdr(x)) + { +#if WITH_GMP + if (is_t_big_integer(car(x))) + return(big_logand(sc, result, x)); +#endif + if (!is_t_integer(car(x))) + return(method_or_bust(sc, car(x), sc->logand_symbol, + (result == -1) ? x : set_ulist_1(sc, make_integer(sc, result), x), + sc->type_names[T_INTEGER], position_of(x, args))); + result &= integer(car(x)); + } + return(make_integer(sc, result)); +} + +static s7_int logand_i_ii(s7_int i1, s7_int i2) {return(i1 & i2);} +static s7_int logand_i_iii(s7_int i1, s7_int i2, s7_int i3) {return(i1 & i2 & i3);} + + +/* -------------------------------- lognot -------------------------------- */ +static s7_pointer g_lognot(s7_scheme *sc, s7_pointer args) +{ + #define H_lognot "(lognot num) returns the negation of num (its complement, the bits that are not on): (lognot 0) -> -1" + #define Q_lognot sc->pcl_i + + s7_pointer x = car(args); + if (is_t_integer(x)) + return(make_integer(sc, ~integer(x))); + +#if WITH_GMP + if (is_t_big_integer(x)) + { + mpz_com(sc->mpz_1, big_integer(x)); + return(mpz_to_integer(sc, sc->mpz_1)); + } +#endif + return(sole_arg_method_or_bust(sc, x, sc->lognot_symbol, args, sc->type_names[T_INTEGER])); +} + +static s7_int lognot_i_i(s7_int i1) {return(~i1);} + + +/* -------------------------------- logbit? -------------------------------- */ +/* logbit? CL is (logbitp index int) using 2^index, but that order strikes me as backwards + * at least gmp got the arg order right! + */ + +static s7_pointer g_logbit(s7_scheme *sc, s7_pointer args) +{ + #define H_logbit "(logbit? int index) returns #t if the index-th bit is on in int, otherwise #f. The argument \ +order here follows gmp, and is the opposite of the CL convention. (logbit? int bit) is the same as (not (zero? (logand int (ash 1 bit))))." + #define Q_logbit s7_make_circular_signature(sc, 1, 2, sc->is_boolean_symbol, sc->is_integer_symbol) + + s7_pointer x = car(args), y = cadr(args); + s7_int index; /* index in gmp is mp_bitcnt which is an unsigned long int */ + + if (!s7_is_integer(x)) + return(method_or_bust(sc, x, sc->logbit_symbol, args, sc->type_names[T_INTEGER], 1)); + if (!s7_is_integer(y)) + return(method_or_bust(sc, y, sc->logbit_symbol, args, sc->type_names[T_INTEGER], 2)); + + index = s7_integer_clamped_if_gmp(sc, y); + if (index < 0) + out_of_range_error_nr(sc, sc->logbit_symbol, int_two, y, it_is_negative_string); + +#if WITH_GMP + if (is_t_big_integer(x)) + return(make_boolean(sc, (mpz_tstbit(big_integer(x), index) != 0))); +#endif + + if (index >= S7_INT_BITS) /* not sure about the >: (logbit? -1 64) ?? */ + return(make_boolean(sc, integer(x) < 0)); + /* (zero? (logand most-positive-fixnum (ash 1 63))) -> ash argument 2, 63, is out of range (shift is too large) + * so logbit? has a wider range than the logand/ash shuffle above. + */ + + /* all these int64_ts are necessary, else C turns it into an int, gets confused about signs etc */ + return(make_boolean(sc, ((((int64_t)(1LL << (int64_t)index)) & (int64_t)integer(x)) != 0))); +} + +static bool logbit_b_7ii(s7_scheme *sc, s7_int i1, s7_int i2) +{ + if (i2 < 0) + { + out_of_range_error_nr(sc, sc->logbit_symbol, int_two, wrap_integer(sc, i1), it_is_negative_string); + return(false); + } + if (i2 >= S7_INT_BITS) return(i1 < 0); + return((((int64_t)(1LL << (int64_t)i2)) & (int64_t)i1) != 0); +} + +static bool logbit_b_7pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + if (is_t_integer(p1)) + { + if (is_t_integer(p2)) + return(logbit_b_7ii(sc, integer(p1), integer(p2))); + return(method_or_bust(sc, p2, sc->logbit_symbol, set_plist_2(sc, p1, p2), sc->type_names[T_INTEGER], 2) != sc->F); + } +#if WITH_GMP + return(g_logbit(sc, set_plist_2(sc, p1, p2))); +#else + return(method_or_bust(sc, p1, sc->logbit_symbol, set_plist_2(sc, p1, p2), sc->type_names[T_INTEGER], 1) != sc->F); +#endif +} + + +/* -------------------------------- ash -------------------------------- */ +static s7_int c_ash(s7_scheme *sc, s7_int arg1, s7_int arg2) +{ + if (arg1 == 0) return(0); + if (arg2 >= S7_INT_BITS) + { + if ((arg1 == -1) && (arg2 == 63)) /* (ash -1 63): most-negative-fixnum */ + return(S7_INT64_MIN); + out_of_range_error_nr(sc, sc->ash_symbol, int_two, wrap_integer(sc, arg2), it_is_too_large_string); + } + if (arg2 < -S7_INT_BITS) + return((arg1 < 0) ? -1 : 0); /* (ash -31 -100) */ + + /* I can't see any point in protecting this: (ash 9223372036854775807 1) -> -2, but anyone using ash must know something about bits */ + if (arg2 < 0) + return(arg1 >> -arg2); + if (arg1 < 0) + { + uint64_t z = (uint64_t)arg1; + return((s7_int)(z << arg2)); + } + return(arg1 << arg2); +} + +static s7_pointer g_ash(s7_scheme *sc, s7_pointer args) +{ + #define H_ash "(ash i1 i2) returns i1 shifted right or left i2 times, i1 << i2, (ash 1 3) -> 8, (ash 8 -3) -> 1" + #define Q_ash sc->pcl_i + +#if WITH_GMP + /* here, as in expt, there are cases like (ash 1 63) which need to be handled as bignums */ + s7_pointer p0 = car(args), p1 = cadr(args); + + /* here, as in expt, there are cases like (ash 1 63) which need to be bignums so there's no easy way to tell when it's safe to drop into g_ash instead */ + if ((s7_is_integer(p0)) && /* this includes bignum ints... */ + (s7_is_integer(p1))) + { + s7_int shift; + bool p0_is_big = is_big_number(p0); + int32_t p0_compared_to_zero = 0; + + if (p0_is_big) + p0_compared_to_zero = mpz_cmp_ui(big_integer(p0), 0); + else + if (integer(p0) > 0) + p0_compared_to_zero = 1; + else p0_compared_to_zero = (integer(p0) < 0) ? -1 : 0; + + if (p0_compared_to_zero == 0) + return(int_zero); + + if (is_big_number(p1)) + { + if (!mpz_fits_sint_p(big_integer(p1))) + { + if (mpz_cmp_ui(big_integer(p1), 0) > 0) + out_of_range_error_nr(sc, sc->ash_symbol, int_two, p1, it_is_too_large_string); + + /* here if p0 is negative, we need to return -1 */ + return((p0_compared_to_zero == 1) ? int_zero : minus_one); + } + shift = mpz_get_si(big_integer(p1)); + } + else + { + shift = integer(p1); + if (shift < S7_INT32_MIN) + return((p0_compared_to_zero == 1) ? int_zero : minus_one); + } + if (shift > S7_INT32_MAX) + out_of_range_error_nr(sc, sc->ash_symbol, int_two, p1, it_is_too_large_string); /* gmp calls abort if overflow here */ + + if (is_t_big_integer(p0)) + mpz_set(sc->mpz_1, big_integer(p0)); + else mpz_set_si(sc->mpz_1, integer(p0)); + + if (shift > 0) /* left */ + mpz_mul_2exp(sc->mpz_1, sc->mpz_1, shift); + else + if (shift < 0) /* right */ + mpz_fdiv_q_2exp(sc->mpz_1, sc->mpz_1, (uint32_t)(-shift)); + + return(mpz_to_integer(sc, sc->mpz_1)); + } + /* else fall through */ +#endif + s7_pointer x = car(args), y = cadr(args); + + if (!s7_is_integer(x)) + return(method_or_bust(sc, x, sc->ash_symbol, args, sc->type_names[T_INTEGER], 1)); + if (!s7_is_integer(y)) + return(method_or_bust(sc, y, sc->ash_symbol, args, sc->type_names[T_INTEGER], 2)); + return(make_integer(sc, c_ash(sc, s7_integer_clamped_if_gmp(sc, x), s7_integer_clamped_if_gmp(sc, y)))); +} + +#if (!WITH_GMP) + static s7_int ash_i_7ii(s7_scheme *sc, s7_int i1, s7_int i2) {return(c_ash(sc, i1, i2));} +#endif +static s7_int lsh_i_ii_unchecked(s7_int i1, s7_int i2) {return(i1 << i2);} /* this may need gmp special handling, and out-of-range as in c_ash */ +static s7_int rsh_i_ii_unchecked(s7_int i1, s7_int i2) {return(i1 >> (-i2));} +static s7_int rsh_i_i2_direct(s7_int i1, s7_int unused_i2) {return(i1 >> 1);} + + +/* -------------------------------- random-state -------------------------------- */ +/* random numbers. The simple version used in clm.c is probably adequate, but here I'll use Marsaglia's MWC algorithm. + * (random num) -> a number (0..num), if num == 0 return 0, use global default state + * (random num state) -> same but use this state + * (random-state seed) -> make a new state + * to save the current seed, use copy, to save it across load, random-state->list and list->random-state. + * random-state? returns #t if its arg is one of these guys + */ + +static s7_pointer random_state_copy(s7_scheme *sc, s7_pointer args) +{ +#if WITH_GMP + return(sc->F); /* I can't find a way to copy a gmp random generator */ +#else + s7_pointer new_r, obj = car(args); + if (!is_random_state(obj)) return(sc->F); + new_cell(sc, new_r, T_RANDOM_STATE); + random_seed(new_r) = random_seed(obj); + random_carry(new_r) = random_carry(obj); + return(new_r); +#endif +} +#if S7_DEBUGGING && (!WITH_GMP) + static s7_int last_carry = 0; + /* 2083801278 */ +#endif + +s7_pointer s7_random_state(s7_scheme *sc, s7_pointer args) +{ + #define H_random_state "(random-state seed (carry plausible-default)) returns a new random number state initialized with 'seed'. \ +Pass this as the second argument to 'random' to get a repeatable random number sequence:\n\ + (let ((seed (random-state 1234))) (random 1.0 seed))" + #define Q_random_state s7_make_circular_signature(sc, 1, 2, sc->is_random_state_symbol, sc->is_integer_symbol) + +#if WITH_GMP + s7_pointer r, seed; + if (is_null(args)) + return(sc->F); /* how to find current state, if any? */ + + seed = car(args); + if (!s7_is_integer(seed)) + return(sole_arg_method_or_bust(sc, seed, sc->random_state_symbol, args, sc->type_names[T_INTEGER])); + + if (is_t_integer(seed)) + seed = s7_int_to_big_integer(sc, integer(seed)); + + new_cell(sc, r, T_RANDOM_STATE); + gmp_randinit_default(random_gmp_state(r)); /* Mersenne twister */ + gmp_randseed(random_gmp_state(r), big_integer(seed)); /* this is ridiculously slow! */ + add_big_random_state(sc, r); + return(r); +#else + s7_pointer r1, r2, p; + s7_int i1, i2; + if (is_null(args)) + return(sc->default_random_state); + + r1 = car(args); + if (!s7_is_integer(r1)) + return(method_or_bust(sc, r1, sc->random_state_symbol, args, sc->type_names[T_INTEGER], 1)); + i1 = integer(r1); + if (i1 < 0) + out_of_range_error_nr(sc, sc->random_state_symbol, int_one, r1, it_is_negative_string); + if (is_null(cdr(args))) + { + new_cell(sc, p, T_RANDOM_STATE); + random_seed(p) = (uint64_t)i1; + random_carry(p) = 1675393560; /* should this be dependent on the seed? */ +#if S7_DEBUGGING + last_carry = 1675393560; +#endif + return(p); + } + + r2 = cadr(args); + if (!s7_is_integer(r2)) + return(method_or_bust(sc, r2, sc->random_state_symbol, args, sc->type_names[T_INTEGER], 2)); + i2 = integer(r2); + if (i2 < 0) + out_of_range_error_nr(sc, sc->random_state_symbol, int_two, r2, it_is_negative_string); + + new_cell(sc, p, T_RANDOM_STATE); + random_seed(p) = (uint64_t)i1; + random_carry(p) = (uint64_t)i2; +#if S7_DEBUGGING + last_carry = i2; +#endif + return(p); +#endif +} + +#define g_random_state s7_random_state + +static s7_pointer random_state_getter(s7_scheme *sc, s7_pointer r, s7_int loc) +{ +#if (!WITH_GMP) + if (loc == 0) return(make_integer(sc, random_seed(r))); + if (loc == 1) return(make_integer(sc, random_carry(r))); +#endif + return(sc->F); +} + +static s7_pointer random_state_setter(s7_scheme *sc, s7_pointer r, s7_int loc, s7_pointer val) +{ +#if (!WITH_GMP) + if (is_t_integer(val)) + { + s7_int i = s7_integer_clamped_if_gmp(sc, val); + if (loc == 0) random_seed(r) = i; + if (loc == 1) random_carry(r) = i; + } +#endif + return(sc->F); +} + + +/* -------------------------------- random-state? -------------------------------- */ +static s7_pointer g_is_random_state(s7_scheme *sc, s7_pointer args) +{ + #define H_is_random_state "(random-state? obj) returns #t if obj is a random-state object (from random-state)." + #define Q_is_random_state sc->pl_bt + check_boolean_method(sc, is_random_state, sc->is_random_state_symbol, args); +} + +bool s7_is_random_state(s7_pointer p) {return(type(p) == T_RANDOM_STATE);} + + +/* -------------------------------- random-state->list -------------------------------- */ +s7_pointer s7_random_state_to_list(s7_scheme *sc, s7_pointer args) +{ + #define H_random_state_to_list "(random-state->list r) returns the random state object as a list.\ +You can later apply random-state to this list to continue a random number sequence from any point." + #define Q_random_state_to_list s7_make_signature(sc, 2, (WITH_GMP) ? sc->is_list_symbol : sc->is_pair_symbol, sc->is_random_state_symbol) + +#if WITH_GMP + if ((is_pair(args)) && + (!is_random_state(car(args)))) + return(method_or_bust(sc, car(args), sc->random_state_to_list_symbol, args, a_random_state_object_string, 1)); + return(sc->nil); +#else + s7_pointer r = (is_null(args)) ? sc->default_random_state : car(args); + if (!is_random_state(r)) + return(method_or_bust(sc, r, sc->random_state_to_list_symbol, args, a_random_state_object_string, 1)); + return(list_2(sc, make_integer(sc, random_seed(r)), make_integer_unchecked(sc, random_carry(r)))); +#endif +} + +#define g_random_state_to_list s7_random_state_to_list + +void s7_set_default_random_state(s7_scheme *sc, s7_int seed, s7_int carry) +{ +#if (!WITH_GMP) + s7_pointer p; + new_cell(sc, p, T_RANDOM_STATE); + random_seed(p) = (uint64_t)seed; + random_carry(p) = (uint64_t)carry; + sc->default_random_state = p; +#endif +} + + +/* -------------------------------- random -------------------------------- */ +#if WITH_GMP +static double next_random(s7_scheme *sc) +#else +static double next_random(s7_pointer r) +#endif +{ +#if (!WITH_GMP) + /* The multiply-with-carry generator for 32-bit integers: + * x(n)=a*x(n-1) + carry mod 2^32 + * Choose multiplier a from this list: + * 1791398085 1929682203 1683268614 1965537969 1675393560 1967773755 1517746329 1447497129 1655692410 1606218150 + * 2051013963 1075433238 1557985959 1781943330 1893513180 1631296680 2131995753 2083801278 1873196400 1554115554 + * ( or any 'a' for which both a*2^32-1 and a*2^31-1 are prime) + */ + #define RAN_MULT 2131995753UL + + double result; + uint64_t temp = random_seed(r) * RAN_MULT + random_carry(r); + random_seed(r) = (temp & 0xffffffffUL); + random_carry(r) = (temp >> 32); + result = (double)((uint32_t)(random_seed(r))) / 4294967295.5; + /* divisor was 2^32-1 = 4294967295.0, but somehow this can round up once in a billion tries? + * do we want the double just less than 2^32? + * can the multiply-add+logand above return 0? I'm getting 0's from (random (expt 2 62)) + */ + + /* (let ((mx 0) (mn 1000)) (do ((i 0 (+ i 1))) ((= i 10000)) (let ((val (random 123))) (set! mx (max mx val)) (set! mn (min mn val)))) (list mn mx)) */ + return(result); +#else + mpfr_urandomb(sc->mpfr_1, random_gmp_state(sc->default_random_state)); + return(mpfr_get_d(sc->mpfr_1, MPFR_RNDN)); +#endif +} + +static s7_pointer g_random(s7_scheme *sc, s7_pointer args) +{ + #define H_random "(random num (state #f)) returns a random number of the same type as num between zero and num, equalling num only if num is zero" + #define Q_random s7_make_signature(sc, 3, sc->is_number_symbol, sc->is_number_symbol, sc->is_random_state_symbol) + s7_pointer r, num; + + /* if we disallow (random 0) the programmer has to protect every call on random with (if (eqv? x 0) 0 (random x)). If + * we claim we're using a half-open interval, then we should also disallow (random 0.0); otherwise the following + * must be true: (let* ((x 0.0) (y (random x))) (and (>= y 0.0) (< y x))). The definition above is consistent + * with (random 0) -> 0, simpler to use in practice, and certainly no worse than (/ 0 0) -> 1. + */ + if (is_null(cdr(args))) + r = sc->default_random_state; + else + { + r = cadr(args); + if (!is_random_state(r)) + return(method_or_bust(sc, r, sc->random_symbol, args, a_random_state_object_string, 2)); + } + num = car(args); + switch (type(num)) + { +#if (!WITH_GMP) + case T_INTEGER: + return(make_integer(sc, (s7_int)(integer(num) * next_random(r)))); + case T_RATIO: + { + s7_double x = fraction(num), error; + s7_int numer = 0, denom = 1; + /* the error here needs to take the size of the fraction into account. Otherwise, if + * error is (say) 1e-6 and the fraction is (say) 9000000/9223372036854775807, + * c_rationalize will always return 0. But even that isn't foolproof: + * (random 1/562949953421312) -> 1/376367230475000 + */ + if ((x < 1.0e-10) && (x > -1.0e-10)) + { + /* 1e-12 is not tight enough: + * (random 1/2251799813685248) -> 1/2250240579436280 + * (random -1/4503599627370496) -> -1/4492889778435526 + * (random 1/140737488355328) -> 1/140730223985746 + * (random -1/35184372088832) -> -1/35183145492420 + * (random -1/70368744177664) -> -1/70366866392738 + * (random 1/4398046511104) -> 1/4398033095756 + * (random 1/137438953472) -> 1/137438941127 + */ + if (numerator(num) < -10) + numer = -(s7_int)(floor(-numerator(num) * next_random(r))); + else + if (numerator(num) > 10) + numer = (s7_int)floor(numerator(num) * next_random(r)); + else + { + int64_t diff = S7_INT64_MAX - denominator(num); + numer = numerator(num); + if (diff < 100) + return(make_ratio(sc, numer, denominator(num))); + denom = denominator(num) + (s7_int)floor(diff * next_random(r)); + return(make_ratio_with_div_check(sc, sc->random_symbol, numer, denom)); + } + return(make_ratio(sc, numer, denominator(num))); + } + error = ((x < 1e-6) && (x > -1e-6)) ? 1e-18 : 1e-12; + c_rationalize(x * next_random(r), error, &numer, &denom); + return(make_ratio(sc, numer, denom)); + } + case T_REAL: + return(make_real(sc, real(num) * next_random(r))); + case T_COMPLEX: + return(s7_make_complex(sc, real_part(num) * next_random(r), imag_part(num) * next_random(r))); +#else + case T_INTEGER: + if (integer(num) == 0) return(int_zero); + mpz_set_si(sc->mpz_1, integer(num)); + mpz_urandomm(sc->mpz_1, random_gmp_state(r), sc->mpz_1); + if (integer(num) < 0) mpz_neg(sc->mpz_1, sc->mpz_1); + return(make_integer(sc, mpz_get_si(sc->mpz_1))); + case T_BIG_INTEGER: + if (mpz_cmp_si(big_integer(num), 0) == 0) return(int_zero); + mpz_urandomm(sc->mpz_1, random_gmp_state(r), big_integer(num)); + /* this does not work if num is a negative number -- you get positive results. so check num for sign, and negate result if necessary */ + if (mpz_cmp_ui(big_integer(num), 0) < 0) + mpz_neg(sc->mpz_1, sc->mpz_1); + return(mpz_to_integer(sc, sc->mpz_1)); + case T_RATIO: + mpfr_urandomb(sc->mpfr_1, random_gmp_state(r)); + mpq_set_si(sc->mpq_1, numerator(num), denominator(num)); + mpfr_mul_q(sc->mpfr_1, sc->mpfr_1, sc->mpq_1, MPFR_RNDN); + mpfr_mul_d(sc->mpfr_2, sc->mpfr_1, sc->default_rationalize_error, MPFR_RNDN); + return(big_rationalize(sc, set_plist_2(sc, mpfr_to_big_real(sc, sc->mpfr_1), mpfr_to_big_real(sc, sc->mpfr_2)))); + case T_BIG_RATIO: + mpfr_urandomb(sc->mpfr_1, random_gmp_state(r)); + mpfr_mul_q(sc->mpfr_1, sc->mpfr_1, big_ratio(num), MPFR_RNDN); + mpfr_mul_d(sc->mpfr_2, sc->mpfr_1, sc->default_rationalize_error, MPFR_RNDN); + return(big_rationalize(sc, set_plist_2(sc, mpfr_to_big_real(sc, sc->mpfr_1), mpfr_to_big_real(sc, sc->mpfr_2)))); + case T_REAL: + mpfr_urandomb(sc->mpfr_1, random_gmp_state(r)); + mpfr_mul_d(sc->mpfr_1, sc->mpfr_1, real(num), MPFR_RNDN); + return(make_real(sc, mpfr_get_d(sc->mpfr_1, MPFR_RNDN))); + case T_BIG_REAL: + mpfr_urandomb(sc->mpfr_1, random_gmp_state(r)); + mpfr_mul(sc->mpfr_1, sc->mpfr_1, big_real(num), MPFR_RNDN); + return(mpfr_to_big_real(sc, sc->mpfr_1)); + case T_COMPLEX: + mpc_urandom(sc->mpc_1, random_gmp_state(r)); + mpfr_mul_d(mpc_realref(sc->mpc_1), mpc_realref(sc->mpc_1), real_part(num), MPFR_RNDN); + mpfr_mul_d(mpc_imagref(sc->mpc_1), mpc_imagref(sc->mpc_1), imag_part(num), MPFR_RNDN); + return(s7_make_complex(sc, mpfr_get_d(mpc_realref(sc->mpc_1), MPFR_RNDN), mpfr_get_d(mpc_imagref(sc->mpc_1), MPFR_RNDN))); + case T_BIG_COMPLEX: + mpc_urandom(sc->mpc_1, random_gmp_state(r)); + mpfr_mul(mpc_realref(sc->mpc_1), mpc_realref(sc->mpc_1), mpc_realref(big_complex(num)), MPFR_RNDN); + mpfr_mul(mpc_imagref(sc->mpc_1), mpc_imagref(sc->mpc_1), mpc_imagref(big_complex(num)), MPFR_RNDN); + return(mpc_to_number(sc, sc->mpc_1)); +#endif + default: + return(method_or_bust(sc, num, sc->random_symbol, args, a_number_string, 1)); + } + return(sc->F); +} + +s7_double s7_random(s7_scheme *sc, s7_pointer state) +{ +#if WITH_GMP + mpfr_set_ui(sc->mpfr_1, 1, MPFR_RNDN); + mpfr_urandomb(sc->mpfr_1, random_gmp_state((state) ? state : sc->default_random_state)); + return((s7_double)mpfr_get_d(sc->mpfr_1, MPFR_RNDN)); +#else + return(next_random((state) ? state : sc->default_random_state)); +#endif +} + +static s7_double random_d_7d(s7_scheme *sc, s7_double x) +{ +#if WITH_GMP + return(real(g_random(sc, set_plist_1(sc, wrap_real(sc, x))))); +#else + return(x * next_random(sc->default_random_state)); +#endif +} + +static s7_int random_i_7i(s7_scheme *sc, s7_int i) +{ +#if WITH_GMP + return(integer(g_random(sc, set_plist_1(sc, wrap_integer(sc, i))))); +#else + return((s7_int)(i * next_random(sc->default_random_state))); +#endif +} + +static s7_pointer g_random_i(s7_scheme *sc, s7_pointer args) +{ +#if WITH_GMP + return(g_random(sc, args)); +#else + return(make_integer(sc, (s7_int)(integer(car(args)) * next_random(sc->default_random_state)))); +#endif +} + +static s7_pointer g_random_f(s7_scheme *sc, s7_pointer args) +{ +#if WITH_GMP + return(g_random(sc, args)); +#else + return(make_real(sc, real(car(args)) * next_random(sc->default_random_state))); +#endif +} + +static s7_pointer g_random_1(s7_scheme *sc, s7_pointer args) +{ +#if (!WITH_GMP) + s7_pointer num = car(args), r = sc->default_random_state; + if (is_t_integer(num)) + return(make_integer(sc, (s7_int)(integer(num) * next_random(r)))); + if (is_t_real(num)) + return(make_real(sc, real(num) * next_random(r))); +#endif + return(g_random(sc, args)); +} + +static s7_pointer random_p_p(s7_scheme *sc, s7_pointer num) +{ +#if (!WITH_GMP) + if (is_t_integer(num)) + return(make_integer(sc, (s7_int)(integer(num) * next_random(sc->default_random_state)))); + if (is_t_real(num)) + return(make_real(sc, real(num) * next_random(sc->default_random_state))); +#endif + return(g_random(sc, set_plist_1(sc, num))); +} + +static s7_pointer random_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool ops) +{ + if ((ops) && (args == 1)) + { + s7_pointer arg1 = cadr(expr); + if (is_t_integer(arg1)) + return(sc->random_i); + return((is_t_real(arg1)) ? sc->random_f : sc->random_1); + } + return(f); +} + +static s7_pointer g_add_i_random(s7_scheme *sc, s7_pointer args) +{ +#if WITH_GMP + return(add_p_pp(sc, car(args), random_p_p(sc, cadadr(args)))); +#else + s7_int x = integer(car(args)), y = opt3_int(args); /* cadadr */ + return(make_integer(sc, x + (s7_int)(y * next_random(sc->default_random_state)))); /* (+ -1 (random 1)) -- placement of the (s7_int) cast matters! */ +#endif +} + + +/* -------------------------------- char<->integer -------------------------------- */ +static s7_pointer g_char_to_integer(s7_scheme *sc, s7_pointer args) +{ + #define H_char_to_integer "(char->integer c) converts the character c to an integer" + #define Q_char_to_integer s7_make_signature(sc, 2, sc->is_integer_symbol, sc->is_char_symbol) + + if (!is_character(car(args))) + return(sole_arg_method_or_bust(sc, car(args), sc->char_to_integer_symbol, args, sc->type_names[T_CHARACTER])); + return(small_int(character(car(args)))); +} + +static s7_int char_to_integer_i_7p(s7_scheme *sc, s7_pointer p) +{ + if (!is_character(p)) + return(integer(method_or_bust_p(sc, p, sc->char_to_integer_symbol, sc->type_names[T_CHARACTER]))); + return(character(p)); +} + +static s7_pointer char_to_integer_p_p(s7_scheme *sc, s7_pointer p) +{ + if (!is_character(p)) + return(method_or_bust_p(sc, p, sc->char_to_integer_symbol, sc->type_names[T_CHARACTER])); + return(make_integer(sc, character(p))); +} + +static s7_pointer integer_to_char_p_p(s7_scheme *sc, s7_pointer x) +{ + s7_int ind; + if (!s7_is_integer(x)) + return(method_or_bust_p(sc, x, sc->integer_to_char_symbol, sc->type_names[T_INTEGER])); + ind = s7_integer_clamped_if_gmp(sc, x); + if ((ind < 0) || (ind >= NUM_CHARS)) + sole_arg_out_of_range_error_nr(sc, sc->integer_to_char_symbol, x, wrap_string(sc, "it doen't fit in an unsigned byte", 33)); + return(chars[(uint8_t)ind]); +} + +static s7_pointer g_integer_to_char(s7_scheme *sc, s7_pointer args) +{ + #define H_integer_to_char "(integer->char i) converts the non-negative integer i to a character" + #define Q_integer_to_char s7_make_signature(sc, 2, sc->is_char_symbol, sc->is_integer_symbol) + return(integer_to_char_p_p(sc, car(args))); +} + +static s7_pointer integer_to_char_p_i(s7_scheme *sc, s7_int ind) +{ + if ((ind < 0) || (ind >= NUM_CHARS)) + sole_arg_out_of_range_error_nr(sc, sc->integer_to_char_symbol, wrap_integer(sc, ind), + wrap_string(sc, "it doen't fit in an unsigned byte", 33)); /* int2 s7_out... uses 1 */ + return(chars[(uint8_t)ind]); +} + + +static uint8_t uppers[256], lowers[256]; +static void init_uppers(void) +{ + for (int32_t i = 0; i < 256; i++) + { + uppers[i] = (uint8_t)toupper(i); + lowers[i] = (uint8_t)tolower(i); + } +} + +static void init_chars(void) +{ + s7_cell *cells = (s7_cell *)Calloc(NUM_CHARS + 1, sizeof(s7_cell)); + + chars = (s7_pointer *)Malloc((NUM_CHARS + 1) * sizeof(s7_pointer)); /* chars is declared far above */ + chars[0] = &cells[0]; + eof_object = chars[0]; + set_full_type(eof_object, T_EOF | T_IMMUTABLE | T_UNHEAP); + eof_name_length(eof_object) = 6; + eof_name(eof_object) = "#"; + chars++; /* now chars[EOF] == chars[-1] == # */ + cells++; + + for (int32_t i = 0; i < NUM_CHARS; i++) + { + s7_pointer cp = &cells[i]; + uint8_t c = (uint8_t)i; + + set_type_bit(cp, T_IMMUTABLE | T_CHARACTER | T_UNHEAP); + set_optimize_op(cp, OP_CONSTANT); + character(cp) = c; + upper_character(cp) = (uint8_t)toupper(i); + is_char_alphabetic(cp) = (bool)isalpha(i); + is_char_numeric(cp) = (bool)isdigit(i); + is_char_whitespace(cp) = white_space[i]; + is_char_uppercase(cp) = (((bool)isupper(i)) || ((i >= 192) && (i < 208))); + is_char_lowercase(cp) = (bool)islower(i); + chars[i] = cp; + + #define make_character_name(S) memcpy((void *)(&(character_name(cp))), (const void *)(S), character_name_length(cp) = (int32_t)strlen(S)) + switch (c) + { + case ' ': make_character_name("#\\space"); break; + case '\n': make_character_name("#\\newline"); break; + case '\r': make_character_name("#\\return"); break; + case '\t': make_character_name("#\\tab"); break; + case '\0': make_character_name("#\\null"); break; + case (char)0x1b: make_character_name("#\\escape"); break; + case (char)0x7f: make_character_name("#\\delete"); break; + case (char)7: make_character_name("#\\alarm"); break; + case (char)8: make_character_name("#\\backspace"); break; + default: + #define P_SIZE 12 + character_name_length(cp) = snprintf((char *)(&(character_name(cp))), P_SIZE, ((c < 32) || (c >= 127)) ? "#\\x%x" : "#\\%c", c); + break; + }} +} + + +/* -------------------------------- char-upcase, char-downcase ----------------------- */ +static s7_pointer char_upcase_p_p(s7_scheme *sc, s7_pointer c) +{ + if (!is_character(c)) + return(method_or_bust_p(sc, c, sc->char_upcase_symbol, sc->type_names[T_CHARACTER])); + return(chars[upper_character(c)]); +} + +static s7_pointer char_upcase_p_p_unchecked(s7_scheme *unused_sc, s7_pointer c) {return(chars[upper_character(c)]);} + +static s7_pointer g_char_upcase(s7_scheme *sc, s7_pointer args) +{ + #define H_char_upcase "(char-upcase c) converts the character c to upper case" + #define Q_char_upcase sc->pcl_c + return(char_upcase_p_p(sc, car(args))); +} + +static s7_pointer g_char_downcase(s7_scheme *sc, s7_pointer args) +{ + #define H_char_downcase "(char-downcase c) converts the character c to lower case" + #define Q_char_downcase sc->pcl_c + if (!is_character(car(args))) + return(sole_arg_method_or_bust(sc, car(args), sc->char_downcase_symbol, args, sc->type_names[T_CHARACTER])); + return(chars[lowers[character(car(args))]]); +} + + +/* -------------------------------- char-alphabetic? char-numeric? char-whitespace? -------------------------------- */ +static s7_pointer g_is_char_alphabetic(s7_scheme *sc, s7_pointer args) +{ + #define H_is_char_alphabetic "(char-alphabetic? c) returns #t if the character c is alphabetic" + #define Q_is_char_alphabetic sc->pl_bc + if (!is_character(car(args))) + return(sole_arg_method_or_bust(sc, car(args), sc->is_char_alphabetic_symbol, args, sc->type_names[T_CHARACTER])); + return(make_boolean(sc, is_char_alphabetic(car(args)))); + /* isalpha returns #t for (integer->char 226) and others in that range */ +} + +static bool is_char_alphabetic_b_7p(s7_scheme *sc, s7_pointer c) +{ + if (!is_character(c)) + sole_arg_wrong_type_error_nr(sc, sc->is_char_alphabetic_symbol, c, sc->type_names[T_CHARACTER]); + /* return(sole_arg_method_or_bust(sc, c, sc->is_char_alphabetic_symbol, set_plist_1(sc, c), sc->type_names[T_CHARACTER]) != sc->F); */ /* slower? see tmisc */ + return(is_char_alphabetic(c)); +} + +static s7_pointer is_char_alphabetic_p_p(s7_scheme *sc, s7_pointer c) +{ + if (!is_character(c)) + return(sole_arg_method_or_bust(sc, c, sc->is_char_alphabetic_symbol, set_plist_1(sc, c), sc->type_names[T_CHARACTER])); + return(make_boolean(sc, is_char_alphabetic(c))); +} + +static s7_pointer g_is_char_numeric(s7_scheme *sc, s7_pointer args) +{ + #define H_is_char_numeric "(char-numeric? c) returns #t if the character c is a digit" + #define Q_is_char_numeric sc->pl_bc + + s7_pointer arg = car(args); + if (!is_character(arg)) + return(sole_arg_method_or_bust(sc, arg, sc->is_char_numeric_symbol, args, sc->type_names[T_CHARACTER])); + return(make_boolean(sc, is_char_numeric(arg))); +} + +static bool is_char_numeric_b_7p(s7_scheme *sc, s7_pointer c) +{ + if (!is_character(c)) + sole_arg_wrong_type_error_nr(sc, sc->is_char_numeric_symbol, c, sc->type_names[T_CHARACTER]); + /* return(sole_arg_method_or_bust(sc, c, sc->is_char_numeric_symbol, set_plist_1(sc, c), sc->type_names[T_CHARACTER]) != sc->F); */ /* as above */ + return(is_char_numeric(c)); +} + +static s7_pointer is_char_numeric_p_p(s7_scheme *sc, s7_pointer c) +{ + if (!is_character(c)) + return(sole_arg_method_or_bust(sc, c, sc->is_char_numeric_symbol, set_plist_1(sc, c), sc->type_names[T_CHARACTER])); + return(make_boolean(sc, is_char_numeric(c))); +} + + +static s7_pointer g_is_char_whitespace(s7_scheme *sc, s7_pointer args) +{ + #define H_is_char_whitespace "(char-whitespace? c) returns #t if the character c is non-printing character" + #define Q_is_char_whitespace sc->pl_bc + + s7_pointer arg = car(args); + if (!is_character(arg)) + return(sole_arg_method_or_bust(sc, arg, sc->is_char_whitespace_symbol, args, sc->type_names[T_CHARACTER])); + return(make_boolean(sc, is_char_whitespace(arg))); +} + +static bool is_char_whitespace_b_7p(s7_scheme *sc, s7_pointer c) +{ + if (!is_character(c)) + sole_arg_wrong_type_error_nr(sc, sc->is_char_whitespace_symbol, c, sc->type_names[T_CHARACTER]); + return(is_char_whitespace(c)); +} + +static s7_pointer is_char_whitespace_p_p(s7_scheme *sc, s7_pointer c) +{ + if (!is_character(c)) + return(sole_arg_method_or_bust(sc, c, sc->is_char_whitespace_symbol, set_plist_1(sc, c), sc->type_names[T_CHARACTER])); + return(make_boolean(sc, is_char_whitespace(c))); +} + +static s7_pointer is_char_whitespace_p_p_unchecked(s7_scheme *sc, s7_pointer c) {return(make_boolean(sc, is_char_whitespace(c)));} + + +/* -------------------------------- char-upper-case? char-lower-case? -------------------------------- */ +static s7_pointer g_is_char_upper_case(s7_scheme *sc, s7_pointer args) +{ + #define H_is_char_upper_case "(char-upper-case? c) returns #t if the character c is in upper case" + #define Q_is_char_upper_case sc->pl_bc + + s7_pointer arg = car(args); + if (!is_character(arg)) + return(sole_arg_method_or_bust(sc, arg, sc->is_char_upper_case_symbol, args, sc->type_names[T_CHARACTER])); + return(make_boolean(sc, is_char_uppercase(arg))); +} + +static bool is_char_upper_case_b_7p(s7_scheme *sc, s7_pointer c) +{ + if (!is_character(c)) + return(sole_arg_method_or_bust(sc, c, sc->is_char_upper_case_symbol, set_plist_1(sc, c), sc->type_names[T_CHARACTER]) != sc->F); + return(is_char_uppercase(c)); +} + +static s7_pointer g_is_char_lower_case(s7_scheme *sc, s7_pointer args) +{ + #define H_is_char_lower_case "(char-lower-case? c) returns #t if the character c is in lower case" + #define Q_is_char_lower_case sc->pl_bc + + s7_pointer arg = car(args); + if (!is_character(arg)) + return(sole_arg_method_or_bust(sc, arg, sc->is_char_lower_case_symbol, args, sc->type_names[T_CHARACTER])); + return(make_boolean(sc, is_char_lowercase(arg))); +} + +static bool is_char_lower_case_b_7p(s7_scheme *sc, s7_pointer c) +{ + if (!is_character(c)) + return(sole_arg_method_or_bust(sc, c, sc->is_char_lower_case_symbol, set_plist_1(sc, c), sc->type_names[T_CHARACTER]) != sc->F); + return(is_char_lowercase(c)); +} + + +/* -------------------------------- char? -------------------------------- */ +static s7_pointer g_is_char(s7_scheme *sc, s7_pointer args) +{ + #define H_is_char "(char? obj) returns #t if obj is a character" + #define Q_is_char sc->pl_bt + check_boolean_method(sc, is_character, sc->is_char_symbol, args); +} + +static s7_pointer is_char_p_p(s7_scheme *sc, s7_pointer p) {return((is_character(p)) ? sc->T : sc->F);} + +s7_pointer s7_make_character(s7_scheme *sc, uint8_t c) {return(chars[c]);} + +bool s7_is_character(s7_pointer p) {return(is_character(p));} + +uint8_t s7_character(s7_pointer p) {return(character(p));} + + +/* -------------------------------- char? char>=? char=? -------------------------------- */ +static int32_t charcmp(uint8_t c1, uint8_t c2) +{ + return((c1 == c2) ? 0 : (c1 < c2) ? -1 : 1); + /* not tolower here -- the single case is apparently supposed to be upper case + * this matters in a case like (char-ciis_char_symbol); + if (f != sc->undefined) + return(is_true(sc, s7_apply_function(sc, f, set_plist_1(sc, p)))); + } + return(false); +} + +static s7_pointer char_with_error_check(s7_scheme *sc, s7_pointer x, s7_pointer args, s7_pointer caller) +{ + for (s7_pointer y = cdr(x); is_pair(y); y = cdr(y)) /* before returning #f, check for bad trailing arguments */ + if (!is_character_via_method(sc, car(y))) + wrong_type_error_nr(sc, caller, position_of(y, args), car(y), sc->type_names[T_CHARACTER]); + return(sc->F); +} + +static s7_pointer g_char_cmp(s7_scheme *sc, s7_pointer args, int32_t val, s7_pointer sym) +{ + s7_pointer y = car(args); + if (!is_character(y)) + return(method_or_bust(sc, y, sym, args, sc->type_names[T_CHARACTER], 1)); + for (s7_pointer x = cdr(args); is_pair(x); y = car(x), x = cdr(x)) + { + if (!is_character(car(x))) + return(method_or_bust(sc, car(x), sym, set_ulist_1(sc, y, x), sc->type_names[T_CHARACTER], position_of(x, args))); + if (charcmp(character(y), character(car(x))) != val) + return(char_with_error_check(sc, x, args, sym)); + } + return(sc->T); +} + +static s7_pointer g_char_cmp_not(s7_scheme *sc, s7_pointer args, int32_t val, s7_pointer sym) +{ + s7_pointer y = car(args); + if (!is_character(y)) + return(method_or_bust(sc, y, sym, args, sc->type_names[T_CHARACTER], 1)); + for (s7_pointer x = cdr(args); is_pair(x); y = car(x), x = cdr(x)) + { + if (!is_character(car(x))) + return(method_or_bust(sc, car(x), sym, set_ulist_1(sc, y, x), sc->type_names[T_CHARACTER], position_of(x, args))); + if (charcmp(character(y), character(car(x))) == val) + return(char_with_error_check(sc, x, args, sym)); + } + return(sc->T); +} + +static s7_pointer g_chars_are_equal(s7_scheme *sc, s7_pointer args) +{ + #define H_chars_are_equal "(char=? char ...) returns #t if all the character arguments are equal" + #define Q_chars_are_equal sc->pcl_bc + + s7_pointer y = car(args); + if (!is_character(y)) + return(method_or_bust(sc, y, sc->char_eq_symbol, args, sc->type_names[T_CHARACTER], 1)); + for (s7_pointer x = cdr(args); is_pair(x); x = cdr(x)) + { + if (!is_character(car(x))) + return(method_or_bust(sc, car(x), sc->char_eq_symbol, set_ulist_1(sc, y, x), sc->type_names[T_CHARACTER], position_of(x, args))); + if (car(x) != y) + return(char_with_error_check(sc, x, args, sc->char_eq_symbol)); + } + return(sc->T); +} + + +static s7_pointer g_chars_are_less(s7_scheme *sc, s7_pointer args) +{ + #define H_chars_are_less "(charpcl_bc + return(g_char_cmp(sc, args, -1, sc->char_lt_symbol)); +} + +static s7_pointer g_chars_are_greater(s7_scheme *sc, s7_pointer args) +{ + #define H_chars_are_greater "(char>? char ...) returns #t if all the character arguments are decreasing" + #define Q_chars_are_greater sc->pcl_bc + return(g_char_cmp(sc, args, 1, sc->char_gt_symbol)); +} + +static s7_pointer g_chars_are_geq(s7_scheme *sc, s7_pointer args) +{ + #define H_chars_are_geq "(char>=? char ...) returns #t if all the character arguments are equal or decreasing" + #define Q_chars_are_geq sc->pcl_bc + return(g_char_cmp_not(sc, args, -1, sc->char_geq_symbol)); +} + +static s7_pointer g_chars_are_leq(s7_scheme *sc, s7_pointer args) +{ + #define H_chars_are_leq "(char<=? char ...) returns #t if all the character arguments are equal or increasing" + #define Q_chars_are_leq sc->pcl_bc + return(g_char_cmp_not(sc, args, 1, sc->char_leq_symbol)); +} + +static s7_pointer g_simple_char_eq(s7_scheme *sc, s7_pointer args) {return(make_boolean(sc, car(args) == cadr(args)));} /* chooser checks types */ + +#define check_char2_args(Sc, Caller, P1, P2) \ + do { \ + if (!is_character(P1)) return(method_or_bust(Sc, P1, Caller, set_plist_2(Sc, P1, P2), sc->type_names[T_CHARACTER], 1) != sc->F); \ + if (!is_character(P2)) return(method_or_bust(Sc, P2, Caller, set_plist_2(Sc, P1, P2), sc->type_names[T_CHARACTER], 2) != sc->F); \ + } while (0) + +static bool char_lt_b_unchecked(s7_pointer p1, s7_pointer p2) {return(p1 < p2);} +static bool char_lt_b_7pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + check_char2_args(sc, sc->char_lt_symbol, p1, p2); + return(p1 < p2); +} + +static bool char_leq_b_unchecked(s7_pointer p1, s7_pointer p2) {return(p1 <= p2);} +static bool char_leq_b_7pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + check_char2_args(sc, sc->char_leq_symbol, p1, p2); + return(p1 <= p2); +} + +static bool char_gt_b_unchecked(s7_pointer p1, s7_pointer p2) {return(p1 > p2);} +static bool char_gt_b_7pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + check_char2_args(sc, sc->char_gt_symbol, p1, p2); + return(p1 > p2); +} + +static bool char_geq_b_unchecked(s7_pointer p1, s7_pointer p2) {return(p1 >= p2);} +static bool char_geq_b_7pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + check_char2_args(sc, sc->char_geq_symbol, p1, p2); + return(p1 >= p2); +} + +static bool char_eq_b_unchecked(s7_pointer p1, s7_pointer p2) {return(p1 == p2);} + +static bool char_eq_b_7pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + if (!is_character(p1)) return(method_or_bust(sc, p1, sc->char_eq_symbol, set_plist_2(sc, p1, p2), sc->type_names[T_CHARACTER], 1) != sc->F); + if (p1 == p2) return(true); + if (!is_character(p2)) return(method_or_bust(sc, p2, sc->char_eq_symbol, set_plist_2(sc, p1, p2), sc->type_names[T_CHARACTER], 2) != sc->F); + return(false); +} + +static s7_pointer char_eq_p_pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + if (!is_character(p1)) return(method_or_bust(sc, p1, sc->char_eq_symbol, set_plist_2(sc, p1, p2), sc->type_names[T_CHARACTER], 1)); + if (p1 == p2) return(sc->T); + if (!is_character(p2)) return(method_or_bust(sc, p2, sc->char_eq_symbol, set_plist_2(sc, p1, p2), sc->type_names[T_CHARACTER], 2)); + return(sc->F); +} + +static s7_pointer g_char_equal_2(s7_scheme *sc, s7_pointer args) +{ + if (!is_character(car(args))) return(method_or_bust(sc, car(args), sc->char_eq_symbol, args, sc->type_names[T_CHARACTER], 1)); + if (car(args) == cadr(args)) + return(sc->T); + if (!is_character(cadr(args))) return(method_or_bust(sc, cadr(args), sc->char_eq_symbol, args, sc->type_names[T_CHARACTER], 2)); + return(sc->F); +} + +static s7_pointer g_char_less_2(s7_scheme *sc, s7_pointer args) +{ + if (!is_character(car(args))) return(method_or_bust(sc, car(args), sc->char_lt_symbol, args, sc->type_names[T_CHARACTER], 1)); + if (!is_character(cadr(args))) return(method_or_bust(sc, cadr(args), sc->char_lt_symbol, args, sc->type_names[T_CHARACTER], 2)); + return(make_boolean(sc, character(car(args)) < character(cadr(args)))); +} + +static s7_pointer g_char_greater_2(s7_scheme *sc, s7_pointer args) +{ + if (!is_character(car(args))) return(method_or_bust(sc, car(args), sc->char_gt_symbol, args, sc->type_names[T_CHARACTER], 1)); + if (!is_character(cadr(args))) return(method_or_bust(sc, cadr(args), sc->char_gt_symbol, args, sc->type_names[T_CHARACTER], 2)); + return(make_boolean(sc, character(car(args)) > character(cadr(args)))); +} + +static bool returns_char(s7_scheme *sc, s7_pointer arg) {return(argument_type(sc, arg) == sc->is_char_symbol);} + +static s7_pointer char_equal_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool ops) +{ + if (args != 2) return(f); + if (ops) + { + s7_pointer arg1 = cadr(expr), arg2 = caddr(expr); + if ((returns_char(sc, arg1)) && (returns_char(sc, arg2))) + return(sc->simple_char_eq); + } + return(sc->char_equal_2); +} + +static s7_pointer char_less_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer unused_expr, bool unused_ops) +{ + return((args == 2) ? sc->char_less_2 : f); +} + +static s7_pointer char_greater_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer unused_expr, bool unused_ops) +{ + return((args == 2) ? sc->char_greater_2 : f); +} + + +/* -------------------------------- char-ci? char-ci>=? char-ci=? -------------------------------- */ +#if (!WITH_PURE_S7) +static s7_pointer g_char_cmp_ci(s7_scheme *sc, s7_pointer args, int32_t val, s7_pointer sym) +{ + s7_pointer y = car(args); + if (!is_character(y)) + return(method_or_bust(sc, y, sym, args, sc->type_names[T_CHARACTER], 1)); + + for (s7_pointer x = cdr(args); is_pair(x); y = car(x), x = cdr(x)) + { + if (!is_character(car(x))) + return(method_or_bust(sc, car(x), sym, set_ulist_1(sc, y, x), sc->type_names[T_CHARACTER], position_of(x, args))); + if (charcmp(upper_character(y), upper_character(car(x))) != val) + return(char_with_error_check(sc, x, args, sym)); + } + return(sc->T); +} + +static s7_pointer g_char_cmp_ci_not(s7_scheme *sc, s7_pointer args, int32_t val, s7_pointer sym) +{ + s7_pointer y = car(args); + if (!is_character(y)) + return(method_or_bust(sc, y, sym, args, sc->type_names[T_CHARACTER], 1)); + for (s7_pointer x = cdr(args); is_pair(x); y = car(x), x = cdr(x)) + { + if (!is_character(car(x))) + return(method_or_bust(sc, car(x), sym, set_ulist_1(sc, y, x), sc->type_names[T_CHARACTER], position_of(x, args))); + if (charcmp(upper_character(y), upper_character(car(x))) == val) + return(char_with_error_check(sc, x, args, sym)); + } + return(sc->T); +} + +static s7_pointer g_chars_are_ci_equal(s7_scheme *sc, s7_pointer args) +{ + #define H_chars_are_ci_equal "(char-ci=? char ...) returns #t if all the character arguments are equal, ignoring case" + #define Q_chars_are_ci_equal sc->pcl_bc + return(g_char_cmp_ci(sc, args, 0, sc->char_ci_eq_symbol)); +} + +static s7_pointer g_chars_are_ci_less(s7_scheme *sc, s7_pointer args) +{ + #define H_chars_are_ci_less "(char-cipcl_bc + return(g_char_cmp_ci(sc, args, -1, sc->char_ci_lt_symbol)); +} + +static s7_pointer g_chars_are_ci_greater(s7_scheme *sc, s7_pointer args) +{ + #define H_chars_are_ci_greater "(char-ci>? char ...) returns #t if all the character arguments are decreasing, ignoring case" + #define Q_chars_are_ci_greater sc->pcl_bc + return(g_char_cmp_ci(sc, args, 1, sc->char_ci_gt_symbol)); +} + +static s7_pointer g_chars_are_ci_geq(s7_scheme *sc, s7_pointer args) +{ + #define H_chars_are_ci_geq "(char-ci>=? char ...) returns #t if all the character arguments are equal or decreasing, ignoring case" + #define Q_chars_are_ci_geq sc->pcl_bc + return(g_char_cmp_ci_not(sc, args, -1, sc->char_ci_geq_symbol)); +} + +static s7_pointer g_chars_are_ci_leq(s7_scheme *sc, s7_pointer args) +{ + #define H_chars_are_ci_leq "(char-ci<=? char ...) returns #t if all the character arguments are equal or increasing, ignoring case" + #define Q_chars_are_ci_leq sc->pcl_bc + return(g_char_cmp_ci_not(sc, args, 1, sc->char_ci_leq_symbol)); +} + + +static bool char_ci_lt_b_unchecked(s7_pointer p1, s7_pointer p2) {return(upper_character(p1) < upper_character(p2));} +static bool char_ci_lt_b_7pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + check_char2_args(sc, sc->char_ci_lt_symbol, p1, p2); + return(upper_character(p1) < upper_character(p2)); +} + +static bool char_ci_leq_b_unchecked(s7_pointer p1, s7_pointer p2) {return(upper_character(p1) <= upper_character(p2));} +static bool char_ci_leq_b_7pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + check_char2_args(sc, sc->char_ci_leq_symbol, p1, p2); + return(upper_character(p1) <= upper_character(p2)); +} + +static bool char_ci_gt_b_unchecked(s7_pointer p1, s7_pointer p2) {return(upper_character(p1) > upper_character(p2));} +static bool char_ci_gt_b_7pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + check_char2_args(sc, sc->char_ci_gt_symbol, p1, p2); + return(upper_character(p1) > upper_character(p2)); +} + +static bool char_ci_geq_b_unchecked(s7_pointer p1, s7_pointer p2) {return(upper_character(p1) >= upper_character(p2));} +static bool char_ci_geq_b_7pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + check_char2_args(sc, sc->char_ci_geq_symbol, p1, p2); + return(upper_character(p1) >= upper_character(p2)); +} + +static bool char_ci_eq_b_unchecked(s7_pointer p1, s7_pointer p2) {return(upper_character(p1) == upper_character(p2));} +static bool char_ci_eq_b_7pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + check_char2_args(sc, sc->char_ci_eq_symbol, p1, p2); + return(upper_character(p1) == upper_character(p2)); +} + +#endif /* not pure s7 */ + + +/* -------------------------------- char-position -------------------------------- */ +static s7_pointer g_char_position(s7_scheme *sc, s7_pointer args) +{ + #define H_char_position "(char-position char-or-str str (start 0)) returns the position of the first occurrence of char in str, or #f" + #define Q_char_position s7_make_signature(sc, 4, \ + s7_make_signature(sc, 2, sc->is_integer_symbol, sc->not_symbol), \ + s7_make_signature(sc, 2, sc->is_char_symbol, sc->is_string_symbol), \ + sc->is_string_symbol, sc->is_integer_symbol) + const char *porig, *pset; + s7_int start, pos, len; /* not "int" because start arg might be most-negative-fixnum */ + s7_pointer arg1 = car(args), arg2; + + if ((!is_character(arg1)) && + (!is_string(arg1))) + return(method_or_bust(sc, arg1, sc->char_position_symbol, args, sc->type_names[T_CHARACTER], 1)); + + arg2 = cadr(args); + if (!is_string(arg2)) + return(method_or_bust(sc, arg2, sc->char_position_symbol, args, sc->type_names[T_STRING], 2)); + + if (is_pair(cddr(args))) + { + s7_pointer arg3 = caddr(args); + if (!s7_is_integer(arg3)) + return(method_or_bust(sc, arg3, sc->char_position_symbol, args, sc->type_names[T_INTEGER], 3)); + start = s7_integer_clamped_if_gmp(sc, arg3); + if (start < 0) + wrong_type_error_nr(sc, sc->char_position_symbol, 3, arg3, a_non_negative_integer_string); + } + else start = 0; + + porig = string_value(arg2); + len = string_length(arg2); + if (start >= len) return(sc->F); + + if (is_character(arg1)) + { + char c = character(arg1); + const char *p = strchr((const char *)(porig + start), (int)c); /* use strchrnul in Gnu C to catch embedded null case */ + return((p) ? make_integer(sc, p - porig) : sc->F); + } + if (string_length(arg1) == 0) + return(sc->F); + pset = string_value(arg1); + + pos = strcspn((const char *)(porig + start), (const char *)pset); + if ((pos + start) < len) + return(make_integer(sc, pos + start)); + + /* if the string has an embedded null, we can get erroneous results here -- + * perhaps check for null at pos+start? What about a searched-for string that also has embedded nulls? + */ + return(sc->F); +} + +static s7_pointer char_position_p_ppi(s7_scheme *sc, s7_pointer p1, s7_pointer p2, s7_int start) +{ + /* p1 is char, p2 is string */ + const char *porig, *p; + s7_int len; + char c; + + if (!is_string(p2)) + wrong_type_error_nr(sc, sc->char_position_symbol, 2, p2, sc->type_names[T_STRING]); + if (start < 0) + wrong_type_error_nr(sc, sc->char_position_symbol, 3, wrap_integer(sc, start), a_non_negative_integer_string); + + c = character(p1); + len = string_length(p2); + porig = string_value(p2); + if (start >= len) return(sc->F); + p = strchr((const char *)(porig + start), (int)c); + if (p) return(make_integer(sc, p - porig)); + return(sc->F); +} + +static s7_pointer g_char_position_csi(s7_scheme *sc, s7_pointer args) +{ + /* assume char arg1, no end */ + const char *porig, *p; + char c = character(car(args)); + s7_pointer arg2 = cadr(args); + s7_int start, len; + + if (!is_string(arg2)) + return(g_char_position(sc, args)); + + len = string_length(arg2); /* can't return #f here if len==0 -- need start error check first */ + porig = string_value(arg2); + + if (is_pair(cddr(args))) + { + s7_pointer arg3 = caddr(args); + if (!s7_is_integer(arg3)) + return(g_char_position(sc, args)); + start = s7_integer_clamped_if_gmp(sc, arg3); + if (start < 0) + wrong_type_error_nr(sc, sc->char_position_symbol, 3, arg3, a_non_negative_integer_string); + if (start >= len) return(sc->F); + } + else start = 0; + + if (len == 0) return(sc->F); + p = strchr((const char *)(porig + start), (int)c); + return((p) ? make_integer(sc, p - porig) : sc->F); +} + +static s7_pointer char_position_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool ops) +{ + if (!ops) return(f); + if ((is_character(cadr(expr))) && ((args == 2) || (args == 3))) + return(sc->char_position_csi); + return(f); +} + + +/* -------------------------------- string-position -------------------------------- */ +static s7_pointer g_string_position(s7_scheme *sc, s7_pointer args) +{ + #define H_string_position "(string-position str1 str2 (start 0)) returns the starting position of str1 in str2 or #f" + #define Q_string_position s7_make_signature(sc, 4, \ + s7_make_signature(sc, 2, sc->is_integer_symbol, sc->not_symbol), \ + sc->is_string_symbol, sc->is_string_symbol, sc->is_integer_symbol) + const char *s1, *s2, *p2; + s7_int start = 0; + s7_pointer s1p = car(args), s2p = cadr(args); + + if (!is_string(s1p)) + return(method_or_bust(sc, s1p, sc->string_position_symbol, args, sc->type_names[T_STRING], 1)); + if (!is_string(s2p)) + return(method_or_bust(sc, s2p, sc->string_position_symbol, args, sc->type_names[T_STRING], 2)); + + if (is_pair(cddr(args))) + { + s7_pointer arg3 = caddr(args); + if (!s7_is_integer(arg3)) + return(method_or_bust(sc, arg3, sc->string_position_symbol, args, sc->type_names[T_INTEGER], 3)); + start = s7_integer_clamped_if_gmp(sc, arg3); + if (start < 0) + wrong_type_error_nr(sc, sc->string_position_symbol, 3, caddr(args), a_non_negative_integer_string); + } + + if (string_length(s1p) == 0) + return(sc->F); + s1 = string_value(s1p); + s2 = string_value(s2p); + if (start >= string_length(s2p)) + return(sc->F); + + p2 = strstr((const char *)(s2 + start), s1); + return((p2) ? make_integer(sc, p2 - s2) : sc->F); +} + + +/* -------------------------------- strings -------------------------------- */ +bool s7_is_string(s7_pointer p) {return(is_string(p));} + +static s7_pointer g_is_string(s7_scheme *sc, s7_pointer args) +{ + #define H_is_string "(string? obj) returns #t if obj is a string" + #define Q_is_string sc->pl_bt + check_boolean_method(sc, is_string, sc->is_string_symbol, args); +} + + +static s7_pointer nil_string; /* permanent "" */ + +s7_int s7_string_length(s7_pointer str) {return(string_length(str));} + + +#define NUM_STRING_WRAPPERS 8 + +static s7_pointer wrap_string(s7_scheme *sc, const char *str, s7_int len) +{ + s7_pointer x = car(sc->string_wrappers); + sc->string_wrappers = cdr(sc->string_wrappers); + string_value(x) = (char *)str; + string_length(x) = len; + return(x); +} + +s7_pointer s7_make_string_wrapper(s7_scheme *sc, const char *str) {return(wrap_string(sc, str, safe_strlen(str)));} +s7_pointer s7_make_string_wrapper_with_length(s7_scheme *sc, const char *str, s7_int len) {return(wrap_string(sc, str, len));} + +static Inline s7_pointer inline_make_empty_string(s7_scheme *sc, s7_int len, char fill) +{ + s7_pointer x; + block_t *b; + if (len == 0) return(nil_string); + new_cell(sc, x, T_STRING); + b = inline_mallocate(sc, len + 1); + string_block(x) = b; + string_value(x) = (char *)block_data(b); + if (fill != '\0') + local_memset((void *)(string_value(x)), fill, len); + string_value(x)[len] = 0; + string_hash(x) = 0; + string_length(x) = len; + add_string(sc, x); + return(x); +} + +static s7_pointer make_empty_string(s7_scheme *sc, s7_int len, char fill) {return(inline_make_empty_string(sc, len, fill));} + +s7_pointer s7_make_string(s7_scheme *sc, const char *str) {return((str) ? make_string_with_length(sc, str, safe_strlen(str)) : nil_string);} + +static char *make_semipermanent_c_string(s7_scheme *sc, const char *str) /* strcpy but avoid malloc */ +{ + s7_int len = safe_strlen(str); + char *x = (char *)permalloc(sc, len + 1); + memcpy((void *)x, (const void *)str, len); + x[len] = 0; + return(x); +} + +s7_pointer s7_make_semipermanent_string(s7_scheme *sc, const char *str) /* for (s7) string permanent within one s7 instance (freed upon s7_free) */ +{ + s7_pointer x; + s7_int len; + if (!str) return(nil_string); + x = alloc_pointer(sc); + set_full_type(x, T_STRING | T_IMMUTABLE | T_UNHEAP); + set_optimize_op(x, OP_CONSTANT); + len = safe_strlen(str); + string_length(x) = len; + string_block(x) = NULL; + string_value(x) = (char *)permalloc(sc, len + 1); + memcpy((void *)string_value(x), (const void *)str, len); + string_value(x)[len] = 0; + string_hash(x) = 0; + return(x); +} + +static s7_pointer make_permanent_string(const char *str) /* for (s7) strings outside all s7 GC's */ +{ + s7_pointer x = (s7_pointer)Calloc(1, sizeof(s7_cell)); + s7_int len = safe_strlen(str); + set_full_type(x, T_STRING | T_IMMUTABLE | T_UNHEAP); + set_optimize_op(x, OP_CONSTANT); + string_length(x) = len; + string_block(x) = NULL; + string_value(x) = (char *)str; + string_hash(x) = 0; + return(x); +} + +s7_pointer s7_make_permanent_string(s7_scheme *sc, const char *str) /* keep s7_scheme* arg for backwards compatibility */ +{ + return(make_permanent_string(str)); +} + +static void init_strings(void) +{ + nil_string = make_permanent_string(""); + nil_string->tf.flag = T_STRING | T_UNHEAP; + set_optimize_op(nil_string, OP_CONSTANT); + + car_a_list_string = make_permanent_string("a pair whose car is also a pair"); + cdr_a_list_string = make_permanent_string("a pair whose cdr is also a pair"); + + caar_a_list_string = make_permanent_string("a pair whose caar is also a pair"); + cadr_a_list_string = make_permanent_string("a pair whose cadr is also a pair"); + cdar_a_list_string = make_permanent_string("a pair whose cdar is also a pair"); + cddr_a_list_string = make_permanent_string("a pair whose cddr is also a pair"); + + caaar_a_list_string = make_permanent_string("a pair whose caaar is also a pair"); + caadr_a_list_string = make_permanent_string("a pair whose caadr is also a pair"); + cadar_a_list_string = make_permanent_string("a pair whose cadar is also a pair"); + caddr_a_list_string = make_permanent_string("a pair whose caddr is also a pair"); + cdaar_a_list_string = make_permanent_string("a pair whose cdaar is also a pair"); + cdadr_a_list_string = make_permanent_string("a pair whose cdadr is also a pair"); + cddar_a_list_string = make_permanent_string("a pair whose cddar is also a pair"); + cdddr_a_list_string = make_permanent_string("a pair whose cdddr is also a pair"); + + a_list_string = make_permanent_string("a list"); + an_eq_func_string = make_permanent_string("a procedure that can take two arguments"); + an_association_list_string = make_permanent_string("an association list"); + a_normal_real_string = make_permanent_string("a normal real"); + a_rational_string = make_permanent_string("an integer or a ratio"); + a_number_string = make_permanent_string("a number"); + a_procedure_string = make_permanent_string("a procedure"); + a_procedure_or_a_macro_string = make_permanent_string("a procedure or a macro"); + a_normal_procedure_string = make_permanent_string("a normal procedure"); + a_let_string = make_permanent_string("a let (environment)"); + a_proper_list_string = make_permanent_string("a proper list"); + a_boolean_string = make_permanent_string("a boolean"); + a_byte_vector_string = make_permanent_string("a byte-vector"); + an_input_port_string = make_permanent_string("an input port"); + an_open_input_port_string = make_permanent_string("an open input port"); + an_open_output_port_string = make_permanent_string("an open output port"); + an_output_port_string = make_permanent_string("an output port"); + an_output_port_or_f_string = make_permanent_string("an output port or #f"); + an_input_string_port_string = make_permanent_string("an input string port"); + an_input_file_port_string = make_permanent_string("an input file port"); + an_output_string_port_string = make_permanent_string("an output string port"); + an_output_file_port_string = make_permanent_string("an output file port"); + a_thunk_string = make_permanent_string("a thunk"); + a_symbol_string = make_permanent_string("a symbol"); + a_non_negative_integer_string = make_permanent_string("a non-negative integer"); + an_unsigned_byte_string = make_permanent_string("an unsigned byte"); + something_applicable_string = make_permanent_string("a procedure or something applicable"); + a_random_state_object_string = make_permanent_string("a random-state object"); + a_format_port_string = make_permanent_string("#f, #t, (), or an open output port"); + a_non_constant_symbol_string = make_permanent_string("a non-constant symbol"); + a_sequence_string = make_permanent_string("a sequence"); + a_valid_radix_string = make_permanent_string("it should be between 2 and 16"); + result_is_too_large_string = make_permanent_string("result is too large"); + it_is_too_large_string = make_permanent_string("it is too large"); + it_is_too_small_string = make_permanent_string("it is less than the start position"); + it_is_negative_string = make_permanent_string("it is negative"); + it_is_nan_string = make_permanent_string("NaN usually indicates a numerical error"); + it_is_infinite_string = make_permanent_string("it is infinite"); + too_many_indices_string = make_permanent_string("too many indices"); + parameter_set_twice_string = make_permanent_string("parameter set twice, ~S in ~S"); + immutable_error_string = make_permanent_string("can't ~S ~S (it is immutable)"); + cant_bind_immutable_string = make_permanent_string("~A: can't bind an immutable object: ~S"); + intermediate_too_large_string = make_permanent_string("intermediate result is too large"); +#if (!HAVE_COMPLEX_NUMBERS) + no_complex_numbers_string = make_permanent_string("this version of s7 does not support complex numbers"); +#endif + keyword_value_missing_string = make_permanent_string("~A: keyword argument's value is missing: ~S in ~S"); + + format_string_1 = make_permanent_string("format: ~S ~{~S~^ ~}: ~A"); + format_string_2 = make_permanent_string("format: ~S: ~A"); + format_string_3 = make_permanent_string("format: ~S ~{~S~^ ~}~&~NT^: ~A"); + format_string_4 = make_permanent_string("format: ~S~&~NT^: ~A"); + + too_many_arguments_string = make_permanent_string("~S: too many arguments: ~A"); + not_enough_arguments_string = make_permanent_string("~S: not enough arguments: ~A"); +} + + +/* -------------------------------- make-string -------------------------------- */ +s7_pointer s7_make_string_with_length(s7_scheme *sc, const char *str, s7_int len) {return(make_string_with_length(sc, str, len));} + +static s7_pointer g_make_string(s7_scheme *sc, s7_pointer args) +{ + #define H_make_string "(make-string len (val #\\space)) makes a string of length len filled with the character val (default: space)" + #define Q_make_string s7_make_signature(sc, 3, sc->is_string_symbol, sc->is_integer_symbol, sc->is_char_symbol) + + s7_pointer n = car(args); + s7_int len; + char fill; + if (!s7_is_integer(n)) + { + check_method(sc, n, sc->make_string_symbol, args); + wrong_type_error_nr(sc, sc->make_string_symbol, 1, n, sc->type_names[T_INTEGER]); + } + if ((is_pair(cdr(args))) && + (!is_character(cadr(args)))) + return(method_or_bust(sc, cadr(args), sc->make_string_symbol, args, sc->type_names[T_CHARACTER], 2)); + + len = s7_integer_clamped_if_gmp(sc, n); + if (len == 0) return(nil_string); + if ((len < 0) || (len > sc->max_string_length)) + out_of_range_error_nr(sc, sc->make_string_symbol, int_one, n, (len < 0) ? it_is_negative_string : it_is_too_large_string); + if (is_null(cdr(args))) + return(make_empty_string(sc, len, '\0')); /* #\null here means "don't fill/clear" */ + fill = s7_character(cadr(args)); + n = make_empty_string(sc, len, fill); + if (fill == '\0') + memclr((void *)string_value(n), (size_t)len); + return(n); +} + +static s7_pointer make_string_p_i(s7_scheme *sc, s7_int len) +{ + if (len == 0) return(nil_string); + if ((len < 0) || (len > sc->max_string_length)) + out_of_range_error_nr(sc, sc->make_string_symbol, int_one, wrap_integer(sc, len), (len < 0) ? it_is_negative_string : it_is_too_large_string); + return(make_empty_string(sc, len, '\0')); +} + + +#if (!WITH_PURE_S7) +/* -------------------------------- string-length -------------------------------- */ +static s7_pointer g_string_length(s7_scheme *sc, s7_pointer args) +{ + #define H_string_length "(string-length str) returns the length of the string str" + #define Q_string_length s7_make_signature(sc, 2, sc->is_integer_symbol, sc->is_string_symbol) + s7_pointer p = car(args); + if (!is_string(p)) + return(sole_arg_method_or_bust(sc, p, sc->string_length_symbol, args, sc->type_names[T_STRING])); + return(make_integer(sc, string_length(p))); +} + +static s7_int string_length_i_7p(s7_scheme *sc, s7_pointer p) +{ + if (!is_string(p)) + return(integer(method_or_bust_p(sc, p, sc->string_length_symbol, sc->type_names[T_STRING]))); + return(string_length(p)); +} +#endif + + +/* -------------------------------- string-up|downcase -------------------------------- */ +static s7_pointer g_string_downcase(s7_scheme *sc, s7_pointer args) +{ + #define H_string_downcase "(string-downcase str) returns the lower case version of str." + #define Q_string_downcase sc->pcl_s + + s7_pointer p = car(args), newstr; + s7_int i, len; + uint8_t *nstr; + const uint8_t *ostr; + + if (!is_string(p)) + return(method_or_bust_p(sc, p, sc->string_downcase_symbol, sc->type_names[T_STRING])); + len = string_length(p); + newstr = make_empty_string(sc, len, 0); + + ostr = (const uint8_t *)string_value(p); + nstr = (uint8_t *)string_value(newstr); + if (len >= 128) + { + i = len - 1; + while (i >= 8) + LOOP_8(nstr[i] = lowers[(uint8_t)ostr[i]]; i--); + while (i >= 0) {nstr[i] = lowers[(uint8_t)ostr[i]]; i--;} + } + else + for (i = 0; i < len; i++) nstr[i] = lowers[(uint8_t)ostr[i]]; + return(newstr); +} + +static s7_pointer g_string_upcase(s7_scheme *sc, s7_pointer args) +{ + #define H_string_upcase "(string-upcase str) returns the upper case version of str." + #define Q_string_upcase sc->pcl_s + + s7_pointer p = car(args), newstr; + s7_int i, len; + uint8_t *nstr; + const uint8_t *ostr; + + if (!is_string(p)) + return(method_or_bust_p(sc, p, sc->string_upcase_symbol, sc->type_names[T_STRING])); + len = string_length(p); + newstr = make_empty_string(sc, len, 0); + + ostr = (const uint8_t *)string_value(p); + nstr = (uint8_t *)string_value(newstr); + if (len >= 128) + { + i = len - 1; + while (i >= 8) + LOOP_8(nstr[i] = uppers[(uint8_t)ostr[i]]; i--); + while (i >= 0) {nstr[i] = uppers[(uint8_t)ostr[i]]; i--;} + } + else + for (i = 0; i < len; i++) nstr[i] = uppers[(uint8_t)ostr[i]]; + return(newstr); +} + + +/* -------------------------------- string-ref -------------------------------- */ +static s7_pointer string_ref_1(s7_scheme *sc, s7_pointer strng, s7_pointer index) +{ + char *str; + s7_int ind; + + if (!s7_is_integer(index)) + return(method_or_bust_pp(sc, index, sc->string_ref_symbol, strng, index, sc->type_names[T_INTEGER], 2)); + ind = s7_integer_clamped_if_gmp(sc, index); + if (ind < 0) + out_of_range_error_nr(sc, sc->string_ref_symbol, int_two, index, it_is_negative_string); + if (ind >= string_length(strng)) + out_of_range_error_nr(sc, sc->string_ref_symbol, int_two, index, it_is_too_large_string); + + str = string_value(strng); + return(chars[((uint8_t *)str)[ind]]); +} + +static s7_pointer g_string_ref(s7_scheme *sc, s7_pointer args) +{ + #define H_string_ref "(string-ref str index) returns the character at the index-th element of the string str" + #define Q_string_ref s7_make_signature(sc, 3, sc->is_char_symbol, sc->is_string_symbol, sc->is_integer_symbol) + + s7_pointer strng = car(args); + if (!is_string(strng)) + return(method_or_bust(sc, strng, sc->string_ref_symbol, args, sc->type_names[T_STRING], 1)); + return(string_ref_1(sc, strng, cadr(args))); +} + +static s7_pointer string_ref_p_pi(s7_scheme *sc, s7_pointer p1, s7_int i1) +{ + if (!is_string(p1)) + return(method_or_bust(sc, p1, sc->string_ref_symbol, set_plist_2(sc, p1, make_integer(sc, i1)), sc->type_names[T_STRING], 1)); + if ((i1 < 0) || (i1 >= string_length(p1))) + out_of_range_error_nr(sc, sc->string_ref_symbol, int_two, wrap_integer(sc, i1), (i1 < 0) ? it_is_negative_string : it_is_too_large_string); + return(chars[((uint8_t *)string_value(p1))[i1]]); +} + +static s7_pointer string_ref_p_pp(s7_scheme *sc, s7_pointer p1, s7_pointer i1) +{ + if (!is_string(p1)) + return(method_or_bust_pp(sc, p1, sc->string_ref_symbol, p1, i1, sc->type_names[T_STRING], 1)); + return(string_ref_1(sc, p1, i1)); +} + +static s7_pointer string_ref_p_p0(s7_scheme *sc, s7_pointer p1, s7_pointer unused_i1) +{ + if (!is_string(p1)) + return(method_or_bust_pp(sc, p1, sc->string_ref_symbol, p1, int_zero, sc->type_names[T_STRING], 1)); + if (string_length(p1) <= 0) + out_of_range_error_nr(sc, sc->string_ref_symbol, int_two, int_zero, it_is_too_large_string); + return(chars[((uint8_t *)string_value(p1))[0]]); +} + +static s7_pointer string_plast_via_method(s7_scheme *sc, s7_pointer p1) /* tmock */ +{ + s7_pointer len = method_or_bust_p(sc, p1, sc->length_symbol, sc->type_names[T_STRING]); + return(method_or_bust_with_type_pi(sc, p1, sc->string_ref_symbol, p1, integer(len) - 1, sc->type_names[T_STRING], 1)); +} + +static s7_pointer string_ref_p_plast(s7_scheme *sc, s7_pointer p1, s7_pointer unused_i1) +{ + if (!is_string(p1)) + return(string_plast_via_method(sc, p1)); + if (string_length(p1) <= 0) + out_of_range_error_nr(sc, sc->string_ref_symbol, int_two, wrap_integer(sc, string_length(p1) - 1), it_is_too_large_string); + return(chars[((uint8_t *)string_value(p1))[string_length(p1) - 1]]); +} + +static inline s7_pointer string_ref_p_pi_unchecked(s7_scheme *sc, s7_pointer p1, s7_int i1) +{ + if ((i1 < 0) || (i1 >= string_length(p1))) + out_of_range_error_nr(sc, sc->string_ref_symbol, int_two, wrap_integer(sc, i1), (i1 < 0) ? it_is_negative_string : it_is_too_large_string); + return(chars[((uint8_t *)string_value(p1))[i1]]); +} + +static s7_pointer string_ref_p_pi_direct(s7_scheme *unused_sc, s7_pointer p1, s7_int i1) {return(chars[((uint8_t *)string_value(p1))[i1]]);} + + +/* -------------------------------- string-set! -------------------------------- */ +static s7_pointer g_string_set(s7_scheme *sc, s7_pointer args) +{ + #define H_string_set "(string-set! str index chr) sets the index-th element of the string str to the character chr" + #define Q_string_set s7_make_signature(sc, 4, sc->is_char_symbol, sc->is_string_symbol, sc->is_integer_symbol, sc->is_char_symbol) + + s7_pointer strng = car(args), c, index = cadr(args); + char *str; + s7_int ind; + + if (!is_mutable_string(strng)) + return(mutable_method_or_bust(sc, strng, sc->string_set_symbol, args, sc->type_names[T_STRING], 1)); + if (!s7_is_integer(index)) + return(method_or_bust(sc, index, sc->string_set_symbol, args, sc->type_names[T_INTEGER], 2)); + + ind = s7_integer_clamped_if_gmp(sc, index); + if (ind < 0) + out_of_range_error_nr(sc, sc->string_set_symbol, int_two, index, a_non_negative_integer_string); + if (ind >= string_length(strng)) + out_of_range_error_nr(sc, sc->string_set_symbol, int_two, index, it_is_too_large_string); + + str = string_value(strng); + c = caddr(args); + if (!is_character(c)) + return(method_or_bust(sc, c, sc->string_set_symbol, args, sc->type_names[T_CHARACTER], 3)); + + str[ind] = (char)s7_character(c); + return(c); +} + +static s7_pointer string_set_p_pip(s7_scheme *sc, s7_pointer p1, s7_int i1, s7_pointer p2) +{ + if (!is_string(p1)) + wrong_type_error_nr(sc, sc->string_set_symbol, 1, p1, sc->type_names[T_STRING]); + if (!is_character(p2)) + wrong_type_error_nr(sc, sc->string_set_symbol, 2, p2, sc->type_names[T_CHARACTER]); + if ((i1 >= 0) && (i1 < string_length(p1))) + string_value(p1)[i1] = s7_character(p2); + else out_of_range_error_nr(sc, sc->string_set_symbol, int_two, wrap_integer(sc, i1), (i1 < 0) ? it_is_negative_string : it_is_too_large_string); + return(p2); +} + +static s7_pointer string_set_p_pip_unchecked(s7_scheme *sc, s7_pointer p1, s7_int i1, s7_pointer p2) +{ + if ((i1 >= 0) && (i1 < string_length(p1))) + string_value(p1)[i1] = s7_character(p2); + else out_of_range_error_nr(sc, sc->string_set_symbol, int_two, wrap_integer(sc, i1), (i1 < 0) ? it_is_negative_string : it_is_too_large_string); + return(p2); +} + +static s7_pointer string_set_p_pip_direct(s7_scheme *unused_sc, s7_pointer p1, s7_int i1, s7_pointer p2) {string_value(p1)[i1] = s7_character(p2); return(p2);} + + +/* -------------------------------- string-append -------------------------------- */ +static s7_pointer c_object_length(s7_scheme *sc, s7_pointer obj); + +static bool sequence_is_empty(s7_scheme *sc, s7_pointer obj) /* "is_empty" is some C++ struct?? */ +{ + switch (type(obj)) + { + case T_BYTE_VECTOR: case T_INT_VECTOR: case T_FLOAT_VECTOR: + case T_VECTOR: return(vector_length(obj) == 0); + case T_NIL: return(true); + case T_PAIR: return(false); + case T_STRING: return(string_length(obj) == 0); + case T_HASH_TABLE: return(hash_table_entries(obj) == 0); + case T_C_OBJECT: return(s7_is_eqv(sc, c_object_length(sc, obj), int_zero)); + case T_LET: if (obj != sc->rootlet) return(!tis_slot(let_slots(obj))); /* (append (rootlet) #f) */ + default: return(false); + } +} + +static s7_int sequence_length(s7_scheme *sc, s7_pointer lst) +{ + switch (type(lst)) + { + case T_PAIR: + { + s7_int len = s7_list_length(sc, lst); + return((len == 0) ? -1 : len); + } + case T_NIL: return(0); + case T_BYTE_VECTOR: case T_INT_VECTOR: case T_FLOAT_VECTOR: + case T_VECTOR: return(vector_length(lst)); + case T_STRING: return(string_length(lst)); + case T_HASH_TABLE: return(hash_table_entries(lst)); + case T_LET: return(let_length(sc, lst)); + case T_C_OBJECT: + { + s7_pointer x = c_object_length(sc, lst); + if (s7_is_integer(x)) + return(s7_integer_clamped_if_gmp(sc, x)); + }} + return(-1); +} + +static s7_pointer s7_copy_1(s7_scheme *sc, s7_pointer caller, s7_pointer args); + +static void string_append_2(s7_scheme *sc, s7_pointer newstr, s7_pointer args, const s7_pointer stop_arg, s7_pointer caller) +{ + s7_int len; + char *pos; + s7_pointer x; + for (pos = string_value(newstr), x = args; x != stop_arg; x = cdr(x)) + if (is_string(car(x))) + { + len = string_length(car(x)); + if (len > 0) + { + memcpy(pos, string_value(car(x)), len); + pos += len; + }} + else + if (!sequence_is_empty(sc, car(x))) + { + char *old_str = string_value(newstr); + string_value(newstr) = pos; + len = sequence_length(sc, car(x)); + s7_copy_1(sc, caller, set_plist_2(sc, car(x), newstr)); + string_value(newstr) = old_str; + pos += len; + } +} + +static s7_pointer g_string_append_1(s7_scheme *sc, s7_pointer args, s7_pointer caller) +{ + #define H_string_append "(string-append str1 ...) appends all its string arguments into one string" + #define Q_string_append sc->pcl_s + + s7_int len = 0; + s7_pointer x, newstr; + bool just_strings = true; + + if (is_null(args)) + return(nil_string); + + s7_gc_protect_via_stack(sc, args); + /* get length for new string */ + for (x = args; is_not_null(x); x = cdr(x)) + { + s7_pointer p = car(x); + if (is_string(p)) + len += string_length(p); + else + { + s7_int newlen; + if (!is_sequence(p)) + { + unstack(sc); + wrong_type_error_nr(sc, caller, position_of(x, args), p, sc->type_names[T_STRING]); + } + if (has_active_methods(sc, p)) /* look for string-append and if found, cobble up a plausible intermediate call */ + { + s7_pointer func = find_method_with_let(sc, p, caller); + if (func != sc->undefined) + { + if (len == 0) + { + unstack(sc); + return(s7_apply_function(sc, func, x)); /* not args (string-append "" "" ...) */ + } + newstr = make_empty_string(sc, len, 0); + string_append_2(sc, newstr, args, x, caller); + unstack(sc); + return(s7_apply_function(sc, func, set_ulist_1(sc, newstr, x))); + }} + if ((caller == sc->string_append_symbol) || (caller == sc->symbol_symbol)) + { + unstack(sc); + wrong_type_error_nr(sc, caller, position_of(x, args), p, sc->type_names[T_STRING]); + } + newlen = sequence_length(sc, p); + if (newlen < 0) + { + unstack(sc); + wrong_type_error_nr(sc, caller, position_of(x, args), p, sc->type_names[T_STRING]); + } + just_strings = false; + len += newlen; + }} + if (len == 0) + { + unstack(sc); + return(nil_string); + } + if (len > sc->max_string_length) + { + unstack(sc); + error_nr(sc, sc->out_of_range_symbol, + set_elist_4(sc, wrap_string(sc, "~S new string length, ~D, is larger than (*s7* 'max-string-length): ~D", 70), + caller, wrap_integer(sc, len), wrap_integer(sc, sc->max_string_length))); + } + newstr = inline_make_empty_string(sc, len, 0); + if (just_strings) + { + x = args; + for (char *pos = string_value(newstr); is_not_null(x); x = cdr(x)) + { + len = string_length(car(x)); + if (len > 0) + { + memcpy(pos, string_value(car(x)), len); + pos += len; + }}} + else string_append_2(sc, newstr, args, sc->nil, caller); + unstack(sc); + return(newstr); +} + +static s7_pointer g_string_append(s7_scheme *sc, s7_pointer args) {return(g_string_append_1(sc, args, sc->string_append_symbol));} + +static inline s7_pointer string_append_1(s7_scheme *sc, s7_pointer s1, s7_pointer s2) +{ + if ((is_string(s1)) && (is_string(s2))) + { + s7_int len, pos = string_length(s1); + s7_pointer newstr; + if (pos == 0) return(make_string_with_length(sc, string_value(s2), string_length(s2))); + len = pos + string_length(s2); + if (len == pos) return(make_string_with_length(sc, string_value(s1), string_length(s1))); + if (len > sc->max_string_length) + error_nr(sc, sc->out_of_range_symbol, + set_elist_4(sc, wrap_string(sc, "~S new string length, ~D, is larger than (*s7* 'max-string-length): ~D", 70), + sc->string_append_symbol, wrap_integer(sc, len), wrap_integer(sc, sc->max_string_length))); + newstr = make_empty_string(sc, len, 0); /* len+1 0-terminated */ + memcpy(string_value(newstr), string_value(s1), pos); + memcpy((char *)(string_value(newstr) + pos), string_value(s2), string_length(s2)); + return(newstr); + } + return(g_string_append_1(sc, list_2(sc, s1, s2), sc->string_append_symbol)); +} + +static s7_pointer string_append_p_pp(s7_scheme *sc, s7_pointer s1, s7_pointer s2) {return(string_append_1(sc, s1, s2));} + +static s7_pointer g_string_append_2(s7_scheme *sc, s7_pointer args) {return(string_append_1(sc, car(args), cadr(args)));} + +static void check_for_substring_temp(s7_scheme *sc, s7_pointer expr); + +static s7_pointer string_append_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool unused_ops) +{ + check_for_substring_temp(sc, expr); + return((args == 2) ? sc->string_append_2 : f); +} + + +/* -------------------------------- substring -------------------------------- */ +static s7_pointer start_and_end(s7_scheme *sc, s7_pointer caller, s7_pointer args, int32_t position, s7_pointer index_args, s7_int *start, s7_int *end) +{ + /* we assume that *start=0 and *end=length, that end is "exclusive", return true if the start/end points are not changed */ + s7_pointer pstart = car(index_args); + s7_int index; + + if (!s7_is_integer(pstart)) + return(method_or_bust(sc, pstart, caller, args, sc->type_names[T_INTEGER], position)); + index = s7_integer_clamped_if_gmp(sc, pstart); + if ((index < 0) || + (index > *end)) /* *end == length here */ + out_of_range_error_nr(sc, caller, small_int(position), pstart, (index < 0) ? it_is_negative_string : it_is_too_large_string); + *start = index; + + if (is_pair(cdr(index_args))) + { + s7_pointer pend = cadr(index_args); + if (!s7_is_integer(pend)) + return(method_or_bust(sc, pend, caller, args, sc->type_names[T_INTEGER], position + 1)); + index = s7_integer_clamped_if_gmp(sc, pend); + if ((index < *start) || + (index > *end)) + out_of_range_error_nr(sc, caller, small_int(position + 1), pend, (index < *start) ? it_is_too_small_string : it_is_too_large_string); + *end = index; + } + return(sc->unused); +} + +static s7_pointer g_substring(s7_scheme *sc, s7_pointer args) +{ + #define H_substring "(substring str start (end (length str))) returns the portion of the string str between start and \ +end: (substring \"01234\" 1 2) -> \"1\"" + #define Q_substring s7_make_circular_signature(sc, 2, 3, sc->is_string_symbol, sc->is_string_symbol, sc->is_integer_symbol) + + s7_pointer x, str = car(args); + s7_int start = 0, end, len; + char *s; + + if (!is_string(str)) + return(method_or_bust(sc, str, sc->substring_symbol, args, sc->type_names[T_STRING], 1)); + end = string_length(str); + if (!is_null(cdr(args))) + { + x = start_and_end(sc, sc->substring_symbol, args, 2, cdr(args), &start, &end); + if (x != sc->unused) return(x); + } + s = string_value(str); + len = end - start; + if (len == 0) return(nil_string); + x = inline_make_string_with_length(sc, (char *)(s + start), len); + string_value(x)[len] = 0; + return(x); +} + +static s7_pointer g_substring_uncopied(s7_scheme *sc, s7_pointer args) +{ + s7_pointer str = car(args); + s7_int start = 0, end; + + if (!is_string(str)) + return(method_or_bust(sc, str, sc->substring_symbol, args, sc->type_names[T_STRING], 1)); + end = string_length(str); + if (!is_null(cdr(args))) + { + s7_pointer x = start_and_end(sc, sc->substring_symbol, args, 2, cdr(args), &start, &end); + if (x != sc->unused) return(x); + } + return(wrap_string(sc, (char *)(string_value(str) + start), end - start)); +} + +static s7_pointer substring_uncopied_p_pii(s7_scheme *sc, s7_pointer str, s7_int start, s7_int end) +{ + /* is_string arg1 checked in opt */ + if ((end < start) || (end > string_length(str))) + out_of_range_error_nr(sc, sc->substring_symbol, int_three, wrap_integer(sc, end), (end < start) ? it_is_too_small_string : it_is_too_large_string); + if (start < 0) + out_of_range_error_nr(sc, sc->substring_symbol, int_two, wrap_integer(sc, start), it_is_negative_string); + return(wrap_string(sc, (char *)(string_value(str) + start), end - start)); +} + +static s7_pointer g_get_output_string(s7_scheme *sc, s7_pointer args); + +static void check_for_substring_temp(s7_scheme *sc, s7_pointer expr) +{ + int32_t substrs = 0; + /* don't use substring_uncopied for arg if arg is returned: (reverse! (write-string (substring x ...))) */ + for (s7_pointer p = cdr(expr); is_pair(p); p = cdr(p)) + { + s7_pointer arg = car(p); + if ((is_pair(arg)) && + (is_symbol(car(arg))) && + (is_safely_optimized(arg)) && + (has_fn(arg))) + { + if (fn_proc(arg) == g_substring) + { + if (substrs < NUM_STRING_WRAPPERS) + set_c_function(arg, sc->substring_uncopied); + substrs++; + } + else + if (fn_proc(arg) == g_symbol_to_string) + set_c_function(arg, sc->symbol_to_string_uncopied); + else + if ((fn_proc(arg) == g_get_output_string) && (is_null(cddr(arg)))) + set_c_function(arg, sc->get_output_string_uncopied); + }} +} + +static s7_pointer string_substring_chooser(s7_scheme *sc, s7_pointer f, int32_t unused_args, s7_pointer expr, bool unused_ops) +{ + /* used by several string functions */ + check_for_substring_temp(sc, expr); + return(f); +} + + +/* -------------------------------- string-copy -------------------------------- */ +static s7_pointer g_string_copy(s7_scheme *sc, s7_pointer args) +{ + #define H_string_copy "(string-copy str dest-str (dest-start 0) dest-end) returns a copy of its string argument. If dest-str is given, \ + string-copy copies its first argument into the second, starting at dest-start in the second string and returns dest-str" + #define Q_string_copy s7_make_signature(sc, 5, sc->is_string_symbol, sc->is_string_symbol, sc->is_string_symbol, sc->is_integer_symbol, sc->is_integer_symbol) + + s7_pointer source = car(args), p, dest; + s7_int start, end; + + if (!is_string(source)) + return(method_or_bust(sc, source, sc->string_copy_symbol, args, sc->type_names[T_STRING], 1)); + if (is_null(cdr(args))) + return(make_string_with_length(sc, string_value(source), string_length(source))); + + dest = cadr(args); + if (!is_string(dest)) + wrong_type_error_nr(sc, sc->string_copy_symbol, 2, dest, sc->type_names[T_STRING]); + if (is_immutable_string(dest)) + immutable_object_error_nr(sc, set_elist_2(sc, wrap_string(sc, "can't string-copy to ~S; it is immutable", 40), dest)); + + end = string_length(dest); + p = cddr(args); + if (is_null(p)) + start = 0; + else + { + if (!s7_is_integer(car(p))) + wrong_type_error_nr(sc, sc->string_copy_symbol, 3, car(p), sc->type_names[T_INTEGER]); + start = s7_integer_clamped_if_gmp(sc, car(p)); + if (start < 0) start = 0; + p = cdr(p); + if (is_null(p)) + end = start + string_length(source); + else + { + if (!s7_is_integer(car(p))) + wrong_type_error_nr(sc, sc->string_copy_symbol, 4, car(p), sc->type_names[T_INTEGER]); + end = s7_integer_clamped_if_gmp(sc, car(p)); + if (end < 0) end = start; + }} + if (end > string_length(dest)) end = string_length(dest); + if (end <= start) return(dest); + if ((end - start) > string_length(source)) end = start + string_length(source); + memmove((void *)(string_value(dest) + start), (void *)(string_value(source)), end - start); + /* although I haven't tracked down a case, libasan+auto-tester reported sourcechar #xf0)) (string (integer->char #x70))) + * and null or lack thereof does not say anything about the string end + */ + size_t len1 = (size_t)string_length(s1); + size_t len2 = (size_t)string_length(s2); + size_t len = (len1 > len2) ? len2 : len1; + char *str1 = string_value(s1); + char *str2 = string_value(s2); + + if (len < sizeof(size_t)) + for (size_t i = 0; i < len; i++) + { + if ((uint8_t)(str1[i]) < (uint8_t )(str2[i])) + return(-1); + if ((uint8_t)(str1[i]) > (uint8_t)(str2[i])) + return(1); + } + else + { + /* this algorithm from stackoverflow(?), with various changes (original did not work for large strings, etc) */ + size_t i = 0, last = len / sizeof(size_t); + for (const size_t *ptr1 = (size_t *)str1, *ptr2 = (size_t *)str2; i < last; i++) + if (ptr1[i] != ptr2[i]) + break; + for (size_t pos = i * sizeof(size_t); pos < len; pos++) + { + if ((uint8_t)str1[pos] < (uint8_t)str2[pos]) + return(-1); + if ((uint8_t)str1[pos] > (uint8_t)str2[pos]) + return(1); + }} + if (len1 < len2) + return(-1); + return((len1 > len2) ? 1 : 0); +} + +static bool is_string_via_method(s7_scheme *sc, s7_pointer p) +{ + if (s7_is_string(p)) + return(true); + if (has_active_methods(sc, p)) + { + s7_pointer f = find_method_with_let(sc, p, sc->is_string_symbol); + if (f != sc->undefined) + return(is_true(sc, s7_apply_function(sc, f, set_plist_1(sc, p)))); + } + return(false); +} + +static s7_pointer g_string_cmp(s7_scheme *sc, s7_pointer args, int32_t val, s7_pointer sym) +{ + s7_pointer y = car(args); + if (!is_string(y)) + return(method_or_bust(sc, y, sym, args, sc->type_names[T_STRING], 1)); + for (s7_pointer x = cdr(args); is_not_null(x); y = car(x), x = cdr(x)) + { + if (!is_string(car(x))) + return(method_or_bust(sc, car(x), sym, set_ulist_1(sc, y, x), sc->type_names[T_STRING], position_of(x, args))); + if (scheme_strcmp(y, car(x)) != val) + { + for (y = cdr(x); is_pair(y); y = cdr(y)) + if (!is_string_via_method(sc, car(y))) + wrong_type_error_nr(sc, sym, position_of(y, args), car(y), sc->type_names[T_STRING]); + return(sc->F); + }} + return(sc->T); +} + +static s7_pointer g_string_cmp_not(s7_scheme *sc, s7_pointer args, int32_t val, s7_pointer sym) +{ + s7_pointer y = car(args); + if (!is_string(y)) + return(method_or_bust(sc, y, sym, args, sc->type_names[T_STRING], 1)); + for (s7_pointer x = cdr(args); is_not_null(x); y = car(x), x = cdr(x)) + { + if (!is_string(car(x))) + return(method_or_bust(sc, car(x), sym, set_ulist_1(sc, y, x), sc->type_names[T_STRING], position_of(x, args))); + if (scheme_strcmp(y, car(x)) == val) + { + for (y = cdr(x); is_pair(y); y = cdr(y)) + if (!is_string_via_method(sc, car(y))) + wrong_type_error_nr(sc, sym, position_of(y, args), car(y), sc->type_names[T_STRING]); + return(sc->F); + }} + return(sc->T); +} + +static bool scheme_strings_are_equal(s7_pointer x, s7_pointer y) +{ + return((string_length(x) == string_length(y)) && + (strings_are_equal_with_length(string_value(x), string_value(y), string_length(x)))); +} + +static s7_pointer g_strings_are_equal(s7_scheme *sc, s7_pointer args) +{ + #define H_strings_are_equal "(string=? str ...) returns #t if all the string arguments are equal" + #define Q_strings_are_equal sc->pcl_bs + + /* C-based check stops at null, but we can have embedded nulls. + * (let ((s1 "1234") (s2 "1245")) (string-set! s1 1 #\null) (string-set! s2 1 #\null) (string=? s1 s2)) + */ + s7_pointer y = car(args); + bool happy = true; + + if (!is_string(y)) + return(method_or_bust(sc, y, sc->string_eq_symbol, args, sc->type_names[T_STRING], 1)); + for (s7_pointer x = cdr(args); is_pair(x); x = cdr(x)) + { + s7_pointer p = car(x); + if (y != p) + { + if (!is_string(p)) + return(method_or_bust(sc, p, sc->string_eq_symbol, set_ulist_1(sc, y, x), sc->type_names[T_STRING], position_of(x, args))); + if (happy) + happy = scheme_strings_are_equal(p, y); + }} + return((happy) ? sc->T : sc->F); +} + +static s7_pointer g_strings_are_less(s7_scheme *sc, s7_pointer args) +{ + #define H_strings_are_less "(stringpcl_bs + return(g_string_cmp(sc, args, -1, sc->string_lt_symbol)); +} + +static s7_pointer g_strings_are_greater(s7_scheme *sc, s7_pointer args) +{ + #define H_strings_are_greater "(string>? str ...) returns #t if all the string arguments are decreasing" + #define Q_strings_are_greater sc->pcl_bs + return(g_string_cmp(sc, args, 1, sc->string_gt_symbol)); +} + +static s7_pointer g_strings_are_geq(s7_scheme *sc, s7_pointer args) +{ + #define H_strings_are_geq "(string>=? str ...) returns #t if all the string arguments are equal or decreasing" + #define Q_strings_are_geq sc->pcl_bs + return(g_string_cmp_not(sc, args, -1, sc->string_geq_symbol)); +} + +static s7_pointer g_strings_are_leq(s7_scheme *sc, s7_pointer args) +{ + #define H_strings_are_leq "(string<=? str ...) returns #t if all the string arguments are equal or increasing" + #define Q_strings_are_leq sc->pcl_bs + return(g_string_cmp_not(sc, args, 1, sc->string_leq_symbol)); +} + +static s7_pointer g_string_equal_2(s7_scheme *sc, s7_pointer args) +{ + if (!is_string(car(args))) + return(method_or_bust(sc, car(args), sc->string_eq_symbol, args, sc->type_names[T_STRING], 1)); + if (!is_string(cadr(args))) + return(method_or_bust(sc, cadr(args), sc->string_eq_symbol, args, sc->type_names[T_STRING], 2)); + return(make_boolean(sc, scheme_strings_are_equal(car(args), cadr(args)))); +} + +static s7_pointer g_string_equal_2c(s7_scheme *sc, s7_pointer args) +{ + if (!is_string(car(args))) + return(method_or_bust(sc, car(args), sc->string_eq_symbol, args, sc->type_names[T_STRING], 1)); + return(make_boolean(sc, scheme_strings_are_equal(car(args), cadr(args)))); +} + +static s7_pointer string_eq_p_pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + if (!is_string(p1)) + return(method_or_bust(sc, p1, sc->string_eq_symbol, set_plist_2(sc, p1, p2), sc->type_names[T_STRING], 1)); + if (!is_string(p2)) + return(method_or_bust(sc, p2, sc->string_eq_symbol, set_plist_2(sc, p1, p2), sc->type_names[T_STRING], 2)); + return(make_boolean(sc, scheme_strings_are_equal(p1, p2))); +} + +static s7_pointer g_string_less_2(s7_scheme *sc, s7_pointer args) +{ + if (!is_string(car(args))) + return(method_or_bust(sc, car(args), sc->string_lt_symbol, args, sc->type_names[T_STRING], 1)); + if (!is_string(cadr(args))) + return(method_or_bust(sc, cadr(args), sc->string_lt_symbol, args, sc->type_names[T_STRING], 2)); + return(make_boolean(sc, scheme_strcmp(car(args), cadr(args)) == -1)); +} + +static s7_pointer string_lt_p_pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + if (!is_string(p1)) + return(method_or_bust(sc, p1, sc->string_lt_symbol, set_plist_2(sc, p1, p2), sc->type_names[T_STRING], 1)); + if (!is_string(p2)) + return(method_or_bust(sc, p2, sc->string_lt_symbol, set_plist_2(sc, p1, p2), sc->type_names[T_STRING], 2)); + return(make_boolean(sc, scheme_strcmp(p1, p2) == -1)); +} + +static s7_pointer g_string_greater_2(s7_scheme *sc, s7_pointer args) +{ + if (!is_string(car(args))) + return(method_or_bust(sc, car(args), sc->string_gt_symbol, args, sc->type_names[T_STRING], 1)); + if (!is_string(cadr(args))) + return(method_or_bust(sc, cadr(args), sc->string_gt_symbol, args, sc->type_names[T_STRING], 2)); + return(make_boolean(sc, scheme_strcmp(car(args), cadr(args)) == 1)); +} + +static s7_pointer string_gt_p_pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + if (!is_string(p1)) + return(method_or_bust(sc, p1, sc->string_gt_symbol, set_plist_2(sc, p1, p2), sc->type_names[T_STRING], 1)); + if (!is_string(p2)) + return(method_or_bust(sc, p2, sc->string_gt_symbol, set_plist_2(sc, p1, p2), sc->type_names[T_STRING], 2)); + return(make_boolean(sc, scheme_strcmp(p1, p2) == 1)); +} + +#define check_string2_args(Sc, Caller, P1, P2) \ + do { \ + if (!is_string(p1)) return(method_or_bust(sc, P1, Caller, set_plist_2(Sc, P1, P2), sc->type_names[T_STRING], 1) != Sc->F); \ + if (!is_string(p2)) return(method_or_bust(sc, P2, Caller, set_plist_2(Sc, P1, P2), sc->type_names[T_STRING], 2) != Sc->F); \ + } while (0) + +static bool string_lt_b_unchecked(s7_pointer p1, s7_pointer p2) {return(scheme_strcmp(p1, p2) == -1);} +static bool string_lt_b_7pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + check_string2_args(sc, sc->string_lt_symbol, p1, p2); + return(scheme_strcmp(p1, p2) == -1); +} + +static bool string_leq_b_unchecked(s7_pointer p1, s7_pointer p2) {return(scheme_strcmp(p1, p2) != 1);} +static bool string_leq_b_7pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + check_string2_args(sc, sc->string_leq_symbol, p1, p2); + return(scheme_strcmp(p1, p2) != 1); +} + +static bool string_gt_b_unchecked(s7_pointer p1, s7_pointer p2) {return(scheme_strcmp(p1, p2) == 1);} +static bool string_gt_b_7pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + check_string2_args(sc, sc->string_gt_symbol, p1, p2); + return(scheme_strcmp(p1, p2) == 1); +} + +static bool string_geq_b_unchecked(s7_pointer p1, s7_pointer p2) {return(scheme_strcmp(p1, p2) != -1);} +static bool string_geq_b_7pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + check_string2_args(sc, sc->string_geq_symbol, p1, p2); + return(scheme_strcmp(p1, p2) != -1); +} + +static bool string_eq_b_unchecked(s7_pointer p1, s7_pointer p2) {return(scheme_strings_are_equal(p1, p2));} +static bool string_eq_b_7pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + check_string2_args(sc, sc->string_eq_symbol, p1, p2); + return(scheme_strings_are_equal(p1, p2)); +} + +static s7_pointer string_equal_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool unused_ops) +{ + check_for_substring_temp(sc, expr); + return((args == 2) ? ((is_string(caddr(expr))) ? sc->string_equal_2c : sc->string_equal_2) : f); +} + +static s7_pointer string_less_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool unused_ops) +{ + check_for_substring_temp(sc, expr); + return((args == 2) ? sc->string_less_2 : f); +} + +static s7_pointer string_greater_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool unused_ops) +{ + check_for_substring_temp(sc, expr); + return((args == 2) ? sc->string_greater_2 : f); +} + + +#if (!WITH_PURE_S7) +static int32_t scheme_strcasecmp(s7_pointer s1, s7_pointer s2) +{ + /* same as scheme_strcmp -- watch out for unwanted sign! and lack of trailing null (length sets string end). + */ + s7_int len1 = string_length(s1); + s7_int len2 = string_length(s2); + s7_int len = (len1 > len2) ? len2 : len1; + const uint8_t *str1 = (const uint8_t *)string_value(s1); + const uint8_t *str2 = (const uint8_t *)string_value(s2); + + for (s7_int i = 0; i < len; i++) + { + if (uppers[(int32_t)str1[i]] < uppers[(int32_t)str2[i]]) + return(-1); + if (uppers[(int32_t)str1[i]] > uppers[(int32_t)str2[i]]) + return(1); + } + if (len1 < len2) + return(-1); + return((len1 > len2) ? 1 : 0); +} + +static bool scheme_strequal_ci(s7_pointer s1, s7_pointer s2) +{ + /* same as scheme_strcmp -- watch out for unwanted sign! */ + s7_int len = string_length(s1); + s7_int len2 = string_length(s2); + const uint8_t *str1, *str2; + + if (len != len2) return(false); + str1 = (const uint8_t *)string_value(s1); + str2 = (const uint8_t *)string_value(s2); + for (s7_int i = 0; i < len; i++) + if (uppers[(int32_t)str1[i]] != uppers[(int32_t)str2[i]]) + return(false); + return(true); +} + +static s7_pointer check_rest_are_strings(s7_scheme *sc, s7_pointer sym, s7_pointer x, s7_pointer args) +{ + for (s7_pointer y = x; is_pair(y); y = cdr(y)) + if (!is_string_via_method(sc, car(y))) + wrong_type_error_nr(sc, sym, position_of(y, args), car(y), sc->type_names[T_STRING]); + return(sc->F); +} + +static s7_pointer g_string_ci_cmp(s7_scheme *sc, s7_pointer args, int32_t val, s7_pointer sym) +{ + s7_pointer y = car(args); + + if (!is_string(y)) + return(method_or_bust(sc, y, sym, args, sc->type_names[T_STRING], 1)); + + for (s7_pointer x = cdr(args); is_not_null(x); y = car(x), x = cdr(x)) + { + if (!is_string(car(x))) + return(method_or_bust(sc, car(x), sym, set_ulist_1(sc, y, x), sc->type_names[T_STRING], position_of(x, args))); + if (val == 0) + { + if (!scheme_strequal_ci(y, car(x))) + return(check_rest_are_strings(sc, sym, cdr(x), args)); + } + else + if (scheme_strcasecmp(y, car(x)) != val) + return(check_rest_are_strings(sc, sym, cdr(x), args)); + } + return(sc->T); +} + +static s7_pointer g_string_ci_cmp_not(s7_scheme *sc, s7_pointer args, int32_t val, s7_pointer sym) +{ + s7_pointer y = car(args); + + if (!is_string(y)) + return(method_or_bust(sc, y, sym, args, sc->type_names[T_STRING], 1)); + for (s7_pointer x = cdr(args); is_not_null(x); y = car(x), x = cdr(x)) + { + if (!is_string(car(x))) + return(method_or_bust(sc, car(x), sym, set_ulist_1(sc, y, x), sc->type_names[T_STRING], position_of(x, args))); + if (scheme_strcasecmp(y, car(x)) == val) + return(check_rest_are_strings(sc, sym, cdr(x), args)); + } + return(sc->T); +} + +static s7_pointer g_strings_are_ci_equal(s7_scheme *sc, s7_pointer args) +{ + #define H_strings_are_ci_equal "(string-ci=? str ...) returns #t if all the string arguments are equal, ignoring case" + #define Q_strings_are_ci_equal sc->pcl_bs + return(g_string_ci_cmp(sc, args, 0, sc->string_ci_eq_symbol)); +} + +static s7_pointer g_strings_are_ci_less(s7_scheme *sc, s7_pointer args) +{ + #define H_strings_are_ci_less "(string-cipcl_bs + return(g_string_ci_cmp(sc, args, -1, sc->string_ci_lt_symbol)); +} + +static s7_pointer g_strings_are_ci_greater(s7_scheme *sc, s7_pointer args) +{ + #define H_strings_are_ci_greater "(string-ci>? str ...) returns #t if all the string arguments are decreasing, ignoring case" + #define Q_strings_are_ci_greater sc->pcl_bs + return(g_string_ci_cmp(sc, args, 1, sc->string_ci_gt_symbol)); +} + +static s7_pointer g_strings_are_ci_geq(s7_scheme *sc, s7_pointer args) +{ + #define H_strings_are_ci_geq "(string-ci>=? str ...) returns #t if all the string arguments are equal or decreasing, ignoring case" + #define Q_strings_are_ci_geq sc->pcl_bs + return(g_string_ci_cmp_not(sc, args, -1, sc->string_ci_geq_symbol)); +} + +static s7_pointer g_strings_are_ci_leq(s7_scheme *sc, s7_pointer args) +{ + #define H_strings_are_ci_leq "(string-ci<=? str ...) returns #t if all the string arguments are equal or increasing, ignoring case" + #define Q_strings_are_ci_leq sc->pcl_bs + return(g_string_ci_cmp_not(sc, args, 1, sc->string_ci_leq_symbol)); +} + +static bool string_ci_lt_b_unchecked(s7_pointer p1, s7_pointer p2) {return(scheme_strcasecmp(p1, p2) == -1);} +static bool string_ci_lt_b_7pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + check_string2_args(sc, sc->string_ci_lt_symbol, p1, p2); + return(scheme_strcasecmp(p1, p2) == -1); +} + +static bool string_ci_leq_b_unchecked(s7_pointer p1, s7_pointer p2) {return(scheme_strcasecmp(p1, p2) != 1);} +static bool string_ci_leq_b_7pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + check_string2_args(sc, sc->string_ci_leq_symbol, p1, p2); + return(scheme_strcasecmp(p1, p2) != 1); +} + +static bool string_ci_gt_b_unchecked(s7_pointer p1, s7_pointer p2) {return(scheme_strcasecmp(p1, p2) == 1);} +static bool string_ci_gt_b_7pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + check_string2_args(sc, sc->string_ci_gt_symbol, p1, p2); + return(scheme_strcasecmp(p1, p2) == 1); +} + +static bool string_ci_geq_b_unchecked(s7_pointer p1, s7_pointer p2) {return(scheme_strcasecmp(p1, p2) != -1);} +static bool string_ci_geq_b_7pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + check_string2_args(sc, sc->string_ci_geq_symbol, p1, p2); + return(scheme_strcasecmp(p1, p2) != -1); +} + +static bool string_ci_eq_b_unchecked(s7_pointer p1, s7_pointer p2) {return(scheme_strcasecmp(p1, p2) == 0);} +static bool string_ci_eq_b_7pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + check_string2_args(sc, sc->string_ci_eq_symbol, p1, p2); + return(scheme_strcasecmp(p1, p2) == 0); +} +#endif /* pure s7 */ + +static s7_pointer g_string_fill_1(s7_scheme *sc, s7_pointer caller, s7_pointer args) +{ + s7_pointer x = car(args), chr; + s7_int start = 0, end; + + if (!is_string(x)) + return(method_or_bust(sc, x, caller, args, sc->type_names[T_STRING], 1)); /* not two methods here */ + if (is_immutable_string(x)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, caller, x)); + + chr = cadr(args); + if (!is_character(chr)) + return(method_or_bust(sc, chr, caller, args, sc->type_names[T_CHARACTER], 2)); + + end = string_length(x); + if (!is_null(cddr(args))) + { + s7_pointer p = start_and_end(sc, caller, args, 3, cddr(args), &start, &end); + if (p != sc->unused) + return(p); + if (start == end) return(chr); + } + if (end == 0) return(chr); + local_memset((void *)(string_value(x) + start), (int32_t)character(chr), end - start); /* not memclr even if chr=#\null! */ + return(chr); +} + + +/* -------------------------------- string-fill! -------------------------------- */ +#if (!WITH_PURE_S7) +static s7_pointer g_string_fill(s7_scheme *sc, s7_pointer args) +{ + #define H_string_fill "(string-fill! str chr start end) fills the string str with the character chr" + #define Q_string_fill s7_make_signature(sc, 5, \ + s7_make_signature(sc, 2, sc->is_char_symbol, sc->is_integer_symbol), \ + sc->is_string_symbol, sc->is_char_symbol, sc->is_integer_symbol, sc->is_integer_symbol) + return(g_string_fill_1(sc, sc->string_fill_symbol, args)); +} +#endif + + +/* -------------------------------- string -------------------------------- */ +const char *s7_string(s7_pointer p) {return(string_value(p));} + +static s7_pointer g_string_1(s7_scheme *sc, s7_pointer args, s7_pointer sym) +{ + int32_t i, len; + s7_pointer x, newstr; + char *str; + if (is_null(args)) return(nil_string); + + /* get length for new string and check arg types */ + for (len = 0, x = args; is_not_null(x); len++, x = cdr(x)) + { + s7_pointer p = car(x); + if (!is_character(p)) + { + if (has_active_methods(sc, p)) + { + s7_pointer func = find_method_with_let(sc, p, sym); + if (func != sc->undefined) + { + s7_pointer y; + if (len == 0) + return(s7_apply_function(sc, func, args)); + newstr = make_empty_string(sc, len, 0); + str = string_value(newstr); + for (i = 0, y = args; y != x; i++, y = cdr(y)) + str[i] = character(car(y)); + return(g_string_append_1(sc, set_plist_2(sc, newstr, s7_apply_function(sc, func, x)), sym)); + }} + wrong_type_error_nr(sc, sym, len + 1, car(x), sc->type_names[T_CHARACTER]); + }} + newstr = inline_make_empty_string(sc, len, 0); + str = string_value(newstr); + for (i = 0, x = args; is_not_null(x); i++, x = cdr(x)) + str[i] = character(car(x)); + return(newstr); +} + +static s7_pointer g_string(s7_scheme *sc, s7_pointer args) +{ + #define H_string "(string chr...) appends all its character arguments into one string" + #define Q_string s7_make_circular_signature(sc, 1, 2, sc->is_string_symbol, sc->is_char_symbol) + return((is_null(args)) ? nil_string : g_string_1(sc, args, sc->string_symbol)); +} + +static s7_pointer g_string_c1(s7_scheme *sc, s7_pointer args) +{ + s7_pointer c = car(args), str; + /* no multiple values here because no pairs below */ + if (!is_character(c)) + return(method_or_bust(sc, c, sc->string_symbol, args, sc->type_names[T_CHARACTER], 1)); + str = inline_make_empty_string(sc, 1, 0); /* can't put character(c) here because null is handled specially */ + string_value(str)[0] = character(c); + return(str); +} + +static s7_pointer string_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool unused_ops) +{ + return(((args == 1) && (!is_pair(cadr(expr)))) ? sc->string_c1 : f); +} + +static s7_pointer string_p_p(s7_scheme *sc, s7_pointer p) +{ + s7_pointer str; + if (!is_character(p)) return(g_string_1(sc, set_plist_1(sc, p), sc->string_symbol)); + str = inline_make_empty_string(sc, 1, 0); + string_value(str)[0] = character(p); + return(str); +} + + +/* -------------------------------- list->string -------------------------------- */ +#if (!WITH_PURE_S7) +static s7_pointer g_list_to_string(s7_scheme *sc, s7_pointer args) +{ + #define H_list_to_string "(list->string lst) appends all the list's characters into one string; (apply string lst)" + #define Q_list_to_string s7_make_signature(sc, 2, sc->is_string_symbol, sc->is_proper_list_symbol) + + if (is_null(car(args))) + return(nil_string); + if (!s7_is_proper_list(sc, car(args))) + return(method_or_bust_p(sc, car(args), sc->list_to_string_symbol, + wrap_string(sc, "a (proper, non-circular) list of characters", 43))); + return(g_string_1(sc, car(args), sc->list_to_string_symbol)); +} +#endif + + +/* -------------------------------- string->list -------------------------------- */ +static s7_pointer string_to_list(s7_scheme *sc, const char *str, s7_int len) +{ + s7_pointer result; + if (len == 0) + return(sc->nil); + check_free_heap_size(sc, len); + init_temp(sc->y, sc->nil); + for (s7_int i = len - 1; i >= 0; i--) + sc->y = cons_unchecked(sc, chars[((uint8_t)str[i])], sc->y); + result = sc->y; + sc->y = sc->unused; + return(result); +} + +#if (!WITH_PURE_S7) +static s7_pointer g_string_to_list(s7_scheme *sc, s7_pointer args) +{ + #define H_string_to_list "(string->list str start end) returns the elements of the string str in a list; (map values str)" + #define Q_string_to_list s7_make_circular_signature(sc, 2, 3, sc->is_proper_list_symbol, sc->is_string_symbol, sc->is_integer_symbol) + + s7_int start = 0, end; + s7_pointer p, str = car(args); + + if (!is_string(str)) + return(sole_arg_method_or_bust(sc, str, sc->string_to_list_symbol, args, sc->type_names[T_STRING])); + end = string_length(str); + if (!is_null(cdr(args))) + { + p = start_and_end(sc, sc->string_to_list_symbol, args, 2, cdr(args), &start, &end); + if (p != sc->unused) return(p); + if (start == end) return(sc->nil); + } + else + if (end == 0) return(sc->nil); + if ((end - start) > sc->max_list_length) + out_of_range_error_nr(sc, sc->string_to_list_symbol, int_one, str, it_is_too_large_string); + + sc->w = sc->nil; + check_free_heap_size(sc, end - start); + for (s7_int i = end - 1; i >= start; i--) + sc->w = cons_unchecked(sc, chars[((uint8_t)string_value(str)[i])], sc->w); + p = sc->w; + sc->w = sc->unused; + return(p); +} + +static s7_pointer string_to_list_p_p(s7_scheme *sc, s7_pointer str) +{ + s7_int i, len; + s7_pointer p; + const uint8_t *val; + if (!is_string(str)) return(sole_arg_method_or_bust(sc, str, sc->string_to_list_symbol, set_plist_1(sc, str), sc->type_names[T_STRING])); + len = string_length(str); + if (len == 0) return(sc->nil); + check_free_heap_size(sc, len); + val = (const uint8_t *)string_value(str); + for (p = sc->nil, i = len - 1; i >= 0; i--) p = cons_unchecked(sc, chars[val[i]], p); + return(p); +} +#endif + + +/* -------------------------------- port-closed? -------------------------------- */ +static s7_pointer g_is_port_closed(s7_scheme *sc, s7_pointer args) +{ + #define H_is_port_closed "(port-closed? p) returns #t if the port p is closed." + #define Q_is_port_closed s7_make_signature(sc, 2, sc->is_boolean_symbol, \ + s7_make_signature(sc, 3, sc->is_input_port_symbol, sc->is_output_port_symbol, sc->not_symbol)) + s7_pointer x = car(args); + if ((is_input_port(x)) || (is_output_port(x))) + return(make_boolean(sc, port_is_closed(x))); + if ((x == current_output_port(sc)) && (x == sc->F)) + return(sc->F); + return(method_or_bust_p(sc, x, sc->is_port_closed_symbol, wrap_string(sc, "a port", 6))); +} + +static bool is_port_closed_b_7p(s7_scheme *sc, s7_pointer x) +{ + if ((is_input_port(x)) || (is_output_port(x))) + return(port_is_closed(x)); + if ((x == current_output_port(sc)) && (x == sc->F)) + return(false); + return(method_or_bust_p(sc, x, sc->is_port_closed_symbol, wrap_string(sc, "a port", 6)) != sc->F); +} + + +/* -------------------------------- port-position -------------------------------- */ +static s7_pointer g_port_position(s7_scheme *sc, s7_pointer args) +{ + #define H_port_position "(port-position input-port) returns the current location (in bytes) \ +in the port's data where the next read will take place." + #define Q_port_position s7_make_signature(sc, 2, sc->is_integer_symbol, sc->is_input_port_symbol) + + s7_pointer port = car(args); + if (!(is_input_port(port))) + sole_arg_wrong_type_error_nr(sc, sc->port_position_symbol, port, sc->type_names[T_INPUT_PORT]); + if (port_is_closed(port)) + sole_arg_wrong_type_error_nr(sc, sc->port_position_symbol, port, an_open_input_port_string); + if (is_string_port(port)) + return(make_integer(sc, port_position(port))); +#if (!MS_WINDOWS) + if (is_file_port(port)) + return(make_integer(sc, ftell(port_file(port)))); +#endif + return(int_zero); +} + +static s7_pointer g_set_port_position(s7_scheme *sc, s7_pointer args) +{ + s7_pointer port = car(args), pos; + s7_int position; + + if (!(is_input_port(port))) + wrong_type_error_nr(sc, wrap_string(sc, "set! port-position", 18), 1, port, an_input_port_string); + if (port_is_closed(port)) + wrong_type_error_nr(sc, wrap_string(sc, "set! port-position", 18), 1, port, an_open_input_port_string); + + pos = cadr(args); + if (!is_t_integer(pos)) + wrong_type_error_nr(sc, wrap_string(sc, "set! port-position", 18), 2, pos, sc->type_names[T_INTEGER]); + position = s7_integer_clamped_if_gmp(sc, pos); + if (position < 0) + out_of_range_error_nr(sc, sc->port_position_symbol, int_two, pos, it_is_negative_string); + if (is_string_port(port)) + port_position(port) = (position > port_data_size(port)) ? port_data_size(port) : position; +#if (!MS_WINDOWS) + else + if (is_file_port(port)) + { + rewind(port_file(port)); + fseek(port_file(port), (long)position, SEEK_SET); + } +#endif + return(pos); +} + + +/* -------------------------------- port-file -------------------------------- */ +static s7_pointer g_port_file(s7_scheme *sc, s7_pointer args) +{ + #define H_port_file "(port-file port) returns the FILE* pointer associated with the port, wrapped in a c-pointer object" + #define Q_port_file s7_make_signature(sc, 2, sc->is_c_pointer_symbol, s7_make_signature(sc, 2, sc->is_input_port_symbol, sc->is_output_port_symbol)) + + s7_pointer port = car(args); + if ((!is_input_port(port)) && + (!is_output_port(port))) + sole_arg_wrong_type_error_nr(sc, sc->port_file_symbol, port, wrap_string(sc, "a port", 6)); + if (port_is_closed(port)) + sole_arg_wrong_type_error_nr(sc, sc->port_file_symbol, port, wrap_string(sc, "an open port", 12)); +#if (!MS_WINDOWS) + if (is_file_port(port)) + return(s7_make_c_pointer_with_type(sc, (void *)(port_file(port)), sc->file__symbol, sc->F)); +#endif + return(s7_make_c_pointer(sc, NULL)); +} + + +/* -------------------------------- port-line-number -------------------------------- */ +static s7_pointer port_line_number_p_p(s7_scheme *sc, s7_pointer x) +{ + if ((!(is_input_port(x))) || + (port_is_closed(x))) + return(method_or_bust_p(sc, x, sc->port_line_number_symbol, an_input_port_string)); + return(make_integer(sc, port_line_number(x))); +} + +static s7_pointer g_port_line_number(s7_scheme *sc, s7_pointer args) +{ + #define H_port_line_number "(port-line-number input-file-port) returns the current read line number of port" + #define Q_port_line_number s7_make_signature(sc, 2, sc->is_integer_symbol, sc->is_input_port_symbol) + return(port_line_number_p_p(sc, (is_null(args)) ? current_input_port(sc) : car(args))); +} + +s7_int s7_port_line_number(s7_scheme *sc, s7_pointer p) +{ + if (!(is_input_port(p))) + sole_arg_wrong_type_error_nr(sc, sc->port_line_number_symbol, p, sc->type_names[T_INPUT_PORT]); + return(port_line_number(p)); +} + +static s7_pointer g_set_port_line_number(s7_scheme *sc, s7_pointer args) +{ + s7_pointer p, line; + if ((is_null(car(args))) || + ((is_null(cdr(args))) && (is_t_integer(car(args))))) + p = current_input_port(sc); + else + { + p = car(args); + if (!(is_input_port(p))) + wrong_type_error_nr(sc, wrap_string(sc, "set! port-line-number", 21), 1, p, an_input_port_string); + } + line = (is_null(cdr(args)) ? car(args) : cadr(args)); + if (!is_t_integer(line)) + wrong_type_error_nr(sc, wrap_string(sc, "set! port-line-number", 21), 2, line, sc->type_names[T_INTEGER]); + port_line_number(p) = integer(line); + return(line); +} + + +/* -------------------------------- port-filename -------------------------------- */ +const char *s7_port_filename(s7_scheme *sc, s7_pointer x) +{ + if (((is_input_port(x)) || + (is_output_port(x))) && + (!port_is_closed(x))) + return(port_filename(x)); + return(NULL); +} + +static s7_pointer port_filename_p_p(s7_scheme *sc, s7_pointer x) +{ + if (((is_input_port(x)) || (is_output_port(x))) && + (!port_is_closed(x))) + { + if (port_filename(x)) + return(make_string_with_length(sc, port_filename(x), port_filename_length(x))); /* not wrapper here! */ + return(nil_string); + /* otherwise (eval-string (port-filename)) and (string->symbol (port-filename)) segfault */ + } + return(method_or_bust_p(sc, x, sc->port_filename_symbol, wrap_string(sc, "an open port", 12))); +} + +static s7_pointer g_port_filename(s7_scheme *sc, s7_pointer args) +{ + #define H_port_filename "(port-filename file-port) returns the filename associated with port" + #define Q_port_filename s7_make_signature(sc, 2, sc->is_string_symbol, s7_make_signature(sc, 2, sc->is_input_port_symbol, sc->is_output_port_symbol)) + return(port_filename_p_p(sc, (is_null(args)) ? current_input_port(sc) : car(args))); +} + + +/* -------------------------------- pair-line-number -------------------------------- */ +static s7_pointer pair_line_number_p_p(s7_scheme *sc, s7_pointer p) +{ + if (!is_pair(p)) + return(method_or_bust_p(sc, p, sc->pair_line_number_symbol, sc->type_names[T_PAIR])); + return((has_location(p)) ? make_integer(sc, pair_line_number(p)) : sc->F); +} + +static s7_pointer g_pair_line_number(s7_scheme *sc, s7_pointer args) +{ + #define H_pair_line_number "(pair-line-number pair) returns the line number at which it read 'pair', or #f if no such number is available" + #define Q_pair_line_number s7_make_signature(sc, 2, s7_make_signature(sc, 2, sc->is_integer_symbol, sc->not_symbol), sc->is_pair_symbol) + return(pair_line_number_p_p(sc, car(args))); +} + + +/* -------------------------------- pair-filename -------------------------------- */ +static s7_pointer g_pair_filename(s7_scheme *sc, s7_pointer args) +{ + #define H_pair_filename "(pair-filename pair) returns the name of the file containing 'pair'" + #define Q_pair_filename s7_make_signature(sc, 2, s7_make_signature(sc, 2, sc->is_string_symbol, sc->not_symbol), sc->is_pair_symbol) + + s7_pointer p = car(args); + if (is_pair(p)) + return((has_location(p)) ? sc->file_names[pair_file_number(p)] : sc->F); /* maybe also pair_file_number(p) > 0 */ + check_method(sc, p, sc->pair_filename_symbol, args); + sole_arg_wrong_type_error_nr(sc, sc->pair_filename_symbol, p, sc->type_names[T_PAIR]); + return(NULL); +} + + +/* -------------------------------- input-port? -------------------------------- */ +bool s7_is_input_port(s7_scheme *sc, s7_pointer p) {return(is_input_port(p));} +static bool is_input_port_b(s7_pointer p) {return(is_input_port(p));} + +static s7_pointer g_is_input_port(s7_scheme *sc, s7_pointer args) +{ + #define H_is_input_port "(input-port? p) returns #t if p is an input port" + #define Q_is_input_port sc->pl_bt + check_boolean_method(sc, is_input_port, sc->is_input_port_symbol, args); +} + + +/* -------------------------------- output-port? -------------------------------- */ +bool s7_is_output_port(s7_scheme *sc, s7_pointer p) {return(is_output_port(p));} +static bool is_output_port_b(s7_pointer p) {return(is_output_port(p));} + +static s7_pointer g_is_output_port(s7_scheme *sc, s7_pointer args) +{ + #define H_is_output_port "(output-port? p) returns #t if p is an output port" + #define Q_is_output_port sc->pl_bt + check_boolean_method(sc, is_output_port, sc->is_output_port_symbol, args); +} + + +/* -------------------------------- current-input-port -------------------------------- */ +s7_pointer s7_current_input_port(s7_scheme *sc) {return(current_input_port(sc));} + +static s7_pointer g_current_input_port(s7_scheme *sc, s7_pointer unused_args) +{ + #define H_current_input_port "(current-input-port) returns the current input port" + #define Q_current_input_port s7_make_signature(sc, 1, sc->is_input_port_symbol) + return(current_input_port(sc)); +} + +static s7_pointer g_set_current_input_port(s7_scheme *sc, s7_pointer args) +{ + #define H_set_current_input_port "(set-current-input-port port) sets the current-input port to port and returns the previous value of the input port" + #define Q_set_current_input_port s7_make_signature(sc, 2, sc->is_input_port_symbol, sc->is_input_port_symbol) + + s7_pointer port = car(args), old_port = current_input_port(sc); + if ((is_input_port(port)) && + (!port_is_closed(port))) + set_current_input_port(sc, port); + else + { + check_method(sc, port, sc->set_current_input_port_symbol, args); + sole_arg_wrong_type_error_nr(sc, sc->set_current_input_port_symbol, port, an_open_input_port_string); + } + return(old_port); +} + +s7_pointer s7_set_current_input_port(s7_scheme *sc, s7_pointer port) +{ + s7_pointer old_port = current_input_port(sc); + set_current_input_port(sc, port); + return(old_port); +} + + +/* -------------------------------- current-output-port -------------------------------- */ +s7_pointer s7_current_output_port(s7_scheme *sc) {return(current_output_port(sc));} + +s7_pointer s7_set_current_output_port(s7_scheme *sc, s7_pointer port) +{ + s7_pointer old_port = current_output_port(sc); + set_current_output_port(sc, port); + return(old_port); +} + +static s7_pointer g_current_output_port(s7_scheme *sc, s7_pointer unused_args) +{ + #define H_current_output_port "(current-output-port) returns the current output port" + #define Q_current_output_port s7_make_signature(sc, 1, s7_make_signature(sc, 2, sc->is_output_port_symbol, sc->not_symbol)) + return(current_output_port(sc)); +} + +static s7_pointer g_set_current_output_port(s7_scheme *sc, s7_pointer args) +{ + #define H_set_current_output_port "(set-current-output-port port) sets the current-output port to port and returns the previous value of the output port" + #define Q_set_current_output_port s7_make_signature(sc, 2, \ + s7_make_signature(sc, 2, sc->is_output_port_symbol, sc->not_symbol), \ + s7_make_signature(sc, 2, sc->is_output_port_symbol, sc->not_symbol)) + s7_pointer port = car(args); + s7_pointer old_port = current_output_port(sc); + if (((is_output_port(port)) && + (!port_is_closed(port))) || (port == sc->F)) + set_current_output_port(sc, port); + else + { + check_method(sc, port, sc->set_current_output_port_symbol, args); + sole_arg_wrong_type_error_nr(sc, sc->set_current_output_port_symbol, port, an_output_port_or_f_string); + } + return(old_port); +} + + +/* -------------------------------- current-error-port -------------------------------- */ +s7_pointer s7_current_error_port(s7_scheme *sc) {return(current_error_port(sc));} + +s7_pointer s7_set_current_error_port(s7_scheme *sc, s7_pointer port) +{ + s7_pointer old_port = current_error_port(sc); + set_current_error_port(sc, port); + return(old_port); +} + +static s7_pointer g_current_error_port(s7_scheme *sc, s7_pointer unused_args) +{ + #define H_current_error_port "(current-error-port) returns the current error port" + #define Q_current_error_port s7_make_signature(sc, 1, s7_make_signature(sc, 2, sc->is_output_port_symbol, sc->not_symbol)) + return(current_error_port(sc)); +} + +static s7_pointer g_set_current_error_port(s7_scheme *sc, s7_pointer args) +{ + #define H_set_current_error_port "(set-current-error-port port) sets the current-error port to port and returns the previous value of the error port" + #define Q_set_current_error_port s7_make_signature(sc, 2, \ + s7_make_signature(sc, 2, sc->is_output_port_symbol, sc->not_symbol), \ + s7_make_signature(sc, 2, sc->is_output_port_symbol, sc->not_symbol)) + s7_pointer port = car(args); + s7_pointer old_port = current_error_port(sc); + if (((is_output_port(port)) && + (!port_is_closed(port))) || (port == sc->F)) + set_current_error_port(sc, port); + else + { + check_method(sc, port, sc->set_current_error_port_symbol, args); + sole_arg_wrong_type_error_nr(sc, sc->set_current_error_port_symbol, port, an_output_port_or_f_string); + } + return(old_port); +} + + +/* -------------------------------- char-ready? -------------------------------- */ +#if (!WITH_PURE_S7) +static s7_pointer g_is_char_ready(s7_scheme *sc, s7_pointer args) +{ + #define H_is_char_ready "(char-ready? (port (current-input-port))) returns #t if a character is ready for input on the given port" + #define Q_is_char_ready s7_make_signature(sc, 2, sc->is_boolean_symbol, sc->is_input_port_symbol) + s7_pointer pt, res; + + if (is_null(args)) + return(make_boolean(sc, (is_input_port(current_input_port(sc))) && (is_string_port(current_input_port(sc))))); + + pt = car(args); + if (!is_input_port(pt)) + return(method_or_bust_p(sc, pt, sc->is_char_ready_symbol, an_input_port_string)); + if (port_is_closed(pt)) + sole_arg_wrong_type_error_nr(sc, sc->is_char_ready_symbol, pt, an_open_input_port_string); + if (!is_function_port(pt)) + return(make_boolean(sc, is_string_port(pt))); + + res = (*(port_input_function(pt)))(sc, S7_IS_CHAR_READY, pt); + if (is_multiple_value(res)) /* can only happen if more than one value in res */ + { + clear_multiple_value(res); + error_nr(sc, sc->bad_result_symbol, set_elist_2(sc, wrap_string(sc, "input-function-port char-ready? returned: ~S", 44), res)); + } + return(make_boolean(sc, (res != sc->F))); /* char-ready? returns a boolean */ +} +#endif + +/* -------- ports -------- */ +static int32_t closed_port_read_char(s7_scheme *sc, s7_pointer port); +static s7_pointer closed_port_read_line(s7_scheme *sc, s7_pointer port, bool with_eol); +static void closed_port_write_char(s7_scheme *sc, uint8_t c, s7_pointer port); +static void closed_port_write_string(s7_scheme *sc, const char *str, s7_int len, s7_pointer port); +static void closed_port_display(s7_scheme *sc, const char *s, s7_pointer port); + +static void close_closed_port(s7_scheme *sc, s7_pointer port) {return;} + +static port_functions_t closed_port_functions = + {closed_port_read_char, closed_port_write_char, closed_port_write_string, NULL, NULL, NULL, NULL, + closed_port_read_line, closed_port_display, close_closed_port}; + + +static void close_input_file(s7_scheme *sc, s7_pointer p) +{ + if (port_filename(p)) /* for string ports, this is the original input file name */ + { + liberate(sc, port_filename_block(p)); + port_filename(p) = NULL; + } + if (port_file(p)) + { + fclose(port_file(p)); + port_file(p) = NULL; + } + if (port_needs_free(p)) + free_port_data(sc, p); + + port_port(p)->pf = &closed_port_functions; + port_set_closed(p, true); + port_position(p) = 0; +} + +static void close_input_string(s7_scheme *sc, s7_pointer p) +{ + if (port_filename(p)) /* for string ports, this is the original input file name */ + { + liberate(sc, port_filename_block(p)); + port_filename(p) = NULL; + } + if (port_needs_free(p)) + free_port_data(sc, p); + + port_port(p)->pf = &closed_port_functions; + port_set_closed(p, true); + port_position(p) = 0; +} + +static void close_simple_input_string(s7_scheme *sc, s7_pointer p) +{ +#if S7_DEBUGGING + if (port_filename(p)) fprintf(stderr, "%s: port has a filename\n", __func__); + if (port_needs_free(p)) fprintf(stderr, "%s: port needs free\n", __func__); +#endif + port_port(p)->pf = &closed_port_functions; + port_set_closed(p, true); + port_position(p) = 0; +} + +void s7_close_input_port(s7_scheme *sc, s7_pointer p) {port_close(p)(sc, p);} + + +/* -------------------------------- close-input-port -------------------------------- */ +static s7_pointer g_close_input_port(s7_scheme *sc, s7_pointer args) +{ + #define H_close_input_port "(close-input-port port) closes the port" + #define Q_close_input_port s7_make_signature(sc, 2, sc->is_unspecified_symbol, sc->is_input_port_symbol) + + s7_pointer pt = car(args); + if (!is_input_port(pt)) + return(method_or_bust_p(sc, pt, sc->close_input_port_symbol, an_input_port_string)); + if ((!is_immutable_port(pt)) && /* (close-input-port *stdin*) */ + (!is_loader_port(pt))) /* top-level unmatched (close-input-port (current-input-port)) should not clobber the loader's input port */ + s7_close_input_port(sc, pt); + return(sc->unspecified); +} + + +/* -------------------------------- flush-output-port -------------------------------- */ +static noreturn void file_error_nr(s7_scheme *sc, const char *caller, const char *descr, const char *name) +{ + error_nr(sc, sc->io_error_symbol, + set_elist_4(sc, wrap_string(sc, "~A: ~A ~S", 9), + s7_make_string_wrapper(sc, caller), + s7_make_string_wrapper(sc, descr), + s7_make_string_wrapper(sc, name))); +} + +bool s7_flush_output_port(s7_scheme *sc, s7_pointer p) +{ + bool result = true; + if ((is_output_port(p)) && /* type=T_OUTPUT_PORT, so this excludes #f */ + (is_file_port(p)) && + (!port_is_closed(p)) && + (port_file(p))) + { + if (port_position(p) > 0) + { + result = (fwrite((void *)(port_data(p)), 1, port_position(p), port_file(p)) == (size_t)port_position(p)); + port_position(p) = 0; + } + if (fflush(port_file(p)) == -1) + file_error_nr(sc, "flush-output-port", strerror(errno), port_filename(p)); + } + return(result); +} + +static s7_pointer g_flush_output_port(s7_scheme *sc, s7_pointer args) +{ + #define H_flush_output_port "(flush-output-port port) flushes the file port (that is, it writes any accumulated output to the output file)" + #define Q_flush_output_port s7_make_signature(sc, 2, sc->T, s7_make_signature(sc, 2, sc->is_output_port_symbol, sc->not_symbol)) + + s7_pointer pt = (is_null(args)) ? current_output_port(sc) : car(args); + if (!is_output_port(pt)) + { + if (pt == sc->F) return(pt); + check_method(sc, pt, sc->flush_output_port_symbol, args); + sole_arg_wrong_type_error_nr(sc, sc->flush_output_port_symbol, pt, an_output_port_or_f_string); + } + if (!s7_flush_output_port(sc, pt)) + error_nr(sc, sc->io_error_symbol, set_elist_2(sc, wrap_string(sc, "flush-output-port ~S failed", 27), pt)); + return(pt); +} + + +/* -------------------------------- close-output-port -------------------------------- */ +static void close_output_file(s7_scheme *sc, s7_pointer p) +{ + if (port_filename(p)) /* only a file output port has a filename(?) */ + { + liberate(sc, port_filename_block(p)); + port_filename(p) = NULL; + port_filename_length(p) = 0; + } + if (port_file(p)) + { +#if (WITH_WARNINGS) + if ((port_position(p) > 0) && + (fwrite((void *)(port_data(p)), 1, port_position(p), port_file(p)) != (size_t)port_position(p))) + s7_warn(sc, 64, "fwrite trouble in close-output-port\n"); +#else + if (port_position(p) > 0) + fwrite((void *)(port_data(p)), 1, port_position(p), port_file(p)); +#endif + if (fflush(port_file(p)) == -1) + s7_warn(sc, 64, "fflush in close-output-port: %s\n", strerror(errno)); + fclose(port_file(p)); + port_file(p) = NULL; + } + port_port(p)->pf = &closed_port_functions; + port_set_closed(p, true); + port_position(p) = 0; +} + +static void close_output_string(s7_scheme *sc, s7_pointer p) +{ + if (port_data(p)) + { + port_data(p) = NULL; + port_data_size(p) = 0; + } + port_port(p)->pf = &closed_port_functions; + port_set_closed(p, true); + port_position(p) = 0; +} + +static void close_output_port(s7_scheme *sc, s7_pointer p) {port_close(p)(sc, p);} + +void s7_close_output_port(s7_scheme *sc, s7_pointer p) +{ + if ((p == sc->F) || (is_immutable_port(p))) return; /* can these happen? */ + close_output_port(sc, p); +} + +static s7_pointer g_close_output_port(s7_scheme *sc, s7_pointer args) +{ + #define H_close_output_port "(close-output-port port) closes the port" + #define Q_close_output_port s7_make_signature(sc, 2, sc->is_unspecified_symbol, s7_make_signature(sc, 2, sc->is_output_port_symbol, sc->not_symbol)) + + s7_pointer pt = car(args); + if (!is_output_port(pt)) + { + if (pt == sc->F) return(sc->unspecified); + check_method(sc, pt, sc->close_output_port_symbol, args); + sole_arg_wrong_type_error_nr(sc, sc->close_output_port_symbol, pt, an_output_port_or_f_string); + } + s7_close_output_port(sc, pt); + return(sc->unspecified); +} + + +/* -------- read character functions -------- */ + +static int32_t file_read_char(s7_scheme *sc, s7_pointer port) {return(fgetc(port_file(port)));} + +static int32_t function_read_char(s7_scheme *sc, s7_pointer port) +{ + s7_pointer res = (*(port_input_function(port)))(sc, S7_READ_CHAR, port); + if (is_eof(res)) return(EOF); + if (!is_character(res)) /* port_input_function might return some non-character */ + { + if (is_multiple_value(res)) + { + clear_multiple_value(res); + error_nr(sc, sc->bad_result_symbol, set_elist_2(sc, wrap_string(sc, "input-function-port read-char returned: ~S", 42), res)); + } + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "input-function-port read-char returned: ~S", 42), res)); + } + return((int32_t)character(res)); /* kinda nutty -- we return chars[this] in g_read_char! */ +} + +static int32_t string_read_char(s7_scheme *sc, s7_pointer port) +{ + return((port_data_size(port) <= port_position(port)) ? EOF : (uint8_t)port_data(port)[port_position(port)++]); /* port_string_length is 0 if no port string */ +} + +static int32_t output_read_char(s7_scheme *sc, s7_pointer port) +{ + sole_arg_wrong_type_error_nr(sc, sc->read_char_symbol, port, an_input_port_string); + return(0); +} + +static int32_t closed_port_read_char(s7_scheme *sc, s7_pointer port) +{ + sole_arg_wrong_type_error_nr(sc, sc->read_char_symbol, port, an_open_input_port_string); + return(0); +} + + +/* -------- read line functions -------- */ + +static s7_pointer output_read_line(s7_scheme *sc, s7_pointer port, bool with_eol) +{ + sole_arg_wrong_type_error_nr(sc, sc->read_line_symbol, port, an_input_port_string); + return(NULL); +} + +static s7_pointer closed_port_read_line(s7_scheme *sc, s7_pointer port, bool with_eol) +{ + sole_arg_wrong_type_error_nr(sc, sc->read_line_symbol, port, an_open_input_port_string); + return(NULL); +} + +static s7_pointer function_read_line(s7_scheme *sc, s7_pointer port, bool with_eol) +{ + s7_pointer res = (*(port_input_function(port)))(sc, S7_READ_LINE, port); + if (is_multiple_value(res)) + { + clear_multiple_value(res); + error_nr(sc, sc->bad_result_symbol, set_elist_2(sc, wrap_string(sc, "input-function-port read-line returned: ~S", 42), res)); + } + return(res); +} + +static s7_pointer stdin_read_line(s7_scheme *sc, s7_pointer port, bool with_eol) +{ + if (!sc->read_line_buf) + { + sc->read_line_buf_size = 1024; + sc->read_line_buf = (char *)Malloc(sc->read_line_buf_size); + } + if (fgets(sc->read_line_buf, sc->read_line_buf_size, stdin)) + return(s7_make_string(sc, sc->read_line_buf)); /* fgets adds the trailing '\0' */ + return(nil_string); /* make_string_with_length(sc, NULL, 0)); */ +} + +static s7_pointer file_read_line(s7_scheme *sc, s7_pointer port, bool with_eol) +{ + /* read into read_line_buf concatenating reads until newline found. string is read_line_buf to pos-of-newline. + * reset file position to reflect newline pos. + */ + int32_t reads = 0; + char *str; + s7_int read_size; + if (!sc->read_line_buf) + { + sc->read_line_buf_size = 1024; + sc->read_line_buf = (char *)Malloc(sc->read_line_buf_size); + } + read_size = sc->read_line_buf_size; + str = fgets(sc->read_line_buf, read_size, port_file(port)); /* reads size-1 at most, EOF and newline also terminate read */ + if (!str) return(eof_object); /* EOF or error with no char read */ + + while (true) + { + s7_int cur_size; + char *buf; + const char *snew = strchr(sc->read_line_buf, (int)'\n'); /* or maybe just strlen + end-of-string=newline */ + if (snew) + { + s7_int pos = (s7_int)(snew - sc->read_line_buf); + port_line_number(port)++; + return(inline_make_string_with_length(sc, sc->read_line_buf, (with_eol) ? (pos + 1) : pos)); + } + reads++; + cur_size = strlen(sc->read_line_buf); + if ((cur_size + reads) < read_size) /* end of data, no newline */ + return(make_string_with_length(sc, sc->read_line_buf, cur_size)); + + /* need more data */ + sc->read_line_buf_size *= 2; + sc->read_line_buf = (char *)Realloc(sc->read_line_buf, sc->read_line_buf_size); + buf = (char *)(sc->read_line_buf + cur_size); + str = fgets(buf, read_size, port_file(port)); + if (!str) return(eof_object); + read_size = sc->read_line_buf_size; + } + return(eof_object); +} + +static s7_pointer string_read_line(s7_scheme *sc, s7_pointer port, bool with_eol) +{ + s7_int i; + const char *port_str = (const char *)port_data(port); + s7_int port_start = port_position(port); + const char *start = port_str + port_start; + const char *cur = (const char *)strchr(start, (int)'\n'); /* this can run off the end making valgrind unhappy, but I think it's innocuous */ + if (cur) + { + port_line_number(port)++; + i = cur - port_str; + port_position(port) = i + 1; + return(inline_make_string_with_length(sc, start, ((with_eol) ? i + 1 : i) - port_start)); + } + i = port_data_size(port); + port_position(port) = i; + if (i <= port_start) /* the < part can happen -- if not caught we try to create a string of length - 1 -> segfault */ + return(eof_object); + return(make_string_with_length(sc, start, i - port_start)); +} + + +/* -------- write character functions -------- */ + +static void resize_port_data(s7_scheme *sc, s7_pointer pt, s7_int new_size) +{ + s7_int loc = port_data_size(pt); + block_t *nb; + + if (new_size < loc) return; + if (new_size > sc->max_port_data_size) + error_nr(sc, make_symbol(sc, "port-too-big", 12), + set_elist_1(sc, wrap_string(sc, "port data size has grown past (*s7* 'max-port-data-size)", 56))); + + nb = reallocate(sc, port_data_block(pt), new_size); + port_data_block(pt) = nb; + port_data(pt) = (uint8_t *)(block_data(nb)); + port_data_size(pt) = new_size; +} + +static void string_write_char_resized(s7_scheme *sc, uint8_t c, s7_pointer pt) +{ + /* this division looks repetitive, but it is much faster */ + resize_port_data(sc, pt, port_data_size(pt) * 2); + port_data(pt)[port_position(pt)++] = c; +} + +static void string_write_char(s7_scheme *sc, uint8_t c, s7_pointer pt) +{ + if (port_position(pt) < port_data_size(pt)) + port_data(pt)[port_position(pt)++] = c; + else string_write_char_resized(sc, c, pt); +} + +static void stdout_write_char(s7_scheme *sc, uint8_t c, s7_pointer port) {fputc(c, stdout);} +static void stderr_write_char(s7_scheme *sc, uint8_t c, s7_pointer port) {fputc(c, stderr);} + +static void function_write_char(s7_scheme *sc, uint8_t c, s7_pointer port) +{ + push_stack_direct(sc, OP_NO_VALUES); + /* sc->args = sc->nil; */ + (*(port_output_function(port)))(sc, c, port); + unstack_with(sc, OP_NO_VALUES); +#if 1 + memcpy((void *)sc, (void *)(sc->stack_end), 3 * sizeof(s7_pointer)); /* code/let/args */ +#else + sc->code = stack_end_code(sc); + sc->args = stack_end_args(sc); +#endif +} + +static Inline void inline_file_write_char(s7_scheme *sc, uint8_t c, s7_pointer port) +{ + if (port_position(port) == sc->output_port_data_size) + { + fwrite((void *)(port_data(port)), 1, sc->output_port_data_size, port_file(port)); + port_position(port) = 0; + } + port_data(port)[port_position(port)++] = c; +} + +static void file_write_char(s7_scheme *sc, uint8_t c, s7_pointer port) {inline_file_write_char(sc, c, port);} + +static void input_write_char(s7_scheme *sc, uint8_t c, s7_pointer port) +{ + sole_arg_wrong_type_error_nr(sc, sc->write_char_symbol, port, an_output_port_string); +} + +static void closed_port_write_char(s7_scheme *sc, uint8_t c, s7_pointer port) +{ + sole_arg_wrong_type_error_nr(sc, sc->write_char_symbol, port, an_open_output_port_string); +} + + +/* -------- write string functions -------- */ + +static void input_write_string(s7_scheme *sc, const char *str, s7_int len, s7_pointer port) +{ + sole_arg_wrong_type_error_nr(sc, sc->write_symbol, port, an_output_port_string); +} + +static void closed_port_write_string(s7_scheme *sc, const char *str, s7_int len, s7_pointer port) +{ + sole_arg_wrong_type_error_nr(sc, sc->write_symbol, port, an_open_output_port_string); +} + +static void input_display(s7_scheme *sc, const char *s, s7_pointer port) +{ + sole_arg_wrong_type_error_nr(sc, sc->display_symbol, port, an_output_port_string); +} + +static void closed_port_display(s7_scheme *sc, const char *s, s7_pointer port) +{ + sole_arg_wrong_type_error_nr(sc, sc->display_symbol, port, an_open_output_port_string); +} + +static void stdout_write_string(s7_scheme *sc, const char *str, s7_int len, s7_pointer port) +{ + if (str[len] == '\0') + fputs(str, stdout); + else + for (s7_int i = 0; i < len; i++) + fputc(str[i], stdout); +} + +static void stderr_write_string(s7_scheme *sc, const char *str, s7_int len, s7_pointer port) +{ + if (str[len] == '\0') + fputs(str, stderr); + else + for (s7_int i = 0; i < len; i++) + fputc(str[i], stderr); +} + +static void string_write_string_resized(s7_scheme *sc, const char *str, s7_int len, s7_pointer pt) +{ + s7_int new_len = port_position(pt) + len; /* len is known to be non-zero, str might not be 0-terminated */ + resize_port_data(sc, pt, new_len * 2); + memcpy((void *)(port_data(pt) + port_position(pt)), (const void *)str, len); + port_position(pt) = new_len; +} + +static void string_write_string(s7_scheme *sc, const char *str, s7_int len, s7_pointer pt) +{ + if ((S7_DEBUGGING) && (len == 0)) {fprintf(stderr, "string_write_string len == 0\n"); abort();} + if (port_position(pt) + len < port_data_size(pt)) + { + memcpy((void *)(port_data(pt) + port_position(pt)), (const void *)str, len); + /* memcpy is much faster than the equivalent while loop, and faster than using the 4-bytes-at-a-time shuffle */ + port_position(pt) += len; + } + else string_write_string_resized(sc, str, len, pt); +} + +static void file_write_string(s7_scheme *sc, const char *str, s7_int len, s7_pointer pt) +{ + s7_int new_len = port_position(pt) + len; + if (new_len >= sc->output_port_data_size) + { + if (port_position(pt) > 0) + { +#if (WITH_WARNINGS) + if (fwrite((void *)(port_data(pt)), 1, port_position(pt), port_file(pt)) != (size_t)port_position(pt)) + s7_warn(sc, 64, "fwrite trouble in write-string\n"); +#else + fwrite((void *)(port_data(pt)), 1, port_position(pt), port_file(pt)); +#endif + port_position(pt) = 0; + } + fwrite((const void *)str, 1, len, port_file(pt)); + } + else + { + memcpy((void *)(port_data(pt) + port_position(pt)), (const void *)str, len); + port_position(pt) = new_len; + } +} + +static void string_display(s7_scheme *sc, const char *s, s7_pointer port) +{ + if (s) string_write_string(sc, s, safe_strlen(s), port); +} + +static void file_display(s7_scheme *sc, const char *s, s7_pointer port) +{ + if (s) + { + if (port_position(port) > 0) + { +#if (WITH_WARNINGS) + if (fwrite((void *)(port_data(port)), 1, port_position(port), port_file(port)) != (size_t)port_position(port)) + s7_warn(sc, 64, "fwrite trouble in display\n"); +#else + fwrite((void *)(port_data(port)), 1, port_position(port), port_file(port)); +#endif + port_position(port) = 0; + } +#if (WITH_WARNINGS) + if (fputs(s, port_file(port)) == EOF) + s7_warn(sc, 64, "write to %s: %s\n", port_filename(port), strerror(errno)); +#else + fputs(s, port_file(port)); +#endif + } +} + +static void function_display(s7_scheme *sc, const char *s, s7_pointer port) +{ + if (!s) return; + push_stack_direct(sc, OP_NO_VALUES); + /* sc->args = sc->nil; */ /* is this needed? */ + for (; *s; s++) + (*(port_output_function(port)))(sc, *s, port); + unstack_with(sc, OP_NO_VALUES); +#if 1 + memcpy((void *)sc, (void *)(sc->stack_end), 3 * sizeof(s7_pointer)); /* code/let/args */ +#else + sc->code = stack_end_code(sc); /* sc->curlet = stack_end_let(sc) */ + sc->args = stack_end_args(sc); +#endif +} + +static void function_write_string(s7_scheme *sc, const char *str, s7_int len, s7_pointer pt) +{ + push_stack_direct(sc, OP_NO_VALUES); + /* sc->args = sc->nil; */ /* is this needed? */ + for (s7_int i = 0; i < len; i++) + (*(port_output_function(pt)))(sc, str[i], pt); + unstack_with(sc, OP_NO_VALUES); +#if 1 + memcpy((void *)sc, (void *)(sc->stack_end), 3 * sizeof(s7_pointer)); /* code/let/args */ +#else + sc->code = stack_end_code(sc); + sc->args = stack_end_args(sc); +#endif +} + +static void stdout_display(s7_scheme *sc, const char *s, s7_pointer port) {if (s) fputs(s, stdout);} +static void stderr_display(s7_scheme *sc, const char *s, s7_pointer port) {if (s) fputs(s, stderr);} + + +/* -------------------------------- write-string -------------------------------- */ +static s7_pointer g_write_string(s7_scheme *sc, s7_pointer args) +{ + #define H_write_string "(write-string str port start end) writes str to port." + #define Q_write_string s7_make_circular_signature(sc, 3, 4, \ + sc->is_string_symbol, sc->is_string_symbol, \ + s7_make_signature(sc, 2, sc->is_output_port_symbol, sc->not_symbol),\ + sc->is_integer_symbol) + s7_pointer str = car(args), port; + s7_int start = 0, end; + if (!is_string(str)) + return(method_or_bust(sc, str, sc->write_string_symbol, args, sc->type_names[T_STRING], 1)); + + end = string_length(str); + if (!is_null(cdr(args))) + { + s7_pointer inds = cddr(args); + port = cadr(args); + if (!is_null(inds)) + { + s7_pointer p = start_and_end(sc, sc->write_string_symbol, args, 3, inds, &start, &end); + if (p != sc->unused) return(p); + }} + else port = current_output_port(sc); + if (!is_output_port(port)) + { + if (port == sc->F) + { + s7_int len; + if ((start == 0) && (end == string_length(str))) + return(str); + len = (s7_int)(end - start); + return(make_string_with_length(sc, (char *)(string_value(str) + start), len)); + } + check_method(sc, port, sc->write_string_symbol, args); + wrong_type_error_nr(sc, sc->write_string_symbol, 2, port, an_output_port_or_f_string); + } + if (port_is_closed(port)) wrong_type_error_nr(sc, sc->write_string_symbol, 2, port, an_open_output_port_string); + if (start == end) return(str); + port_write_string(port)(sc, (char *)(string_value(str) + start), (end - start), port); + return(str); +} + +static s7_pointer write_string_p_pp(s7_scheme *sc, s7_pointer str, s7_pointer port) +{ + if (!is_string(str)) + return(method_or_bust_pp(sc, str, sc->write_string_symbol, str, port, sc->type_names[T_STRING], 1)); + if (!is_output_port(port)) + { + if (port == sc->F) return(str); + return(method_or_bust_pp(sc, port, sc->write_string_symbol, str, port, an_output_port_string, 2)); + } + if (string_length(str) > 0) + port_write_string(port)(sc, string_value(str), string_length(str), port); + return(str); +} + + +/* -------- skip to newline readers -------- */ +static token_t token(s7_scheme *sc); + +static token_t file_read_semicolon(s7_scheme *sc, s7_pointer pt) +{ + int32_t c; + do (c = fgetc(port_file(pt))); while ((c != '\n') && (c != EOF)); + port_line_number(pt)++; + return((c == EOF) ? TOKEN_EOF : token(sc)); +} + +static token_t string_read_semicolon(s7_scheme *sc, s7_pointer pt) +{ + const char *str = (const char *)(port_data(pt) + port_position(pt)); + const char *orig_str = strchr(str, (int)'\n'); + if (!orig_str) + { + port_position(pt) = port_data_size(pt); + return(TOKEN_EOF); + } + port_position(pt) += (orig_str - str + 1); /* + 1 because strchr leaves orig_str pointing at the newline */ + port_line_number(pt)++; + return(token(sc)); +} + + +/* -------- white space readers -------- */ + +static int32_t file_read_white_space(s7_scheme *sc, s7_pointer port) +{ + int32_t c; + while (is_white_space(c = fgetc(port_file(port)))) + if (c == '\n') + port_line_number(port)++; + return(c); +} + +static int32_t terminated_string_read_white_space(s7_scheme *sc, s7_pointer pt) +{ + const uint8_t *str = (const uint8_t *)(port_data(pt) + port_position(pt)); + uint8_t c; + /* here we know we have null termination and white_space[#\null] is false */ + while (white_space[c = *str++]) /* 255 is not -1 = EOF */ + if (c == '\n') + port_line_number(pt)++; + port_position(pt) = (c) ? str - port_data(pt) : port_data_size(pt); + return((int32_t)c); +} + + +/* -------- name readers -------- */ +#define BASE_10 10 + +static s7_pointer file_read_name_or_sharp(s7_scheme *sc, s7_pointer pt, bool atom_case) +{ + int32_t c; + s7_int i = 1; /* sc->strbuf[0] has the first char of the string we're reading */ + do { + c = fgetc(port_file(pt)); /* might return EOF */ + if (c == '\n') + port_line_number(pt)++; + + sc->strbuf[i++] = (unsigned char)c; + if (i >= sc->strbuf_size) + resize_strbuf(sc, i); + } while ((c != EOF) && (char_ok_in_a_name[c])); + + if ((i == 2) && + (sc->strbuf[0] == '\\')) + sc->strbuf[2] = '\0'; + else + { + if (c != EOF) + { + if (c == '\n') + port_line_number(pt)--; + ungetc(c, port_file(pt)); + } + sc->strbuf[i - 1] = '\0'; + } + if (atom_case) + return(make_atom(sc, sc->strbuf, BASE_10, SYMBOL_OK, WITH_OVERFLOW_ERROR)); + return(make_sharp_constant(sc, sc->strbuf, WITH_OVERFLOW_ERROR, pt, true)); +} + +static s7_pointer file_read_name(s7_scheme *sc, s7_pointer pt) {return(file_read_name_or_sharp(sc, pt, true));} +static s7_pointer file_read_sharp(s7_scheme *sc, s7_pointer pt) {return(file_read_name_or_sharp(sc, pt, false));} + +static s7_pointer string_read_name_no_free(s7_scheme *sc, s7_pointer pt) +{ + /* sc->strbuf[0] has the first char of the string we're reading */ + s7_pointer result; + uint8_t *str = (uint8_t *)(port_data(pt) + port_position(pt)); + + if (char_ok_in_a_name[*str]) + { + s7_int k; + uint8_t *orig_str = str - 1; + str++; + while (char_ok_in_a_name[*str]) str++; + k = str - orig_str; + if (*str != 0) + port_position(pt) += (k - 1); + else port_position(pt) = port_data_size(pt); + /* this is equivalent to: + * str = strpbrk(str, "(); \"\t\r\n"); + * if (!str) {k = strlen(orig_str); str = (char *)(orig_str + k);} else k = str - orig_str; + * but slightly faster. + */ + if (!number_table[*orig_str]) + return(inline_make_symbol(sc, (const char *)orig_str, k)); + + /* eval_c_string string is a constant so we can't set and unset the token's end char */ + if ((k + 1) >= sc->strbuf_size) + resize_strbuf(sc, k + 1); + + memcpy((void *)(sc->strbuf), (void *)orig_str, k); + sc->strbuf[k] = '\0'; + return(make_atom(sc, sc->strbuf, BASE_10, SYMBOL_OK, WITH_OVERFLOW_ERROR)); + } + result = sc->singletons[(uint8_t)(sc->strbuf[0])]; + if (!result) + { + sc->strbuf[1] = '\0'; + result = make_symbol(sc, sc->strbuf, 1); + sc->singletons[(uint8_t)(sc->strbuf[0])] = result; + } + return(result); +} + +static s7_pointer string_read_sharp(s7_scheme *sc, s7_pointer pt) +{ + /* sc->strbuf[0] has the first char of the string we're reading. + * since a *#readers* function might want to get further input, we can't mess with the input even when it is otherwise safe + */ + char *str = (char *)(port_data(pt) + port_position(pt)); + if (char_ok_in_a_name[(uint8_t)*str]) + { + s7_int k; + char *orig_str = (char *)(str - 1); + str++; + while (char_ok_in_a_name[(uint8_t)(*str)]) {str++;} + k = str - orig_str; + port_position(pt) += (k - 1); + if ((k + 1) >= sc->strbuf_size) + resize_strbuf(sc, k + 1); + memcpy((void *)(sc->strbuf), (void *)orig_str, k); + sc->strbuf[k] = '\0'; + return(make_sharp_constant(sc, sc->strbuf, WITH_OVERFLOW_ERROR, pt, true)); + } + if (sc->strbuf[0] == 'f') + return(sc->F); + if (sc->strbuf[0] == 't') + return(sc->T); + if (sc->strbuf[0] == '\\') + { + /* must be from #\( and friends -- a character that happens to be not ok-in-a-name */ + sc->strbuf[1] = str[0]; + sc->strbuf[2] = '\0'; + port_position(pt)++; + } + else sc->strbuf[1] = '\0'; + return(make_sharp_constant(sc, sc->strbuf, WITH_OVERFLOW_ERROR, pt, true)); +} + +static s7_pointer string_read_name(s7_scheme *sc, s7_pointer pt) +{ + /* port_string was allocated (and read from a file) so we can mess with it directly */ + s7_pointer result; + uint8_t *str = (uint8_t *)(port_data(pt) + port_position(pt)); + if (char_ok_in_a_name[*str]) + { + s7_int k; + uint8_t endc; + uint8_t *orig_str = str - 1; + str++; + while (char_ok_in_a_name[*str]) str++; + k = str - orig_str; + port_position(pt) += (k - 1); + if (!number_table[*orig_str]) + return(inline_make_symbol(sc, (const char *)orig_str, k)); + endc = *str; + *str = 0; + result = make_atom(sc, (char *)orig_str, BASE_10, SYMBOL_OK, WITH_OVERFLOW_ERROR); + *str = endc; + return(result); + } + result = sc->singletons[(uint8_t)(sc->strbuf[0])]; + if (!result) + { + sc->strbuf[1] = '\0'; + result = make_symbol(sc, sc->strbuf, 1); + sc->singletons[(uint8_t)(sc->strbuf[0])] = result; + } + return(result); +} + +static void port_set_filename(s7_scheme *sc, s7_pointer p, const char *name, size_t len) +{ + block_t *b = inline_mallocate(sc, len + 1); + port_filename_block(p) = b; + port_filename(p) = (char *)block_data(b); + memcpy((void *)block_data(b), (const void *)name, len); + port_filename(p)[len] = '\0'; +} + +static block_t *mallocate_port(s7_scheme *sc) +{ + #define PORT_LIST 8 /* sizeof(port_t): 160 */ + block_t *p = sc->block_lists[PORT_LIST]; + if (p) + sc->block_lists[PORT_LIST] = (block_t *)block_next(p); + else + { /* this is mallocate without the index calc */ + p = mallocate_block(sc); + block_data(p) = (void *)permalloc(sc, (size_t)(1 << PORT_LIST)); + block_set_index(p, PORT_LIST); + } + block_set_size(p, sizeof(port_t)); + return(p); +} + +static port_functions_t input_file_functions = + {file_read_char, input_write_char, input_write_string, file_read_semicolon, file_read_white_space, + file_read_name, file_read_sharp, file_read_line, input_display, close_input_file}; + +static port_functions_t input_string_functions_1 = + {string_read_char, input_write_char, input_write_string, string_read_semicolon, terminated_string_read_white_space, + string_read_name, string_read_sharp, string_read_line, input_display, close_input_string}; + +static s7_pointer read_file(s7_scheme *sc, FILE *fp, const char *name, s7_int max_size, const char *caller) +{ + s7_pointer port; +#if (!MS_WINDOWS) + s7_int size; +#endif + s7_int port_loc; + block_t *b = mallocate_port(sc); + + new_cell(sc, port, T_INPUT_PORT); + port_loc = gc_protect_1(sc, port); + port_block(port) = b; + port_port(port) = (port_t *)block_data(b); + port_set_closed(port, false); + port_set_string_or_function(port, sc->nil); + /* if we're constantly opening files, and each open saves the file name in permanent memory, we gradually core-up */ + port_filename_length(port) = safe_strlen(name); + port_set_filename(sc, port, name, port_filename_length(port)); + port_line_number(port) = 1; /* first line is numbered 1 */ + port_file_number(port) = 0; + add_input_port(sc, port); + +#if (!MS_WINDOWS) + /* this doesn't work in MS C */ + fseek(fp, 0, SEEK_END); + size = ftell(fp); + rewind(fp); + + /* pseudo files (under /proc for example) have size=0, but we can read them, so don't assume a 0 length file is empty */ + + if ((size > 0) && /* if (size != 0) we get (open-input-file "/dev/tty") -> (open "/dev/tty") read 0 bytes of an expected -1? */ + ((max_size < 0) || (size < max_size))) /* load uses max_size = -1 */ + { + block_t *block = mallocate(sc, size + 2); + uint8_t *content = (uint8_t *)(block_data(block)); + size_t bytes = fread(content, sizeof(uint8_t), size, fp); + if (bytes != (size_t)size) + { + if (current_output_port(sc) != sc->F) + { + char tmp[256]; + int32_t len = snprintf(tmp, 256, "(%s \"%s\") read %ld bytes of an expected %" ld64 "?", caller, name, (long)bytes, size); + port_write_string(current_output_port(sc))(sc, tmp, clamp_length(len, 256), current_output_port(sc)); + } + size = bytes; + } + content[size] = '\0'; + content[size + 1] = '\0'; + fclose(fp); + + port_file(port) = NULL; /* make valgrind happy */ + port_type(port) = STRING_PORT; + port_data(port) = content; + port_data_block(port) = block; + port_data_size(port) = size; + port_position(port) = 0; + port_needs_free(port) = true; + port_port(port)->pf = &input_string_functions_1; + } + else + { + port_file(port) = fp; + port_type(port) = FILE_PORT; + port_data(port) = NULL; + port_data_block(port) = NULL; + port_data_size(port) = 0; + port_position(port) = 0; + port_needs_free(port) = false; + port_port(port)->pf = &input_file_functions; + } +#else + /* _stat64 is no better than the fseek/ftell route, and + * GetFileSizeEx and friends requires Windows.h which makes hash of everything else. + * fread until done takes too long on big files, so use a file port + */ + port_file(port) = fp; + port_type(port) = FILE_PORT; + port_needs_free(port) = false; + port_data(port) = NULL; + port_data_block(port) = NULL; + port_data_size(port) = 0; + port_position(port) = 0; + port_port(port)->pf = &input_file_functions; +#endif + s7_gc_unprotect_at(sc, port_loc); + return(port); +} + + +/* -------------------------------- open-input-file -------------------------------- */ +static int32_t remember_file_name(s7_scheme *sc, const char *file) +{ + for (int32_t i = 0; i <= sc->file_names_top; i++) + if (safe_strcmp(file, string_value(sc->file_names[i]))) + return(i); + + sc->file_names_top++; + if (sc->file_names_top >= sc->file_names_size) + { + int32_t old_size = 0; + /* what if file_names_size is greater than file_bits in pair|profile_file? */ + if (sc->file_names_size == 0) + { + sc->file_names_size = INITIAL_FILE_NAMES_SIZE; + sc->file_names = (s7_pointer *)Malloc(sc->file_names_size * sizeof(s7_pointer)); + } + else + { + old_size = sc->file_names_size; + sc->file_names_size *= 2; + sc->file_names = (s7_pointer *)Realloc(sc->file_names, sc->file_names_size * sizeof(s7_pointer)); + } + for (int32_t i = old_size; i < sc->file_names_size; i++) + sc->file_names[i] = sc->F; + } + sc->file_names[sc->file_names_top] = s7_make_semipermanent_string(sc, file); + return(sc->file_names_top); +} + + +#ifndef MAX_SIZE_FOR_STRING_PORT + #define MAX_SIZE_FOR_STRING_PORT 10000000 +#endif + +static s7_pointer make_input_file(s7_scheme *sc, const char *name, FILE *fp) +{ + return(read_file(sc, fp, name, MAX_SIZE_FOR_STRING_PORT, "open")); +} + + +#if (!MS_WINDOWS) +#include +#endif + +static bool is_directory(const char *filename) +{ +#if (!MS_WINDOWS) + #ifdef S_ISDIR + struct stat statbuf; + return((stat(filename, &statbuf) >= 0) && + (S_ISDIR(statbuf.st_mode))); + #endif +#endif + return(false); +} + +static block_t *expand_filename(s7_scheme *sc, const char *name) +{ +#if WITH_GCC + if ((name[0] == '~') && (name[1] == '/')) /* catch one special case, "~/..." */ + { + char *home = getenv("HOME"); + if (home) + { + s7_int len = safe_strlen(name) + safe_strlen(home) + 1; + block_t *b = mallocate(sc, len); + char *filename = (char *)block_data(b); + filename[0] = '\0'; + catstrs(filename, len, home, (const char *)(name + 1), (char *)NULL); + return(b); + }} +#endif + return(NULL); +} + +static s7_pointer open_input_file_1(s7_scheme *sc, const char *name, const char *mode, const char *caller) +{ + FILE *fp; +#if WITH_GCC + block_t *b; +#endif + /* see if we can open this file before allocating a port */ + if (is_directory(name)) + file_error_nr(sc, caller, "file is a directory:", name); + errno = 0; + fp = fopen(name, mode); + if (fp) + return(make_input_file(sc, name, fp)); + +#if (!MS_WINDOWS) + if (errno == EINVAL) + file_error_nr(sc, caller, "invalid mode", mode); +#if WITH_GCC + if ((!name) || (!*name)) + file_error_nr(sc, caller, strerror(errno), name); + b = expand_filename(sc, name); + if (b) + { + char *new_name = (char *)block_data(b); + fp = fopen(new_name, mode); + liberate(sc, b); + if (fp) + return(make_input_file(sc, name, fp)); + } +#endif +#endif + file_error_nr(sc, caller, strerror(errno), name); + return(sc->io_error_symbol); +} + +s7_pointer s7_open_input_file(s7_scheme *sc, const char *name, const char *mode) +{ + return(open_input_file_1(sc, name, mode, "open-input-file")); +} + +static s7_pointer g_open_input_file(s7_scheme *sc, s7_pointer args) +{ + #define H_open_input_file "(open-input-file filename (mode \"r\")) opens filename for reading" + #define Q_open_input_file s7_make_signature(sc, 3, sc->is_input_port_symbol, sc->is_string_symbol, sc->is_string_symbol) + + s7_pointer mode, name = car(args); + /* open-input-file can create a new output file if the file to be opened does not exist, and the "a" mode is given */ + + if (!is_string(name)) + return(method_or_bust(sc, name, sc->open_input_file_symbol, args, sc->type_names[T_STRING], 1)); + if (!is_pair(cdr(args))) + return(open_input_file_1(sc, string_value(name), "r", "open-input-file")); + + mode = cadr(args); + if (!is_string(mode)) + return(method_or_bust(sc, mode, sc->open_input_file_symbol, args, wrap_string(sc, "a string (a mode such as \"r\")", 29), 2)); + /* since scheme allows embedded nulls, dumb stuff is accepted here: (open-input-file file "a\x00b") -- should this be an error? */ + return(open_input_file_1(sc, string_value(name), string_value(mode), "open-input-file")); +} + +static void close_stdin(s7_scheme *sc, s7_pointer port) {return;} +static void close_stdout(s7_scheme *sc, s7_pointer port) {return;} +static void close_stderr(s7_scheme *sc, s7_pointer port) {return;} + +static const port_functions_t stdin_functions = + {file_read_char, input_write_char, input_write_string, file_read_semicolon, file_read_white_space, + file_read_name, file_read_sharp, stdin_read_line, input_display, close_stdin}; + +static const port_functions_t stdout_functions = + {output_read_char, stdout_write_char, stdout_write_string, NULL, NULL, NULL, NULL, output_read_line, stdout_display, close_stdout}; + +static const port_functions_t stderr_functions = + {output_read_char, stderr_write_char, stderr_write_string, NULL, NULL, NULL, NULL, output_read_line, stderr_display, close_stderr}; + +static void init_standard_ports(s7_scheme *sc) +{ + s7_pointer x; + + /* standard output */ + x = alloc_pointer(sc); + set_full_type(x, T_OUTPUT_PORT | T_IMMUTABLE | T_UNHEAP); + port_port(x) = (port_t *)Calloc(1, sizeof(port_t)); + port_type(x) = FILE_PORT; + port_data(x) = NULL; + port_data_block(x) = NULL; + port_set_closed(x, false); + port_filename_length(x) = 8; + port_set_filename(sc, x, "*stdout*", 8); + port_file_number(x) = remember_file_name(sc, port_filename(x)); /* these numbers need to be correct for the evaluator (*function* data) */ + port_line_number(x) = 0; + port_file(x) = stdout; + port_needs_free(x) = false; + port_port(x)->pf = &stdout_functions; + sc->standard_output = x; + + /* standard error */ + x = alloc_pointer(sc); + set_full_type(x, T_OUTPUT_PORT | T_IMMUTABLE | T_UNHEAP); + port_port(x) = (port_t *)Calloc(1, sizeof(port_t)); + port_type(x) = FILE_PORT; + port_data(x) = NULL; + port_data_block(x) = NULL; + port_set_closed(x, false); + port_filename_length(x) = 8; + port_set_filename(sc, x, "*stderr*", 8); + port_file_number(x) = remember_file_name(sc, port_filename(x)); + port_line_number(x) = 0; + port_file(x) = stderr; + port_needs_free(x) = false; + port_port(x)->pf = &stderr_functions; + sc->standard_error = x; + + /* standard input */ + x = alloc_pointer(sc); + set_full_type(x, T_INPUT_PORT | T_IMMUTABLE | T_UNHEAP); + port_port(x) = (port_t *)Calloc(1, sizeof(port_t)); + port_type(x) = FILE_PORT; + port_set_closed(x, false); + port_set_string_or_function(x, sc->nil); + port_filename_length(x) = 7; + port_set_filename(sc, x, "*stdin*", 7); + port_file_number(x) = remember_file_name(sc, port_filename(x)); + port_line_number(x) = 0; + port_file(x) = stdin; + port_data_block(x) = NULL; + port_needs_free(x) = false; + port_port(x)->pf = &stdin_functions; + sc->standard_input = x; + + s7_define_variable_with_documentation(sc, "*stdin*", sc->standard_input, "*stdin* is the built-in input port, C's stdin"); + s7_define_variable_with_documentation(sc, "*stdout*", sc->standard_output, "*stdout* is the built-in buffered output port, C's stdout"); + s7_define_variable_with_documentation(sc, "*stderr*", sc->standard_error, "*stderr* is the built-in unbuffered output port, C's stderr"); + + set_current_input_port(sc, sc->standard_input); + set_current_output_port(sc, sc->standard_output); + set_current_error_port(sc, sc->standard_error); + sc->current_file = NULL; + sc->current_line = -1; +} + + +/* -------------------------------- open-output-file -------------------------------- */ +static const port_functions_t output_file_functions = + {output_read_char, file_write_char, file_write_string, NULL, NULL, NULL, NULL, output_read_line, file_display, close_output_file}; + +s7_pointer s7_open_output_file(s7_scheme *sc, const char *name, const char *mode) +{ + FILE *fp; + s7_pointer x; + block_t *block, *b; + /* see if we can open this file before allocating a port */ + + errno = 0; + fp = fopen(name, mode); + if (!fp) + { +#if (!MS_WINDOWS) + if (errno == EINVAL) + file_error_nr(sc, "open-output-file", "invalid mode", mode); +#endif + file_error_nr(sc, "open-output-file", strerror(errno), name); + } + new_cell(sc, x, T_OUTPUT_PORT); + b = mallocate_port(sc); + port_block(x) = b; + port_port(x) = (port_t *)block_data(b); + port_type(x) = FILE_PORT; + port_set_closed(x, false); + port_filename_length(x) = safe_strlen(name); + port_set_filename(sc, x, name, port_filename_length(x)); + port_line_number(x) = 1; + port_file_number(x) = 0; + port_file(x) = fp; + port_needs_free(x) = true; /* hmm -- I think these are freed via s7_close_output_port -> close_output_port */ + port_position(x) = 0; + port_data_size(x) = sc->output_port_data_size; + block = mallocate(sc, sc->output_port_data_size); + port_data_block(x) = block; + port_data(x) = (uint8_t *)(block_data(block)); + port_port(x)->pf = &output_file_functions; + add_output_port(sc, x); + return(x); +} + +static s7_pointer g_open_output_file(s7_scheme *sc, s7_pointer args) +{ + #define H_open_output_file "(open-output-file filename (mode \"w\")) opens filename for writing" + #define Q_open_output_file s7_make_signature(sc, 3, sc->is_output_port_symbol, sc->is_string_symbol, sc->is_string_symbol) + + s7_pointer name = car(args); + if (!is_string(name)) + return(method_or_bust(sc, name, sc->open_output_file_symbol, args, sc->type_names[T_STRING], 1)); + if (!is_pair(cdr(args))) + return(s7_open_output_file(sc, string_value(name), "w")); + if (!is_string(cadr(args))) + return(method_or_bust(sc, cadr(args), sc->open_output_file_symbol, args, wrap_string(sc, "a string (a mode such as \"w\")", 29), 2)); + return(s7_open_output_file(sc, string_value(name), string_value(cadr(args)))); +} + + +/* -------------------------------- open-input-string -------------------------------- */ + /* a version of string ports using a pointer to the current location and a pointer to the end + * (rather than an integer for both, indexing from the base string) was not faster. + */ + +static const port_functions_t input_string_functions = + {string_read_char, input_write_char, input_write_string, string_read_semicolon, terminated_string_read_white_space, + string_read_name_no_free, string_read_sharp, string_read_line, input_display, close_simple_input_string}; + +static s7_pointer open_input_string(s7_scheme *sc, const char *input_string, s7_int len) +{ + s7_pointer x; + block_t *b = mallocate_port(sc); + new_cell(sc, x, T_INPUT_PORT); + port_block(x) = b; + port_port(x) = (port_t *)block_data(b); + port_type(x) = STRING_PORT; + port_set_closed(x, false); + port_set_string_or_function(x, sc->nil); + port_data(x) = (uint8_t *)input_string; + port_data_block(x) = NULL; + port_data_size(x) = len; + port_position(x) = 0; + port_filename_block(x) = NULL; + port_filename_length(x) = 0; + port_filename(x) = NULL; + port_file_number(x) = 0; + port_line_number(x) = 0; + port_file(x) = NULL; + port_needs_free(x) = false; +#if S7_DEBUGGING + if ((len > 0) && (input_string[len] != '\0')) + { + fprintf(stderr, "%s[%d]: read_white_space string is not terminated: len: %" ld64 ", at end: %c%c, str: %s", + __func__, __LINE__, len, input_string[len - 1], input_string[len], input_string); + abort(); + } +#endif + port_port(x)->pf = &input_string_functions; + add_input_string_port(sc, x); + return(x); +} + +static /* inline */ s7_pointer open_and_protect_input_string(s7_scheme *sc, s7_pointer str) /* why inline here? */ +{ + s7_pointer p = open_input_string(sc, string_value(str), string_length(str)); + port_set_string_or_function(p, str); + return(p); +} + +s7_pointer s7_open_input_string(s7_scheme *sc, const char *input_string) +{ + return(open_input_string(sc, input_string, safe_strlen(input_string))); +} + +static s7_pointer g_open_input_string(s7_scheme *sc, s7_pointer args) +{ + #define H_open_input_string "(open-input-string str) opens an input port reading str" + #define Q_open_input_string s7_make_signature(sc, 2, sc->is_input_port_symbol, sc->is_string_symbol) + + s7_pointer input_string = car(args); + if (!is_string(input_string)) + return(sole_arg_method_or_bust(sc, input_string, sc->open_input_string_symbol, args, sc->type_names[T_STRING])); + return(open_and_protect_input_string(sc, input_string)); +} + + +/* -------------------------------- open-output-string -------------------------------- */ +#define FORMAT_PORT_LENGTH 128 +/* the large majority (> 99% in my tests) of the output strings have less than 128 chars when the port is finally closed + * 256 is slightly slower (the calloc time below dominates the realloc time in string_write_string) + * 64 is much slower (realloc dominates) + */ + +static const port_functions_t output_string_functions = + {output_read_char, string_write_char, string_write_string, NULL, NULL, NULL, NULL, output_read_line, string_display, close_output_string}; + +s7_pointer s7_open_output_string(s7_scheme *sc) +{ + s7_pointer x; + block_t *b = mallocate_port(sc); + block_t *block = inline_mallocate(sc, sc->initial_string_port_length); + new_cell(sc, x, T_OUTPUT_PORT); + port_block(x) = b; + port_port(x) = (port_t *)block_data(b); + port_type(x) = STRING_PORT; + port_set_closed(x, false); + port_data_size(x) = sc->initial_string_port_length; + port_data_block(x) = block; + port_data(x) = (uint8_t *)(block_data(block)); + port_data(x)[0] = '\0'; /* in case s7_get_output_string before any output */ + port_position(x) = 0; + port_needs_free(x) = true; + port_filename_block(x) = NULL; + port_filename_length(x) = 0; /* protect against (port-filename (open-output-string)) */ + port_filename(x) = NULL; + port_port(x)->pf = &output_string_functions; + add_output_port(sc, x); + return(x); +} + +static s7_pointer g_open_output_string(s7_scheme *sc, s7_pointer unused_args) +{ + #define H_open_output_string "(open-output-string) opens an output string port" + #define Q_open_output_string s7_make_signature(sc, 1, sc->is_output_port_symbol) + return(s7_open_output_string(sc)); +} + + +/* -------------------------------- get-output-string -------------------------------- */ +const char *s7_get_output_string(s7_scheme *sc, s7_pointer p) +{ + port_data(p)[port_position(p)] = '\0'; + return((const char *)port_data(p)); +} + +s7_pointer s7_output_string(s7_scheme *sc, s7_pointer p) +{ + port_data(p)[port_position(p)] = '\0'; + return(make_string_with_length(sc, (const char *)port_data(p), port_position(p))); +} + +static inline void check_get_output_string_port(s7_scheme *sc, s7_pointer p) +{ + if (port_is_closed(p)) + wrong_type_error_nr(sc, sc->get_output_string_symbol, 1, p, wrap_string(sc, "an active (open) string port", 28)); + if (port_position(p) > sc->max_string_length) + error_nr(sc, sc->out_of_range_symbol, + set_elist_2(sc, wrap_string(sc, "get-output-string port-position ~D is greater than (*s7* 'max-string-length)", 76), + wrap_integer(sc, port_position(p)))); +} +/* if pos>max and clear, where should the clear be? Not here because we might want to see output in error handler. + * similarly below if pos>size how can we call make_string (out-of-bounds) and ignore error? + * if pos>size shouldn't we raise an error somewhere? + */ + +static s7_pointer g_get_output_string(s7_scheme *sc, s7_pointer args) +{ + #define H_get_output_string "(get-output-string port clear-port) returns the output accumulated in port. \ +If the optional 'clear-port' is #t, the current string is flushed." + #define Q_get_output_string s7_make_signature(sc, 3, sc->is_string_symbol, \ + s7_make_signature(sc, 2, sc->is_output_port_symbol, sc->not_symbol), sc->is_boolean_symbol) + s7_pointer p; + bool clear_port = false; + + if (is_pair(cdr(args))) + { + p = cadr(args); + if (!is_boolean(p)) + wrong_type_error_nr(sc, sc->get_output_string_symbol, 2, p, sc->type_names[T_BOOLEAN]); + clear_port = (p == sc->T); + } + p = car(args); + if ((!is_output_port(p)) || (!is_string_port(p))) + { + if (p == sc->F) return(nil_string); + check_method(sc, p, sc->get_output_string_symbol, args); + wrong_type_error_nr(sc, sc->get_output_string_symbol, 1, p, wrap_string(sc, "an open string output port or #f", 32)); + } + check_get_output_string_port(sc, p); + + if ((clear_port) && + (port_position(p) < port_data_size(p))) + { + block_t *block; + s7_pointer result = block_to_string(sc, port_data_block(p), port_position(p)); + /* this is slightly faster than make_string_with_length(sc, (char *)(port_data(p)), port_position(p)): we're trading a mallocate for a memcpy */ + port_data_size(p) = sc->initial_string_port_length; + block = inline_mallocate(sc, port_data_size(p)); + port_data_block(p) = block; + port_data(p) = (uint8_t *)(block_data(block)); + port_position(p) = 0; + port_data(p)[0] = '\0'; + return(result); + } + return(make_string_with_length(sc, (const char *)port_data(p), port_position(p))); +} + +static void op_get_output_string(s7_scheme *sc) +{ + s7_pointer port = sc->code; + if (!is_output_port(port)) + wrong_type_error_nr(sc, sc->with_output_to_string_symbol, 1, port, wrap_string(sc, "an open string output port", 26)); + check_get_output_string_port(sc, port); + + if (port_position(port) >= port_data_size(port)) /* can the > part happen? */ + sc->value = block_to_string(sc, reallocate(sc, port_data_block(port), port_position(port) + 1), port_position(port)); + else sc->value = block_to_string(sc, port_data_block(port), port_position(port)); + + port_data(port) = NULL; + port_data_size(port) = 0; + port_data_block(port) = NULL; + port_needs_free(port) = false; +} + +static s7_pointer g_get_output_string_uncopied(s7_scheme *sc, s7_pointer args) +{ + s7_pointer p = car(args); + if ((!is_output_port(p)) || (!is_string_port(p))) + { + if (p == sc->F) return(nil_string); + return(method_or_bust_p(sc, p, sc->get_output_string_symbol, wrap_string(sc, "an output string port", 21))); + } + check_get_output_string_port(sc, p); + return(wrap_string(sc, (const char *)port_data(p), port_position(p))); +} + + +/* -------------------------------- open-input-function -------------------------------- */ +static s7_pointer g_closed_input_function_port(s7_scheme *sc, s7_pointer unused_args) +{ + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_1(sc, wrap_string(sc, "attempt to read from a closed input-function port", 49))); + return(NULL); +} + +static void close_input_function(s7_scheme *sc, s7_pointer p) +{ + port_port(p)->pf = &closed_port_functions; + port_set_string_or_function(p, sc->closed_input_function); /* from s7_make_function so it is GC-protected */ + port_set_closed(p, true); +} + +static const port_functions_t input_function_functions = + {function_read_char, input_write_char, input_write_string, NULL, NULL, NULL, NULL, function_read_line, input_display, close_input_function}; + +static void function_port_set_defaults(s7_pointer x) +{ + port_type(x) = FUNCTION_PORT; + port_data(x) = NULL; + port_data_block(x) = NULL; + port_set_closed(x, false); + port_needs_free(x) = false; + port_filename_block(x) = NULL; /* next three protect against port-filename misunderstandings */ + port_filename(x) = NULL; + port_filename_length(x) = 0; + port_file_number(x) = 0; + port_line_number(x) = 0; + port_file(x) = NULL; +} + +s7_pointer s7_open_input_function(s7_scheme *sc, s7_pointer (*function)(s7_scheme *sc, s7_read_t read_choice, s7_pointer port)) +{ + s7_pointer x; + block_t *b = mallocate_port(sc); + new_cell(sc, x, T_INPUT_PORT); + port_block(x) = b; + port_port(x) = (port_t *)block_data(b); + function_port_set_defaults(x); + port_set_string_or_function(x, sc->nil); + port_input_function(x) = function; + port_port(x)->pf = &input_function_functions; + add_input_port(sc, x); + return(x); +} + +static void init_open_input_function_choices(s7_scheme *sc) +{ + sc->open_input_function_choices[S7_READ] = sc->read_symbol; + sc->open_input_function_choices[S7_READ_CHAR] = sc->read_char_symbol; + sc->open_input_function_choices[S7_READ_LINE] = sc->read_line_symbol; + sc->open_input_function_choices[S7_PEEK_CHAR] = sc->peek_char_symbol; +#if (!WITH_PURE_S7) + sc->open_input_function_choices[S7_IS_CHAR_READY] = sc->is_char_ready_symbol; +#endif +} + +static s7_pointer input_scheme_function_wrapper(s7_scheme *sc, s7_read_t read_choice, s7_pointer port) +{ + return(s7_apply_function(sc, port_string_or_function(port), set_plist_1(sc, sc->open_input_function_choices[(int)read_choice]))); +} + +static s7_pointer g_open_input_function(s7_scheme *sc, s7_pointer args) +{ + #define H_open_input_function "(open-input-function func) opens an input function port" + #define Q_open_input_function s7_make_signature(sc, 2, sc->is_input_port_symbol, s7_make_signature(sc, 2, sc->is_procedure_symbol, sc->is_macro_symbol)) + + s7_pointer port, func = car(args); + + if (!is_any_procedure(func)) /* is_procedure is too lenient: we need to flag (open-input-function (block)) for example */ + sole_arg_wrong_type_error_nr(sc, sc->open_input_function_symbol, func, a_procedure_string); + if (!s7_is_aritable(sc, func, 1)) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_2(sc, wrap_string(sc, "input-function-port function, ~A, should take one argument", 58), func)); + + port = s7_open_input_function(sc, input_scheme_function_wrapper); + port_set_string_or_function(port, func); + return(port); +} + + +/* -------------------------------- open-output-function -------------------------------- */ +static s7_pointer g_closed_output_function_port(s7_scheme *sc, s7_pointer unused_args) +{ + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_1(sc, wrap_string(sc, "attempt to write to a closed output-function port", 49))); + return(NULL); +} + +static void close_output_function(s7_scheme *sc, s7_pointer p) +{ + port_port(p)->pf = &closed_port_functions; + port_set_string_or_function(p, sc->closed_output_function); + port_set_closed(p, true); +} + +static const port_functions_t output_function_functions = + {output_read_char, function_write_char, function_write_string, NULL, NULL, NULL, NULL, output_read_line, function_display, close_output_function}; + +s7_pointer s7_open_output_function(s7_scheme *sc, void (*function)(s7_scheme *sc, uint8_t c, s7_pointer port)) +{ + s7_pointer x; + block_t *b = mallocate_port(sc); + new_cell(sc, x, T_OUTPUT_PORT); + port_block(x) = b; + port_port(x) = (port_t *)block_data(b); + function_port_set_defaults(x); + port_output_function(x) = function; + port_set_string_or_function(x, sc->nil); + port_port(x)->pf = &output_function_functions; + add_output_port(sc, x); + return(x); +} + +static void output_scheme_function_wrapper(s7_scheme *sc, uint8_t c, s7_pointer port) +{ + s7_apply_function(sc, port_string_or_function(port), set_plist_1(sc, make_integer(sc, c))); +} + +static s7_pointer g_open_output_function(s7_scheme *sc, s7_pointer args) +{ + #define H_open_output_function "(open-output-function func) opens an output function port" + #define Q_open_output_function s7_make_signature(sc, 2, sc->is_output_port_symbol, s7_make_signature(sc, 2, sc->is_procedure_symbol, sc->is_macro_symbol)) + + s7_pointer port, func = car(args); + + if (!is_any_procedure(func)) + sole_arg_wrong_type_error_nr(sc, sc->open_output_function_symbol, func, a_procedure_string); + if (!s7_is_aritable(sc, func, 1)) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_2(sc, wrap_string(sc, "output-function-port function, ~A, should take one argument", 59), func)); + + port = s7_open_output_function(sc, output_scheme_function_wrapper); + port_set_string_or_function(port, func); + mark_function[T_OUTPUT_PORT] = mark_output_port; + return(port); +} + + +/* -------- current-input-port stack -------- */ +#define INPUT_PORT_STACK_INITIAL_SIZE 4 + +static inline void push_input_port(s7_scheme *sc, s7_pointer new_port) +{ + if (sc->input_port_stack_loc >= sc->input_port_stack_size) + { + sc->input_port_stack_size *= 2; + sc->input_port_stack = (s7_pointer *)Realloc(sc->input_port_stack, sc->input_port_stack_size * sizeof(s7_pointer)); + } + sc->input_port_stack[sc->input_port_stack_loc++] = current_input_port(sc); + set_current_input_port(sc, new_port); +} + +static void pop_input_port(s7_scheme *sc) +{ + set_current_input_port(sc, (sc->input_port_stack_loc > 0) ? sc->input_port_stack[--(sc->input_port_stack_loc)] : sc->standard_input); +} + +static s7_pointer input_port_if_not_loading(s7_scheme *sc) +{ + s7_pointer port = current_input_port(sc); + int32_t c; + if (!is_loader_port(port)) /* this flag is turned off by the reader macros, so we aren't in that context */ + return(port); + c = port_read_white_space(port)(sc, port); + if (c > 0) /* we can get either EOF or NULL at the end */ + { + backchar(c, port); + return(NULL); + } + return(sc->standard_input); +} + + +/* -------------------------------- read-char -------------------------------- */ +s7_pointer s7_read_char(s7_scheme *sc, s7_pointer port) +{ + int32_t c = port_read_character(port)(sc, port); + return((c == EOF) ? eof_object : chars[c]); +} + +static s7_pointer g_read_char(s7_scheme *sc, s7_pointer args) +{ + #define H_read_char "(read-char (port (current-input-port))) returns the next character in the input port" + #define Q_read_char s7_make_signature(sc, 2, s7_make_signature(sc, 2, sc->is_char_symbol, sc->is_eof_object_symbol), sc->is_input_port_symbol) + + s7_pointer port; + if (is_not_null(args)) + port = car(args); + else + { + port = input_port_if_not_loading(sc); + if (!port) return(eof_object); + } + if (!is_input_port(port)) + return(method_or_bust_p(sc, port, sc->read_char_symbol, an_input_port_string)); + return(chars[port_read_character(port)(sc, port)]); +} + +static s7_pointer read_char_p_p(s7_scheme *sc, s7_pointer port) +{ + if (!is_input_port(port)) + return(method_or_bust_p(sc, port, sc->read_char_symbol, an_input_port_string)); + return(chars[port_read_character(port)(sc, port)]); +} + +static s7_pointer g_read_char_1(s7_scheme *sc, s7_pointer args) +{ + s7_pointer port = car(args); + if (!is_input_port(port)) + return(method_or_bust_p(sc, port, sc->read_char_symbol, an_input_port_string)); + return(chars[port_read_character(port)(sc, port)]); +} + +static s7_pointer read_char_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer unused_expr, bool unused_ops) +{ + return((args == 1) ? sc->read_char_1 : f); +} + + +/* -------------------------------- write-char -------------------------------- */ +s7_pointer s7_write_char(s7_scheme *sc, s7_pointer c, s7_pointer pt) +{ + if (pt != sc->F) + port_write_character(pt)(sc, s7_character(c), pt); + return(c); +} + +static s7_pointer write_char_p_pp(s7_scheme *sc, s7_pointer c, s7_pointer port) +{ + if (!is_character(c)) + return(method_or_bust_pp(sc, c, sc->write_char_symbol, c, port, sc->type_names[T_CHARACTER], 1)); + if (!is_output_port(port)) + { + if (port == sc->F) return(c); + check_method(sc, port, sc->write_char_symbol, set_mlist_2(sc, c, port)); + wrong_type_error_nr(sc, sc->write_char_symbol, 2, port, an_output_port_or_f_string); + } + port_write_character(port)(sc, s7_character(c), port); + return(c); +} + +static s7_pointer g_write_char(s7_scheme *sc, s7_pointer args) +{ + #define H_write_char "(write-char char (port (current-output-port))) writes char to the output port" + #define Q_write_char s7_make_signature(sc, 3, sc->is_char_symbol, sc->is_char_symbol, s7_make_signature(sc, 2, sc->is_output_port_symbol, sc->not_symbol)) + return(write_char_p_pp(sc, car(args), (is_pair(cdr(args))) ? cadr(args) : current_output_port(sc))); +} + +static s7_pointer write_char_p_p(s7_scheme *sc, s7_pointer c) +{ + if (!is_character(c)) + return(method_or_bust_p(sc, c, sc->write_char_symbol, sc->type_names[T_CHARACTER])); + if (current_output_port(sc) == sc->F) return(c); + port_write_character(current_output_port(sc))(sc, s7_character(c), current_output_port(sc)); + return(c); +} + +/* (with-output-to-string (lambda () (write-char #\space))) -> " " + * (with-output-to-string (lambda () (write #\space))) -> "#\\space" + * (with-output-to-string (lambda () (display #\space))) -> " " + * is this correct? It's what Guile does. write-char is actually display-char. + */ + + +/* -------------------------------- peek-char -------------------------------- */ +s7_pointer s7_peek_char(s7_scheme *sc, s7_pointer port) +{ + int32_t c; /* needs to be an int32_t so EOF=-1, but not 255 */ + if (is_string_port(port)) + return((port_data_size(port) <= port_position(port)) ? chars[EOF] : chars[(uint8_t)port_data(port)[port_position(port)]]); + c = port_read_character(port)(sc, port); + if (c == EOF) return(eof_object); + backchar(c, port); + return(chars[c]); +} + +static s7_pointer g_peek_char(s7_scheme *sc, s7_pointer args) +{ + #define H_peek_char "(peek-char (port (current-input-port))) returns the next character in the input port, but does not remove it from the input stream" + #define Q_peek_char s7_make_signature(sc, 2, s7_make_signature(sc, 2, sc->is_char_symbol, sc->is_eof_object_symbol), sc->is_input_port_symbol) + + s7_pointer res, port = (is_not_null(args)) ? car(args) : current_input_port(sc); + if (!is_input_port(port)) + return(method_or_bust_p(sc, port, sc->peek_char_symbol, an_input_port_string)); + if (port_is_closed(port)) + sole_arg_wrong_type_error_nr(sc, sc->peek_char_symbol, port, an_open_input_port_string); + if (!is_function_port(port)) + return(s7_peek_char(sc, port)); + + res = (*(port_input_function(port)))(sc, S7_PEEK_CHAR, port); + if (is_multiple_value(res)) + { + clear_multiple_value(res); + error_nr(sc, sc->bad_result_symbol, + set_elist_2(sc, wrap_string(sc, "input-function-port peek-char returned multiple values: ~S", 58), res)); + } + if (!is_character(res)) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_2(sc, wrap_string(sc, "input-function-port peek-char returned: ~S", 42), res)); + return(res); +} + + +/* -------------------------------- read-byte -------------------------------- */ +static s7_pointer g_read_byte(s7_scheme *sc, s7_pointer args) +{ + #define H_read_byte "(read-byte (port (current-input-port))): reads a byte from the input port" + #define Q_read_byte s7_make_signature(sc, 2, s7_make_signature(sc, 2, sc->is_byte_symbol, sc->is_eof_object_symbol), sc->is_input_port_symbol) + + s7_pointer port; + int32_t c; + + if (is_not_null(args)) + port = car(args); + else + { + port = input_port_if_not_loading(sc); + if (!port) return(eof_object); + } + if (!is_input_port(port)) + return(method_or_bust_p(sc, port, sc->read_byte_symbol, an_input_port_string)); + if (port_is_closed(port)) /* avoid reporting caller here as read-char */ + sole_arg_wrong_type_error_nr(sc, sc->read_byte_symbol, port, an_open_input_port_string); + c = port_read_character(port)(sc, port); + return((c == EOF) ? eof_object : small_int(c)); +} + + +/* -------------------------------- write-byte -------------------------------- */ +static s7_pointer g_write_byte(s7_scheme *sc, s7_pointer args) +{ + #define H_write_byte "(write-byte byte (port (current-output-port))): writes byte to the output port" + #define Q_write_byte s7_make_signature(sc, 3, sc->is_byte_symbol, sc->is_byte_symbol, s7_make_signature(sc, 2, sc->is_output_port_symbol, sc->not_symbol)) + + s7_pointer port, b = car(args); + s7_int val; + if (!s7_is_integer(b)) + return(method_or_bust(sc, b, sc->write_byte_symbol, args, sc->type_names[T_INTEGER], 1)); + + val = s7_integer_clamped_if_gmp(sc, b); + if ((val < 0) || (val > 255)) /* need to check this before port==#f, else (write-byte most-positive-fixnum #f) is not an error */ + wrong_type_error_nr(sc, sc->write_byte_symbol, 1, b, an_unsigned_byte_string); + + port = (is_pair(cdr(args))) ? cadr(args) : current_output_port(sc); + if (!is_output_port(port)) + { + if (port == sc->F) return(b); + check_method(sc, port, sc->write_byte_symbol, args); + wrong_type_error_nr(sc, sc->write_byte_symbol, 2, port, an_output_port_or_f_string); + } + if (port_is_closed(port)) /* avoid reporting caller here as write-char */ + wrong_type_error_nr(sc, sc->write_byte_symbol, 2, port, an_open_output_port_string); + + port_write_character(port)(sc, (uint8_t)val, port); + return(b); +} + + +/* -------------------------------- read-line -------------------------------- */ +static s7_pointer g_read_line(s7_scheme *sc, s7_pointer args) +{ + #define H_read_line "(read-line port (with-eol #f)) returns the next line from port, or #. \ +If 'with-eol' is not #f, read-line includes the trailing end-of-line character." + #define Q_read_line s7_make_signature(sc, 3, \ + s7_make_signature(sc, 2, sc->is_string_symbol, sc->is_eof_object_symbol), \ + sc->is_input_port_symbol, sc->is_boolean_symbol) + s7_pointer port; + bool with_eol = false; + if (is_not_null(args)) + { + port = car(args); + if (!is_input_port(port)) + return(method_or_bust(sc, port, sc->read_line_symbol, args, an_input_port_string, 1)); + + if (is_not_null(cdr(args))) + with_eol = (cadr(args) != sc->F); /* perhaps this should insist on #t: (read-line port (c-pointer 0)) */ + } + else + { + port = input_port_if_not_loading(sc); + if (!port) return(eof_object); + } + return(port_read_line(port)(sc, port, with_eol)); +} + +static s7_pointer read_line_p_pp(s7_scheme *sc, s7_pointer port, s7_pointer with_eol) +{ + if (!is_input_port(port)) + return(method_or_bust_pp(sc, port, sc->read_line_symbol, port, with_eol, an_input_port_string, 1)); + return(port_read_line(port)(sc, port, with_eol != sc->F)); +} + +static s7_pointer read_line_p_p(s7_scheme *sc, s7_pointer port) +{ + if (!is_input_port(port)) + return(method_or_bust_p(sc, port, sc->read_line_symbol, an_input_port_string)); + return(port_read_line(port)(sc, port, false)); /* with_eol default is #f */ +} + + +/* -------------------------------- read-string -------------------------------- */ +static s7_pointer g_read_string(s7_scheme *sc, s7_pointer args) +{ + /* read-chars would be a better name -- read-string could mean CL-style read-from-string (like eval-string) + * similarly read-bytes could return a byte-vector (rather than r7rs's read-bytevector) + * and write-string -> write-chars, write-bytevector -> write-bytes + */ + #define H_read_string "(read-string k port) reads k characters from port into a new string and returns it." + #define Q_read_string s7_make_signature(sc, 3, \ + s7_make_signature(sc, 2, sc->is_string_symbol, sc->is_eof_object_symbol), \ + sc->is_integer_symbol, sc->is_input_port_symbol) + s7_pointer k = car(args), port, s; + s7_int nchars; + uint8_t *str; + + if (!s7_is_integer(k)) + return(method_or_bust(sc, k, sc->read_string_symbol, args, sc->type_names[T_INTEGER], 1)); + nchars = s7_integer_clamped_if_gmp(sc, k); + if ((nchars < 0) || (nchars > sc->max_string_length)) + out_of_range_error_nr(sc, sc->read_string_symbol, int_one, k, (nchars < 0) ? it_is_negative_string : it_is_too_large_string); + + if (!is_null(cdr(args))) + port = cadr(args); + else + { + port = input_port_if_not_loading(sc); + if (!port) return(eof_object); + } + if (!is_input_port(port)) + return(method_or_bust_pp(sc, port, sc->read_string_symbol, k, port, an_input_port_string, 2)); + if (port_is_closed(port)) + wrong_type_error_nr(sc, sc->read_string_symbol, 2, port, an_open_input_port_string); + + s = make_empty_string(sc, nchars, 0); + if (nchars == 0) return(s); + str = (uint8_t *)string_value(s); + if (is_string_port(port)) + { + s7_int pos = port_position(port); + s7_int end = port_data_size(port); + s7_int len = end - pos; + if (len > nchars) len = nchars; + if (len <= 0) return(eof_object); + memcpy((void *)str, (void *)(port_data(port) + pos), len); + string_length(s) = len; + str[len] = '\0'; + port_position(port) += len; + return(s); + } + if (is_file_port(port)) + { + size_t len = fread((void *)str, 1, nchars, port_file(port)); + str[len] = '\0'; + string_length(s) = len; + return(s); + } + for (s7_int i = 0; i < nchars; i++) + { + int32_t c = port_read_character(port)(sc, port); + if (c == EOF) + { + if (i == 0) + return(eof_object); + string_length(s) = i; + return(s); + } + str[i] = (uint8_t)c; + } + return(s); +} + + +/* -------------------------------- read -------------------------------- */ +#define declare_jump_info() bool old_longjmp; int32_t old_jump_loc, jump_loc; Jmp_Buf *old_goto_start; Jmp_Buf new_goto_start + +#define store_jump_info(Sc) \ + do { \ + old_longjmp = Sc->longjmp_ok; \ + old_jump_loc = Sc->setjmp_loc; \ + old_goto_start = Sc->goto_start; \ + } while (0) + +#define restore_jump_info(Sc) \ + do { \ + Sc->longjmp_ok = old_longjmp; \ + Sc->setjmp_loc = old_jump_loc; \ + Sc->goto_start = old_goto_start; \ + if ((jump_loc == ERROR_JUMP) && \ + (Sc->longjmp_ok)) \ + LongJmp(*(Sc->goto_start), ERROR_JUMP); \ + } while (0) + +#define set_jump_info(Sc, Tag) \ + do { \ + Sc->longjmp_ok = true; \ + Sc->setjmp_loc = Tag; \ + jump_loc = SetJmp(new_goto_start, 1); \ + Sc->goto_start = &new_goto_start; \ + } while (0) + +static s7_pointer eval(s7_scheme *sc, opcode_t first_op); + +s7_pointer s7_read(s7_scheme *sc, s7_pointer port) +{ + if (is_input_port(port)) + { + s7_pointer old_let = sc->curlet; + declare_jump_info(); + sc->curlet = sc->nil; + push_input_port(sc, port); + store_jump_info(sc); + set_jump_info(sc, READ_SET_JUMP); + if (jump_loc != NO_JUMP) + { + if (jump_loc != ERROR_JUMP) + eval(sc, sc->cur_op); + } + else + { + push_stack_no_let_no_code(sc, OP_BARRIER, port); + push_stack_direct(sc, OP_EVAL_DONE); + eval(sc, OP_READ_INTERNAL); + if (sc->tok == TOKEN_EOF) + sc->value = eof_object; + if ((sc->cur_op == OP_EVAL_DONE) && /* pushed above */ + (stack_top_op(sc) == OP_BARRIER)) + pop_stack(sc); + } + pop_input_port(sc); + set_curlet(sc, old_let); + restore_jump_info(sc); + return(sc->value); + } + sole_arg_wrong_type_error_nr(sc, sc->read_symbol, port, an_input_port_string); + return(NULL); +} + +static s7_pointer g_read(s7_scheme *sc, s7_pointer args) +{ + #define H_read "(read (port (current-input-port))) returns the next object in the input port, or # at the end" + #define Q_read s7_make_signature(sc, 2, sc->T, sc->is_input_port_symbol) + + s7_pointer port; + if (is_not_null(args)) + port = car(args); + else + { + port = input_port_if_not_loading(sc); + if (!port) return(eof_object); + } + if (!is_input_port(port)) + return(method_or_bust_p(sc, port, sc->read_symbol, an_input_port_string)); + + if (is_function_port(port)) + { + s7_pointer res = (*(port_input_function(port)))(sc, S7_READ, port); + if (is_multiple_value(res)) + { + clear_multiple_value(res); + error_nr(sc, sc->bad_result_symbol, set_elist_2(sc, wrap_string(sc, "input-function-port read returned: ~S", 37), res)); + } + return(res); + } + if ((is_string_port(port)) && + (port_data_size(port) <= port_position(port))) + return(eof_object); + + push_input_port(sc, port); + push_stack_op_let(sc, OP_READ_DONE); /* this stops the internal read process so we only get one form */ + push_stack_op_let(sc, OP_READ_INTERNAL); + return(port); +} + + +/* -------------------------------- load -------------------------------- */ +#if WITH_MULTITHREAD_CHECKS +typedef struct { + s7_scheme* sc; + const int32_t lock_count; /* Remember lock count in case we have skipped calls to leave_track_scope by longjmp-ing */ +} lock_scope_t; + +static lock_scope_t enter_lock_scope(s7_scheme *sc) +{ + int32_t result = pthread_mutex_trylock(&sc->lock); + if (result != 0) + { + fprintf(stderr, "pthread_mutex_trylock failed: %d (EBUSY: %d)", result, EBUSY); + abort(); + } + sc->lock_count++; + { + lock_scope_t st = {.sc = sc, .lock_count = sc->lock_count}; + return(st); + } +} + +static void leave_lock_scope(lock_scope_t *st) +{ + while (st->sc->lock_count > st->lock_count) + { + st->sc->lock_count--; + pthread_mutex_unlock(&st->sc->lock); + } +} + +#define TRACK(Sc) lock_scope_t lock_scope __attribute__ ((__cleanup__(leave_lock_scope))) = enter_lock_scope(Sc) +#else +#define TRACK(Sc) +#endif + +/* various changes in this section courtesy of Woody Douglass 12-Jul-19 */ + +static block_t *search_load_path(s7_scheme *sc, const char *name) +{ + s7_pointer lst = s7_load_path(sc); + if (is_pair(lst)) + { +#if MS_WINDOWS || defined(__linux__) + #define S7_FILENAME_MAX 4096 /* so we can handle 4095 chars (need trailing null) -- this limit could be added to *s7* */ +#else + #define S7_FILENAME_MAX 1024 +#endif + /* linux: PATH_MAX: 4096, windows: MAX_PATH: unlimited?, Mac: 1016?, BSD: MAX_PATH_LENGTH: 1024 */ + block_t *b = mallocate(sc, S7_FILENAME_MAX); + char *filename = (char *)block_data(b); + s7_int name_len = safe_strlen(name); + for (s7_pointer dir_names = lst; is_pair(dir_names); dir_names = cdr(dir_names)) + { + const char *new_dir = string_value(car(dir_names)); + if (new_dir) + { + if ((WITH_WARNINGS) && (string_length(car(dir_names)) + name_len >= S7_FILENAME_MAX)) + s7_warn(sc, 256, "load: file + directory name too long: %" ld64 " + %" ld64 " > %d\n", + name_len, string_length(car(dir_names)), S7_FILENAME_MAX); + filename[0] = '\0'; + if (new_dir[strlen(new_dir) - 1] == '/') + catstrs(filename, S7_FILENAME_MAX, new_dir, name, (char *)NULL); + else catstrs(filename, S7_FILENAME_MAX, new_dir, "/", name, (char *)NULL); +#ifdef _MSC_VER + if (_access(filename, 0) != -1) + return(b); +#else + if (access(filename, F_OK) == 0) + return(b); +#endif + }} + liberate(sc, b); + } + return(NULL); +} + +#if WITH_C_LOADER +#include + +static block_t *full_filename(s7_scheme *sc, const char *filename) +{ + char *rtn; + block_t *block; + if ((S7_DEBUGGING) && ((!filename) || (!*filename))) fprintf(stderr, "%s[%d]: filename is %s\n", __func__, __LINE__, filename); + if (filename[0] == '/') + { + s7_int len = safe_strlen(filename); + block = mallocate(sc, len + 1); + rtn = (char *)block_data(block); + memcpy((void *)rtn, (const void *)filename, len); + rtn[len] = '\0'; + } + else + { + char *pwd = getcwd(NULL, 0); /* docs say this means it will return a new string of the right size */ + size_t pwd_len = safe_strlen(pwd); + size_t filename_len = safe_strlen(filename); + s7_int len = pwd_len + filename_len + 2; /* not 1! we need room for the '/' and the terminating 0 */ + block = mallocate(sc, len); + rtn = (char *)block_data(block); + if (pwd) + { + memcpy((void *)rtn, (void *)pwd, pwd_len); + rtn[pwd_len] = '/'; + memcpy((void *)(rtn + pwd_len + 1), (const void *)filename, filename_len); + rtn[pwd_len + filename_len + 1] = '\0'; + free(pwd); + } + else /* isn't this an error? -- perhaps warn about getcwd, strerror(errno) */ + { + memcpy((void *)rtn, (const void *)filename, filename_len); + rtn[filename_len] = '\0'; + }} + return(block); +} + +static s7_pointer load_shared_object(s7_scheme *sc, const char *fname, s7_pointer let) +{ + /* if fname ends in .so|.dylib, try loading it as a C shared object: (load "/home/bil/cl/m_j0.so" (inlet 'init_func 'init_m_j0)) */ + s7_int fname_len = safe_strlen(fname); + if (((fname_len > 3) && + (local_strcmp((const char *)(fname + (fname_len - 3)), ".so"))) || /* linux */ + ((fname_len > 6) && + (local_strcmp((const char *)(fname + (fname_len - 3)), ".dylib")))) /* mac */ + { + void *library; + char *pwd_name = NULL; + block_t *pname = NULL; + + if ((access(fname, F_OK) == 0) || (fname[0] == '/')) + { + pname = full_filename(sc, fname); + pwd_name = (char *)block_data(pname); + } + else + { + block_t *searched = search_load_path(sc, fname); /* returns NULL if *load-path* is nil, or if nothing matches */ + if (searched) + { + if (((const char *)block_data(searched))[0] == '/') + pname = searched; + else + { /* this is necessary, at least in Linux -- we can't blithely dlopen whatever is passed to us */ + pname = full_filename(sc, (const char *)block_data(searched)); + liberate(sc, searched); + } + pwd_name = (char *)block_data(pname); + } + else /* perhaps no *load-path* entries */ + { + pname = full_filename(sc, fname); + pwd_name = (char *)block_data(pname); + }} + if ((S7_DEBUGGING) && (!pname)) fprintf(stderr, "pname is null\n"); + library = dlopen((pname) ? pwd_name : fname, RTLD_NOW); + if (!library) + s7_warn(sc, 512, "load %s failed: %s\n", (pname) ? pwd_name : fname, dlerror()); + else + if (let) /* look for 'init_func in let */ + { + s7_pointer init = let_ref(sc, let, make_symbol(sc, "init_func", 9)); + /* init is a symbol (surely not a gensym?), so it should not need to be protected */ + if (!is_symbol(init)) + s7_warn(sc, 512, "can't load %s: no init function\n", fname); + else + { + const char *init_name; + void *init_func; + + if (hook_has_functions(sc->load_hook)) + s7_apply_function(sc, sc->load_hook, set_plist_1(sc, s7_make_string(sc, (pname) ? (const char *)pwd_name : fname))); + + init_name = symbol_name(init); + init_func = dlsym(library, init_name); + if (init_func) + { + typedef void (*dl_func)(s7_scheme *sc); + typedef s7_pointer (*dl_func_with_args)(s7_scheme *sc, s7_pointer args); + s7_pointer init_args = let_ref(sc, let, make_symbol(sc, "init_args", 9)); + s7_pointer p; + gc_protect_via_stack(sc, init_args); + if (is_pair(init_args)) + { + p = ((dl_func_with_args)init_func)(sc, init_args); + set_stack_protected2(sc, p); + } + /* if caller includes init_args, but init_func is actually a dl_func, it seems to be ok, + * but the returned value is whatever was last computed in the init_func. + */ + else + { + /* if the init_func is expecting args, but caller forgets init_args, this gives a segfault when + * init_func accesses the forgotten args. s7_is_valid can't catch this currently -- + * we need a better way to tell that a random value can't be a cell pointer (scan permallocs and use heap_location?) + */ + ((dl_func)init_func)(sc); + p = sc->F; + } + unstack(sc); + if (pname) liberate(sc, pname); + return(p); + } + s7_warn(sc, 512, "loaded %s, but can't find init_func %s, dlerror: %s, let: %s\n", + fname, init_name, dlerror(), display(let)); + dlclose(library); + } + if (S7_DEBUGGING) fprintf(stderr, "init_func trouble in %s, %s\n", fname, display(init)); + if (pname) liberate(sc, pname); + return(sc->undefined); + } + if (pname) liberate(sc, pname); + } + return(NULL); +} +#endif + +static s7_pointer load_file_1(s7_scheme *sc, const char *filename) +{ + char *local_file_name = NULL; + FILE* fp = fopen(filename, "r"); +#if WITH_GCC + if (!fp) /* catch one special case, "~/..." since it causes 99.9% of the "can't load ..." errors */ + { + block_t *b = expand_filename(sc, filename); + if (b) + { + fp = fopen((char *)block_data(b), "r"); + if (fp) local_file_name = copy_string((char *)block_data(b)); + liberate(sc, b); + }} +#endif + if (!fp) + { + const char *fname; + block_t *b = search_load_path(sc, filename); + if (!b) return(NULL); + fname = (const char *)block_data(b); + fp = fopen(fname, "r"); + if (fp) local_file_name = copy_string_with_length(fname, safe_strlen(fname)); + liberate(sc, b); + } + if (fp) + { + s7_pointer port; + if (hook_has_functions(sc->load_hook)) + s7_apply_function(sc, sc->load_hook, set_plist_1(sc, s7_make_string(sc, (local_file_name) ? local_file_name : filename))); + port = read_file(sc, fp, (local_file_name) ? local_file_name : filename, -1, "load"); /* -1 = read entire file into string, this is currently not tweakable */ + port_file_number(port) = remember_file_name(sc, (local_file_name) ? local_file_name : filename); + if (local_file_name) free(local_file_name); + set_loader_port(port); + push_input_port(sc, port); + return(port); + } + return(NULL); +} + +s7_pointer s7_load_with_environment(s7_scheme *sc, const char *filename, s7_pointer e) +{ + /* returns either the value of the load or NULL if filename not found */ + s7_pointer port; + declare_jump_info(); + TRACK(sc); + if (e == sc->s7_starlet) return(NULL); + +#if WITH_C_LOADER + port = load_shared_object(sc, filename, (is_null(e)) ? sc->rootlet : e); + if (port) return(port); +#endif + + if (is_directory(filename)) return(NULL); + port = load_file_1(sc, filename); + if (!port) return(NULL); + + set_curlet(sc, (e == sc->rootlet) ? sc->nil : e); + push_stack(sc, OP_LOAD_RETURN_IF_EOF, port, sc->code); + + store_jump_info(sc); + set_jump_info(sc, LOAD_SET_JUMP); + if (jump_loc == NO_JUMP) + eval(sc, OP_READ_INTERNAL); + else + if (jump_loc != ERROR_JUMP) + eval(sc, sc->cur_op); + pop_input_port(sc); + if (is_input_port(port)) + s7_close_input_port(sc, port); + restore_jump_info(sc); + + if (is_multiple_value(sc->value)) + sc->value = splice_in_values(sc, multiple_value(sc->value)); + return(sc->value); +} + +s7_pointer s7_load(s7_scheme *sc, const char *filename) {return(s7_load_with_environment(sc, filename, sc->nil));} + +s7_pointer s7_load_c_string_with_environment(s7_scheme *sc, const char *content, s7_int bytes, s7_pointer e) +{ + s7_pointer port; + s7_int port_loc; + declare_jump_info(); + TRACK(sc); + + if (content[bytes] != 0) + error_nr(sc, make_symbol(sc, "bad-data", 8), set_elist_1(sc, wrap_string(sc, "s7_load_c_string content is not terminated", 42))); + port = open_input_string(sc, content, bytes); + port_loc = gc_protect_1(sc, port); + set_loader_port(port); + push_input_port(sc, port); + set_curlet(sc, (e == sc->rootlet) ? sc->nil : e); + push_stack(sc, OP_LOAD_RETURN_IF_EOF, port, sc->code); + s7_gc_unprotect_at(sc, port_loc); + + store_jump_info(sc); + set_jump_info(sc, LOAD_SET_JUMP); + if (jump_loc == NO_JUMP) + eval(sc, OP_READ_INTERNAL); + else + if (jump_loc != ERROR_JUMP) + eval(sc, sc->cur_op); + pop_input_port(sc); + if (is_input_port(port)) + s7_close_input_port(sc, port); + restore_jump_info(sc); + + if (is_multiple_value(sc->value)) + sc->value = splice_in_values(sc, multiple_value(sc->value)); + return(sc->value); +} + +s7_pointer s7_load_c_string(s7_scheme *sc, const char *content, s7_int bytes) +{ + return(s7_load_c_string_with_environment(sc, content, bytes, sc->nil)); +} + +static s7_pointer g_load(s7_scheme *sc, s7_pointer args) +{ + #define H_load "(load file (let (rootlet))) loads the scheme file 'file'. The 'let' argument \ +defaults to the rootlet. To load into the current environment instead, pass (curlet)." + #define Q_load s7_make_signature(sc, 3, sc->values_symbol, sc->is_string_symbol, sc->is_let_symbol) + + s7_pointer name = car(args); + const char *fname; + + if (!is_string(name)) + return(method_or_bust(sc, name, sc->load_symbol, args, sc->type_names[T_STRING], 1)); + + if (is_pair(cdr(args))) + { + s7_pointer e = cadr(args); + if (!is_let(e)) + wrong_type_error_nr(sc, sc->load_symbol, 2, e, a_let_string); + if (e == sc->s7_starlet) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_2(sc, wrap_string(sc, "can't load ~S into *s7*", 23), name)); + set_curlet(sc, (e == sc->rootlet) ? sc->nil : e); + } + else sc->curlet = sc->nil; + + fname = string_value(name); + if ((!fname) || (!(*fname))) /* fopen("", "r") returns a file pointer?? */ + error_nr(sc, sc->out_of_range_symbol, + set_elist_2(sc, wrap_string(sc, "load's first argument, ~S, should be a filename", 47), name)); + + if (is_directory(fname)) + error_nr(sc, sc->io_error_symbol, + set_elist_2(sc, wrap_string(sc, "load: ~S is a directory", 23), wrap_string(sc, fname, safe_strlen(fname)))); +#if WITH_C_LOADER + { + s7_pointer p = load_shared_object(sc, fname, (is_null(sc->curlet)) ? sc->rootlet : sc->curlet); + if (p) return(p); + } +#endif + errno = 0; + if (!load_file_1(sc, fname)) + file_error_nr(sc, "load", strerror(errno), fname); + + push_stack_op_let(sc, OP_LOAD_CLOSE_AND_POP_IF_EOF); /* was pushing args and code, but I don't think they're used later */ + push_stack_op_let(sc, OP_READ_INTERNAL); + return(sc->unspecified); +} + + +/* -------- *load-path* -------- */ +s7_pointer s7_load_path(s7_scheme *sc) {return(s7_symbol_value(sc, sc->load_path_symbol));} + +s7_pointer s7_add_to_load_path(s7_scheme *sc, const char *dir) +{ + s7_symbol_set_value(sc, sc->load_path_symbol, cons(sc, s7_make_string(sc, dir), s7_symbol_value(sc, sc->load_path_symbol))); + return(s7_symbol_value(sc, sc->load_path_symbol)); +} + +static s7_pointer g_load_path_set(s7_scheme *sc, s7_pointer args) +{ + /* new value must be either () or a proper list of strings */ + s7_pointer x; + if (is_null(cadr(args))) return(cadr(args)); + if (!is_pair(cadr(args))) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "can't set *load-path* to ~S", 27), cadr(args))); + for (x = cadr(args); is_pair(x); x = cdr(x)) + if (!is_string(car(x))) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "can't set *load-path* to ~S", 27), cadr(args))); + if (!is_null(x)) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "can't set *load-path* to ~S", 27), cadr(args))); + return(cadr(args)); +} + +/* -------- *cload-directory* -------- */ +static s7_pointer g_cload_directory_set(s7_scheme *sc, s7_pointer args) +{ + /* this sets the directory for cload.scm's output */ + s7_pointer cl_dir = cadr(args); + if (!is_string(cl_dir)) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "can't set *cload-directory* to ~S", 33), cadr(args))); + s7_symbol_set_value(sc, sc->cload_directory_symbol, cl_dir); + if (string_length(cl_dir) > 0) /* was strlen(string_value)? */ + s7_add_to_load_path(sc, (const char *)(string_value(cl_dir))); + /* should this remove the previous *cload-directory* name first? or not affect *load-path* at all? */ + return(cl_dir); +} + + +/* ---------------- autoload ---------------- */ +#define INITIAL_AUTOLOAD_NAMES_SIZE 4 + +void s7_autoload_set_names(s7_scheme *sc, const char **names, s7_int size) +{ + /* names should be sorted alphabetically by the symbol name (the even indexes in the names array) + * size is the number of symbol names (half the size of the names array( + * the idea here is that by sticking to string constants we can handle 90% of the work at compile-time, + * with less start-up memory. Then eventually we'll add C libraries and every name in those libraries + * will come as an import once dlopen has picked up the library. + */ + if (sc->safety > 1) + for (int32_t i = 0, k = 2; k < (size * 2); i += 2, k += 2) + if ((names[i]) && (names[k]) && (strcmp(names[i], names[k]) > 0)) + { + s7_warn(sc, 256, "%s: names[%d]: %s is out of order\n", __func__, k, names[k]); + break; + } + if (!sc->autoload_names) + { + sc->autoload_names = (const char ***)Calloc(INITIAL_AUTOLOAD_NAMES_SIZE, sizeof(const char **)); + sc->autoload_names_sizes = (s7_int *)Calloc(INITIAL_AUTOLOAD_NAMES_SIZE, sizeof(s7_int)); + sc->autoloaded_already = (bool **)Calloc(INITIAL_AUTOLOAD_NAMES_SIZE, sizeof(bool *)); + sc->autoload_names_top = INITIAL_AUTOLOAD_NAMES_SIZE; + sc->autoload_names_loc = 0; + } + else + if (sc->autoload_names_loc >= sc->autoload_names_top) + { + sc->autoload_names_top *= 2; + sc->autoload_names = (const char ***)Realloc(sc->autoload_names, sc->autoload_names_top * sizeof(const char **)); + sc->autoload_names_sizes = (s7_int *)Realloc(sc->autoload_names_sizes, sc->autoload_names_top * sizeof(s7_int)); + sc->autoloaded_already = (bool **)Realloc(sc->autoloaded_already, sc->autoload_names_top * sizeof(bool *)); + for (s7_int i = sc->autoload_names_loc; i < sc->autoload_names_top; i++) + { + sc->autoload_names[i] = NULL; + sc->autoload_names_sizes[i] = 0; + sc->autoloaded_already[i] = NULL; + }} + sc->autoload_names[sc->autoload_names_loc] = names; + sc->autoload_names_sizes[sc->autoload_names_loc] = size; + sc->autoloaded_already[sc->autoload_names_loc] = (bool *)Calloc(size, sizeof(bool)); + sc->autoload_names_loc++; +} + +static const char *find_autoload_name(s7_scheme *sc, s7_pointer symbol, bool *already_loaded, bool loading) +{ + s7_int l = 0, libs = sc->autoload_names_loc; + const char *name = symbol_name(symbol); + for (s7_int lib = 0; lib < libs; lib++) + { + s7_int u = sc->autoload_names_sizes[lib] - 1; + const char **names = sc->autoload_names[lib]; + while (true) + { + s7_int comp, pos; + const char *this_name; + if (u < l) break; + pos = (l + u) / 2; + this_name = names[pos * 2]; + comp = strcmp(this_name, name); + if (comp == 0) + { + *already_loaded = sc->autoloaded_already[lib][pos]; + if (loading) sc->autoloaded_already[lib][pos] = true; + return(names[pos * 2 + 1]); /* file name given func name */ + } + if (comp < 0) + l = pos + 1; + else u = pos - 1; + }} + return(NULL); +} + +s7_pointer s7_autoload(s7_scheme *sc, s7_pointer symbol, s7_pointer file_or_function) +{ + /* add '(symbol . file) to s7's autoload table */ + if (is_null(sc->autoload_table)) + sc->autoload_table = s7_make_hash_table(sc, sc->default_hash_table_length); /* add_hash_table here, perhaps sc->hash_tables->loc-- */ + if (sc->safety >= MORE_SAFETY_WARNINGS) + { + const s7_pointer p = s7_hash_table_ref(sc, sc->autoload_table, symbol); + if ((p != sc->F) && (p != file_or_function)) + s7_warn(sc, 256, "'%s autoload value changed\n", symbol_name(symbol)); + } + s7_hash_table_set(sc, sc->autoload_table, symbol, file_or_function); + return(file_or_function); +} + +static s7_pointer g_autoload(s7_scheme *sc, s7_pointer args) +{ + #define H_autoload "(autoload symbol file-or-function) adds the symbol to its table of autoloadable symbols. \ +If that symbol is encountered as an unbound variable, s7 either loads the file (following *load-path*), or calls \ +the function. The function takes one argument, the calling environment. Presumably the symbol is defined \ +in the file, or by the function." + #define Q_autoload s7_make_signature(sc, 3, sc->T, sc->is_symbol_symbol, sc->T) + + s7_pointer sym = car(args), value; + if (is_string(sym)) + { + if (string_length(sym) == 0) /* (autoload "" ...) */ + wrong_type_error_nr(sc, sc->autoload_symbol, 1, sym, wrap_string(sc, "a symbol-name or a symbol", 25)); + sym = make_symbol(sc, string_value(sym), string_length(sym)); + } + if (!is_symbol(sym)) + { + check_method(sc, sym, sc->autoload_symbol, args); + wrong_type_error_nr(sc, sc->autoload_symbol, 1, sym, wrap_string(sc, "a string (symbol-name) or a symbol", 34)); + } + if (is_keyword(sym)) + wrong_type_error_nr(sc, sc->autoload_symbol, 1, sym, wrap_string(sc, "a normal symbol (a keyword is never unbound)", 44)); + + value = cadr(args); + if (is_string(value)) + return(s7_autoload(sc, sym, s7_immutable(make_string_with_length(sc, string_value(value), string_length(value))))); + if (((is_closure(value)) || (is_closure_star(value))) && + (s7_is_aritable(sc, value, 1))) + return(s7_autoload(sc, sym, value)); + + check_method(sc, value, sc->autoload_symbol, args); + wrong_type_error_nr(sc, sc->autoload_symbol, 2, value, wrap_string(sc, "a string (file-name) or a thunk", 31)); + return(NULL); /* make tcc happy */ +} + + +/* -------------------------------- *autoload* -------------------------------- */ +static s7_pointer g_autoloader(s7_scheme *sc, s7_pointer args) /* the *autoload* function */ +{ + #define H_autoloader "(*autoload* sym) returns the autoload info for the symbol sym, or #f." + #define Q_autoloader s7_make_signature(sc, 2, sc->T, sc->is_symbol_symbol) + + s7_pointer sym = car(args); + if (!is_symbol(sym)) + { + check_method(sc, sym, sc->autoloader_symbol, set_plist_1(sc, sym)); + wrong_type_error_nr(sc, wrap_string(sc, "*autoload*", 10), 1, sym, sc->type_names[T_SYMBOL]); + } + if (sc->autoload_names) + { + bool loaded = false; + const char *file = find_autoload_name(sc, sym, &loaded, false); + if (file) + return(s7_make_string(sc, file)); + } + if (is_hash_table(sc->autoload_table)) + return(s7_hash_table_ref(sc, sc->autoload_table, sym)); + return(sc->F); +} + + +/* ---------------- require ---------------- */ +static bool is_memq(const s7_pointer sym, s7_pointer lst) +{ + for (s7_pointer x = lst; is_pair(x); x = cdr(x)) + if (sym == car(x)) + return(true); + return(false); +} + +static s7_pointer g_require(s7_scheme *sc, s7_pointer args) +{ + #define H_require "(require symbol . symbols) loads each file associated with each symbol if it has not been loaded already.\ +The symbols refer to the argument to \"provide\". (require lint.scm)" + /* #define Q_require s7_make_circular_signature(sc, 1, 2, sc->T, sc->is_symbol_symbol) */ + + s7_gc_protect_via_stack(sc, args); + for (s7_pointer p = args; is_pair(p); p = cdr(p)) + { + s7_pointer sym; + if (is_symbol(car(p))) + sym = car(p); + else + if ((is_proper_quote(sc, car(p))) && + (is_symbol(cadar(p)))) + sym = cadar(p); + else + { + unstack(sc); + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_2(sc, wrap_string(sc, "require: ~S is not a symbol", 27), car(p))); + } + if ((!is_memq(sym, s7_symbol_value(sc, sc->features_symbol))) && + (sc->is_autoloading)) + { + s7_pointer f = g_autoloader(sc, set_plist_1(sc, sym)); + if (is_false(sc, f)) + { + unstack(sc); + error_nr(sc, sc->autoload_error_symbol, + set_elist_2(sc, wrap_string(sc, "require: no autoload info for ~S", 32), sym)); + } + if (hook_has_functions(sc->autoload_hook)) + s7_apply_function(sc, sc->autoload_hook, set_plist_2(sc, sym, f)); + if (is_string(f)) + s7_load_with_environment(sc, string_value(f), sc->curlet); + else + if (is_closure(f)) /* f should be a function of one argument, the current (calling) environment */ + s7_call(sc, f, set_ulist_1(sc, sc->curlet, sc->nil)); + }} + if (stack_top_op(sc) == OP_GC_PROTECT) unstack(sc); /* op_error_quit if load failed in scheme in Snd */ + return(sc->T); +} + + +/* ---------------- provided? ---------------- */ +static s7_pointer g_is_provided(s7_scheme *sc, s7_pointer args) +{ + #define H_is_provided "(provided? symbol) returns #t if symbol is a member of the *features* list" + #define Q_is_provided s7_make_signature(sc, 2, sc->is_boolean_symbol, sc->is_symbol_symbol) + + s7_pointer sym = car(args), topf, x; + if (!is_symbol(sym)) + return(method_or_bust_p(sc, sym, sc->is_provided_symbol, sc->type_names[T_SYMBOL])); + + /* here the *features* list is spread out (or can be anyway) along the curlet chain, + * so we need to travel back all the way to the top level checking each *features* list in turn. + * Since *features* grows via cons (newest first), we can stop the scan if we hit the shared + * top-level at least. + */ + topf = global_value(sc->features_symbol); + if (is_memq(sym, topf)) + return(sc->T); + + if (is_global(sc->features_symbol)) + return(sc->F); + for (x = sc->curlet; symbol_id(sc->features_symbol) < let_id(x); x = let_outlet(x)); + for (; is_let(x); x = let_outlet(x)) + for (s7_pointer y = let_slots(x); tis_slot(y); y = next_slot(y)) + if ((slot_symbol(y) == sc->features_symbol) && + (slot_value(y) != topf) && + (is_memq(sym, slot_value(y)))) + return(sc->T); + return(sc->F); +} + +bool s7_is_provided(s7_scheme *sc, const char *feature) +{ + return(is_memq(make_symbol_with_strlen(sc, feature), s7_symbol_value(sc, sc->features_symbol))); /* this goes from local outward */ +} + +static bool is_provided_b_7p(s7_scheme *sc, s7_pointer sym) +{ + if (!is_symbol(sym)) + return(method_or_bust_p(sc, sym, sc->is_provided_symbol, sc->type_names[T_SYMBOL]) != sc->F); + return(is_memq(sym, s7_symbol_value(sc, sc->features_symbol))); +} + + +/* ---------------- provide ---------------- */ +static s7_pointer c_provide(s7_scheme *sc, s7_pointer sym) +{ + /* this has to be relative to the curlet: (load file env) + * the things loaded are only present in env, and go away with it, so should not be in the global *features* list + */ + s7_pointer p; + if (!is_symbol(sym)) + return(method_or_bust_p(sc, sym, sc->provide_symbol, sc->type_names[T_SYMBOL])); + + if ((S7_DEBUGGING) && (sc->curlet == sc->rootlet)) fprintf(stderr, "%s[%d]: curlet==rootlet!\n", __func__, __LINE__); + if ((sc->curlet == sc->nil) || (sc->curlet == sc->shadow_rootlet)) + p = global_slot(sc->features_symbol); + else p = symbol_to_local_slot(sc, sc->features_symbol, sc->curlet); /* if sc->curlet is nil, this returns the global slot, else local slot */ + if ((is_slot(p)) && (is_immutable_slot(p))) + s7_warn(sc, 256, "provide: *features* is immutable!\n"); + else + { + s7_pointer lst = slot_value(s7_slot(sc, sc->features_symbol)); /* in either case, we want the current *features* list */ + if (p == sc->undefined) + add_slot_checked_with_id(sc, sc->curlet, sc->features_symbol, cons(sc, sym, lst)); + else + if ((!is_memq(sym, lst)) && (!is_memq(sym, slot_value(p)))) + slot_set_value(p, cons(sc, sym, slot_value(p))); + } + return(sym); +} + +static s7_pointer g_provide(s7_scheme *sc, s7_pointer args) +{ + #define H_provide "(provide symbol) adds symbol to the *features* list" + #define Q_provide s7_make_signature(sc, 2, sc->is_symbol_symbol, sc->is_symbol_symbol) + + if ((is_immutable(sc->curlet)) && + (sc->curlet != sc->nil)) + error_nr(sc, sc->immutable_error_symbol, + set_elist_2(sc, wrap_string(sc, "can't provide '~S (current environment is immutable)", 52), car(args))); + return(c_provide(sc, car(args))); +} + +void s7_provide(s7_scheme *sc, const char *feature) {c_provide(sc, make_symbol_with_strlen(sc, feature));} + + +static s7_pointer g_features_set(s7_scheme *sc, s7_pointer args) /* *features* setter */ +{ + s7_pointer nf = cadr(args); + if (is_null(nf)) + return(sc->nil); + if ((!is_pair(nf)) || + (s7_list_length(sc, nf) <= 0)) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "can't set *features* to ~S", 26), nf)); + for (s7_pointer p = nf; is_pair(p); p = cdr(p)) + if (!is_symbol(car(p))) + sole_arg_wrong_type_error_nr(sc, sc->features_symbol, car(p), sc->type_names[T_SYMBOL]); + return(nf); +} + +static s7_pointer g_libraries_set(s7_scheme *sc, s7_pointer args) /* *libraries* setter */ +{ + s7_pointer nf = cadr(args); + if (is_null(nf)) + return(sc->nil); + if ((!is_pair(nf)) || + (s7_list_length(sc, nf) <= 0)) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "can't set *libraries* to ~S", 27), nf)); + for (s7_pointer p = nf; is_pair(p); p = cdr(p)) + if ((!is_pair(car(p))) || + (!is_string(caar(p))) || + (!is_let(cdar(p)))) + sole_arg_wrong_type_error_nr(sc, sc->libraries_symbol, car(p), wrap_string(sc, "a list of conses of the form (string . let)", 43)); + return(nf); +} + + +/* -------------------------------- eval-string -------------------------------- */ +s7_pointer s7_eval_c_string_with_environment(s7_scheme *sc, const char *str, s7_pointer e) +{ + s7_pointer code, port, result; + TRACK(sc); + push_stack_direct(sc, OP_GC_PROTECT); /* not gc protection here, but restoration of original context */ + port = s7_open_input_string(sc, str); + code = s7_read(sc, port); + s7_close_input_port(sc, port); + result = s7_eval(sc, T_Ext(code), e); + if (unchecked_stack_top_op(sc) == OP_GC_PROTECT) unstack(sc); /* pop_stack(sc); */ + return(result); +} + +s7_pointer s7_eval_c_string(s7_scheme *sc, const char *str) {return(s7_eval_c_string_with_environment(sc, str, sc->nil));} + +static s7_pointer g_eval_string(s7_scheme *sc, s7_pointer args) +{ + #define H_eval_string "(eval-string str (let (curlet))) returns the result of evaluating the string str as Scheme code" + #define Q_eval_string s7_make_signature(sc, 3, sc->values_symbol, sc->is_string_symbol, sc->is_let_symbol) + + s7_pointer port, str = car(args); + if (!is_string(str)) + return(method_or_bust(sc, str, sc->eval_string_symbol, args, sc->type_names[T_STRING], 1)); + if (string_length(str) == 0) + return(sc->F); /* (eval-string "") -> #f */ + + if (is_not_null(cdr(args))) + { + s7_pointer e = cadr(args); + if (!is_let(e)) + wrong_type_error_nr(sc, sc->eval_string_symbol, 2, e, a_let_string); + set_curlet(sc, (e == sc->rootlet) ? sc->nil : e); + } + sc->temp3 = sc->args; /* see t101-aux-17.scm */ + push_stack(sc, OP_EVAL_STRING, args, sc->code); + port = open_and_protect_input_string(sc, str); + push_input_port(sc, port); + push_stack_op_let(sc, OP_READ_INTERNAL); + return(sc->F); /* I think this means that sc->value defaults to #f in op_eval_string below, so (eval-string "") mimics (eval) -> #f */ +} + +static s7_pointer eval_string_chooser(s7_scheme *sc, s7_pointer f, int32_t unused_args, s7_pointer expr, bool unused_ops) +{ + check_for_substring_temp(sc, expr); + return(f); +} + +static s7_pointer op_eval_string(s7_scheme *sc) +{ + while (s7_peek_char(sc, current_input_port(sc)) != eof_object) /* (eval-string "(+ 1 2) this is a mistake") */ + { + int32_t tk = token(sc); /* (eval-string "(+ 1 2) ; a comment (not a mistake)") */ + if (tk != TOKEN_EOF) + { + s7_pointer trail_data; + s7_int trail_len = port_data_size(current_input_port(sc)) - port_position(current_input_port(sc)) + 1; + if (trail_len > 32) trail_len = 32; + trail_data = make_string_with_length(sc, (const char *)(port_data(current_input_port(sc)) + port_position(current_input_port(sc)) - 1), trail_len); + s7_close_input_port(sc, current_input_port(sc)); + pop_input_port(sc); + error_nr(sc, sc->read_error_symbol, set_elist_2(sc, wrap_string(sc, "eval-string trailing junk: ~S", 29), trail_data)); + }} + s7_close_input_port(sc, current_input_port(sc)); + pop_input_port(sc); + sc->code = sc->value; + set_current_code(sc, sc->code); + return(NULL); +} + + +/* -------------------------------- call-with-input-string -------------------------------- */ +static s7_pointer call_with_input(s7_scheme *sc, s7_pointer port, s7_pointer args) +{ + s7_pointer p = cadr(args); + port_set_string_or_function(port, car(args)); + push_stack(sc, OP_UNWIND_INPUT, sc->unused, port); /* # here is a marker (needed) */ + push_stack(sc, OP_APPLY, list_1(sc, port), p); + return(sc->F); +} + +static s7_pointer g_call_with_input_string(s7_scheme *sc, s7_pointer args) +{ + #define H_call_with_input_string "(call-with-input-string str proc) opens a string port for str and applies proc to it" + #define Q_call_with_input_string sc->pl_sf + /* (call-with-input-string "44" (lambda (p) (+ 1 (read p)))) -> 45 */ + + s7_pointer str = car(args), proc = cadr(args); + if (!is_string(str)) + return(method_or_bust(sc, str, sc->call_with_input_string_symbol, args, sc->type_names[T_STRING], 1)); + + if (is_let(proc)) + check_method(sc, proc, sc->call_with_input_string_symbol, args); + + if (!s7_is_aritable(sc, proc, 1)) + wrong_type_error_nr(sc, sc->call_with_input_string_symbol, 2, proc, + wrap_string(sc, "a procedure of one argument (the port)", 38)); + if ((is_continuation(proc)) || (is_goto(proc))) + wrong_type_error_nr(sc, sc->call_with_input_string_symbol, 2, proc, a_normal_procedure_string); + return(call_with_input(sc, open_and_protect_input_string(sc, str), args)); +} + + +/* -------------------------------- call-with-input-file -------------------------------- */ +static s7_pointer g_call_with_input_file(s7_scheme *sc, s7_pointer args) +{ + #define H_call_with_input_file "(call-with-input-file filename proc) opens filename and calls proc with the input port as its argument" + #define Q_call_with_input_file sc->pl_sf + + s7_pointer str = car(args), proc = cadr(args); + if (!is_string(str)) + return(method_or_bust(sc, str, sc->call_with_input_file_symbol, args, sc->type_names[T_STRING], 1)); + + if (!s7_is_aritable(sc, proc, 1)) + wrong_type_error_nr(sc, sc->call_with_input_file_symbol, 2, proc, + wrap_string(sc, "a procedure of one argument (the port)", 38)); + if ((is_continuation(proc)) || (is_goto(proc))) + wrong_type_error_nr(sc, sc->call_with_input_file_symbol, 2, proc, a_normal_procedure_string); + return(call_with_input(sc, open_input_file_1(sc, string_value(str), "r", "call-with-input-file"), args)); +} + + +/* -------------------------------- with-input-from-string -------------------------------- */ +static s7_pointer with_input(s7_scheme *sc, s7_pointer port, s7_pointer args) +{ + s7_pointer old_input_port = current_input_port(sc); + set_current_input_port(sc, port); + port_set_string_or_function(port, car(args)); + push_stack(sc, OP_UNWIND_INPUT, old_input_port, port); + push_stack(sc, OP_APPLY, sc->nil, cadr(args)); + return(sc->F); +} + +static s7_pointer g_with_input_from_string(s7_scheme *sc, s7_pointer args) +{ + #define H_with_input_from_string "(with-input-from-string str thunk) opens str as the temporary current-input-port and calls thunk" + #define Q_with_input_from_string sc->pl_sf + + s7_pointer str = car(args); + if (!is_string(str)) + return(method_or_bust(sc, str, sc->with_input_from_string_symbol, args, sc->type_names[T_STRING], 1)); + if (cadr(args) == global_value(sc->read_symbol)) + { + if (string_length(str) == 0) + return(eof_object); + push_input_port(sc, current_input_port(sc)); + set_current_input_port(sc, open_and_protect_input_string(sc, str)); + port_set_string_or_function(current_input_port(sc), str); + push_stack(sc, OP_UNWIND_INPUT, sc->unused, current_input_port(sc)); + push_stack_op_let(sc, OP_READ_DONE); + push_stack_op_let(sc, OP_READ_INTERNAL); + return(current_input_port(sc)); + } + if (!is_thunk(sc, cadr(args))) + return(method_or_bust(sc, cadr(args), sc->with_input_from_string_symbol, args, a_thunk_string, 2)); + /* since the arguments are evaluated before we get here, we can get some confusing situations: + * (with-input-from-string "#x2.1" (read)) + * (read) -> whatever it can get from the current input port! + * ";with-input-from-string argument 2, #, is untyped but should be a thunk" + * (with-input-from-string "" (read-line)) -> hangs awaiting stdin input + * also this can't be split into wifs and wifs_read because we need the runtime value of 'read + */ + return(with_input(sc, open_and_protect_input_string(sc, str), args)); +} + + +/* -------------------------------- with-input-from-file -------------------------------- */ +static s7_pointer g_with_input_from_file(s7_scheme *sc, s7_pointer args) +{ + #define H_with_input_from_file "(with-input-from-file filename thunk) opens filename as the temporary current-input-port and calls thunk" + #define Q_with_input_from_file sc->pl_sf + + if (!is_string(car(args))) + return(method_or_bust(sc, car(args), sc->with_input_from_file_symbol, args, sc->type_names[T_STRING], 1)); + if (!is_thunk(sc, cadr(args))) + return(method_or_bust(sc, cadr(args), sc->with_input_from_file_symbol, args, a_thunk_string, 2)); + return(with_input(sc, open_input_file_1(sc, string_value(car(args)), "r", "with-input-from-file"), args)); +} + +static s7_pointer with_string_in(s7_scheme *sc, s7_pointer unused_args) +{ + s7_pointer old_port = current_input_port(sc); + set_current_input_port(sc, open_and_protect_input_string(sc, sc->value)); + push_stack(sc, OP_UNWIND_INPUT, old_port, current_input_port(sc)); + sc->curlet = inline_make_let(sc, sc->curlet); + return(opt2_pair(sc->code)); +} + +static s7_pointer with_file_in(s7_scheme *sc, s7_pointer unused_args) +{ + s7_pointer old_port = current_input_port(sc); + set_current_input_port(sc, open_input_file_1(sc, string_value(sc->value), "r", "with-input-from-file")); + push_stack(sc, OP_UNWIND_INPUT, old_port, current_input_port(sc)); + sc->curlet = inline_make_let(sc, sc->curlet); + return(opt2_pair(sc->code)); +} + +static s7_pointer with_file_out(s7_scheme *sc, s7_pointer unused_args) +{ + s7_pointer old_port = current_output_port(sc); + set_current_output_port(sc, s7_open_output_file(sc, string_value(sc->value), "w")); + push_stack(sc, OP_UNWIND_OUTPUT, old_port, current_output_port(sc)); + sc->curlet = make_let(sc, sc->curlet); + return(opt2_pair(sc->code)); +} + +static s7_pointer call_string_in(s7_scheme *sc, s7_pointer unused_args) +{ + s7_pointer port = open_and_protect_input_string(sc, sc->value); + push_stack(sc, OP_UNWIND_INPUT, sc->unused, port); + sc->curlet = inline_make_let_with_slot(sc, sc->curlet, opt3_sym(sc->code), port); + return(opt2_pair(sc->code)); +} + +static s7_pointer call_file_in(s7_scheme *sc, s7_pointer unused_args) +{ + s7_pointer port = open_input_file_1(sc, string_value(sc->value), "r", "with-input-from-file"); + push_stack(sc, OP_UNWIND_INPUT, sc->unused, port); + sc->curlet = inline_make_let_with_slot(sc, sc->curlet, opt3_sym(sc->code), port); + return(opt2_pair(sc->code)); +} + +static s7_pointer call_file_out(s7_scheme *sc, s7_pointer unused_args) +{ + s7_pointer port = s7_open_output_file(sc, string_value(sc->value), "w"); + push_stack(sc, OP_UNWIND_OUTPUT, sc->unused, port); + sc->curlet = make_let_with_slot(sc, sc->curlet, opt3_sym(sc->code), port); + return(opt2_pair(sc->code)); +} + +static s7_pointer c_function_name_to_symbol(s7_scheme *sc, s7_pointer f) +{ + if (!is_c_function(f)) /* c_function* uses c_sym slot for arg_names */ + return(make_symbol(sc, c_function_name(f), c_function_name_length(f))); + if (!c_function_symbol(f)) + c_function_symbol(f) = make_symbol(sc, c_function_name(f), c_function_name_length(f)); + return(c_function_symbol(f)); +} + +#define op_with_io_1(Sc) (((s7_function)(opt1(Sc->code, OPT1_ANY)))(Sc, Sc->nil)) +static s7_pointer op_lambda(s7_scheme *sc, s7_pointer code); + +static void op_with_io_1_method(s7_scheme *sc) +{ + s7_pointer lt = sc->value; + if (has_active_methods(sc, lt)) + { + s7_pointer method = car(sc->code); + if (is_c_function(method)) /* #_call-with-input-string et al */ + method = c_function_name_to_symbol(sc, method); + push_stack(sc, OP_GC_PROTECT, lt, sc->code); + sc->code = caddr(sc->code); + sc->value = op_lambda(sc, sc->code); /* don't unstack */ + sc->value = find_and_apply_method(sc, lt, method, list_2(sc, lt, sc->value)); + } + else + if (is_symbol(car(sc->code))) /* might be e.g. #_call-with-input-string so use c_function_name */ + wrong_type_error_nr(sc, car(sc->code), 1, lt, sc->type_names[T_STRING]); + else wrong_type_error_nr(sc, wrap_string(sc, c_function_name(car(sc->code)), c_function_name_length(car(sc->code))), 1, lt, sc->type_names[T_STRING]); +} + +static bool op_with_io_op(s7_scheme *sc) +{ + sc->value = cadr(sc->code); + if (is_string(sc->value)) + { + sc->code = op_with_io_1(sc); + return(false); + } + push_stack_no_args_direct(sc, OP_WITH_IO_1); + sc->code = sc->value; + return(true); +} + +static void op_with_output_to_string(s7_scheme *sc) +{ + s7_pointer old_port = current_output_port(sc); + set_current_output_port(sc, s7_open_output_string(sc)); + push_stack(sc, OP_UNWIND_OUTPUT, old_port, current_output_port(sc)); + sc->curlet = inline_make_let(sc, sc->curlet); + push_stack(sc, OP_GET_OUTPUT_STRING, old_port, current_output_port(sc)); + sc->code = opt2_pair(sc->code); +} + +static void op_call_with_output_string(s7_scheme *sc) +{ + s7_pointer port = s7_open_output_string(sc); + push_stack(sc, OP_UNWIND_OUTPUT, sc->unused, port); + sc->curlet = inline_make_let_with_slot(sc, sc->curlet, opt3_sym(sc->code), port); + push_stack(sc, OP_GET_OUTPUT_STRING, sc->unused, port); + sc->code = opt2_pair(sc->code); +} + + +/* -------------------------------- iterators -------------------------------- */ +#if S7_DEBUGGING +static s7_pointer titr_let(s7_scheme *sc, s7_pointer p, const char *func, int32_t line) +{ + if (!is_let(iterator_sequence(p))) + { + fprintf(stderr, "%s%s[%d]: let iterator sequence is %s%s\n", + bold_text, func, line, check_name(sc, unchecked_type(iterator_sequence(p))), unbold_text); + if (sc->stop_at_error) abort(); + } + return(p); +} + +static s7_pointer titr_pair(s7_scheme *sc, s7_pointer p, const char *func, int32_t line) +{ + if (!is_pair(iterator_sequence(p))) + { + fprintf(stderr, "%s%s[%d]: pair iterator sequence is %s%s\n", + bold_text, func, line, check_name(sc, unchecked_type(iterator_sequence(p))), unbold_text); + if (sc->stop_at_error) abort(); + } + return(p); +} + +static s7_pointer titr_hash(s7_scheme *sc, s7_pointer p, const char *func, int32_t line) +{ + if (!is_hash_table(iterator_sequence(p))) + { + fprintf(stderr, "%s%s[%d]: hash iterator sequence is %s%s\n", + bold_text, func, line, check_name(sc, unchecked_type(iterator_sequence(p))), unbold_text); + if (sc->stop_at_error) abort(); + } + return(p); +} + +static s7_pointer titr_len(s7_scheme *sc, s7_pointer p, const char *func, int32_t line) +{ + if ((is_hash_table(iterator_sequence(p))) || (is_pair(iterator_sequence(p)))) + { + fprintf(stderr, "%s%s[%d]: iterator length sequence is %s%s\n", + bold_text, func, line, check_name(sc, unchecked_type(iterator_sequence(p))), unbold_text); + if (sc->stop_at_error) abort(); + } + return(p); +} + +static s7_pointer titr_pos(s7_scheme *sc, s7_pointer p, const char *func, int32_t line) +{ + if (((is_let(iterator_sequence(p))) && (iterator_sequence(p) != sc->rootlet) && (iterator_sequence(p) != sc->s7_starlet)) || + (is_pair(iterator_sequence(p)))) + { + fprintf(stderr, "%s%s[%d]: iterator-position sequence is %s%s\n", + bold_text, func, line, check_name(sc, unchecked_type(iterator_sequence(p))), unbold_text); + if (sc->stop_at_error) abort(); + } + return(p); +} +#endif + + +/* -------------------------------- iterator? -------------------------------- */ +static s7_pointer g_is_iterator(s7_scheme *sc, s7_pointer args) +{ + #define H_is_iterator "(iterator? obj) returns #t if obj is an iterator." + #define Q_is_iterator sc->pl_bt + + s7_pointer x = car(args); + if (is_iterator(x)) return(sc->T); + /* closure itself is not an iterator: (let ((c1 (let ((+iterator+ #t) (a 0)) (lambda () (set! a (+ a 1)))))) (iterate c1)): error (a function not an iterator) */ + check_boolean_method(sc, is_iterator, sc->is_iterator_symbol, args); + return(sc->F); +} + +bool s7_is_iterator(s7_pointer obj) {return(is_iterator(obj));} + +static bool is_iterator_b_7p(s7_scheme *sc, s7_pointer obj) {return(g_is_iterator(sc, set_plist_1(sc, obj)) != sc->F);} + + +static s7_pointer iterator_copy(s7_scheme *sc, s7_pointer p) +{ + /* fields are obj cur [loc|lcur] [len|slow|hcur] next, but untangling them in debugging case is a pain */ + s7_pointer iter; + new_cell(sc, iter, T_ITERATOR | T_SAFE_PROCEDURE); + memcpy((void *)iter, (void *)p, sizeof(s7_cell)); /* picks up ITER_OK I hope */ + return(iter); +} + +static s7_pointer iterator_finished(s7_scheme *sc, s7_pointer iterator) {return(ITERATOR_END);} + +static s7_pointer iterator_quit(s7_pointer iterator) +{ + iterator_next(iterator) = iterator_finished; + clear_iter_ok(iterator); + return(ITERATOR_END); +} + +static s7_pointer let_iterate(s7_scheme *sc, s7_pointer iterator) +{ + s7_pointer p, slot = iterator_current_slot(iterator); + if (!tis_slot(slot)) + return(iterator_quit(iterator)); + iterator_set_current_slot(iterator, next_slot(slot)); + p = iterator_let_cons(iterator); + if (!p) + return(cons(sc, slot_symbol(slot), slot_value(slot))); + set_car(p, slot_symbol(slot)); + set_cdr(p, slot_value(slot)); + return(p); +} + +static s7_pointer rootlet_iterate(s7_scheme *sc, s7_pointer iterator) +{ + s7_pointer slot = iterator_current(iterator); + if (!is_slot(slot)) + return(iterator_quit(iterator)); + if (iterator_position(iterator) < sc->rootlet_entries) + { + iterator_position(iterator)++; + iterator_current(iterator) = rootlet_element(sc->rootlet, iterator_position(iterator)); + } + else iterator_current(iterator) = sc->nil; + return(cons(sc, slot_symbol(slot), slot_value(slot))); +} + +static s7_pointer hash_entry_to_cons(s7_scheme *sc, hash_entry_t *entry, s7_pointer p) +{ + if (!p) + return(cons(sc, hash_entry_key(entry), hash_entry_value(entry))); + set_car(p, hash_entry_key(entry)); + set_cdr(p, hash_entry_value(entry)); + return(p); +} + +static s7_pointer hash_table_iterate(s7_scheme *sc, s7_pointer iterator) +{ + s7_pointer table; + s7_int len; + hash_entry_t **elements; + hash_entry_t *lst = iterator_hash_current(iterator); + + if (lst) + { + iterator_hash_current(iterator) = hash_entry_next(lst); + return(hash_entry_to_cons(sc, lst, iterator_current(iterator))); + } + table = iterator_sequence(iterator); /* using iterator_length and hash_table_entries here was slightly slower */ + len = hash_table_mask(table) + 1; + elements = hash_table_elements(table); + + for (s7_int loc = iterator_position(iterator) + 1; loc < len; loc++) + { + hash_entry_t *x = elements[loc]; + if (x) + { + iterator_position(iterator) = loc; + iterator_hash_current(iterator) = hash_entry_next(x); + return(hash_entry_to_cons(sc, x, iterator_current(iterator))); + }} + if (is_weak_hash_table(table)) + { + clear_weak_hash_iterator(iterator); + weak_hash_iters(table)--; + } + return(iterator_quit(iterator)); +} + +static s7_pointer string_iterate(s7_scheme *sc, s7_pointer obj) +{ + if (iterator_position(obj) < iterator_length(obj)) + return(chars[(uint8_t)(string_value(iterator_sequence(obj))[iterator_position(obj)++])]); + return(iterator_quit(obj)); +} + +static s7_pointer byte_vector_iterate(s7_scheme *sc, s7_pointer obj) +{ + if (iterator_position(obj) < iterator_length(obj)) + return(small_int(byte_vector(iterator_sequence(obj), iterator_position(obj)++))); + return(iterator_quit(obj)); +} + +static s7_pointer float_vector_iterate(s7_scheme *sc, s7_pointer obj) +{ + if (iterator_position(obj) < iterator_length(obj)) + return(make_real(sc, float_vector(iterator_sequence(obj), iterator_position(obj)++))); + return(iterator_quit(obj)); +} + +static s7_pointer int_vector_iterate(s7_scheme *sc, s7_pointer obj) +{ + if (iterator_position(obj) < iterator_length(obj)) + return(make_integer(sc, int_vector(iterator_sequence(obj), iterator_position(obj)++))); + return(iterator_quit(obj)); +} + +static s7_pointer vector_iterate(s7_scheme *sc, s7_pointer obj) +{ + if (iterator_position(obj) < iterator_length(obj)) + return(vector_element(iterator_sequence(obj), iterator_position(obj)++)); + return(iterator_quit(obj)); +} + +static s7_pointer closure_iterate(s7_scheme *sc, s7_pointer obj) +{ + /* this can be confusing: below a hash-table is the "function", and a function is the "iterator" only because with-let exports +iterator+=#t -> infinite loop! + (with-let + (let ((+iterator+ #t)) + (lambda () #)) ; this works because a function has an associated let?? with-let first arg should be a let. + (for-each + (make-hash-table) ; (hash-table) -- ((hash-table) ()) is #f (not an error) + ;(vector 1) ; error: vector-ref second argument, (), is nil but should be an integer + ;(vector) ; error: for-each first argument #() called with 1 argument? + ;(list) ; for-each first argument, (), is nil but should be a procedure or something applicable + (lambda args args) ; function as iterator because local +iterator+ above is #t, never returns # (always () because iterator func takes no args) + ;(lambda (asd) ()) ; error: make-iterator argument, #, is a function but should be a thunk + )) + * similarly: + (with-let + (let ((+documentation+ "hiho")) (curlet)) + (define (f) 1) ; (define (f) "a string" 1) would return doc as "a string" + (display (documentation f)) (newline)) ; "hiho" -- should we block +documentation+ in with-let? + */ + s7_pointer result = s7_call(sc, iterator_sequence(obj), sc->nil); + /* this can't use s7_apply_function -- we need to catch the error handler's longjmp here */ + if (result == ITERATOR_END) + { + iterator_next(obj) = iterator_finished; + clear_iter_ok(obj); + } + return(result); +} + +static s7_pointer c_object_iterate(s7_scheme *sc, s7_pointer obj) +{ + s7_pointer result, p, cur; + if (iterator_position(obj) >= iterator_length(obj)) + return(iterator_quit(obj)); + p = iterator_sequence(obj); + cur = iterator_current(obj); + set_car(cur, p); + set_car(cdr(cur), make_integer(sc, iterator_position(obj))); + result = (*(c_object_ref(sc, p)))(sc, cur); /* used to save/restore sc->x|z here */ + iterator_position(obj)++; + if (result == ITERATOR_END) + { + iterator_next(obj) = iterator_finished; + clear_iter_ok(obj); + } + return(result); +} + +static s7_pointer pair_iterate_1(s7_scheme *sc, s7_pointer obj); +static s7_pointer pair_iterate(s7_scheme *sc, s7_pointer obj) +{ + s7_pointer result; + if (!is_pair(iterator_current(obj))) + return(iterator_quit(obj)); + result = car(iterator_current(obj)); + iterator_current(obj) = cdr(iterator_current(obj)); + if (iterator_current(obj) == iterator_slow(obj)) + iterator_current(obj) = sc->nil; + iterator_next(obj) = pair_iterate_1; + return(result); +} + +static s7_pointer pair_iterate_1(s7_scheme *sc, s7_pointer obj) +{ + s7_pointer result; + if (!is_pair(iterator_current(obj))) + return(iterator_quit(obj)); + result = car(iterator_current(obj)); + iterator_current(obj) = cdr(iterator_current(obj)); + if (iterator_current(obj) == iterator_slow(obj)) + iterator_current(obj) = sc->nil; + else iterator_set_slow(obj, cdr(iterator_slow(obj))); + iterator_next(obj) = pair_iterate; + return(result); +} + +static s7_pointer find_make_iterator_method(s7_scheme *sc, s7_pointer e, s7_pointer iter) +{ + s7_pointer func; + if ((has_active_methods(sc, e)) && + ((func = find_method_with_let(sc, e, sc->make_iterator_symbol)) != sc->undefined)) + { + s7_pointer it; + gc_protect_via_stack(sc, iter); + it = s7_apply_function(sc, func, set_plist_1(sc, e)); + unstack(sc); + if (!is_iterator(it)) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_2(sc, wrap_string(sc, "make-iterator method must return an iterator: ~S", 48), it)); + return(it); + } + return(NULL); +} + + +/* -------------------------------- make-iterator -------------------------------- */ +static s7_pointer funclet_entry(s7_scheme *sc, s7_pointer x, s7_pointer sym) +{ + if ((has_closure_let(x)) && (is_let(closure_let(x)))) + { + s7_pointer val = symbol_to_local_slot(sc, sym, closure_let(x)); + if ((!is_slot(val)) && (is_let(let_outlet(closure_let(x))))) + val = symbol_to_local_slot(sc, sym, let_outlet(closure_let(x))); + if (is_slot(val)) + return(slot_value(val)); + } + return(NULL); +} + +static bool is_iterable_closure(s7_scheme *sc, s7_pointer x) +{ + s7_pointer iter; + if (!is_thunk(sc, x)) + sole_arg_wrong_type_error_nr(sc, sc->make_iterator_symbol, x, a_thunk_string); + iter = funclet_entry(sc, x, sc->local_iterator_symbol); + return((iter) && (iter != sc->F)); +} + +static s7_pointer s7_starlet_make_iterator(s7_scheme *sc, s7_pointer iter); +static s7_int c_object_length_to_int(s7_scheme *sc, s7_pointer obj); + +s7_pointer s7_make_iterator(s7_scheme *sc, s7_pointer e) +{ + s7_pointer iter, p; + new_cell(sc, iter, T_ITERATOR | T_SAFE_PROCEDURE | T_ITER_OK); + iterator_sequence(iter) = e; + + if (is_pair(e)) /* by far the most common case */ + { + iterator_current(iter) = e; + iterator_next(iter) = pair_iterate; + iterator_set_slow(iter, e); + return(iter); + } + if (!is_let(e)) + iterator_position(iter) = 0; + + switch (type(e)) + { + case T_LET: + if (e == sc->rootlet) + { + iterator_current(iter) = rootlet_element(e, 0); /* unfortunately tricky -- let_iterate uses different fields */ + iterator_position(iter) = 0; + iterator_next(iter) = rootlet_iterate; + return(iter); + } + if (e == sc->s7_starlet) + return(s7_starlet_make_iterator(sc, iter)); + p = find_make_iterator_method(sc, e, iter); + if (p) {free_cell(sc, iter); return(p);} + iterator_set_current_slot(iter, let_slots(e)); + iterator_next(iter) = let_iterate; + iterator_let_cons(iter) = NULL; + break; + + case T_HASH_TABLE: + iterator_hash_current(iter) = NULL; + iterator_current(iter) = NULL; + iterator_position(iter) = -1; + iterator_next(iter) = hash_table_iterate; + if (is_weak_hash_table(e)) + { + set_weak_hash_iterator(iter); + weak_hash_iters(e)++; + add_weak_hash_iterator(sc, iter); + } + break; + + case T_STRING: + iterator_length(iter) = string_length(e); + iterator_next(iter) = string_iterate; + break; + + case T_BYTE_VECTOR: + iterator_length(iter) = byte_vector_length(e); + iterator_next(iter) = byte_vector_iterate; + break; + + case T_VECTOR: + iterator_length(iter) = vector_length(e); + iterator_next(iter) = vector_iterate; + break; + + case T_INT_VECTOR: + iterator_length(iter) = vector_length(e); + iterator_next(iter) = int_vector_iterate; + break; + + case T_FLOAT_VECTOR: + iterator_length(iter) = vector_length(e); + iterator_next(iter) = float_vector_iterate; + break; + + case T_NIL: /* (make-iterator #()) -> #, so I guess () should also work */ + iterator_length(iter) = 0; + iterator_next(iter) = iterator_finished; + clear_iter_ok(iter); + break; + + case T_MACRO: case T_MACRO_STAR: + case T_BACRO: case T_BACRO_STAR: + case T_CLOSURE: case T_CLOSURE_STAR: + if (is_iterable_closure(sc, e)) + { + p = list_1_unchecked(sc, int_zero); + iterator_current(iter) = p; + set_mark_seq(iter); + iterator_next(iter) = closure_iterate; + iterator_length(iter) = (has_active_methods(sc, e)) ? closure_length(sc, e) : S7_INT64_MAX; + } + else + { + free_cell(sc, iter); + sole_arg_wrong_type_error_nr(sc, sc->make_iterator_symbol, e, + wrap_string(sc, "a function or macro with a '+iterator+ local that is not #f", 59)); + } + break; + + case T_C_OBJECT: + iterator_length(iter) = c_object_length_to_int(sc, e); + p = find_make_iterator_method(sc, e, iter); + if (p) {free_cell(sc, iter); return(p);} + iterator_current(iter) = list_2_unchecked(sc, e, int_zero); /* if not unchecked, gc protect iter */ + set_mark_seq(iter); + iterator_next(iter) = c_object_iterate; + break; + + default: + free_cell(sc, iter); /* 19-Mar-22 */ + sole_arg_wrong_type_error_nr(sc, sc->make_iterator_symbol, e, a_sequence_string); + } + return(iter); +} + +static s7_pointer g_make_iterator(s7_scheme *sc, s7_pointer args) +{ + #define H_make_iterator "(make-iterator sequence carrier) returns an iterator object that returns the next value \ +in the sequence each time it is called. When it reaches the end, it returns " ITERATOR_END_NAME "." + #define Q_make_iterator s7_make_signature(sc, 3, sc->is_iterator_symbol, sc->is_sequence_symbol, sc->is_pair_symbol) + + /* we need to call s7_make_iterator before fixing up the optional second arg in case let->method */ + s7_pointer seq = car(args); + s7_pointer carrier = (is_pair(cdr(args))) ? cadr(args) : NULL; + s7_pointer iter = s7_make_iterator(sc, seq); + + if (carrier) + { + if (!is_pair(carrier)) + sole_arg_wrong_type_error_nr(sc, sc->make_iterator_symbol, carrier, sc->type_names[T_PAIR]); + if (is_immutable_pair(carrier)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->make_iterator_symbol, carrier)); + + if (is_hash_table(iterator_sequence(iter))) + { + iterator_current(iter) = carrier; + set_mark_seq(iter); + } + else + if ((is_let(iterator_sequence(iter))) && + (iterator_sequence(iter) != sc->rootlet)) + { + iterator_let_cons(iter) = carrier; + set_mark_seq(iter); + }} + return(iter); +} + + +/* -------------------------------- iterate -------------------------------- */ +static s7_pointer g_iterate(s7_scheme *sc, s7_pointer args) +{ + #define H_iterate "(iterate obj) returns the next element from the iterator obj, or " ITERATOR_END_NAME "." + #define Q_iterate s7_make_signature(sc, 2, sc->T, sc->is_iterator_symbol) + s7_pointer iter = car(args); + if (!is_iterator(iter)) + return(sole_arg_method_or_bust(sc, iter, sc->iterate_symbol, args, sc->type_names[T_ITERATOR])); + return((iterator_next(iter))(sc, iter)); +} + +static s7_pointer iterate_p_p(s7_scheme *sc, s7_pointer iter) +{ + if (!is_iterator(iter)) + return(method_or_bust_p(sc, iter, sc->iterate_symbol, sc->type_names[T_ITERATOR])); + return((iterator_next(iter))(sc, iter)); +} + +s7_pointer s7_iterate(s7_scheme *sc, s7_pointer obj) {return((iterator_next(obj))(sc, obj));} + +bool s7_iterator_is_at_end(s7_scheme *sc, s7_pointer obj) +{ + if (!is_iterator(obj)) + sole_arg_wrong_type_error_nr(sc, sc->iterator_is_at_end_symbol, obj, sc->type_names[T_ITERATOR]); + return(!iter_ok(obj)); +} + +static bool op_implicit_iterate(s7_scheme *sc) +{ + s7_pointer s = lookup_checked(sc, car(sc->code)); + if (!is_iterator(s)) {sc->last_function = s; return(false);} + sc->value = (iterator_next(s))(sc, s); + return(true); +} + + +/* -------------------------------- iterator-at-end? -------------------------------- */ +static bool iterator_is_at_end_b_7p(s7_scheme *sc, s7_pointer obj) +{ + if (!is_iterator(obj)) + sole_arg_wrong_type_error_nr(sc, sc->iterator_is_at_end_symbol, obj, sc->type_names[T_ITERATOR]); + return(!iter_ok(obj)); +} + +static s7_pointer g_iterator_is_at_end(s7_scheme *sc, s7_pointer args) +{ + #define H_iterator_is_at_end "(iterator-at-end? iter) returns #t if the iterator has reached the end of its sequence." + #define Q_iterator_is_at_end s7_make_signature(sc, 2, sc->is_boolean_symbol, sc->is_iterator_symbol) + s7_pointer iter = car(args); + if (!is_iterator(iter)) + return(sole_arg_method_or_bust(sc, iter, sc->iterator_is_at_end_symbol, args, sc->type_names[T_ITERATOR])); + return(make_boolean(sc, !iter_ok(iter))); +} + + +/* -------------------------------- iterator-sequence -------------------------------- */ +static s7_pointer g_iterator_sequence(s7_scheme *sc, s7_pointer args) +{ + #define H_iterator_sequence "(iterator-sequence iterator) returns the sequence that iterator is traversing." + #define Q_iterator_sequence s7_make_signature(sc, 2, sc->is_sequence_symbol, sc->is_iterator_symbol) + s7_pointer iter = car(args); + if (!is_iterator(iter)) + return(sole_arg_method_or_bust(sc, iter, sc->iterator_sequence_symbol, args, sc->type_names[T_ITERATOR])); + return(iterator_sequence(iter)); +} + + +/* -------- cycles -------- */ + +#define INITIAL_SHARED_INFO_SIZE 8 + +static int32_t shared_ref(shared_info_t *ci, const s7_pointer p) +{ + /* from print after collecting refs, not called by equality check, only called in object_to_port_with_circle_check_1 */ + s7_pointer *objs = ci->objs; + for (int32_t i = 0; i < ci->top; i++) + if (objs[i] == p) + { + int32_t val = ci->refs[i]; + if (val > 0) + ci->refs[i] = -ci->refs[i]; + return(val); + } + return(0); +} + +static void flip_ref(shared_info_t *ci, const s7_pointer p) +{ + s7_pointer *objs = ci->objs; + for (int32_t i = 0; i < ci->top; i++) + if (objs[i] == p) + { + ci->refs[i] = -ci->refs[i]; + break; + } +} + +static int32_t peek_shared_ref_1(shared_info_t *ci, const s7_pointer p) +{ + /* returns 0 if not found, otherwise the ref value for p */ + s7_pointer *objs = ci->objs; + for (int32_t i = 0; i < ci->top; i++) + if (objs[i] == p) + return(ci->refs[i]); + return(0); +} + +static int32_t peek_shared_ref(shared_info_t *ci, s7_pointer p) +{ + /* returns 0 if not found, otherwise the ref value for p */ + return((is_collected_unchecked(p)) ? peek_shared_ref_1(ci, p) : 0); +} + +static void enlarge_shared_info(shared_info_t *ci) +{ + ci->size *= 2; + ci->size2 = ci->size - 2; + ci->objs = (s7_pointer *)Realloc(ci->objs, ci->size * sizeof(s7_pointer)); + ci->refs = (int32_t *)Realloc(ci->refs, ci->size * sizeof(int32_t)); + ci->defined = (bool *)Realloc(ci->defined, ci->size * sizeof(bool)); + /* this clearing is needed, memclr is not faster */ + for (int32_t i = ci->top; i < ci->size; i++) + { + ci->refs[i] = 0; + ci->objs[i] = NULL; + } +} + +static bool check_collected(s7_pointer top, shared_info_t *ci) +{ + s7_pointer *objs_end = (s7_pointer *)(ci->objs + ci->top); + for (s7_pointer *p = ci->objs; p < objs_end; p++) + if ((*p) == top) + { + int32_t i = (int32_t)(p - ci->objs); + if (ci->refs[i] == 0) + { + ci->has_hits = true; + ci->refs[i] = ++ci->ref; /* if found, set the ref number */ + } + break; + } + set_cyclic(top); + return(true); +} + +static bool collect_shared_info(s7_scheme *sc, shared_info_t *ci, s7_pointer top, bool stop_at_print_length); +static bool hash_keys_not_cyclic(s7_scheme *sc, s7_pointer hash); + +static bool collect_vector_info(s7_scheme *sc, shared_info_t *ci, s7_pointer top, bool stop_at_print_length) +{ + s7_int plen; + bool cyclic = false; + + if (stop_at_print_length) + { + plen = sc->print_length; + if (plen > vector_length(top)) + plen = vector_length(top); + } + else plen = vector_length(top); + + for (s7_int i = 0; i < plen; i++) + { + s7_pointer vel = unchecked_vector_element(top, i); /* "unchecked" because top might be rootlet, I think */ + if ((has_structure(vel)) && + (collect_shared_info(sc, ci, vel, stop_at_print_length))) + { + set_cyclic(vel); + cyclic = true; + if ((is_c_pointer(vel)) || + (is_iterator(vel)) || + (is_c_object(vel))) + check_collected(top, ci); + }} + if (cyclic) set_cyclic(top); + return(cyclic); +} + +static bool collect_shared_info(s7_scheme *sc, shared_info_t *ci, s7_pointer top, bool stop_at_print_length) +{ + /* look for top in current list. + * As we collect objects (guaranteed to have structure) we set the collected bit. If we ever + * encounter an object with that bit on, we've seen it before so we have a possible cycle. + * Once the collection pass is done, we run through our list, and clear all these bits. + */ + bool top_cyclic; + + if (is_collected_or_shared(top)) + return((!is_shared(top)) && (check_collected(top, ci))); + + /* top not seen before -- add it to the list */ + set_collected(top); + + if (ci->top == ci->size) + enlarge_shared_info(ci); + ci->objs[ci->top++] = top; + + top_cyclic = false; + /* now search the rest of this structure */ + if (is_pair(top)) + { + s7_pointer p; + if ((has_structure(car(top))) && + (collect_shared_info(sc, ci, car(top), stop_at_print_length))) + top_cyclic = true; + + for (p = cdr(top); is_pair(p); p = cdr(p)) + { + if (is_collected_or_shared(p)) + { + set_cyclic(top); + set_cyclic(p); + if (!is_shared(p)) + return(check_collected(p, ci)); + if (!top_cyclic) + for (s7_pointer cp = top; cp != p; cp = cdr(cp)) set_shared(cp); + return(top_cyclic); + } + set_collected(p); + if (ci->top == ci->size) + enlarge_shared_info(ci); + ci->objs[ci->top++] = p; + if ((has_structure(car(p))) && + (collect_shared_info(sc, ci, car(p), stop_at_print_length))) + top_cyclic = true; + } + if ((has_structure(p)) && + (collect_shared_info(sc, ci, p, stop_at_print_length))) + { + set_cyclic(top); + return(true); + } + if (!top_cyclic) + for (s7_pointer cp = top; is_pair(cp); cp = cdr(cp)) set_shared(cp); + else set_cyclic(top); + return(top_cyclic); + } + + switch (type(top)) + { + case T_VECTOR: + if (collect_vector_info(sc, ci, top, stop_at_print_length)) + top_cyclic = true; + break; + + case T_ITERATOR: + if ((is_sequence(iterator_sequence(top))) && /* might be a function with +iterator+ local */ + (collect_shared_info(sc, ci, iterator_sequence(top), stop_at_print_length))) + { + if (peek_shared_ref(ci, iterator_sequence(top)) == 0) + check_collected(iterator_sequence(top), ci); + top_cyclic = true; + } + break; + + case T_HASH_TABLE: + if (hash_table_entries(top) > 0) + { + s7_int len = hash_table_mask(top) + 1; + hash_entry_t **entries = hash_table_elements(top); + bool keys_safe = hash_keys_not_cyclic(sc, top); + for (s7_int i = 0; i < len; i++) + for (hash_entry_t *p = entries[i]; p; p = hash_entry_next(p)) + { + if ((!keys_safe) && + (has_structure(hash_entry_key(p))) && + (collect_shared_info(sc, ci, hash_entry_key(p), stop_at_print_length))) + top_cyclic = true; + if ((has_structure(hash_entry_value(p))) && + (collect_shared_info(sc, ci, hash_entry_value(p), stop_at_print_length))) + { + if ((is_c_pointer(hash_entry_value(p))) || + (is_iterator(hash_entry_value(p))) || + (is_c_object(hash_entry_value(p)))) + check_collected(top, ci); + top_cyclic = true; + }}} + break; + + case T_SLOT: /* this can be hit if we somehow collect_shared_info on sc->rootlet via collect_vector_info (see the let case below) */ + if ((has_structure(slot_value(top))) && + (collect_shared_info(sc, ci, slot_value(top), stop_at_print_length))) + top_cyclic = true; + break; + + case T_LET: + if (top == sc->rootlet) + { + if (collect_vector_info(sc, ci, top, stop_at_print_length)) + top_cyclic = true; + } + else + for (s7_pointer q = top; is_let(q) && (q != sc->rootlet); q = let_outlet(q)) + for (s7_pointer p = let_slots(q); tis_slot(p); p = next_slot(p)) + if ((has_structure(slot_value(p))) && + (collect_shared_info(sc, ci, slot_value(p), stop_at_print_length))) + { + top_cyclic = true; + if ((is_c_pointer(slot_value(p))) || + (is_iterator(slot_value(p))) || + (is_c_object(slot_value(p)))) + check_collected(top, ci); + } + break; + + case T_CLOSURE: + case T_CLOSURE_STAR: + if (collect_shared_info(sc, ci, closure_body(top), stop_at_print_length)) + { + if (peek_shared_ref(ci, top) == 0) + check_collected(top, ci); + top_cyclic = true; + } + break; + + case T_C_POINTER: + if ((has_structure(c_pointer_type(top))) && + (collect_shared_info(sc, ci, c_pointer_type(top), stop_at_print_length))) + { + if (peek_shared_ref(ci, c_pointer_type(top)) == 0) + check_collected(c_pointer_type(top), ci); + top_cyclic = true; + } + if ((has_structure(c_pointer_info(top))) && + (collect_shared_info(sc, ci, c_pointer_info(top), stop_at_print_length))) + { + if (peek_shared_ref(ci, c_pointer_info(top)) == 0) + check_collected(c_pointer_info(top), ci); + top_cyclic = true; + } + break; + + case T_C_OBJECT: + if ((c_object_to_list(sc, top)) && + (c_object_set(sc, top)) && + (collect_shared_info(sc, ci, (*(c_object_to_list(sc, top)))(sc, set_plist_1(sc, top)), stop_at_print_length))) + { + if (peek_shared_ref(ci, top) == 0) + check_collected(top, ci); + top_cyclic = true; + } + break; + } + if (!top_cyclic) + set_shared(top); + else set_cyclic(top); + return(top_cyclic); +} + +static shared_info_t *init_circle_info(s7_scheme *sc) +{ + shared_info_t *ci = (shared_info_t *)Calloc(1, sizeof(shared_info_t)); + ci->size = INITIAL_SHARED_INFO_SIZE; + ci->size2 = ci->size - 2; + ci->objs = (s7_pointer *)Malloc(ci->size * sizeof(s7_pointer)); + ci->refs = (int32_t *)Calloc(ci->size, sizeof(int32_t)); /* finder expects 0 = unseen previously */ + ci->defined = (bool *)Calloc(ci->size, sizeof(bool)); + ci->cycle_port = sc->F; + ci->init_port = sc->F; + return(ci); +} + +static inline shared_info_t *new_shared_info(s7_scheme *sc) +{ + shared_info_t *ci = sc->circle_info; + if (ci->top > 0) + { + memclr((void *)(ci->refs), ci->top * sizeof(int32_t)); + memclr((void *)(ci->defined), ci->top * sizeof(bool)); + for (int32_t i = 0; i < ci->top; i++) + clear_cyclic_bits(ci->objs[i]); /* LOOP_4 is not faster */ + ci->top = 0; + } + ci->ref = 0; + ci->has_hits = false; + ci->ctr = 0; + return(ci); +} + +static shared_info_t *make_shared_info(s7_scheme *sc, s7_pointer top, bool stop_at_print_length) +{ + /* for the printer, here only if is_structure(top) and top is not sc->rootlet */ + bool no_problem = true; + s7_int k, stop_len; + + /* check for simple cases first */ + if (is_pair(top)) + { + s7_pointer x = top; + if (stop_at_print_length) + { + s7_pointer slow = top; + stop_len = sc->print_length; + for (k = 0; k < stop_len; k += 2) + { + if (!is_pair(x)) break; + if (has_structure(car(x))) {no_problem = false; break;} + x = cdr(x); + if (!is_pair(x)) break; + if (has_structure(car(x))) {no_problem = false; break;} + x = cdr(x); + slow = cdr(slow); + if (x == slow) {no_problem = false; break;} + }} + else + if (s7_list_length(sc, top) == 0) /* it is circular at the top level (following cdr) */ + no_problem = false; + else + for (; is_pair(x); x = cdr(x)) + if (has_structure(car(x))) {no_problem = false; break;} /* perhaps (and (length > 0)) or vector typer etc */ + if ((no_problem) && + (!is_null(x)) && (has_structure(x))) + no_problem = false; + if (no_problem) return(NULL); + } + else + if (is_normal_vector(top)) /* any other vector can't happen */ + { + stop_len = vector_length(top); + if ((stop_at_print_length) && + (stop_len > sc->print_length)) + stop_len = sc->print_length; + for (k = 0; k < stop_len; k++) + if (has_structure(vector_element(top, k))) {no_problem = false; break;} + if (no_problem) return(NULL); + } +#if 1 + else /* added these 19-Oct-22 -- helps in tgc, but not much elsewhere */ + if ((is_let(top)) && (top != sc->rootlet)) + { + for (s7_pointer lp = top; (no_problem) && (is_let(lp)) && (lp != sc->rootlet); lp = let_outlet(lp)) + for (s7_pointer p = let_slots(lp); tis_slot(p); p = next_slot(p)) + if (has_structure(slot_value(p))) /* slot_symbol need not be checked? */ + {no_problem = false; break;} + if (no_problem) return(NULL); + } + else + if (is_hash_table(top)) + { + s7_int len = hash_table_mask(top) + 1; + hash_entry_t **entries = hash_table_elements(top); + bool keys_safe = hash_keys_not_cyclic(sc, top); + if (hash_table_entries(top) == 0) return(NULL); + for (s7_int i = 0; i < len; i++) + for (hash_entry_t *p = entries[i]; p; p = hash_entry_next(p)) + if (((!keys_safe) && (has_structure(hash_entry_key(p)))) || + (has_structure(hash_entry_value(p)))) + {no_problem = false; break;} + if (no_problem) return(NULL); + } +#endif + if ((S7_DEBUGGING) && (is_any_vector(top)) && (!is_normal_vector(top))) fprintf(stderr, "%s[%d]: got abnormal vector\n", __func__, __LINE__); + + { + shared_info_t *ci = new_shared_info(sc); + /* collect all pointers associated with top */ + bool cyclic = collect_shared_info(sc, ci, top, stop_at_print_length); + s7_pointer *ci_objs = ci->objs; + int32_t *ci_refs = ci->refs; + int32_t refs = 0; + + for (int32_t i = 0; i < ci->top; i++) + clear_collected_and_shared(ci_objs[i]); + + if (!cyclic) + return(NULL); + if (!(ci->has_hits)) + return(NULL); + + /* find if any were referenced twice (once for just being there, so twice=shared) + * we know there's at least one such reference because has_hits is true. + */ + for (int32_t i = 0; i < ci->top; i++) + if (ci_refs[i] > 0) + { + set_collected(ci_objs[i]); + if (i == refs) + refs++; + else + { + ci_objs[refs] = ci_objs[i]; + ci_refs[refs++] = ci_refs[i]; + ci_refs[i] = 0; + ci_objs[i] = NULL; + }} + ci->top = refs; + return(ci); + } +} + + +/* -------------------------------- cyclic-sequences -------------------------------- */ +static s7_pointer cyclic_sequences_p_p(s7_scheme *sc, s7_pointer obj) +{ + if (has_structure(obj)) + { + shared_info_t *ci = (sc->object_out_locked) ? sc->circle_info : make_shared_info(sc, obj, false); /* false=don't stop at print length (vectors etc) */ + if (ci) + { + s7_pointer lst; + sc->w = sc->nil; + check_free_heap_size(sc, ci->top); + for (int32_t i = 0; i < ci->top; i++) + sc->w = cons_unchecked(sc, ci->objs[i], sc->w); + lst = sc->w; + sc->w = sc->unused; + return(lst); + }} + return(sc->nil); +} + +static s7_pointer g_cyclic_sequences(s7_scheme *sc, s7_pointer args) +{ + #define H_cyclic_sequences "(cyclic-sequences obj) returns a list of elements that are cyclic." + #define Q_cyclic_sequences s7_make_signature(sc, 2, sc->is_proper_list_symbol, sc->T) + return(cyclic_sequences_p_p(sc, car(args))); +} + + +/* -------------------------------- object->port (display format etc) -------------------------------- */ +static int32_t circular_list_entries(s7_pointer lst) +{ + int32_t i = 1; + for (s7_pointer x = cdr(lst); ; i++, x = cdr(x)) + { + int32_t j = 0; + for (s7_pointer y = lst; j < i; y = cdr(y), j++) + if (x == y) + return(i); + } +} + +static void object_to_port_with_circle_check_1(s7_scheme *sc, s7_pointer vr, s7_pointer port, use_write_t use_write, shared_info_t *ci); +#define object_to_port_with_circle_check(Sc, Vr, Port, Use_Write, Ci) \ + do { \ + s7_pointer _V_ = Vr; \ + if ((Ci) && (has_structure(_V_))) \ + object_to_port_with_circle_check_1(Sc, _V_, Port, Use_Write, Ci); \ + else object_to_port(Sc, _V_, Port, Use_Write, Ci); \ + } while (0) + +static void (*display_functions[256])(s7_scheme *sc, s7_pointer obj, s7_pointer port, use_write_t use_write, shared_info_t *ci); +#define object_to_port(Sc, Obj, Port, Use_Write, Ci) (*display_functions[unchecked_type(Obj)])(Sc, Obj, Port, Use_Write, Ci) + +static bool string_needs_slashification(const uint8_t *str, s7_int len) +{ + /* we have to go by len (str len) not *s==0 because s7 strings can have embedded nulls */ + const uint8_t *pend = (const uint8_t *)(str + len); + for (const uint8_t *p = str; p < pend; p++) + if (slashify_table[*p]) + return(true); + return(false); +} + +#define IN_QUOTES true +#define NOT_IN_QUOTES false + +static void slashify_string_to_port(s7_scheme *sc, s7_pointer port, const char *p, s7_int len, bool quoted) +{ + const uint8_t *pcur, *pend, *pstart = NULL; + if (len == 0) + { + if (quoted) + port_write_string(port)(sc, "\"\"", 2, port); + return; + } + pend = (const uint8_t *)(p + len); + + /* what about the trailing nulls? Guile writes them out (as does s7 currently) + * but that is not ideal. I'd like to use ~S for error messages, so that + * strings are clearly identified via the double-quotes, but this way of + * writing them is ugly: + * (let ((str (make-string 8 #\null))) (set! (str 0) #\a) str) -> "a\x00\x00\x00\x00\x00\x00\x00" + * but it would be misleading to omit them because: + * (let ((str (make-string 8 #\null))) (set! (str 0) #\a) (string-append str "bc")) -> "a\x00\x00\x00\x00\x00\x00\x00bc" + * also it is problematic to use sc->print_length here (rather than a separate string-print-length) because + * it is normally (say) 12 which truncates just about every string. In CL, *print-length* + * does not affect strings, symbols, or bit-vectors. But if the string is enormous, + * this function can bring us to a complete halt. string-print-length (as a *s7* field) is + * also problematic -- it does not behave as expected in many cases if it is limited to this + * function and string_to_port below, and if set too low, disables the repl. + */ + if (quoted) port_write_character(port)(sc, '"', port); + for (pcur = (const uint8_t *)p; pcur < pend; pcur++) + if (slashify_table[*pcur]) + { + if (pstart) pstart++; else pstart = (const uint8_t *)p; + if (pstart != pcur) + { + port_write_string(port)(sc, (const char *)pstart, pcur - pstart, port); + pstart = pcur; + } + port_write_character(port)(sc, '\\', port); + switch (*pcur) + { + case '"': port_write_character(port)(sc, '"', port); break; + case '\\': port_write_character(port)(sc, '\\', port); break; + case '\'': port_write_character(port)(sc, '\'', port); break; + case '\t': port_write_character(port)(sc, 't', port); break; + case '\r': port_write_character(port)(sc, 'r', port); break; + case '\b': port_write_character(port)(sc, 'b', port); break; + case '\f': port_write_character(port)(sc, 'f', port); break; + case '\?': port_write_character(port)(sc, '?', port); break; + case 'x': port_write_character(port)(sc, 'x', port); break; + default: + { + char buf[5]; + s7_int n = (s7_int)(*pcur); + buf[0] = 'x'; + buf[1] = (n < 16) ? '0' : dignum[(n / 16) % 16]; + buf[2] = dignum[n % 16]; + buf[3] = ';'; + buf[4] = '\0'; + port_write_string(port)(sc, buf, 4, port); + } + break; + }} + if (!pstart) + port_write_string(port)(sc, (const char *)p, len, port); + else + { + pstart++; + if (pstart != pcur) + port_write_string(port)(sc, (const char *)pstart, pcur - pstart, port); + } + if (quoted) port_write_character(port)(sc, '"', port); +} + +static void output_port_to_port(s7_scheme *sc, s7_pointer obj, s7_pointer port, use_write_t use_write, shared_info_t *unused_ci) +{ + if ((obj == sc->standard_output) || + (obj == sc->standard_error)) + port_write_string(port)(sc, port_filename(obj), port_filename_length(obj), port); + else + if (use_write == P_READABLE) + { + if (port_is_closed(obj)) + port_write_string(port)(sc, "(let ((p (open-output-string))) (close-output-port p) p)", 56, port); + else + if (is_string_port(obj)) + { + port_write_string(port)(sc, "(let ((p (open-output-string)))", 31, port); + if (port_position(obj) > 0) + { + port_write_string(port)(sc, " (display ", 10, port); + slashify_string_to_port(sc, port, (const char *)port_data(obj), port_position(obj), IN_QUOTES); + port_write_string(port)(sc, " p)", 3, port); + } + port_write_string(port)(sc, " p)", 3, port); + } + else + if (is_file_port(obj)) + { + char str[256]; + int32_t nlen; + str[0] = '\0'; + nlen = (int32_t)catstrs(str, 256, "(open-output-file \"", port_filename(obj), "\" \"a\")", (char *)NULL); + port_write_string(port)(sc, str, nlen, port); + } + else port_write_string(port)(sc, "#", 23, port); + } + else + { + if (is_string_port(obj)) + port_write_string(port)(sc, "#", 8, port); + else port_write_character(port)(sc, '>', port); + } +} + +static void input_port_to_port(s7_scheme *sc, s7_pointer obj, s7_pointer port, use_write_t use_write, shared_info_t *unused_ci) +{ + if (obj == sc->standard_input) + port_write_string(port)(sc, port_filename(obj), port_filename_length(obj), port); + else + if (use_write == P_READABLE) + { + if (port_is_closed(obj)) + port_write_string(port)(sc, "(call-with-input-string \"\" (lambda (p) p))", 42, port); + else + if (is_function_port(obj)) + port_write_string(port)(sc, "#", 22, port); + else + if (is_file_port(obj)) + { + char str[256]; + int32_t nlen; + str[0] = '\0'; + nlen = (int32_t)catstrs(str, 256, "(open-input-file \"", port_filename(obj), "\")", (char *)NULL); + port_write_string(port)(sc, str, nlen, port); + } + else + { + s7_int data_len = port_data_size(obj) - port_position(obj); + if (data_len > 100) + { + const char *filename = (const char *)s7_port_filename(sc, obj); + if (filename) + { + #define DO_STR_LEN 1024 + char do_str[DO_STR_LEN]; + int32_t len; + do_str[0] = '\0'; + if (port_position(obj) > 0) + { + len = (int32_t)catstrs(do_str, DO_STR_LEN, "(let ((port (open-input-file \"", filename, "\")))", (char *)NULL); + port_write_string(port)(sc, do_str, len, port); + do_str[0] = '\0'; + len = (int32_t)catstrs(do_str, DO_STR_LEN, " (do ((i 0 (+ i 1)) (c (read-char port) (read-char port))) ((= i ", + pos_int_to_str_direct(sc, port_position(obj) - 1), + ") port)))", (char *)NULL); + } + else len = (int32_t)catstrs(do_str, DO_STR_LEN, "(open-input-file \"", filename, "\")", (char *)NULL); + port_write_string(port)(sc, do_str, len, port); + return; + }} + port_write_string(port)(sc, "(open-input-string ", 19, port); + /* not port_write_string here because there might be embedded double-quotes */ + slashify_string_to_port(sc, port, (const char *)(port_data(obj) + port_position(obj)), port_data_size(obj) - port_position(obj), IN_QUOTES); + port_write_character(port)(sc, ')', port); + }} + else + { + if (is_string_port(obj)) + port_write_string(port)(sc, "#", 8, port); + else port_write_character(port)(sc, '>', port); + } +} + +static bool symbol_needs_slashification(s7_scheme *sc, s7_pointer obj) +{ + uint8_t *pend; + char *str = symbol_name(obj); + s7_int len; + + if ((str[0] == '#') || (str[0] == '\'') || (str[0] == ',')) + return(true); + if (is_number(make_atom(sc, str, 10, NO_SYMBOLS, WITHOUT_OVERFLOW_ERROR))) + return(true); + + len = symbol_name_length(obj); + pend = (uint8_t *)(str + len); + for (uint8_t *p = (uint8_t *)str; p < pend; p++) + if (symbol_slashify_table[*p]) + return(true); + set_clean_symbol(obj); + return(false); +} + +static /* inline */ void symbol_to_port(s7_scheme *sc, s7_pointer obj, s7_pointer port, use_write_t use_write, shared_info_t *unused_ci) +{ + /* I think this is the only place we print a symbol's name; ci is needed to be a display_function, it is not used */ + if ((!is_clean_symbol(obj)) && + (symbol_needs_slashification(sc, obj))) + { + port_write_string(port)(sc, "(symbol \"", 9, port); + slashify_string_to_port(sc, port, symbol_name(obj), symbol_name_length(obj), NOT_IN_QUOTES); + port_write_string(port)(sc, "\")", 2, port); + } + else + { + char c = '\0'; + if ((use_write == P_READABLE) || (use_write == P_CODE)) + { + if (!is_keyword(obj)) c = '\''; + } + else if ((use_write == P_KEY) && (!is_keyword(obj))) c = ':'; + if (is_string_port(port)) + { + s7_int new_len = port_position(port) + symbol_name_length(obj) + ((c) ? 1 : 0); + if (new_len >= port_data_size(port)) + resize_port_data(sc, port, new_len * 2); + if (c) port_data(port)[port_position(port)++] = c; + memcpy((void *)(port_data(port) + port_position(port)), (void *)symbol_name(obj), symbol_name_length(obj)); + port_position(port) = new_len; + } + else + { + if (c) port_write_character(port)(sc, c, port); + port_write_string(port)(sc, symbol_name(obj), symbol_name_length(obj), port); + }} +} + +static char *multivector_indices_to_string(s7_scheme *sc, s7_int index, s7_pointer vect, char *str, int32_t str_len, int32_t cur_dim) +{ + s7_int size = vector_dimension(vect, cur_dim); + s7_int ind = index % size; + if (cur_dim > 0) + multivector_indices_to_string(sc, (index - ind) / size, vect, str, str_len, cur_dim - 1); + catstrs(str, str_len, " ", pos_int_to_str_direct(sc, ind), (char *)NULL); + return(str); +} + +#define NOT_P_DISPLAY(Choice) ((Choice == P_DISPLAY) ? P_WRITE : Choice) + +static int32_t multivector_to_port_1(s7_scheme *sc, s7_pointer vec, s7_pointer port, + int32_t out_len, int32_t flat_ref, int32_t dimension, int32_t dimensions, bool *last, + use_write_t use_write, shared_info_t *ci) +{ + if (use_write != P_READABLE) + { + if (*last) + port_write_string(port)(sc, " (", 2, port); + else port_write_character(port)(sc, '(', port); + (*last) = false; + } + for (int32_t i = 0; i < vector_dimension(vec, dimension); i++) + if (dimension == (dimensions - 1)) + { + if (flat_ref < out_len) + { + object_to_port_with_circle_check(sc, vector_getter(vec)(sc, vec, flat_ref), port, NOT_P_DISPLAY(use_write), ci); + + if (use_write == P_READABLE) + port_write_string(port)(sc, ") ", 2, port); + flat_ref++; + } + else + { + port_write_string(port)(sc, "...)", 4, port); + return(flat_ref); + } + if ((use_write != P_READABLE) && + (i < (vector_dimension(vec, dimension) - 1))) + port_write_character(port)(sc, ' ', port); + } + else + if (flat_ref < out_len) + flat_ref = multivector_to_port_1(sc, vec, port, out_len, flat_ref, dimension + 1, dimensions, last, NOT_P_DISPLAY(use_write), ci); + else + { + port_write_string(port)(sc, "...)", 4, port); + return(flat_ref); + } + if (use_write != P_READABLE) + port_write_character(port)(sc, ')', port); + (*last) = true; + return(flat_ref); +} + +static int32_t multivector_to_port(s7_scheme *sc, s7_pointer vec, s7_pointer port, + int32_t out_len, int32_t flat_ref, int32_t dimension, int32_t dimensions, + use_write_t use_write, shared_info_t *ci) +{ + bool last = false; + return(multivector_to_port_1(sc, vec, port, out_len, flat_ref, dimension, dimensions, &last, use_write, ci)); +} + +static void make_vector_to_port(s7_scheme *sc, s7_pointer vect, s7_pointer port) +{ + s7_int vlen; + int32_t plen; + char buf[128]; + const char* vtyp = ""; + + if (is_float_vector(vect)) + vtyp = "float-"; + else + if (is_int_vector(vect)) + vtyp = "int-"; + else + if (is_byte_vector(vect)) + vtyp = "byte-"; + vlen = vector_length(vect); + + if (vector_rank(vect) == 1) + { + plen = (int32_t)catstrs_direct(buf, "(make-", vtyp, "vector ", integer_to_string_no_length(sc, vlen), " ", (const char *)NULL); + port_write_string(port)(sc, buf, plen, port); + } + else + { + s7_int dim; + plen = (int32_t)catstrs_direct(buf, "(make-", vtyp, "vector '(", (const char *)NULL); + port_write_string(port)(sc, buf, plen, port); + for (dim = 0; dim < vector_ndims(vect) - 1; dim++) + { + plen = (int32_t)catstrs_direct(buf, integer_to_string_no_length(sc, vector_dimension(vect, dim)), " ", (const char *)NULL); + port_write_string(port)(sc, buf, plen, port); + } + plen = (int32_t)catstrs_direct(buf, integer_to_string_no_length(sc, vector_dimension(vect, dim)), ") ", (const char *)NULL); + port_write_string(port)(sc, buf, plen, port); + } +} + +static void write_vector_dimensions(s7_scheme *sc, s7_pointer vect, s7_pointer port) +{ + char buf[128]; + s7_int dim, plen; + port_write_string(port)(sc, " '(", 3, port); + for (dim = 0; dim < vector_ndims(vect) - 1; dim++) + { + plen = catstrs_direct(buf, integer_to_string_no_length(sc, vector_dimension(vect, dim)), " ", (const char *)NULL); + port_write_string(port)(sc, buf, plen, port); + } + plen = catstrs_direct(buf, integer_to_string_no_length(sc, vector_dimension(vect, dim)), "))", (const char *)NULL); + port_write_string(port)(sc, buf, plen, port); +} + +static void port_write_vector_typer(s7_scheme *sc, s7_pointer vect, s7_pointer port); + +static void vector_to_port(s7_scheme *sc, s7_pointer vect, s7_pointer port, use_write_t use_write, shared_info_t *ci) +{ + s7_int i, len = vector_length(vect), plen; + bool too_long = false; + char buf[2048]; /* 128 is too small -- this is the list of indices with a few minor flourishes */ + + if (len == 0) + { + if (vector_rank(vect) > 1) + { + plen = catstrs_direct(buf, "#", pos_int_to_str_direct(sc, vector_ndims(vect)), "d()", (const char *)NULL); + port_write_string(port)(sc, buf, plen, port); + } + else port_write_string(port)(sc, "#()", 3, port); + return; + } + if (use_write != P_READABLE) + { + if (sc->print_length == 0) + { + if (vector_rank(vect) > 1) + { + plen = catstrs_direct(buf, "#", pos_int_to_str_direct(sc, vector_ndims(vect)), "d(...)", (const char *)NULL); + port_write_string(port)(sc, buf, plen, port); + } + else port_write_string(port)(sc, "#(...)", 6, port); + return; + } + if (len > sc->print_length) + { + too_long = true; + len = sc->print_length; + }} + if ((!ci) && + (len > 1000)) + { + s7_int vlen = vector_length(vect); + s7_pointer *els = vector_elements(vect); + s7_pointer p0 = els[0]; + for (i = 1; i < vlen; i++) + if (els[i] != p0) + break; + if (i == vlen) + { + make_vector_to_port(sc, vect, port); + object_to_port(sc, p0, port, use_write, NULL); + if (is_typed_vector(vect)) + { + port_write_character(port)(sc, ' ', port); + port_write_vector_typer(sc, vect, port); + } + port_write_character(port)(sc, ')', port); + return; + }} + + check_stack_size(sc); + s7_gc_protect_via_stack(sc, vect); + if (use_write == P_READABLE) + { + int32_t vref; + if ((ci) && + (is_cyclic(vect)) && + ((vref = peek_shared_ref(ci, vect)) != 0)) + { + s7_pointer *els = vector_elements(vect); + if (vref < 0) vref = -vref; + if ((ci->defined[vref]) || (port == ci->cycle_port)) + { + plen = catstrs_direct(buf, "<", pos_int_to_str_direct(sc, vref), ">", (const char *)NULL); + port_write_string(port)(sc, buf, plen, port); + unstack(sc); + return; + } + + if (is_typed_vector(vect)) + port_write_string(port)(sc, "(let (( ", 11, port); + + if (vector_rank(vect) > 1) + port_write_string(port)(sc, "(subvector ", 11, port); + + port_write_string(port)(sc, "(vector", 7, port); /* top level let */ + for (i = 0; i < len; i++) + if (has_structure(els[i])) + { + int32_t eref = peek_shared_ref(ci, els[i]); + port_write_string(port)(sc, " #f", 3, port); + if (eref != 0) + { + if (eref < 0) eref = -eref; + if (vector_rank(vect) > 1) + { + s7_int dimension = vector_rank(vect) - 1; + int32_t str_len = (dimension < 8) ? 128 : ((dimension + 1) * 16); + block_t *b = callocate(sc, str_len); + char *indices = (char *)block_data(b); + multivector_indices_to_string(sc, i, vect, indices, str_len, dimension); /* calls pos_int_to_str_direct, writes to indices */ + plen = catstrs_direct(buf, " (set! (<", pos_int_to_str_direct(sc, vref), ">", + indices, ") <", pos_int_to_str_direct_1(sc, eref), ">) ", (const char *)NULL); + port_write_string(ci->cycle_port)(sc, buf, plen, ci->cycle_port); + liberate(sc, b); + } + else + { + size_t len1 = catstrs_direct(buf, " (set! (<", pos_int_to_str_direct(sc, vref), "> ", integer_to_string(sc, i, &plen), ") <", + pos_int_to_str_direct_1(sc, eref), ">) ", (const char *)NULL); + port_write_string(ci->cycle_port)(sc, buf, len1, ci->cycle_port); + }} + else + { + if (vector_rank(vect) > 1) + { + s7_int dimension = vector_rank(vect) - 1; + int32_t str_len = (dimension < 8) ? 128 : ((dimension + 1) * 16); + block_t *b = callocate(sc, str_len); + char *indices = (char *)block_data(b); + buf[0] = '\0'; + multivector_indices_to_string(sc, i, vect, indices, str_len, dimension); /* writes to indices */ + plen = catstrs(buf, 2048, " (set! (<", pos_int_to_str_direct(sc, vref), ">", indices, ") ", (char *)NULL); + port_write_string(ci->cycle_port)(sc, buf, plen, ci->cycle_port); + liberate(sc, b); + } + else + { + size_t len1 = catstrs_direct(buf, " (set! (<", pos_int_to_str_direct(sc, vref), "> ", integer_to_string_no_length(sc, i), ") ", (const char *)NULL); + port_write_string(ci->cycle_port)(sc, buf, len1, ci->cycle_port); + } + object_to_port_with_circle_check(sc, els[i], ci->cycle_port, P_READABLE, ci); + port_write_string(ci->cycle_port)(sc, ") ", 2, ci->cycle_port); + }} + else + { + port_write_character(port)(sc, ' ', port); + object_to_port_with_circle_check(sc, els[i], port, P_READABLE, ci); + } + port_write_character(port)(sc, ')', port); + if (vector_rank(vect) > 1) + { + plen = catstrs_direct(buf, " 0 ", pos_int_to_str_direct(sc, len), (const char *)NULL); + port_write_string(port)(sc, buf, plen, port); + write_vector_dimensions(sc, vect, port); + } + if (is_typed_vector(vect)) + { + port_write_string(port)(sc, ")) (set! (vector-typer ) ", 28, port); + port_write_vector_typer(sc, vect, port); + port_write_string(port)(sc, ") )", 6, port); + }} + else + { + if (is_typed_vector(vect)) + port_write_string(port)(sc, "(let (( ", 11, port); + /* (let ((v (make-vector 3 'a symbol?))) (object->string v :readable)): "(let (( (vector 'a 'a 'a))) (set! (vector-typer ) symbol?) )" */ + + if (vector_rank(vect) > 1) + port_write_string(port)(sc, "(subvector ", 11, port); + + if (is_immutable_vector(vect)) + port_write_string(port)(sc, "(immutable! ", 12, port); + + port_write_string(port)(sc, "(vector", 7, port); + for (i = 0; i < len; i++) + { + port_write_character(port)(sc, ' ', port); + object_to_port_with_circle_check(sc, vector_element(vect, i), port, P_READABLE, ci); + } + + if (is_immutable_vector(vect)) + port_write_string(port)(sc, "))", 2, port); + else port_write_character(port)(sc, ')', port); + + if (vector_rank(vect) > 1) /* subvector above */ + { + plen = catstrs_direct(buf, " 0 ", pos_int_to_str_direct(sc, len), (const char *)NULL); + port_write_string(port)(sc, buf, plen, port); + write_vector_dimensions(sc, vect, port); + } + if (is_typed_vector(vect)) + { + port_write_string(port)(sc, ")) (set! (vector-typer ) ", 28, port); + port_write_vector_typer(sc, vect, port); + port_write_string(port)(sc, ") )", 6, port); + }}} + else /* not readable write */ + { + if (vector_rank(vect) > 1) + { + if (vector_ndims(vect) > 1) + { + plen = catstrs_direct(buf, "#", pos_int_to_str_direct(sc, vector_ndims(vect)), "d", (const char *)NULL); + port_write_string(port)(sc, buf, plen, port); + } + else port_write_character(port)(sc, '#', port); + multivector_to_port(sc, vect, port, len, 0, 0, vector_ndims(vect), use_write, ci); + } + else + { + port_write_string(port)(sc, "#(", 2, port); + for (i = 0; i < len - 1; i++) + { + object_to_port_with_circle_check(sc, vector_element(vect, i), port, NOT_P_DISPLAY(use_write), ci); + port_write_character(port)(sc, ' ', port); + } + object_to_port_with_circle_check(sc, vector_element(vect, i), port, NOT_P_DISPLAY(use_write), ci); + + if (too_long) + port_write_string(port)(sc, " ...)", 5, port); + else port_write_character(port)(sc, ')', port); + }} + unstack(sc); +} + +static int32_t print_vector_length(s7_scheme *sc, s7_pointer vect, s7_pointer port, use_write_t use_write) +{ + int32_t plen, len = vector_length(vect); + char buf[128]; + const char *vtype = "r"; + + if (is_int_vector(vect)) vtype = "i"; + else if (is_byte_vector(vect)) vtype = "u"; + if (len == 0) + { + if (vector_rank(vect) > 1) + plen = (int32_t)catstrs_direct(buf, "#", vtype, pos_int_to_str_direct(sc, vector_ndims(vect)), "d()", (const char *)(const char *)NULL); + else plen = (int32_t)catstrs_direct(buf, "#", vtype, "()", (const char *)NULL); + port_write_string(port)(sc, buf, plen, port); + return(-1); + } + if (use_write == P_READABLE) + return(len); + if (sc->print_length != 0) + return((len > sc->print_length) ? sc->print_length : len); + + if (vector_rank(vect) > 1) + { + plen = (int32_t)catstrs_direct(buf, "#", vtype, pos_int_to_str_direct(sc, vector_ndims(vect)), "d(...)", (const char *)NULL); + port_write_string(port)(sc, buf, plen, port); + } + else + if (is_int_vector(vect)) + port_write_string(port)(sc, "#i(...)", 7, port); + else + if (is_float_vector(vect)) + port_write_string(port)(sc, "#r(...)", 7, port); + else port_write_string(port)(sc, "#u(...)", 7, port); + return(-1); +} + +static void int_vector_to_port(s7_scheme *sc, s7_pointer vect, s7_pointer port, use_write_t use_write, shared_info_t *unused_ci) +{ + s7_int plen; + bool too_long; + char buf[128]; + const char *p; + s7_int len = print_vector_length(sc, vect, port, use_write); + if (len < 0) return; + too_long = (len < vector_length(vect)); + + if ((use_write == P_READABLE) && + (is_immutable_vector(vect))) + port_write_string(port)(sc, "(immutable! ", 12, port); + + if (len > 1000) + { + s7_int i, vlen = vector_length(vect); + const s7_int *els = int_vector_ints(vect); + s7_int first = els[0]; + for (i = 1; i < vlen; i++) + if (els[i] != first) + break; + if (i == vlen) + { + make_vector_to_port(sc, vect, port); + p = integer_to_string(sc, int_vector(vect, 0), &plen); + port_write_string(port)(sc, p, plen, port); + if ((use_write == P_READABLE) && + (is_immutable_vector(vect))) + port_write_string(port)(sc, "))", 2, port); + else port_write_character(port)(sc, ')', port); + return; + }} + if (vector_rank(vect) == 1) + { + port_write_string(port)(sc, "#i(", 3, port); + if (!is_string_port(port)) + { + p = integer_to_string(sc, int_vector(vect, 0), &plen); + port_write_string(port)(sc, p, plen, port); + for (s7_int i = 1; i < len; i++) + { + plen = catstrs_direct(buf, " ", integer_to_string_no_length(sc, int_vector(vect, i)), (const char *)NULL); + port_write_string(port)(sc, buf, plen, port); + }} + else + { + s7_int new_len = port_position(port); + s7_int next_len = port_data_size(port) - 128; + uint8_t *dbuf = port_data(port); + if (new_len >= next_len) + { + resize_port_data(sc, port, port_data_size(port) * 2); + next_len = port_data_size(port) - 128; + dbuf = port_data(port); + } + p = integer_to_string(sc, int_vector(vect, 0), &plen); + memcpy((void *)(dbuf + new_len), (const void *)p, plen); + new_len += plen; + for (s7_int i = 1; i < len; i++) + { + if (new_len >= next_len) + { + resize_port_data(sc, port, port_data_size(port) * 2); + next_len = port_data_size(port) - 128; + dbuf = port_data(port); + } + plen = catstrs_direct((char *)(dbuf + new_len), " ", integer_to_string_no_length(sc, int_vector(vect, i)), (const char *)NULL); + new_len += plen; + } + port_position(port) = new_len; + } + if (too_long) + port_write_string(port)(sc, " ...)", 5, port); + else port_write_character(port)(sc, ')', port); + } + else + { + plen = catstrs_direct(buf, "#i", pos_int_to_str_direct(sc, vector_ndims(vect)), "d", (const char *)NULL); + port_write_string(port)(sc, buf, plen, port); + s7_gc_protect_via_stack(sc, vect); + multivector_to_port(sc, vect, port, len, 0, 0, vector_ndims(vect), P_DISPLAY, NULL); + unstack(sc); + } + if ((use_write == P_READABLE) && + (is_immutable_vector(vect))) + port_write_character(port)(sc, ')', port); +} + +static void float_vector_to_port(s7_scheme *sc, s7_pointer vect, s7_pointer port, use_write_t use_write, shared_info_t *unused_ci) +{ + #define FV_BUFSIZE 512 /* some floats can take around 312 bytes */ + char buf[FV_BUFSIZE]; + s7_int i, plen; + bool too_long; + const s7_double *els = float_vector_floats(vect); + s7_int len = print_vector_length(sc, vect, port, use_write); + if (len < 0) return; /* vector-length=0 etc */ + too_long = (len < vector_length(vect)); + + if ((use_write == P_READABLE) && + (is_immutable_vector(vect))) + port_write_string(port)(sc, "(immutable! ", 12, port); + + if (len > 1000) + { + s7_int vlen = vector_length(vect); + s7_double first = els[0]; + for (i = 1; i < vlen; i++) + if (els[i] != first) + break; + if (i == vlen) + { + make_vector_to_port(sc, vect, port); + plen = snprintf(buf, FV_BUFSIZE, "%.*g)", sc->float_format_precision, first); + port_write_string(port)(sc, buf, clamp_length(plen, FV_BUFSIZE), port); + if ((use_write == P_READABLE) && + (is_immutable_vector(vect))) + port_write_character(port)(sc, ')', port); + return; + }} + + if (vector_rank(vect) == 1) + { + port_write_string(port)(sc, "#r(", 3, port); + plen = snprintf(buf, FV_BUFSIZE - 4, "%.*g", sc->float_format_precision, els[0]); /* -4 so floatify has room */ + floatify(buf, &plen); + port_write_string(port)(sc, buf, clamp_length(plen, FV_BUFSIZE), port); + for (i = 1; i < len; i++) + { + plen = snprintf(buf, FV_BUFSIZE - 4, " %.*g", sc->float_format_precision, els[i]); + plen--; /* fixup for the initial #\space */ + floatify((char *)(buf + 1), &plen); + port_write_string(port)(sc, buf, clamp_length(plen + 1, FV_BUFSIZE), port); + } + if (too_long) + port_write_string(port)(sc, " ...)", 5, port); + else port_write_character(port)(sc, ')', port); + } + else + { + plen = catstrs_direct(buf, "#r", pos_int_to_str_direct(sc, vector_ndims(vect)), "d", (const char *)NULL); + port_write_string(port)(sc, buf, plen, port); + s7_gc_protect_via_stack(sc, vect); + multivector_to_port(sc, vect, port, len, 0, 0, vector_ndims(vect), P_DISPLAY, NULL); + unstack(sc); + } + if ((use_write == P_READABLE) && + (is_immutable_vector(vect))) + port_write_character(port)(sc, ')', port); +} + +static void byte_vector_to_port(s7_scheme *sc, s7_pointer vect, s7_pointer port, use_write_t use_write, shared_info_t *unused_ci) +{ + s7_int i, plen; + bool too_long; + char buf[128]; + const char *p; + s7_int len = print_vector_length(sc, vect, port, use_write); + if (len < 0) return; + too_long = (len < vector_length(vect)); + + if ((use_write == P_READABLE) && + (is_immutable_vector(vect))) + port_write_string(port)(sc, "(immutable! ", 12, port); + + if (len > 1000) + { + s7_int vlen = vector_length(vect); + const uint8_t *els = byte_vector_bytes(vect); + uint8_t first = els[0]; + for (i = 1; i < vlen; i++) + if (els[i] != first) + break; + if (i == vlen) + { + make_vector_to_port(sc, vect, port); + p = integer_to_string(sc, byte_vector(vect, 0), &plen); /* only 0..10 start out with names: init_small_ints */ + port_write_string(port)(sc, p, plen, port); + if ((use_write == P_READABLE) && + (is_immutable_vector(vect))) + port_write_string(port)(sc, "))", 2, port); + else port_write_character(port)(sc, ')', port); + return; + }} + + if (vector_rank(vect) == 1) + { + port_write_string(port)(sc, "#u(", 3, port); + p = integer_to_string(sc, byte_vector(vect, 0), &plen); + port_write_string(port)(sc, p, plen, port); + for (i = 1; i < len; i++) + { + plen = catstrs_direct(buf, " ", integer_to_string_no_length(sc, byte_vector(vect, i)), (const char *)NULL); + port_write_string(port)(sc, buf, plen, port); + } + if (too_long) + port_write_string(port)(sc, " ...)", 5, port); + else port_write_character(port)(sc, ')', port); + } + else + { + plen = catstrs_direct(buf, "#u", pos_int_to_str_direct(sc, vector_ndims(vect)), "d", (const char *)NULL); + port_write_string(port)(sc, buf, plen, port); + multivector_to_port(sc, vect, port, len, 0, 0, vector_ndims(vect), P_DISPLAY, NULL); + } + if ((use_write == P_READABLE) && + (is_immutable_vector(vect))) + port_write_character(port)(sc, ')', port); +} + +static void string_to_port(s7_scheme *sc, s7_pointer obj, s7_pointer port, use_write_t use_write, shared_info_t *unused_ci) +{ + bool immutable = ((use_write == P_READABLE) && + (is_immutable_string(obj)) && + (string_length(obj) > 0)); /* (immutable "") looks dumb */ + if (immutable) + port_write_string(port)(sc, "(immutable! ", 12, port); + + if (string_length(obj) > 0) + { + /* since string_length is a scheme length, not C, this write can embed nulls from C's point of view */ + if (string_length(obj) > 1000) /* was 10000 28-Feb-18 */ + { + size_t size; + char buf[128]; + buf[0] = string_value(obj)[0]; + buf[1] = '\0'; + size = strspn((const char *)(string_value(obj) + 1), buf); /* if all #\null, this won't work */ + if (size == (size_t)(string_length(obj) - 1)) + { + s7_pointer c = chars[(int32_t)((uint8_t)(buf[0]))]; + int32_t nlen = (int32_t)catstrs_direct(buf, "(make-string ", pos_int_to_str_direct(sc, string_length(obj)), " ", (const char *)NULL); + port_write_string(port)(sc, buf, nlen, port); + port_write_string(port)(sc, character_name(c), character_name_length(c), port); + if (immutable) + port_write_string(port)(sc, "))", 2, port); + else port_write_character(port)(sc, ')', port); + return; + }} + if (use_write == P_DISPLAY) + port_write_string(port)(sc, string_value(obj), string_length(obj), port); + else + if (!string_needs_slashification((const uint8_t *)string_value(obj), string_length(obj))) + { + port_write_character(port)(sc, '"', port); + port_write_string(port)(sc, string_value(obj), string_length(obj), port); + port_write_character(port)(sc, '"', port); + } + else slashify_string_to_port(sc, port, string_value(obj), string_length(obj), IN_QUOTES); + } + else + if (use_write != P_DISPLAY) + port_write_string(port)(sc, "\"\"", 2, port); + + if (immutable) + port_write_character(port)(sc, ')', port); +} + +static s7_int list_length_with_immutable_check(s7_scheme *sc, s7_pointer a, bool *immutable) +{ + s7_pointer slow = a, fast = a; + for (s7_int i = 0; ; i += 2) + { + if (!is_pair(fast)) return((is_null(fast)) ? i : -i); + if (is_immutable_pair(fast)) *immutable = true; + fast = cdr(fast); + if (!is_pair(fast)) return((is_null(fast)) ? (i + 1) : (-i - 1)); + if (is_immutable_pair(fast)) *immutable = true; + fast = cdr(fast); + slow = cdr(slow); + if (fast == slow) return(0); + } + return(0); +} + +static void simple_list_readable_display(s7_scheme *sc, s7_pointer lst, s7_int true_len, s7_int len, s7_pointer port, shared_info_t *ci, bool immutable) +{ + /* the easier cases: no circles or shared refs to patch up */ + s7_pointer x; + + if ((true_len > 0) && (!immutable)) + { + port_write_string(port)(sc, "list", 4, port); + for (x = lst; is_pair(x); x = cdr(x)) + { + port_write_character(port)(sc, ' ', port); + object_to_port_with_circle_check(sc, car(x), port, P_READABLE, ci); + } + port_write_character(port)(sc, ')', port); + } + else + { + s7_int immutable_ctr = 0; + if (is_immutable_pair(lst)) + { + port_write_string(port)(sc, "immutable! (cons ", 17, port); + immutable_ctr++; + } + else port_write_string(port)(sc, "cons ", 5, port); + object_to_port_with_circle_check(sc, car(lst), port, P_READABLE, ci); + + for (x = cdr(lst); is_pair(x); x = cdr(x)) + { + if (is_immutable_pair(x)) + { + port_write_string(port)(sc, " (immutable! (cons ", 19, port); + immutable_ctr++; + } + else port_write_string(port)(sc, " (cons ", 7, port); + object_to_port_with_circle_check(sc, car(x), port, P_READABLE, ci); + } + if (is_null(x)) + port_write_string(port)(sc, " ()", 3, port); + else + { + port_write_character(port)(sc, ' ', port); + object_to_port_with_circle_check(sc, x, port, P_READABLE, ci); + } + for (s7_int i = (true_len <= 0) ? 1 : 0; i < len; i++) + port_write_character(port)(sc, ')', port); + for (s7_int i = 0; i < immutable_ctr; i++) + port_write_character(port)(sc, ')', port); + } +} + +static void pair_to_port(s7_scheme *sc, s7_pointer lst, s7_pointer port, use_write_t use_write, shared_info_t *ci) +{ + s7_pointer x; + s7_int i, len; + bool immutable = false; + s7_int true_len = list_length_with_immutable_check(sc, lst, &immutable); + if (true_len < 0) /* a dotted list -- handle cars, then final cdr */ + len = (-true_len + 1); + else len = (true_len == 0) ? circular_list_entries(lst) : true_len; /* circular list (nil is handled by unique_to_port) */ + + if ((use_write == P_READABLE) && + (ci)) + { + int32_t href = peek_shared_ref(ci, lst); + if (href != 0) + { + if (href < 0) href = -href; + if ((ci->defined[href]) || (port == ci->cycle_port)) + { + char buf[128]; + int32_t plen = (int32_t)catstrs_direct(buf, "<", pos_int_to_str_direct(sc, href), ">", (const char *)NULL); + port_write_string(port)(sc, buf, plen, port); + return; + }}} + + if ((use_write != P_READABLE) && + (car(lst) == sc->quote_symbol) && + (true_len == 2)) + { + /* len == 1 is important, otherwise (list 'quote 1 2) -> '1 2 which looks weird + * or (object->string (apply . `''1)) -> "'quote 1" + * so (quote x) = 'x but (quote x y z) should be left alone (if evaluated, it's an error) + * :readable is tricky because the list might be something like (list 'quote (lambda () #f)) which needs to be evalable back to its original + */ + port_write_character(port)(sc, '\'', port); + object_to_port_with_circle_check(sc, cadr(lst), port, P_WRITE, ci); + return; + } + + if (is_multiple_value(lst)) + port_write_string(port)(sc, "(values ", 8, port); + else port_write_character(port)(sc, '(', port); + + check_stack_size(sc); + s7_gc_protect_via_stack(sc, lst); + + if (use_write == P_READABLE) + { + if (!is_cyclic(lst)) + { + /* here (and in the cyclic case) we need to handle immutable pairs -- this requires using cons rather than list etc */ + simple_list_readable_display(sc, lst, true_len, len, port, ci, immutable); + unstack(sc); + return; + } + if (ci) + { + int32_t plen; + s7_pointer local_port; + char buf[128], lst_name[128]; + bool lst_local = false; + int32_t lst_ref = peek_shared_ref(ci, lst); + if (lst_ref == 0) + { + s7_pointer p; + for (p = lst; is_pair(p); p = cdr(p)) + if ((has_structure(car(p))) || + ((is_pair(cdr(p))) && + (peek_shared_ref(ci, cdr(p)) != 0))) + { + lst_name[0] = '<'; lst_name[1] = 'L'; lst_name[2] = '>'; lst_name[3] = '\0'; + lst_local = true; + port_write_string(port)(sc, "let (( (list", 15, port); /* '(' above */ + break; + } + if (!lst_local) + { + if (has_structure(p)) + { + lst_name[0] = '<'; lst_name[1] = 'L'; lst_name[2] = '>'; lst_name[3] = '\0'; + lst_local = true; + port_write_string(port)(sc, "let (( (list", 15, port); /* '(' above */ + } + else + { + simple_list_readable_display(sc, lst, true_len, len, port, ci, immutable); + unstack(sc); + return; + }}} + else + { + if (lst_ref < 0) lst_ref = -lst_ref; + catstrs_direct(lst_name, "<", pos_int_to_str_direct(sc, lst_ref), ">", (const char *)NULL); + port_write_string(port)(sc, "list", 4, port); /* '(' above */ + } + + for (i = 0, x = lst; (i < len) && (is_pair(x)); x = cdr(x), i++) + { + if ((has_structure(car(x))) && + (is_cyclic(car(x)))) + port_write_string(port)(sc, " #f", 3, port); + else + { + port_write_character(port)(sc, ' ', port); + object_to_port_with_circle_check(sc, car(x), port, use_write, ci); + } + if ((is_pair(cdr(x))) && + (peek_shared_ref(ci, cdr(x)) != 0)) + break; + } + + if (lst_local) + port_write_string(port)(sc, "))) ", 4, port); + else port_write_character(port)(sc, ')', port); + + /* fill in the cyclic entries */ + local_port = ((lst_local) || (ci->cycle_port == sc->F)) ? port : ci->cycle_port; /* (object->string (list-values `(x . 1) (signature (int-vector))) :readable) */ + for (x = lst, i = 0; (i < len) && (is_pair(x)); x = cdr(x), i++) + { + int32_t lref; + if ((has_structure(car(x))) && + (is_cyclic(car(x)))) + { + if (i == 0) + plen = (int32_t)catstrs_direct(buf, " (set-car! ", lst_name, " ", (const char *)NULL); + else plen = (int32_t)catstrs_direct(buf, " (set! (", lst_name, " ", pos_int_to_str_direct(sc, i), ") ", (const char *)NULL); + port_write_string(local_port)(sc, buf, plen, local_port); + lref = peek_shared_ref(ci, car(x)); + if (lref == 0) + object_to_port_with_circle_check(sc, car(x), local_port, use_write, ci); + else + { + if (lref < 0) lref = -lref; + plen = (int32_t)catstrs_direct(buf, "<", pos_int_to_str_direct(sc, lref), ">", (const char *)NULL); + port_write_string(local_port)(sc, buf, plen, local_port); + } + port_write_string(local_port)(sc, ") ", 2, local_port); + } + if ((is_pair(cdr(x))) && + ((lref = peek_shared_ref(ci, cdr(x))) != 0)) + { + if (lref < 0) lref = -lref; + if (i == 0) + plen = (int32_t)catstrs_direct(buf, (lst_local) ? " " : " ", + "(set-cdr! ", lst_name, " <", pos_int_to_str_direct(sc, lref), ">) ", (const char *)NULL); + else + if (i == 1) + plen = (int32_t)catstrs_direct(buf, (lst_local) ? " " : " ", + "(set-cdr! (cdr ", lst_name, ") <", pos_int_to_str_direct(sc, lref), ">) ", (const char *)NULL); + else plen = (int32_t)catstrs_direct(buf, (lst_local) ? " " : " ", + "(set-cdr! (list-tail ", lst_name, " ", pos_int_to_str_direct_1(sc, i), + ") <", pos_int_to_str_direct(sc, lref), ">) ", (const char *)NULL); + port_write_string(local_port)(sc, buf, plen, local_port); + break; + }} + if (true_len < 0) /* dotted list */ + { + s7_pointer end_x; + for (end_x = lst; is_pair(end_x); end_x = cdr(end_x)); /* or maybe faster, start at x? */ + /* we can't depend on the loops above to set x to the last element because they sometimes break out */ + if (true_len == -1) /* cons cell */ + plen = (int32_t)catstrs_direct(buf, (lst_local) ? " " : " ", "(set-cdr! ", lst_name, " ", (const char *)NULL); + else + if (true_len == -2) + plen = (int32_t)catstrs_direct(buf, (lst_local) ? " " : " ", "(set-cdr! (cdr ", lst_name, ") ", (const char *)NULL); + else plen = (int32_t)catstrs_direct(buf, "(set-cdr! (list-tail ", lst_name, " ", pos_int_to_str_direct(sc, len - 2), ") ", (const char *)NULL); + port_write_string(local_port)(sc, buf, plen, local_port); + object_to_port_with_circle_check(sc, end_x, local_port, use_write, ci); + port_write_string(local_port)(sc, ") ", 2, local_port); + } + if (lst_local) + port_write_string(local_port)(sc, " )", 8, local_port); + } + else simple_list_readable_display(sc, lst, true_len, len, port, ci, immutable); + } + else /* not :readable */ + { + s7_int plen = (len > sc->print_length) ? sc->print_length : len; + if (plen <= 0) + { + port_write_string(port)(sc, "(...))", 6, port); /* open paren above about 150 lines, "list" here is wrong if it's a cons */ + unstack(sc); + return; + } + if (ci) + { + for (x = lst, i = 0; (is_pair(x)) && (i < plen) && ((i == 0) || (peek_shared_ref(ci, x) == 0)); i++, x = cdr(x)) + { + ci->ctr++; + if (ci->ctr > sc->print_length) + { + port_write_string(port)(sc, " ...)", 5, port); + unstack(sc); + return; + } + object_to_port_with_circle_check(sc, car(x), port, NOT_P_DISPLAY(use_write), ci); + if (i < (len - 1)) + port_write_character(port)(sc, ' ', port); + } + if (is_not_null(x)) + { + if (plen < len) + port_write_string(port)(sc, " ...", 4, port); + else + { + if ((true_len == 0) && + (i == len)) + port_write_string(port)(sc, " . ", 3, port); + else port_write_string(port)(sc, ". ", 2, port); + object_to_port_with_circle_check(sc, x, port, NOT_P_DISPLAY(use_write), ci); + }} + port_write_character(port)(sc, ')', port); + } + else + { + s7_int len1 = plen - 1; + if (is_string_port(port)) + { + for (x = lst, i = 0; (is_pair(x)) && (i < len1); i++, x = cdr(x)) + { + object_to_port(sc, car(x), port, NOT_P_DISPLAY(use_write), ci); + if (port_position(port) >= sc->objstr_max_len) + { + unstack(sc); + return; + } + if (port_position(port) >= port_data_size(port)) + resize_port_data(sc, port, port_data_size(port) * 2); + port_data(port)[port_position(port)++] = (uint8_t)' '; + }} + else + for (x = lst, i = 0; (is_pair(x)) && (i < len1); i++, x = cdr(x)) + { + object_to_port(sc, car(x), port, NOT_P_DISPLAY(use_write), ci); + port_write_character(port)(sc, ' ', port); + } + if (is_pair(x)) + { + object_to_port(sc, car(x), port, NOT_P_DISPLAY(use_write), ci); + x = cdr(x); + } + if (is_not_null(x)) + { + if (plen < len) + port_write_string(port)(sc, " ...", 4, port); + else + { + port_write_string(port)(sc, ". ", 2, port); + object_to_port(sc, x, port, NOT_P_DISPLAY(use_write), ci); + }} + port_write_character(port)(sc, ')', port); + }} + unstack(sc); +} + +static s7_pointer find_closure(s7_scheme *sc, s7_pointer closure, s7_pointer current_let); +static const char *hash_table_checker_name(s7_scheme *sc, s7_pointer ht); + +static const char *hash_table_typer_name(s7_scheme *sc, s7_pointer typer) +{ + s7_pointer sym; + if (is_c_function(typer)) return(c_function_name(typer)); + if (is_boolean(typer)) return("#t"); + sym = find_closure(sc, typer, closure_let(typer)); + if (is_null(sym)) return(NULL); + return(symbol_name(sym)); +} + +static void hash_typers_to_port(s7_scheme *sc, s7_pointer hash, s7_pointer port) +{ + if (((is_typed_hash_table(hash)) || (is_pair(hash_table_procedures(hash)))) && + ((!is_boolean(hash_table_key_typer(hash))) || (!is_boolean(hash_table_value_typer(hash))))) + { + const char *typer = hash_table_typer_name(sc, hash_table_key_typer(hash)); + port_write_string(port)(sc, " (cons ", 7, port); + port_write_string(port)(sc, typer, safe_strlen(typer), port); + port_write_character(port)(sc, ' ', port); + typer = hash_table_typer_name(sc, hash_table_value_typer(hash)); + port_write_string(port)(sc, typer, safe_strlen(typer), port); + port_write_string(port)(sc, "))", 2, port); + } + else port_write_character(port)(sc, ')', port); +} + +static void hash_table_procedures_to_port(s7_scheme *sc, s7_pointer hash, s7_pointer port, bool closed, shared_info_t *ci) +{ + const char *typer = hash_table_checker_name(sc, hash); + if ((closed) && (is_immutable_hash_table(hash))) + port_write_string(port)(sc, "(immutable! ", 12, port); + + if (typer[0] == '#') /* #f */ + { + if (is_pair(hash_table_procedures(hash))) + { + s7_int nlen = 0; + const char *str = (const char *)integer_to_string(sc, hash_table_mask(hash) + 1, &nlen); + const char *checker = hash_table_typer_name(sc, hash_table_procedures_checker(hash)); + const char *mapper = hash_table_typer_name(sc, hash_table_procedures_mapper(hash)); + if (is_weak_hash_table(hash)) + port_write_string(port)(sc, "(make-weak-hash-table ", 22, port); + else port_write_string(port)(sc, "(make-hash-table ", 17, port); + port_write_string(port)(sc, str, nlen, port); + if ((checker) && (mapper)) + { + if ((is_boolean(hash_table_procedures_checker(hash))) && (is_boolean(hash_table_procedures_mapper(hash)))) + port_write_string(port)(sc, " #f", 3, port); /* no checker/mapper set? */ + else + { + port_write_string(port)(sc, " (cons ", 7, port); + port_write_string(port)(sc, checker, safe_strlen(checker), port); + port_write_character(port)(sc, ' ', port); + port_write_string(port)(sc, mapper, safe_strlen(mapper), port); + port_write_character(port)(sc, ')', port); + }} + else + if ((is_any_closure(hash_table_procedures_checker(hash))) || + (is_any_closure(hash_table_procedures_mapper(hash)))) + { + port_write_string(port)(sc, " (cons ", 7, port); + if (is_any_closure(hash_table_procedures_checker(hash))) + object_to_port_with_circle_check(sc, hash_table_procedures_checker(hash), port, P_READABLE, ci); + else port_write_string(port)(sc, checker, safe_strlen(checker), port); + port_write_character(port)(sc, ' ', port); + if (is_any_closure(hash_table_procedures_mapper(hash))) + object_to_port_with_circle_check(sc, hash_table_procedures_mapper(hash), port, P_READABLE, ci); + else port_write_string(port)(sc, mapper, safe_strlen(mapper), port); + port_write_character(port)(sc, ')', port); + } + else port_write_string(port)(sc, " #f", 3, port); /* no checker/mapper set? */ + hash_typers_to_port(sc, hash, port); + } + else + if (is_weak_hash_table(hash)) + port_write_string(port)(sc, "(weak-hash-table)", 17, port); + else port_write_string(port)(sc, "(hash-table)", 12, port); + } + else + { + s7_int nlen = 0; + const char *str = integer_to_string(sc, hash_table_mask(hash) + 1, &nlen); + if (is_weak_hash_table(hash)) + port_write_string(port)(sc, "(make-weak-hash-table ", 22, port); + else port_write_string(port)(sc, "(make-hash-table ", 17, port); + port_write_string(port)(sc, str, nlen, port); + port_write_character(port)(sc, ' ', port); + port_write_string(port)(sc, typer, safe_strlen(typer), port); + hash_typers_to_port(sc, hash, port); + } + if (is_immutable_hash_table(hash)) + port_write_character(port)(sc, ')', port); +} + +static void hash_table_to_port(s7_scheme *sc, s7_pointer hash, s7_pointer port, use_write_t use_write, shared_info_t *ci) +{ + s7_int gc_iter, len = hash_table_entries(hash); + bool too_long = false, hash_cyclic = false, copied = false, immut = false, letd = false; + s7_pointer iterator, p; + int32_t href = -1; + + if (len == 0) + { + if (use_write == P_READABLE) + hash_table_procedures_to_port(sc, hash, port, true, ci); + else + { + if (is_weak_hash_table(hash)) + port_write_string(port)(sc, "(weak-hash-table)", 17, port); + else port_write_string(port)(sc, "(hash-table)", 12, port); + } + return; + } + + if (use_write != P_READABLE) + { + s7_int plen = sc->print_length; + if (plen <= 0) + { + port_write_string(port)(sc, "(hash-table ...)", 16, port); + return; + } + if (len > plen) + { + too_long = true; + len = plen; + }} + + if ((use_write == P_READABLE) && + (ci)) + { + href = peek_shared_ref(ci, hash); + if (href != 0) + { + if (href < 0) href = -href; + if ((ci->defined[href]) || (port == ci->cycle_port)) + { + char buf[128]; + int32_t plen = catstrs_direct(buf, "<", pos_int_to_str_direct(sc, href), ">", (const char *)NULL); + port_write_string(port)(sc, buf, plen, port); + return; + }}} + + iterator = s7_make_iterator(sc, hash); + gc_iter = gc_protect_1(sc, iterator); + p = cons_unchecked(sc, sc->F, sc->F); + iterator_current(iterator) = p; + set_mark_seq(iterator); + hash_cyclic = ((ci) && (is_cyclic(hash)) && ((href = peek_shared_ref(ci, hash)) != 0)); + + if (use_write == P_READABLE) + { + if ((is_typed_hash_table(hash)) || (is_pair(hash_table_procedures(hash))) || (hash_chosen(hash))) + { + port_write_string(port)(sc, "(let (( ", 11, port); + letd = true; + } + else + if ((is_immutable_hash_table(hash)) && (!hash_cyclic)) + { + port_write_string(port)(sc, "(immutable! ", 12, port); + immut = true; + }} + + if ((use_write == P_READABLE) && + (hash_cyclic)) + { + if (href < 0) href = -href; + if ((!is_typed_hash_table(hash)) && (!is_pair(hash_table_procedures(hash))) && (!hash_chosen(hash))) + { + if (is_weak_hash_table(hash)) + port_write_string(port)(sc, "(weak-hash-table", 16, port); + else port_write_string(port)(sc, "(hash-table", 11, port); /* top level let */ + } + else + { + hash_table_procedures_to_port(sc, hash, port, true, ci); + port_write_character(port)(sc, ')', port); + } + + /* output here is deferred via ci->cycle_port until later in cyclic_out */ + for (s7_int i = 0; i < len; i++) + { + s7_pointer key_val = hash_table_iterate(sc, iterator); + s7_pointer key = car(key_val); + s7_pointer val = cdr(key_val); + char buf[128]; + int32_t eref = peek_shared_ref(ci, val); + int32_t kref = peek_shared_ref(ci, key); + int32_t plen = catstrs_direct(buf, " (set! (<", pos_int_to_str_direct(sc, href), "> ", (const char *)NULL); + port_write_string(ci->cycle_port)(sc, buf, plen, ci->cycle_port); + if (kref != 0) + { + if (kref < 0) kref = -kref; + plen = catstrs_direct(buf, "<", pos_int_to_str_direct(sc, kref), ">", (const char *)NULL); + port_write_string(ci->cycle_port)(sc, buf, plen, ci->cycle_port); + } + else object_to_port(sc, key, ci->cycle_port, P_READABLE, ci); + if (eref != 0) + { + if (eref < 0) eref = -eref; + plen = catstrs_direct(buf, ") <", pos_int_to_str_direct(sc, eref), ">) ", (const char *)NULL); + port_write_string(ci->cycle_port)(sc, buf, plen, ci->cycle_port); + } + else + { + port_write_string(ci->cycle_port)(sc, ") ", 2, ci->cycle_port); + object_to_port_with_circle_check(sc, val, ci->cycle_port, P_READABLE, ci); + port_write_string(ci->cycle_port)(sc, ") ", 2, ci->cycle_port); + }}} + else + { + if (((!is_typed_hash_table(hash)) && (!is_pair(hash_table_procedures(hash))) && (!hash_chosen(hash))) || (use_write != P_READABLE)) + { + if (is_weak_hash_table(hash)) + port_write_string(port)(sc, "(weak-hash-table", 16, port); + else port_write_string(port)(sc, "(hash-table", 11, port); + } + else + { + hash_table_procedures_to_port(sc, hash, port, true, ci); + port_write_character(port)(sc, ')', port); + port_write_string(port)(sc, ") (copy (hash-table", 19, port); + copied = true; + } + for (s7_int i = 0; i < len; i++) + { + s7_pointer key_val = hash_table_iterate(sc, iterator); + port_write_character(port)(sc, ' ', port); + if ((use_write != P_READABLE) && (use_write != P_CODE) && (is_normal_symbol(car(key_val)))) + port_write_character(port)(sc, '\'', port); + object_to_port_with_circle_check(sc, car(key_val), port, NOT_P_DISPLAY(use_write), ci); + port_write_character(port)(sc, ' ', port); + object_to_port_with_circle_check(sc, cdr(key_val), port, NOT_P_DISPLAY(use_write), ci); + } + if (use_write != P_READABLE) + { + if (too_long) + port_write_string(port)(sc, " ...)", 5, port); + else port_write_character(port)(sc, ')', port); + }} + + if (use_write == P_READABLE) + { + if (copied) + { + if (!letd) + { + char buf[128]; + int32_t plen = catstrs_direct(buf, ") <", pos_int_to_str_direct(sc, href), ">", (const char *)NULL); + port_write_string(port)(sc, buf, plen, port); + } + else port_write_string(port)(sc, ") ))", 7, port); + } + else + if (letd) + port_write_string(port)(sc, ") )", 6, port); + else port_write_character(port)(sc, ')', port); + + if ((is_immutable_hash_table(hash)) && (!hash_cyclic) && (!is_typed_hash_table(hash))) + port_write_character(port)(sc, ')', port); + + if ((!immut) && (is_immutable_hash_table(hash)) && (!hash_cyclic)) + port_write_string(port)(sc, ") (immutable! ))", 19, port); + } + s7_gc_unprotect_at(sc, gc_iter); + iterator_current(iterator) = sc->nil; + free_cell(sc, p); /* free_cell(sc, iterator); */ /* 18-Dec-18 removed */ +} + +static void slot_list_to_port(s7_scheme *sc, s7_pointer slot, s7_pointer port, shared_info_t *ci, bool bindings) /* bindings=let/inlet choice */ +{ + bool first_time = true; + for (; tis_slot(slot); slot = next_slot(slot)) + { + if (bindings) + { + if (first_time) + { + port_write_character(port)(sc, '(', port); + first_time = false; + } + else port_write_string(port)(sc, " (", 2, port); + } + else port_write_character(port)(sc, ' ', port); + symbol_to_port(sc, slot_symbol(slot), port, (bindings) ? P_DISPLAY : P_KEY, NULL); /* (object->string (inlet (symbol "(\")") 1) :readable) */ + port_write_character(port)(sc, ' ', port); + object_to_port_with_circle_check(sc, slot_value(slot), port, P_READABLE, ci); + if (bindings) port_write_character(port)(sc, ')', port); + } +} + +static void slot_list_to_port_with_cycle(s7_scheme *sc, s7_pointer obj, s7_pointer slot, s7_pointer port, shared_info_t *ci, bool bindings) +{ + bool first_time = true; + for (; tis_slot(slot); slot = next_slot(slot)) + { + s7_pointer sym = slot_symbol(slot), val = slot_value(slot); + if (bindings) + { + if (first_time) + { + port_write_character(port)(sc, '(', port); + first_time = false; + } + else port_write_string(port)(sc, " (", 2, port); + } + else port_write_character(port)(sc, ' ', port); + symbol_to_port(sc, sym, port, (bindings) ? P_DISPLAY : P_KEY, NULL); + if (has_structure(val)) + { + char buf[128]; + int32_t symref; + int32_t len = catstrs_direct(buf, " (set! (<", pos_int_to_str_direct(sc, -peek_shared_ref(ci, obj)), "> ", (const char *)NULL); + port_write_string(port)(sc, " #f", 3, port); + port_write_string(ci->cycle_port)(sc, buf, len, ci->cycle_port); + symbol_to_port(sc, sym, ci->cycle_port, P_KEY, NULL); + + symref = peek_shared_ref(ci, val); + if (symref != 0) + { + if (symref < 0) symref = -symref; + len = catstrs_direct(buf, ") <", pos_int_to_str_direct(sc, symref), ">) ", (const char *)NULL); + port_write_string(ci->cycle_port)(sc, buf, len, ci->cycle_port); + } + else + { + port_write_string(ci->cycle_port)(sc, ") ", 2, ci->cycle_port); + object_to_port_with_circle_check(sc, val, ci->cycle_port, P_READABLE, ci); + port_write_string(ci->cycle_port)(sc, ") ", 2, ci->cycle_port); + }} + else + { + port_write_character(port)(sc, ' ', port); + object_to_port_with_circle_check(sc, val, port, P_READABLE, ci); + } + if (bindings) port_write_character(port)(sc, ')', port); + if (is_immutable(obj)) + { + char buf[128]; + int32_t len = catstrs_direct(buf, " (immutable! <", pos_int_to_str_direct(sc, -peek_shared_ref(ci, obj)), ">) ", (const char *)NULL); + port_write_string(ci->cycle_port)(sc, buf, len, ci->cycle_port); + }} +} + +static bool let_has_setter(s7_pointer obj) +{ + for (s7_pointer slot = let_slots(obj); tis_slot(slot); slot = next_slot(slot)) + if ((slot_has_setter(slot)) || (is_immutable_slot(slot))) + return(true); + return(false); +} + +static bool slot_setters_to_port(s7_scheme *sc, s7_pointer obj, s7_pointer port, shared_info_t *ci) +{ + bool spaced_out = false; + for (s7_pointer slot = let_slots(obj); tis_slot(slot); slot = next_slot(slot)) + if (slot_has_setter(slot)) + { + if (spaced_out) port_write_character(port)(sc, ' ', port); else spaced_out = true; + port_write_string(port)(sc, "(set! (setter '", 15, port); + symbol_to_port(sc, slot_symbol(slot), port, P_DISPLAY, NULL); + port_write_string(port)(sc, ") ", 2, port); + object_to_port_with_circle_check(sc, slot_setter(slot), port, P_READABLE, ci); + port_write_character(port)(sc, ')', port); + } + return(spaced_out); +} + +static void immutable_slots_to_port(s7_scheme *sc, s7_pointer obj, s7_pointer port, bool spaced_out) +{ + for (s7_pointer slot = let_slots(obj); tis_slot(slot); slot = next_slot(slot)) + if (is_immutable_slot(slot)) + { + if (spaced_out) port_write_character(port)(sc, ' ', port); else spaced_out = true; + port_write_string(port)(sc, "(immutable! '", 13, port); + symbol_to_port(sc, slot_symbol(slot), port, P_DISPLAY, NULL); + port_write_character(port)(sc, ')', port); + } +} + +static void slot_to_port(s7_scheme *sc, s7_pointer obj, s7_pointer port, use_write_t use_write, shared_info_t *ci) +{ + /* the slot symbol might need (symbol...) in which case we don't want the preceding quote */ + symbol_to_port(sc, slot_symbol(obj), port, P_READABLE, NULL); + port_write_character(port)(sc, ' ', port); + object_to_port_with_circle_check(sc, slot_value(obj), port, use_write, ci); +} + +static void let_to_port(s7_scheme *sc, s7_pointer obj, s7_pointer port, use_write_t use_write, shared_info_t *ci) +{ + /* if outer env points to (say) method list, the object needs to specialize object->string itself */ + + if (has_active_methods(sc, obj)) + { + s7_pointer print_func = find_method(sc, obj, sc->object_to_string_symbol); + if (print_func != sc->undefined) + { + s7_pointer p; + /* what needs to be protected here? for one, the function might not return a string! */ + + clear_has_methods(obj); + if ((use_write == P_WRITE) || (use_write == P_CODE)) + p = s7_apply_function(sc, print_func, set_plist_1(sc, obj)); + else p = s7_apply_function(sc, print_func, set_plist_2(sc, obj, (use_write == P_DISPLAY) ? sc->F : sc->readable_keyword)); + set_has_methods(obj); + + if ((is_string(p)) && + (string_length(p) > 0)) + port_write_string(port)(sc, string_value(p), string_length(p), port); + return; + }} + if (obj == sc->rootlet) {port_write_string(port)(sc, "(rootlet)", 9, port); return;} + if (obj == sc->s7_starlet) {port_write_string(port)(sc, "*s7*", 4, port); return;} + if (sc->short_print) {port_write_string(port)(sc, "#", 6, port); return;} + + /* circles can happen here: + * (let () (let ((b (curlet))) (curlet))): #> + * or (let ((b #f)) (set! b (curlet)) (curlet)): #1=# + */ + if (use_write == P_READABLE) + { + int32_t lref; + if ((ci) && + (is_cyclic(obj)) && + ((lref = peek_shared_ref(ci, obj)) != 0)) + { + if (lref < 0) lref = -lref; + if ((ci->defined[lref]) || (port == ci->cycle_port)) + { + char buf[128]; + int32_t len = (int32_t)catstrs_direct(buf, "<", pos_int_to_str_direct(sc, lref), ">", (const char *)NULL); + port_write_string(ci->cycle_port)(sc, buf, len, ci->cycle_port); + return; + } + if ((let_outlet(obj) != sc->nil) && + (let_outlet(obj) != sc->rootlet)) + { + char buf[128]; + int32_t len = (int32_t)catstrs_direct(buf, " (set! (outlet <", pos_int_to_str_direct(sc, lref), ">) ", (const char *)NULL); + port_write_string(ci->cycle_port)(sc, buf, len, ci->cycle_port); + let_to_port(sc, let_outlet(obj), ci->cycle_port, use_write, ci); + port_write_string(ci->cycle_port)(sc, ") ", 2, ci->cycle_port); + } + if (is_openlet(obj)) + port_write_string(port)(sc, "(openlet ", 9, port); + /* not immutable here because we'll need to set the let fields below, then declare it immutable */ + if (let_has_setter(obj)) /* both explicit setters and immutable slots */ + { + port_write_string(port)(sc, "(let (", 6, port); + slot_list_to_port_with_cycle(sc, obj, let_slots(obj), port, ci, true); + port_write_string(port)(sc, ") ", 2, port); + immutable_slots_to_port(sc, obj, port, slot_setters_to_port(sc, obj, port, ci)); + port_write_string(port)(sc, " (curlet))", 10, port); + } + else + { + port_write_string(port)(sc, "(inlet", 6, port); + slot_list_to_port_with_cycle(sc, obj, let_slots(obj), port, ci, false); + port_write_character(port)(sc, ')', port); + } + if (is_openlet(obj)) + port_write_character(port)(sc, ')', port); + } + else + { + if (is_openlet(obj)) + port_write_string(port)(sc, "(openlet ", 9, port); + if (is_immutable_let(obj)) + port_write_string(port)(sc, "(immutable! ", 12, port); + + /* this ignores outlet -- but is that a problem? */ + /* (object->string (let ((i 0)) (set! (setter 'i) integer?) (curlet)) :readable) -> "(let ((i 0)) (set! (setter 'i) #_integer?) (curlet))" */ + if (let_has_setter(obj)) + { + port_write_string(port)(sc, "(let (", 6, port); + slot_list_to_port(sc, let_slots(obj), port, ci, true); + port_write_string(port)(sc, ") ", 2, port); + immutable_slots_to_port(sc, obj, port, slot_setters_to_port(sc, obj, port, ci)); + /* perhaps set outlet here?? */ + port_write_string(port)(sc, " (curlet))", 10, port); + } + else + { + if ((let_outlet(obj) != sc->nil) && + (let_outlet(obj) != sc->rootlet)) + { + int32_t ref; + port_write_string(port)(sc, "(sublet ", 8, port); + if ((ci) && ((ref = peek_shared_ref(ci, let_outlet(obj))) < 0)) + { + char buf[128]; + int32_t len = (int32_t)catstrs_direct(buf, "<", pos_int_to_str_direct(sc, -ref), ">", (const char *)NULL); + port_write_string(port)(sc, buf, len, port); + } + else + { + s7_pointer name = let_ref(sc, obj, sc->class_name_symbol); + if (is_symbol(name)) + symbol_to_port(sc, name, port, P_DISPLAY, NULL); + else let_to_port(sc, let_outlet(obj), port, use_write, ci); + }} + else port_write_string(port)(sc, "(inlet", 6, port); + slot_list_to_port(sc, let_slots(obj), port, ci, false); + port_write_character(port)(sc, ')', port); + } + if (is_immutable_let(obj)) + port_write_character(port)(sc, ')', port); + if (is_openlet(obj)) + port_write_character(port)(sc, ')', port); + }} + else /* not readable write */ + { + s7_pointer slot = let_slots(obj); + port_write_string(port)(sc, "(inlet", 6, port); + for (int32_t i = 1; tis_slot(slot); i++, slot = next_slot(slot)) + { + port_write_character(port)(sc, ' ', port); + slot_to_port(sc, slot, port, use_write, ci); + if ((tis_slot(next_slot(slot))) && (i == sc->print_length)) + { + port_write_string(port)(sc, " ...", 4, port); + break; + }} + port_write_character(port)(sc, ')', port); + } +} + +static void write_macro_readably(s7_scheme *sc, s7_pointer obj, s7_pointer port) +{ + s7_pointer expr, body = closure_body(obj), arglist = closure_args(obj); + /* this doesn't handle recursive macros well -- we need letrec or the equivalent as in write_closure_readably */ + /* (letrec ((m2 (macro (x) `(if (> ,x 0) (m2 (- ,x 1)) 32)))) (object->string m2 :readable)) */ + + port_write_string(port)(sc, (is_either_macro(obj)) ? "(macro" : "(bacro", 6, port); + if ((is_macro_star(obj)) || (is_bacro_star(obj))) + port_write_character(port)(sc, '*', port); + if (is_symbol(arglist)) + { + port_write_character(port)(sc, ' ', port); + port_write_string(port)(sc, symbol_name(arglist), symbol_name_length(arglist), port); + port_write_character(port)(sc, ' ', port); + } + else + if (is_pair(arglist)) + { + port_write_string(port)(sc, " (", 2, port); + for (expr = arglist; is_pair(expr); expr = cdr(expr)) + { + object_to_port(sc, car(expr), port, P_WRITE, NULL); + if (is_pair(cdr(expr))) + port_write_character(port)(sc, ' ', port); + } + if (!is_null(expr)) + { + port_write_string(port)(sc, " . ", 3, port); + object_to_port(sc, expr, port, P_WRITE, NULL); + } + port_write_string(port)(sc, ") ", 2, port); + } + else port_write_string(port)(sc, " () ", 4, port); + + for (expr = body; is_pair(expr); expr = cdr(expr)) + object_to_port(sc, car(expr), port, P_WRITE, NULL); + port_write_character(port)(sc, ')', port); +} + + +static s7_pointer match_symbol(const s7_pointer symbol, s7_pointer e) +{ + for (s7_pointer le = e; is_let(le); le = let_outlet(le)) + for (s7_pointer y = let_slots(le); tis_slot(y); y = next_slot(y)) + if (slot_symbol(y) == symbol) + return(y); + return(NULL); +} + +static bool slot_memq(const s7_pointer symbol, s7_pointer symbols) +{ + for (s7_pointer x = symbols; is_pair(x); x = cdr(x)) + if (slot_symbol(car(x)) == symbol) + return(true); + return(false); +} + +static bool arg_memq(const s7_pointer symbol, s7_pointer args) +{ + for (s7_pointer x = args; is_pair(x); x = cdr(x)) + if ((car(x) == symbol) || + ((is_pair(car(x))) && + (caar(x) == symbol))) + return(true); + return(false); +} + +static void collect_symbol(s7_scheme *sc, s7_pointer sym, s7_pointer e, s7_pointer args, s7_int gc_loc) +{ + if ((!arg_memq(T_Sym(sym), args)) && + (!slot_memq(sym, gc_protected_at(sc, gc_loc)))) + { + s7_pointer slot = match_symbol(sym, e); + if (slot) + gc_protected_at(sc, gc_loc) = cons(sc, slot, gc_protected_at(sc, gc_loc)); + } +} + +static void collect_locals(s7_scheme *sc, s7_pointer body, s7_pointer e, s7_pointer args, s7_int gc_loc) /* currently called only in write_closure_readably */ +{ + if (is_pair(body)) + { + collect_locals(sc, car(body), e, args, gc_loc); + collect_locals(sc, cdr(body), e, args, gc_loc); + } + else + if (is_symbol(body)) + collect_symbol(sc, body, e, args, gc_loc); +} + +static void collect_specials(s7_scheme *sc, s7_pointer e, s7_pointer args, s7_int gc_loc) +{ + collect_symbol(sc, sc->local_signature_symbol, e, args, gc_loc); + collect_symbol(sc, sc->local_setter_symbol, e, args, gc_loc); + collect_symbol(sc, sc->local_documentation_symbol, e, args, gc_loc); + collect_symbol(sc, sc->local_iterator_symbol, e, args, gc_loc); +} + +static s7_pointer find_closure(s7_scheme *sc, s7_pointer closure, s7_pointer current_let) +{ + for (s7_pointer e = current_let; is_let(e); e = let_outlet(e)) + { + if ((is_funclet(e)) || (is_maclet(e))) + { + s7_pointer sym = funclet_function(e); + const s7_pointer f = s7_symbol_local_value(sc, sym, e); + if (f == closure) + return(sym); + } + for (s7_pointer y = let_slots(e); tis_slot(y); y = next_slot(y)) + if (slot_value(y) == closure) + return(slot_symbol(y)); + } + if ((is_any_macro(closure)) && /* can't be a c_macro here */ + (has_pair_macro(closure))) /* maybe macro never called, so no maclet exists */ + return(pair_macro(closure_body(closure))); + return(sc->nil); +} + +static void write_closure_name(s7_scheme *sc, s7_pointer closure, s7_pointer port) +{ + s7_pointer x = find_closure(sc, closure, closure_let(closure)); + if (is_symbol(x)) + { + port_write_string(port)(sc, symbol_name(x), symbol_name_length(x), port); + return; + } + switch (type(closure)) + { + case T_CLOSURE: port_write_string(port)(sc, "#", 3, port); + else + { + s7_pointer args = closure_args(closure); + if (is_symbol(args)) + { + port_write_string(port)(sc, symbol_name(args), symbol_name_length(args), port); + port_write_character(port)(sc, '>', port); /* (lambda a a) -> # */ + } + else + { + port_write_character(port)(sc, '(', port); + x = car(args); + if (is_pair(x)) x = car(x); + port_write_string(port)(sc, symbol_name(x), symbol_name_length(x), port); + if (!is_null(cdr(args))) + { + s7_pointer y; + port_write_character(port)(sc, ' ', port); + if (is_pair(cdr(args))) + { + y = cadr(args); + if (is_pair(y)) + y = car(y); + else + if (y == sc->rest_keyword) + { + port_write_string(port)(sc, ":rest ", 6, port); + args = cdr(args); + y = cadr(args); + if (is_pair(y)) y = car(y); + }} + else + { + port_write_string(port)(sc, ". ", 2, port); + y = cdr(args); + } + port_write_string(port)(sc, symbol_name(y), symbol_name_length(y), port); + if ((is_pair(cdr(args))) && + (!is_null(cddr(args)))) + port_write_string(port)(sc, " ...", 4, port); + } + port_write_string(port)(sc, ")>", 2, port); + }} +} + +static s7_pointer closure_name(s7_scheme *sc, s7_pointer closure) +{ + /* this is used by the error handlers to get the current function name */ + s7_pointer x = find_closure(sc, closure, sc->curlet); + if (is_symbol(x)) + return(x); + if (is_pair(current_code(sc))) + return(current_code(sc)); + return(closure); /* desperation -- the parameter list (caar here) will cause endless confusion in OP_APPLY errors! */ +} + +static s7_pointer pair_append(s7_scheme *sc, s7_pointer a, s7_pointer b) +{ + s7_pointer p = cdr(a), tp; + gc_protect_via_stack(sc, b); + if (is_null(p)) + tp = cons(sc, car(a), b); + else + { + s7_pointer np; + tp = list_1(sc, car(a)); + set_stack_protected2(sc, tp); + for (np = tp; is_pair(p); p = cdr(p), np = cdr(np)) + set_cdr(np, list_1(sc, car(p))); + set_cdr(np, b); + } + unstack(sc); + return(tp); +} + +static void write_closure_readably_1(s7_scheme *sc, s7_pointer obj, s7_pointer arglist, s7_pointer body, s7_pointer port) +{ + s7_int old_print_length = sc->print_length; + + if (type(obj) == T_CLOSURE_STAR) + port_write_string(port)(sc, "(lambda* ", 9, port); + else port_write_string(port)(sc, "(lambda ", 8, port); + + if ((is_pair(arglist)) && + (allows_other_keys(arglist))) + { + sc->temp9 = (is_null(cdr(arglist))) ? + set_plist_2(sc, car(arglist), sc->allow_other_keys_keyword) : + pair_append(sc, arglist, list_1(sc, sc->allow_other_keys_keyword)); + object_to_port(sc, sc->temp9, port, P_WRITE, NULL); + sc->temp9 = sc->unused; + } + else object_to_port(sc, arglist, port, P_WRITE, NULL); /* here we just want the straight output (a b) not (list 'a 'b) */ + + sc->print_length = 1048576; + for (s7_pointer p = body; is_pair(p); p = cdr(p)) + { + port_write_character(port)(sc, ' ', port); + object_to_port(sc, car(p), port, P_WRITE, NULL); + } + port_write_character(port)(sc, ')', port); + sc->print_length = old_print_length; +} + +static void write_closure_readably(s7_scheme *sc, s7_pointer obj, s7_pointer port, shared_info_t *ci) +{ + s7_pointer body = closure_body(obj); + s7_pointer arglist = closure_args(obj); + s7_pointer pe, local_slots, setter = NULL, obj_slot = NULL; + s7_int gc_loc; + bool sent_let = false, sent_letrec = false; + + if (sc->safety > NO_SAFETY) + { + if (tree_is_cyclic(sc, body)) + { + port_write_string(port)(sc, "#", 32, port); /* not s7_error here! */ + return; + } + /* perhaps: if any sequence in the closure_body is cyclic, complain, but how to check without clobbering ci? + * perhaps pass ci, and use make_shared_info if ci=null else continue_shared_info? + * this can happen only if (apply lambda ... cyclic-seq ...) I think + * long-term we need to include closure_body(obj) in the top object_out make_shared_info + */ + } + if (is_symbol(arglist)) arglist = set_dlist_1(sc, arglist); + pe = closure_let(obj); + + gc_loc = gc_protect_1(sc, sc->nil); + collect_locals(sc, body, pe, arglist, gc_loc); /* collect locals used only here (and below) */ + collect_specials(sc, pe, arglist, gc_loc); + + if (s7_is_dilambda(obj)) + { + setter = closure_setter(obj); + if (has_closure_let(setter)) /* collect args etc so need the arglist */ + { + arglist = closure_args(setter); + if (is_symbol(arglist)) arglist = set_dlist_1(sc, arglist); + collect_locals(sc, closure_body(setter), pe, arglist, gc_loc); + }} + + local_slots = T_Lst(gc_protected_at(sc, gc_loc)); /* possibly a list of slots */ + if (!is_null(local_slots)) + { + /* if (let|letrec ((f (lambda () f))) (object->string f :readable)), local_slots: ('f f) */ + /* but we can't handle it below because that leads to an infinite loop */ + for (s7_pointer x = local_slots; is_pair(x); x = cdr(x)) + { + s7_pointer slot = car(x); + if ((!is_any_closure(slot_value(slot))) && /* mutually referencing closures? ./snd -l snd-test 24 hits this in the effects dialogs */ + ((!has_structure(slot_value(slot))) || /* see s7test example, vector has closure that refers to vector */ + (slot_symbol(slot) == sc->local_signature_symbol))) + { + if (!sent_let) + { + port_write_string(port)(sc, "(let (", 6, port); + sent_let = true; + } + port_write_character(port)(sc, '(', port); + port_write_string(port)(sc, symbol_name(slot_symbol(slot)), symbol_name_length(slot_symbol(slot)), port); + port_write_character(port)(sc, ' ', port); + /* (object->string (list (let ((local 1)) (lambda (x) (+ x local)))) :readable) */ + object_to_port(sc, slot_value(slot), port, P_READABLE, NULL); + if (is_null(cdr(x))) + port_write_character(port)(sc, ')', port); + else port_write_string(port)(sc, ") ", 2, port); + }} + if (sent_let) port_write_string(port)(sc, ") ", 2, port); + } + + /* now we need to know if obj is in the closure_let via letrec, and if so, send out letrec+obj name+def below, then close it with obj-name?? + * the two cases are: (let ((f (lambda () f)))...) which is ok now, and (letrec ((f (lambda () f)))...) which needs the letrec + */ + if (!is_null(local_slots)) + for (s7_pointer x = local_slots; is_pair(x); x = cdr(x)) + { + s7_pointer slot = car(x); + if ((is_any_closure(slot_value(slot))) && + (slot_value(slot) == obj)) + { + port_write_string(port)(sc, "(letrec ((", 10, port); /* (letrec ((f (lambda () f))) f) */ + sent_letrec = true; + port_write_string(port)(sc, symbol_name(slot_symbol(slot)), symbol_name_length(slot_symbol(slot)), port); + port_write_character(port)(sc, ' ', port); + obj_slot = slot; + break; + }} + + if (setter) + port_write_string(port)(sc, "(dilambda ", 10, port); + + write_closure_readably_1(sc, obj, closure_args(obj), body, port); + + if (setter) + { + port_write_character(port)(sc, ' ', port); + if (has_closure_let(setter)) + write_closure_readably_1(sc, setter, closure_args(setter), closure_body(setter), port); + else object_to_port_with_circle_check(sc, setter, port, P_READABLE, ci); + port_write_character(port)(sc, ')', port); + } + if (sent_letrec) + { + port_write_string(port)(sc, ")) ", 3, port); + port_write_string(port)(sc, symbol_name(slot_symbol(obj_slot)), symbol_name_length(slot_symbol(obj_slot)), port); + port_write_character(port)(sc, ')', port); + } + + if (sent_let) + port_write_character(port)(sc, ')', port); + + s7_gc_unprotect_at(sc, gc_loc); +} + +static void iterator_to_port(s7_scheme *sc, s7_pointer obj, s7_pointer port, use_write_t use_write, shared_info_t *ci) +{ + if (use_write == P_READABLE) + { + if (iterator_is_at_end(obj)) + { + switch (type(iterator_sequence(obj))) + { + case T_NIL: + case T_PAIR: port_write_string(port)(sc, "(make-iterator ())", 18, port); break; + case T_STRING: port_write_string(port)(sc, "(make-iterator \"\")", 18, port); break; + case T_BYTE_VECTOR: port_write_string(port)(sc, "(make-iterator #u())", 20, port); break; + case T_VECTOR: port_write_string(port)(sc, "(make-iterator #())", 19, port); break; + case T_INT_VECTOR: port_write_string(port)(sc, "(make-iterator #i())", 20, port); break; + case T_FLOAT_VECTOR: port_write_string(port)(sc, "(make-iterator #r())", 20, port); break; + case T_LET: port_write_string(port)(sc, "(make-iterator (inlet))", 23, port); break; + + case T_HASH_TABLE: + if (is_weak_hash_table(iterator_sequence(obj))) + port_write_string(port)(sc, "(make-iterator (weak-hash-table))", 33, port); + else port_write_string(port)(sc, "(make-iterator (hash-table))", 28, port); + break; + + default: + port_write_string(port)(sc, "(make-iterator ())", 18, port); break; /* c-object?? function? */ + }} + else + { + s7_pointer seq = iterator_sequence(obj); + int32_t iter_ref; + if ((ci) && + (is_cyclic(obj)) && + ((iter_ref = peek_shared_ref(ci, obj)) != 0)) + { + /* basically the same as c_pointer_to_port */ + if (!is_cyclic_set(obj)) + { + int32_t nlen; + char buf[128]; + if (iter_ref < 0) iter_ref = -iter_ref; + + if (ci->init_port == sc->F) + { + ci->init_port = s7_open_output_string(sc); + ci->init_loc = gc_protect_1(sc, ci->init_port); + } + port_write_string(port)(sc, "#f", 2, port); + nlen = (int32_t)catstrs_direct(buf, " (set! <", pos_int_to_str_direct(sc, iter_ref), "> (make-iterator ", (const char *)NULL); + port_write_string(ci->init_port)(sc, buf, nlen, ci->init_port); + + flip_ref(ci, seq); + object_to_port_with_circle_check(sc, seq, ci->init_port, use_write, ci); + flip_ref(ci, seq); + + port_write_string(ci->init_port)(sc, "))\n", 3, ci->init_port); + set_cyclic_set(obj); + return; + }} + + if (is_string(seq)) + { + s7_int len = string_length(seq) - iterator_position(obj); + if (len == 0) + port_write_string(port)(sc, "(make-iterator \"\")", 18, port); + else + { + const char *iter_str = (const char *)(string_value(seq) + iterator_position(obj)); + port_write_string(port)(sc, "(make-iterator \"", 16, port); + if (!string_needs_slashification((const uint8_t *)iter_str, len)) + port_write_string(port)(sc, iter_str, len, port); + else slashify_string_to_port(sc, port, iter_str, len, NOT_IN_QUOTES); + port_write_string(port)(sc, "\")", 2, port); + }} + else + { + if (is_pair(seq)) + { + port_write_string(port)(sc, "(make-iterator ", 15, port); + object_to_port_with_circle_check(sc, iterator_current(obj), port, use_write, ci); + port_write_character(port)(sc, ')', port); + } + else + { + if ((is_let(seq)) && (seq != sc->rootlet) && (seq != sc->s7_starlet)) + { + port_write_string(port)(sc, "(let ((iter (make-iterator ", 27, port); + object_to_port_with_circle_check(sc, seq, port, use_write, ci); + port_write_string(port)(sc, "))) ", 4, port); + for (s7_pointer slot = let_slots(seq); slot != iterator_current_slot(obj); slot = next_slot(slot)) + port_write_string(port)(sc, "(iter) ", 7, port); + port_write_string(port)(sc, "iter)", 5, port); + } + else + { + if (iterator_position(obj) > 0) + port_write_string(port)(sc, "(let ((iter (make-iterator ", 27, port); + else port_write_string(port)(sc, "(make-iterator ", 15, port); + object_to_port_with_circle_check(sc, seq, port, use_write, ci); + if (iterator_position(obj) > 0) + { + if (iterator_position(obj) == 1) + port_write_string(port)(sc, "))) (iter) iter)", 16, port); + else + { + char str[128]; + int32_t nlen = (int32_t)catstrs_direct(str, "))) (do ((i 0 (+ i 1))) ((= i ", + pos_int_to_str_direct(sc, iterator_position(obj)), + ") iter) (iter)))", (const char *)NULL); + port_write_string(port)(sc, str, nlen, port); + }} + else port_write_character(port)(sc, ')', port); + }}}}} + else + { + const char *str; + if ((is_hash_table(iterator_sequence(obj))) && (is_weak_hash_table(iterator_sequence(obj)))) + str = "weak-hash-table"; + else str = type_name(sc, iterator_sequence(obj), NO_ARTICLE); + port_write_string(port)(sc, "#', port); + } +} + +static void c_pointer_to_port(s7_scheme *sc, s7_pointer obj, s7_pointer port, use_write_t use_write, shared_info_t *ci) +{ + #define CP_BUFSIZE 128 + char buf[CP_BUFSIZE]; + int32_t nlen; + /* c-pointer is special because we can't set the type or info fields from scheme except via the c-pointer function */ + + if (use_write == P_READABLE) + { + int32_t ref; + if ((ci) && + (is_cyclic(obj)) && + ((ref = peek_shared_ref(ci, obj)) != 0)) + { + port_write_string(port)(sc, "#f", 2, port); + if (!is_cyclic_set(obj)) + { + if (ci->init_port == sc->F) + { + ci->init_port = s7_open_output_string(sc); + ci->init_loc = gc_protect_1(sc, ci->init_port); + } + nlen = snprintf(buf, CP_BUFSIZE, " (set! <%d> (c-pointer %" p64, -ref, (intptr_t)c_pointer(obj)); + port_write_string(ci->init_port)(sc, buf, nlen, ci->init_port); + + if ((c_pointer_type(obj) != sc->F) || + (c_pointer_info(obj) != sc->F)) + { + flip_ref(ci, c_pointer_type(obj)); + + port_write_character(ci->init_port)(sc, ' ', ci->init_port); + object_to_port_with_circle_check(sc, c_pointer_type(obj), ci->init_port, use_write, ci); + + flip_ref(ci, c_pointer_type(obj)); + flip_ref(ci, c_pointer_info(obj)); + + port_write_character(ci->init_port)(sc, ' ', ci->init_port); + object_to_port_with_circle_check(sc, c_pointer_info(obj), ci->init_port, use_write, ci); + + flip_ref(ci, c_pointer_info(obj)); + } + port_write_string(ci->init_port)(sc, "))\n", 3, ci->init_port); + set_cyclic_set(obj); + }} + else + { + nlen = snprintf(buf, CP_BUFSIZE, "(c-pointer %" p64, (intptr_t)c_pointer(obj)); + port_write_string(port)(sc, buf, clamp_length(nlen, CP_BUFSIZE), port); + if ((c_pointer_type(obj) != sc->F) || + (c_pointer_info(obj) != sc->F)) + { + port_write_character(port)(sc, ' ', port); + object_to_port_with_circle_check(sc, c_pointer_type(obj), port, use_write, ci); + port_write_character(port)(sc, ' ', port); + object_to_port_with_circle_check(sc, c_pointer_info(obj), port, use_write, ci); + } + port_write_character(port)(sc, ')', port); + }} + else + { + if ((is_symbol(c_pointer_type(obj))) && + (symbol_name_length(c_pointer_type(obj)) < (CP_BUFSIZE / 2))) + nlen = snprintf(buf, CP_BUFSIZE, "#<%s %p>", symbol_name(c_pointer_type(obj)), c_pointer(obj)); + else nlen = snprintf(buf, CP_BUFSIZE, "#", c_pointer(obj)); + port_write_string(port)(sc, buf, clamp_length(nlen, CP_BUFSIZE), port); + } +} + +static void random_state_to_port(s7_scheme *sc, s7_pointer obj, s7_pointer port, use_write_t use_write, shared_info_t *unused_ci) +{ + #define B_BUFSIZE 128 + char buf[B_BUFSIZE]; + int32_t nlen; +#if WITH_GMP + if (use_write == P_READABLE) + nlen = snprintf(buf, B_BUFSIZE, "#"); + else nlen = snprintf(buf, B_BUFSIZE, "#", obj); +#else + if (use_write == P_READABLE) + nlen = snprintf(buf, B_BUFSIZE, "(random-state %" PRIu64 " %" PRIu64 ")", random_seed(obj), random_carry(obj)); + else nlen = snprintf(buf, B_BUFSIZE, "#", random_seed(obj), random_carry(obj)); +#endif + port_write_string(port)(sc, buf, clamp_length(nlen, B_BUFSIZE), port); +} + +static void display_fallback(s7_scheme *sc, s7_pointer obj, s7_pointer port, use_write_t unused_use_write, shared_info_t *unused_ci) +{ +#if S7_DEBUGGING + print_debugging_state(sc, obj, port); +#else + if (is_free(obj)) + port_write_string(port)(sc, "", 12, port); + else port_write_string(port)(sc, "", 17, port); +#endif +} + +static void unique_to_port(s7_scheme *sc, s7_pointer obj, s7_pointer port, use_write_t unused_use_write, shared_info_t *unused_ci) +{ + port_write_string(port)(sc, unique_name(obj), unique_name_length(obj), port); +} + +static void undefined_to_port(s7_scheme *sc, s7_pointer obj, s7_pointer port, use_write_t use_write, shared_info_t *unused_ci) +{ + if ((obj != sc->undefined) && + (use_write == P_READABLE)) + { + port_write_string(port)(sc, "(with-input-from-string \"", 25, port); + port_write_string(port)(sc, undefined_name(obj), undefined_name_length(obj), port); + port_write_string(port)(sc, "\" read)", 7, port); + } + else port_write_string(port)(sc, undefined_name(obj), undefined_name_length(obj), port); +} + +static void eof_to_port(s7_scheme *sc, s7_pointer obj, s7_pointer port, use_write_t use_write, shared_info_t *unused_ci) +{ + if (use_write == P_READABLE) + port_write_string(port)(sc, "(begin #)", 14, port); + else port_write_string(port)(sc, eof_name(obj), eof_name_length(obj), port); +} + +static void counter_to_port(s7_scheme *sc, s7_pointer unused_obj, s7_pointer port, use_write_t unused_use_write, shared_info_t *unused_ci) +{ + port_write_string(port)(sc, "#", 10, port); +} + +static void integer_to_port(s7_scheme *sc, s7_pointer obj, s7_pointer port, use_write_t unused_use_write, shared_info_t *unused_ci) +{ + if (has_number_name(obj)) + { + if (is_string_port(port)) + { + if (port_position(port) + number_name_length(obj) < port_data_size(port)) + { + memcpy((void *)(port_data(port) + port_position(port)), (void *)number_name(obj), number_name_length(obj)); + port_position(port) += number_name_length(obj); + } + else string_write_string_resized(sc, number_name(obj), number_name_length(obj), port); + } + else port_write_string(port)(sc, number_name(obj), number_name_length(obj), port); + } + else + { + s7_int nlen = 0; + const char *str = integer_to_string(sc, integer(obj), &nlen); + set_number_name(obj, str, nlen); + port_write_string(port)(sc, str, nlen, port); + } +} + +static void number_to_port(s7_scheme *sc, s7_pointer obj, s7_pointer port, use_write_t use_write, shared_info_t *unused_ci) +{ + if (has_number_name(obj)) + port_write_string(port)(sc, number_name(obj), number_name_length(obj), port); + else + { + s7_int nlen = 0; + char *str = number_to_string_base_10(sc, obj, 0, sc->float_format_precision, 'g', &nlen, use_write); /* was 14 */ + if ((nlen < NUMBER_NAME_SIZE) && + (str[0] != 'n') && (str[0] != 'i') && + ((!(is_t_complex(obj))) || + ((!is_NaN(imag_part(obj))) && (!is_inf(imag_part(obj)))))) + set_number_name(obj, str, nlen); + port_write_string(port)(sc, str, nlen, port); + } +} + +#if WITH_GMP +static void big_number_to_port(s7_scheme *sc, s7_pointer obj, s7_pointer port, use_write_t use_write, shared_info_t *unused_ci) +{ + s7_int nlen = 0; + block_t *str = big_number_to_string_with_radix(sc, obj, BASE_10, 0, &nlen, use_write); + port_write_string(port)(sc, (char *)block_data(str), nlen, port); + liberate(sc, str); +} +#endif + +static void syntax_to_port(s7_scheme *sc, s7_pointer obj, s7_pointer port, use_write_t unused_use_write, shared_info_t *unused_ci) +{ + port_display(port)(sc, symbol_name(syntax_symbol(obj)), port); +} + +static void character_to_port(s7_scheme *sc, s7_pointer obj, s7_pointer port, use_write_t use_write, shared_info_t *unused_ci) +{ + if (use_write == P_DISPLAY) + port_write_character(port)(sc, character(obj), port); + else port_write_string(port)(sc, character_name(obj), character_name_length(obj), port); +} + +static void closure_to_port(s7_scheme *sc, s7_pointer obj, s7_pointer port, use_write_t use_write, shared_info_t *ci) +{ + if (has_active_methods(sc, obj)) + { + /* look for object->string method else fallback on ordinary case. + * can't use recursion on closure_let here because then the fallback name is #. + * this is tricky!: (display (openlet (with-let (mock-c-pointer 0) (lambda () 1)))) + * calls object->string on the closure whose closure_let is the mock-c-pointer; + * it has an object->string method that clears mock-c-pointers and tries again... + * so, display methods need to use coverlet/openlet. + */ + s7_pointer print_func = find_method(sc, closure_let(obj), sc->object_to_string_symbol); + if (print_func != sc->undefined) + { + s7_pointer p = s7_apply_function(sc, print_func, set_plist_1(sc, obj)); + if (string_length(p) > 0) + port_write_string(port)(sc, string_value(p), string_length(p), port); + return; + }} + if (use_write == P_READABLE) + write_closure_readably(sc, obj, port, ci); + else write_closure_name(sc, obj, port); +} + +static void macro_to_port(s7_scheme *sc, s7_pointer obj, s7_pointer port, use_write_t use_write, shared_info_t *unused_ci) +{ + if (has_active_methods(sc, obj)) + { + s7_pointer print_func = find_method(sc, closure_let(obj), sc->object_to_string_symbol); + if (print_func != sc->undefined) + { + s7_pointer p = s7_apply_function(sc, print_func, set_plist_1(sc, obj)); + if (string_length(p) > 0) + port_write_string(port)(sc, string_value(p), string_length(p), port); + return; + }} + if (use_write == P_READABLE) + write_macro_readably(sc, obj, port); + else write_closure_name(sc, obj, port); +} + +static void c_function_to_port(s7_scheme *sc, s7_pointer obj, s7_pointer port, use_write_t use_write, shared_info_t *unused_ci) +{ + s7_pointer sym = c_function_name_to_symbol(sc, obj); + if ((!is_global(sym)) && + (is_slot(initial_slot(sym))) && + ((use_write == P_READABLE) || (lookup(sc, sym) != initial_value(sym)))) + { + port_write_string(port)(sc, "#_", 2, port); + port_write_string(port)(sc, c_function_name(obj), c_function_name_length(obj), port); + return; + } + if (c_function_name_length(obj) > 0) + port_write_string(port)(sc, c_function_name(obj), c_function_name_length(obj), port); + else port_write_string(port)(sc, "#", 13, port); +} + +static void c_macro_to_port(s7_scheme *sc, s7_pointer obj, s7_pointer port, use_write_t unused_use_write, shared_info_t *unused_ci) +{ + if (c_macro_name_length(obj) > 0) + port_write_string(port)(sc, c_macro_name(obj), c_macro_name_length(obj), port); + else port_write_string(port)(sc, "#", 10, port); +} + +static void continuation_to_port(s7_scheme *sc, s7_pointer obj, s7_pointer port, use_write_t unused_use_write, shared_info_t *unused_ci) +{ + if (is_symbol(continuation_name(obj))) + { + port_write_string(port)(sc, "#', port); + } + else port_write_string(port)(sc, "#", 15, port); +} + +static void goto_to_port(s7_scheme *sc, s7_pointer obj, s7_pointer port, use_write_t unused_use_write, shared_info_t *unused_ci) +{ + if (is_symbol(call_exit_name(obj))) + { + port_write_string(port)(sc, "#', port); + } + else port_write_string(port)(sc, "#", 7, port); +} + +static void catch_to_port(s7_scheme *sc, s7_pointer unused_obj, s7_pointer port, use_write_t unused_use_write, shared_info_t *unused_ci) +{ + port_write_string(port)(sc, "#", 8, port); +} + +static void dynamic_wind_to_port(s7_scheme *sc, s7_pointer unused_obj, s7_pointer port, use_write_t unused_use_write, shared_info_t *unused_ci) +{ + /* this can happen because (*s7* 'stack) can involve dynamic-wind markers */ + port_write_string(port)(sc, "#", 15, port); +} + +static void c_object_name_to_port(s7_scheme *sc, s7_pointer obj, s7_pointer port) +{ + port_write_string(port)(sc, string_value(c_object_scheme_name(sc, obj)), string_length(c_object_scheme_name(sc, obj)), port); +} + +static void c_object_to_port(s7_scheme *sc, s7_pointer obj, s7_pointer port, use_write_t use_write, shared_info_t *ci) +{ +#if (!DISABLE_DEPRECATED) + if (c_object_print(sc, obj)) + { + char *str = ((*(c_object_print(sc, obj)))(sc, c_object_value(obj))); + port_display(port)(sc, str, port); + free(str); + return; + } +#endif + if (c_object_to_string(sc, obj)) /* plist here and below can clobber args if SHOW_EVAL_ARGS */ + port_display(port)(sc, s7_string((*(c_object_to_string(sc, obj)))(sc, set_mlist_2(sc, obj, (use_write == P_READABLE) ? sc->readable_keyword : sc->T))), port); + else + { + if ((use_write == P_READABLE) && + (c_object_to_list(sc, obj)) && /* to_list and (implicit) set are needed to reconstruct a cyclic c-object, as well as the maker (via type name) */ + (c_object_set(sc, obj))) + { + int32_t href; + s7_pointer old_w = sc->w; + s7_pointer obj_list = ((*(c_object_to_list(sc, obj)))(sc, set_mlist_1(sc, obj))); + s7_pointer p = obj_list; + sc->w = obj_list; + if ((ci) && + (is_cyclic(obj)) && + ((href = peek_shared_ref(ci, obj)) != 0)) + { + if (href < 0) href = -href; + if ((ci->defined[href]) || (port == ci->cycle_port)) + { + char buf[128]; + int32_t nlen = catstrs_direct(buf, "<", pos_int_to_str_direct(sc, href), ">", (const char *)NULL); + port_write_string(port)(sc, buf, nlen, port); + return; + } + port_write_character(port)(sc, '(', port); + c_object_name_to_port(sc, obj, port); + for (int32_t i = 0; is_pair(p); i++, p = cdr(p)) + { + s7_pointer val = car(p); + if (has_structure(val)) + { + char buf[128]; + int32_t symref; + int32_t len = (int32_t)catstrs_direct(buf, " (set! (<", pos_int_to_str_direct(sc, href), "> ", pos_int_to_str_direct_1(sc, i), ") ", (const char *)NULL); + port_write_string(port)(sc, " #f", 3, port); + port_write_string(ci->cycle_port)(sc, buf, len, ci->cycle_port); + + symref = peek_shared_ref(ci, val); + if (symref != 0) + { + if (symref < 0) symref = -symref; + len = (int32_t)catstrs_direct(buf, "<", pos_int_to_str_direct(sc, symref), ">)\n", (const char *)NULL); + port_write_string(ci->cycle_port)(sc, buf, len, ci->cycle_port); + } + else + { + object_to_port_with_circle_check(sc, val, ci->cycle_port, P_READABLE, ci); + port_write_string(ci->cycle_port)(sc, ")\n", 2, ci->cycle_port); + }} + else + { + port_write_character(port)(sc, ' ', port); + object_to_port_with_circle_check(sc, val, port, P_READABLE, ci); + }}} + else + { + port_write_character(port)(sc, '(', port); + c_object_name_to_port(sc, obj, port); + for (p = obj_list; is_pair(p); p = cdr(p)) + { + s7_pointer val = car(p); + port_write_character(port)(sc, ' ', port); + object_to_port_with_circle_check(sc, val, port, P_READABLE, ci); + }} + port_write_character(port)(sc, ')', port); + sc->w = old_w; + } + else + { + char buf[128]; + int32_t nlen; + port_write_string(port)(sc, "#<", 2, port); + c_object_name_to_port(sc, obj, port); + nlen = snprintf(buf, 128, " %p>", obj); + port_write_string(port)(sc, buf, clamp_length(nlen, 128), port); + }} +} + +static void stack_to_port(s7_scheme *sc, const s7_pointer obj, s7_pointer port, use_write_t unused_use_write, shared_info_t *unused_ci) +{ + if (obj == sc->stack) + port_write_string(port)(sc, "#", 16, port); + else port_write_string(port)(sc, "#", 8, port); +} + +static void init_display_functions(void) +{ + for (int32_t i = 0; i < 256; i++) display_functions[i] = display_fallback; + display_functions[T_FLOAT_VECTOR] = float_vector_to_port; + display_functions[T_INT_VECTOR] = int_vector_to_port; + display_functions[T_BYTE_VECTOR] = byte_vector_to_port; + display_functions[T_VECTOR] = vector_to_port; + display_functions[T_PAIR] = pair_to_port; + display_functions[T_HASH_TABLE] = hash_table_to_port; + display_functions[T_ITERATOR] = iterator_to_port; + display_functions[T_LET] = let_to_port; + display_functions[T_BOOLEAN] = unique_to_port; + display_functions[T_NIL] = unique_to_port; + display_functions[T_UNUSED] = unique_to_port; + display_functions[T_UNSPECIFIED] = unique_to_port; + display_functions[T_UNDEFINED] = undefined_to_port; + display_functions[T_EOF] = eof_to_port; + display_functions[T_INPUT_PORT] = input_port_to_port; + display_functions[T_OUTPUT_PORT] = output_port_to_port; + display_functions[T_COUNTER] = counter_to_port; + display_functions[T_STACK] = stack_to_port; + display_functions[T_INTEGER] = integer_to_port; + display_functions[T_RATIO] = number_to_port; + display_functions[T_REAL] = number_to_port; + display_functions[T_COMPLEX] = number_to_port; +#if WITH_GMP + display_functions[T_BIG_INTEGER] = big_number_to_port; + display_functions[T_BIG_RATIO] = big_number_to_port; + display_functions[T_BIG_REAL] = big_number_to_port; + display_functions[T_BIG_COMPLEX] = big_number_to_port; +#endif + display_functions[T_SYMBOL] = symbol_to_port; + display_functions[T_SYNTAX] = syntax_to_port; + display_functions[T_STRING] = string_to_port; + display_functions[T_CHARACTER] = character_to_port; + display_functions[T_CLOSURE] = closure_to_port; + display_functions[T_CLOSURE_STAR] = closure_to_port; + display_functions[T_MACRO] = macro_to_port; + display_functions[T_MACRO_STAR] = macro_to_port; + display_functions[T_BACRO] = macro_to_port; + display_functions[T_BACRO_STAR] = macro_to_port; + display_functions[T_C_RST_NO_REQ_FUNCTION] = c_function_to_port; + display_functions[T_C_FUNCTION] = c_function_to_port; + display_functions[T_C_FUNCTION_STAR] = c_function_to_port; + display_functions[T_C_MACRO] = c_macro_to_port; + display_functions[T_C_POINTER] = c_pointer_to_port; + display_functions[T_RANDOM_STATE] = random_state_to_port; + display_functions[T_CONTINUATION] = continuation_to_port; + display_functions[T_GOTO] = goto_to_port; + display_functions[T_CATCH] = catch_to_port; + display_functions[T_DYNAMIC_WIND] = dynamic_wind_to_port; + display_functions[T_C_OBJECT] = c_object_to_port; + display_functions[T_SLOT] = slot_to_port; +} + +static void object_to_port_with_circle_check_1(s7_scheme *sc, s7_pointer vr, s7_pointer port, use_write_t use_write, shared_info_t *ci) +{ + int32_t ref = (is_collected(vr)) ? shared_ref(ci, vr) : 0; + if (ref == 0) + object_to_port(sc, vr, port, use_write, ci); + else + { + char buf[32]; + int32_t nlen; + if (ref > 0) + { + if (use_write == P_READABLE) + { + if (ci->defined[ref]) + { + flip_ref(ci, vr); + nlen = (int32_t)catstrs_direct(buf, "<", pos_int_to_str_direct(sc, ref), ">", (const char *)NULL); + port_write_string(port)(sc, buf, nlen, port); + return; + } + object_to_port(sc, vr, port, P_READABLE, ci); + } + else + { /* "normal" printout involving #n= and #n# */ + s7_int len = 0; + char *p = pos_int_to_str(sc, (s7_int)ref, &len, '='); + *--p = '#'; + port_write_string(port)(sc, p, len, port); + object_to_port(sc, vr, port, NOT_P_DISPLAY(use_write), ci); + }} + else + if (use_write == P_READABLE) + { + nlen = (int32_t)catstrs_direct(buf, "<", pos_int_to_str_direct(sc, -ref), ">", (const char *)NULL); + port_write_string(port)(sc, buf, nlen, port); + } + else + { + s7_int len = 0; + char *p = pos_int_to_str(sc, (s7_int)(-ref), &len, '#'); + *--p = '#'; + port_write_string(port)(sc, p, len, port); + }} +} + +static s7_pointer cyclic_out(s7_scheme *sc, s7_pointer obj, s7_pointer port, shared_info_t *ci) +{ + int32_t ref, len; + char buf[128]; + + ci->cycle_port = s7_open_output_string(sc); + ci->cycle_loc = gc_protect_1(sc, ci->cycle_port); + + port_write_string(port)(sc, "(let (", 6, port); + for (int32_t i = 0; i < ci->top; i++) + { + ref = peek_shared_ref(ci, ci->objs[i]); /* refs may be in any order */ + if (ref < 0) {ref = -ref; flip_ref(ci, ci->objs[i]);} + len = (int32_t)catstrs_direct(buf, (i == 0) ? "(<" : "\n (<", pos_int_to_str_direct(sc, ref), "> ", (const char *)NULL); + port_write_string(port)(sc, buf, len, port); + ci->defined[ref] = false; + object_to_port_with_circle_check(sc, ci->objs[i], port, P_READABLE, ci); + port_write_character(port)(sc, ')', port); + ci->defined[ref] = true; + if (peek_shared_ref(ci, ci->objs[i]) > 0) flip_ref(ci, ci->objs[i]); /* ref < 0 -> use <%d> in object_to_port */ + } + port_write_string(port)(sc, ")\n", 2, port); + + if (ci->init_port != sc->F) + { + port_write_string(port)(sc, (const char *)(port_data(ci->init_port)), port_position(ci->init_port), port); + s7_close_output_port(sc, ci->init_port); + s7_gc_unprotect_at(sc, ci->init_loc); + ci->init_port = sc->F; + } + + if (port_position(ci->cycle_port) > 0) /* 0 if e.g. (object->string (object->let (rootlet)) :readable) */ + port_write_string(port)(sc, (const char *)(port_data(ci->cycle_port)), port_position(ci->cycle_port), port); + s7_close_output_port(sc, ci->cycle_port); + s7_gc_unprotect_at(sc, ci->cycle_loc); + ci->cycle_port = sc->F; + + if ((is_immutable(obj)) && (!is_let(obj))) + port_write_string(port)(sc, " (immutable! ", 14, port); + else port_write_string(port)(sc, " ", 2, port); + + ref = peek_shared_ref(ci, obj); + if (ref == 0) + object_to_port_with_circle_check(sc, obj, port, P_READABLE, ci); + else + { + len = (int32_t)catstrs_direct(buf, "<", pos_int_to_str_direct(sc, (ref < 0) ? -ref : ref), ">", (const char *)NULL); + port_write_string(port)(sc, buf, len, port); + } + + if ((is_immutable(obj)) && (!is_let(obj))) + port_write_string(port)(sc, "))\n", 3, port); + else port_write_string(port)(sc, ")\n", 2, port); + return(obj); +} + +static void object_out_1(s7_scheme *sc, s7_pointer obj, s7_pointer strport, use_write_t choice) +{ + if (sc->object_out_locked) + object_to_port_with_circle_check(sc, T_Pos(obj), strport, choice, sc->circle_info); + else + { + shared_info_t *ci = make_shared_info(sc, T_Pos(obj), choice != P_READABLE); + if (ci) + { + sc->object_out_locked = true; + if (choice == P_READABLE) + cyclic_out(sc, obj, strport, ci); + else object_to_port_with_circle_check(sc, T_Pos(obj), strport, choice, ci); + sc->object_out_locked = false; + } + else object_to_port(sc, obj, strport, choice, NULL); + } +} + +static inline s7_pointer object_out(s7_scheme *sc, s7_pointer obj, s7_pointer strport, use_write_t choice) +{ + if ((has_structure(obj)) && + (obj != sc->rootlet)) + object_out_1(sc, obj, strport, choice); + else object_to_port(sc, obj, strport, choice, NULL); + return(obj); +} + +static s7_pointer new_format_port(s7_scheme *sc) +{ + s7_pointer x = alloc_pointer(sc); + s7_int len = FORMAT_PORT_LENGTH; + block_t *block, *b; + set_full_type(x, T_OUTPUT_PORT); + b = mallocate_port(sc); + port_block(x) = b; + port_port(x) = (port_t *)block_data(b); + port_type(x) = STRING_PORT; + port_set_closed(x, false); + port_data_size(x) = len; + port_next(x) = NULL; + block = mallocate(sc, len); + port_data(x) = (uint8_t *)(block_data(block)); + port_data_block(x) = block; + port_data(x)[0] = '\0'; + port_position(x) = 0; + port_needs_free(x) = false; + port_port(x)->pf = &output_string_functions; + return(x); +} + +static inline s7_pointer open_format_port(s7_scheme *sc) +{ + s7_pointer x = sc->format_ports; + if (!x) return(new_format_port(sc)); + sc->format_ports = (s7_pointer)(port_next(x)); + port_position(x) = 0; + port_data(x)[0] = '\0'; + return(x); +} + +static void close_format_port(s7_scheme *sc, s7_pointer port) +{ + port_next(port) = (struct block_t *)(sc->format_ports); + sc->format_ports = port; +} + +char *s7_object_to_c_string(s7_scheme *sc, s7_pointer obj) +{ + char *str; + s7_pointer strport; + s7_int len; + + TRACK(sc); + if ((sc->safety > NO_SAFETY) && + (!s7_is_valid(sc, obj))) + s7_warn(sc, 256, "the second argument to %s (the object): %p, is not an s7 object\n", __func__, obj); + + strport = open_format_port(sc); + object_out(sc, T_Pos(obj), strport, P_WRITE); + len = port_position(strport); + if ((S7_DEBUGGING) && (len == 0)) fprintf(stderr, "%s[%d]: len == 0\n", __func__, __LINE__); + /* if (len == 0) {close_format_port(sc, strport); return(NULL);} */ /* probably never happens */ + str = (char *)Malloc(len + 1); + memcpy((void *)str, (void *)port_data(strport), len); + str[len] = '\0'; + close_format_port(sc, strport); + return(str); +} + +static inline void restore_format_port(s7_scheme *sc, s7_pointer strport) +{ + block_t *block = inline_mallocate(sc, FORMAT_PORT_LENGTH); + port_data(strport) = (uint8_t *)(block_data(block)); + port_data_block(strport) = block; + port_data(strport)[0] = '\0'; + port_position(strport) = 0; + port_data_size(strport) = FORMAT_PORT_LENGTH; + port_needs_free(strport) = false; + close_format_port(sc, strport); +} + + +/* -------------------------------- object->string -------------------------------- */ +s7_pointer s7_object_to_string(s7_scheme *sc, s7_pointer obj, bool use_write) /* unavoidable backwards compatibility rigidity here */ +{ + s7_pointer strport, res; + + if ((sc->safety > NO_SAFETY) && + (!s7_is_valid(sc, obj))) + s7_warn(sc, 256, "the second argument to %s (the object): %p, is not an s7 object\n", __func__, obj); + + strport = open_format_port(sc); + object_out(sc, obj, strport, (use_write) ? P_WRITE : P_DISPLAY); + + if (port_position(strport) >= port_data_size(strport)) + res = block_to_string(sc, reallocate(sc, port_data_block(strport), port_position(strport) + 1), port_position(strport)); + else res = block_to_string(sc, port_data_block(strport), port_position(strport)); + restore_format_port(sc, strport); + return(res); +} + +static s7_pointer g_object_to_string(s7_scheme *sc, s7_pointer args) +{ + #define H_object_to_string "(object->string obj (write #t) (max-len (*s7* 'most-positive-fixnum))) returns a string representation of obj." + #define Q_object_to_string s7_make_signature(sc, 4, \ + sc->is_string_symbol, sc->T, \ + s7_make_signature(sc, 2, sc->is_boolean_symbol, sc->is_keyword_symbol), sc->is_integer_symbol) + use_write_t choice; + s7_pointer obj = car(args), strport, res; + s7_int out_len, pending_max = S7_INT64_MAX; + bool old_openlets = sc->has_openlets; + + if (is_not_null(cdr(args))) + { + s7_pointer arg = cadr(args); + if (arg == sc->F) choice = P_DISPLAY; + else {if (arg == sc->T) choice = P_WRITE; + else {if (arg == sc->readable_keyword) choice = P_READABLE; + else {if (arg == sc->display_keyword) choice = P_DISPLAY; + else {if (arg == sc->write_keyword) choice = P_WRITE; + else wrong_type_error_nr(sc, sc->object_to_string_symbol, 2, arg, wrap_string(sc, "a boolean or :readable", 22));}}}} + + if (is_not_null(cddr(args))) + { + arg = caddr(args); + if (!s7_is_integer(arg)) + { + if (choice == P_READABLE) /* (object->string #r(1 2 3) :readable "hi") */ + wrong_type_error_nr(sc, sc->object_to_string_symbol, 3, arg, sc->type_names[T_INTEGER]); + return(method_or_bust(sc, arg, sc->object_to_string_symbol, args, sc->type_names[T_INTEGER], 3)); + } + if (s7_integer_clamped_if_gmp(sc, arg) < 0) + out_of_range_error_nr(sc, sc->object_to_string_symbol, int_three, arg, a_non_negative_integer_string); + pending_max = s7_integer_clamped_if_gmp(sc, arg); + }} + else choice = P_WRITE; + /* can't use s7_object_to_string here anymore because it assumes use_write arg is a boolean */ + + if (choice == P_READABLE) + sc->has_openlets = false; /* so (object->string obj :readable) ignores obj's object->string method -- is this a good idea? */ + else check_method(sc, obj, sc->object_to_string_symbol, args); + + strport = open_format_port(sc); + sc->objstr_max_len = pending_max; + object_out(sc, obj, strport, choice); + sc->objstr_max_len = S7_INT64_MAX; + out_len = port_position(strport); + + if ((pending_max >= 0) && + (out_len > pending_max)) + { + if (choice == P_READABLE) /* (object->string #r(1 2 3) :readable 4) */ + { + close_format_port(sc, strport); + sc->has_openlets = old_openlets; + out_of_range_error_nr(sc, sc->object_to_string_symbol, int_three, wrap_integer(sc, out_len), wrap_string(sc, "the readable string is too long", 31)); + } + out_len = pending_max; + if (out_len < 3) + { + close_format_port(sc, strport); + sc->has_openlets = old_openlets; + return(make_string_with_length(sc, "...", 3)); + } + for (s7_int i = out_len - 3; i < out_len; i++) + port_data(strport)[i] = (uint8_t)'.'; + } + if (out_len >= port_data_size(strport)) /* this can happen (but only == I think) */ + res = block_to_string(sc, reallocate(sc, port_data_block(strport), out_len + 1), out_len); + else res = block_to_string(sc, port_data_block(strport), out_len); + restore_format_port(sc, strport); + sc->has_openlets = old_openlets; + return(res); +} + +#if S7_DEBUGGING +const char *s7_object_to_c_string_x(s7_scheme *sc, s7_pointer obj, s7_pointer urchoice); +const char *s7_object_to_c_string_x(s7_scheme *sc, s7_pointer obj, s7_pointer urchoice) {return(string_value(g_object_to_string(sc, list_2(sc, obj, urchoice))));} +#endif + + +/* -------------------------------- newline -------------------------------- */ +void s7_newline(s7_scheme *sc, s7_pointer port) +{ + if (port != sc->F) + port_write_character(port)(sc, (uint8_t)'\n', port); +} + +#define newline_char chars[(uint8_t)'\n'] + +static s7_pointer g_newline(s7_scheme *sc, s7_pointer args) +{ + #define H_newline "(newline (port (current-output-port))) writes a carriage return to the port" + #define Q_newline s7_make_signature(sc, 2, sc->is_char_symbol, s7_make_signature(sc, 2, sc->is_output_port_symbol, sc->not_symbol)) + + s7_pointer port = (is_not_null(args)) ? car(args) : current_output_port(sc); + if (!is_output_port(port)) + { + if (port == sc->F) return(newline_char); + check_method(sc, port, sc->newline_symbol, args); + sole_arg_wrong_type_error_nr(sc, sc->newline_symbol, port, an_output_port_or_f_string); /* 0 -> "zeroth" */ + } + if (port_is_closed(port)) + sole_arg_wrong_type_error_nr(sc, sc->newline_symbol, port, an_open_output_port_string); + s7_newline(sc, port); + return(newline_char); /* return(sc->unspecified) until 28-Sep-17, but for example (display c) returns c */ +} + +static s7_pointer newline_p(s7_scheme *sc) +{ + s7_newline(sc, current_output_port(sc)); + return(newline_char); +} + +static s7_pointer newline_p_p(s7_scheme *sc, s7_pointer port) +{ + if (!is_output_port(port)) + { + if (port == sc->F) return(newline_char); + return(method_or_bust_p(sc, port, sc->newline_symbol, an_output_port_string)); + } + s7_newline(sc, port); + return(newline_char); +} + + +/* -------------------------------- write -------------------------------- */ +s7_pointer s7_write(s7_scheme *sc, s7_pointer obj, s7_pointer port) +{ + if (port != sc->F) + { + if (port_is_closed(port)) + wrong_type_error_nr(sc, sc->write_symbol, 2, port, an_open_output_port_string); + object_out(sc, obj, port, P_WRITE); + } + return(obj); +} + +static s7_pointer write_p_pp(s7_scheme *sc, s7_pointer x, s7_pointer port) +{ + if (!is_output_port(port)) + { + if (port == sc->F) return(x); + check_method(sc, port, sc->write_symbol, set_mlist_2(sc, x, port)); + wrong_type_error_nr(sc, sc->write_symbol, 2, port, an_output_port_or_f_string); + } + if (port_is_closed(port)) + wrong_type_error_nr(sc, sc->write_symbol, 2, port, an_open_output_port_string); + return(object_out(sc, x, port, P_WRITE)); +} + +static s7_pointer g_write(s7_scheme *sc, s7_pointer args) +{ + #define H_write "(write obj (port (current-output-port))) writes (object->string obj) to the output port" + #define Q_write s7_make_signature(sc, 3, sc->T, sc->T, s7_make_signature(sc, 2, sc->is_output_port_symbol, sc->not_symbol)) + check_method(sc, car(args), sc->write_symbol, args); + return(write_p_pp(sc, car(args), (is_pair(cdr(args))) ? cadr(args) : current_output_port(sc))); +} + +static s7_pointer write_p_p(s7_scheme *sc, s7_pointer x) +{ + return((current_output_port(sc) == sc->F) ? x : object_out(sc, x, current_output_port(sc), P_WRITE)); +} + + +/* -------------------------------- display -------------------------------- */ +s7_pointer s7_display(s7_scheme *sc, s7_pointer obj, s7_pointer port) +{ + if (port != sc->F) + { + if (port_is_closed(port)) + wrong_type_error_nr(sc, sc->display_symbol, 2, port, an_open_output_port_string); + object_out(sc, obj, port, P_DISPLAY); + } + return(obj); +} + +static s7_pointer display_p_pp(s7_scheme *sc, s7_pointer x, s7_pointer port) +{ + if (!is_output_port(port)) + { + if (port == sc->F) return(x); + check_method(sc, port, sc->display_symbol, set_mlist_2(sc, x, port)); + wrong_type_error_nr(sc, sc->display_symbol, 2, port, an_output_port_or_f_string); + } + if (port_is_closed(port)) + wrong_type_error_nr(sc, sc->display_symbol, 2, port, an_open_output_port_string); + check_method(sc, x, sc->display_symbol, set_plist_2(sc, x, port)); + return(object_out(sc, x, port, P_DISPLAY)); +} + +static s7_pointer g_display(s7_scheme *sc, s7_pointer args) +{ + #define H_display "(display obj (port (current-output-port))) prints obj" + #define Q_display s7_make_signature(sc, 3, sc->T, sc->T, s7_make_signature(sc, 2, sc->is_output_port_symbol, sc->not_symbol)) + return(display_p_pp(sc, car(args), (is_pair(cdr(args))) ? cadr(args) : current_output_port(sc))); +} + +static s7_pointer g_display_2(s7_scheme *sc, s7_pointer args) {return(display_p_pp(sc, car(args), cadr(args)));} + +static s7_pointer g_display_f(s7_scheme *unused_sc, s7_pointer args) {return(car(args));} + +static s7_pointer display_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool unused_ops) +{ + if (args == 2) /* not check_for_substring_temp(sc, expr) here -- display returns arg so can be immutable if substring_uncopied */ + return((caddr(expr) == sc->F) ? sc->display_f : sc->display_2); + return(f); +} + +static s7_pointer display_p_p(s7_scheme *sc, s7_pointer x) +{ + if (current_output_port(sc) == sc->F) return(x); + check_method(sc, x, sc->display_symbol, set_plist_1(sc, x)); + return(object_out(sc, x, current_output_port(sc), P_DISPLAY)); +} + +/* display may not be following the spec: (display '("a" #\b)): ("a" #\b), whereas Guile says (a b) */ + + +/* -------------------------------- call-with-output-string -------------------------------- */ +static s7_pointer g_call_with_output_string(s7_scheme *sc, s7_pointer args) +{ + #define H_call_with_output_string "(call-with-output-string proc) opens a string port applies proc to it, then returns the collected output" + #define Q_call_with_output_string s7_make_signature(sc, 2, sc->is_string_symbol, s7_make_signature(sc, 2, sc->is_procedure_symbol, sc->is_macro_symbol)) + + s7_pointer port, proc = car(args); + if (is_let(proc)) + check_method(sc, proc, sc->call_with_output_string_symbol, args); + if ((!is_any_procedure(proc)) || /* this disallows goto/continuation */ + (!s7_is_aritable(sc, proc, 1))) + return(method_or_bust(sc, proc, sc->call_with_output_string_symbol, args, wrap_string(sc, "a procedure of one argument (the port)", 38), 1)); + + port = s7_open_output_string(sc); + push_stack(sc, OP_UNWIND_OUTPUT, sc->unused, port); /* # here is a marker (needed) */ + push_stack(sc, OP_GET_OUTPUT_STRING, sc->unused, port); /* args checked in call_with_exit */ + push_stack(sc, OP_APPLY, list_1_unchecked(sc, port), proc); + return(sc->F); +} + + +/* -------------------------------- call-with-output-file -------------------------------- */ +static s7_pointer g_call_with_output_file(s7_scheme *sc, s7_pointer args) +{ + #define H_call_with_output_file "(call-with-output-file filename proc) opens filename and calls proc with the output port as its argument" + #define Q_call_with_output_file sc->pl_sf + + s7_pointer port, file = car(args), proc = cadr(args); + if (!is_string(file)) + return(method_or_bust(sc, file, sc->call_with_output_file_symbol, args, sc->type_names[T_STRING], 1)); + if ((!is_any_procedure(proc)) || + (!s7_is_aritable(sc, proc, 1))) + return(method_or_bust(sc, proc, sc->call_with_output_file_symbol, args, wrap_string(sc, "a procedure of one argument (the port)", 38), 2)); + + port = s7_open_output_file(sc, string_value(file), "w"); + push_stack(sc, OP_UNWIND_OUTPUT, sc->unused, port); /* # here is a marker (needed) */ + push_stack(sc, OP_APPLY, list_1_unchecked(sc, port), proc); + return(sc->F); +} + + +/* -------------------------------- with-output-to-string -------------------------------- */ +static s7_pointer g_with_output_to_string(s7_scheme *sc, s7_pointer args) +{ + #define H_with_output_to_string "(with-output-to-string thunk) opens a string as a temporary current-output-port, \ +calls thunk, then returns the collected output" + #define Q_with_output_to_string s7_make_signature(sc, 2, sc->is_string_symbol, s7_make_signature(sc, 2, sc->is_procedure_symbol, sc->is_macro_symbol)) + + s7_pointer old_output_port, p = car(args); + if (!is_thunk(sc, p)) + return(method_or_bust(sc, p, sc->with_output_to_string_symbol, args, a_thunk_string, 1)); + if ((is_continuation(p)) || (is_goto(p))) + wrong_type_error_nr(sc, sc->with_output_to_string_symbol, 1, p, a_normal_procedure_string); + + old_output_port = current_output_port(sc); + set_current_output_port(sc, s7_open_output_string(sc)); + push_stack(sc, OP_UNWIND_OUTPUT, old_output_port, current_output_port(sc)); + push_stack(sc, OP_GET_OUTPUT_STRING, old_output_port, current_output_port(sc)); + push_stack(sc, OP_APPLY, sc->nil, p); + return(sc->F); +} + + +/* -------------------------------- with-output-to-file -------------------------------- */ +static s7_pointer g_with_output_to_file(s7_scheme *sc, s7_pointer args) +{ + #define H_with_output_to_file "(with-output-to-file filename thunk) opens filename as the temporary current-output-port and calls thunk" + #define Q_with_output_to_file sc->pl_sf + + s7_pointer old_output_port, file = car(args), proc = cadr(args); + if (!is_string(file)) + return(method_or_bust(sc, file, sc->with_output_to_file_symbol, args, sc->type_names[T_STRING], 1)); + if (!is_thunk(sc, proc)) + return(method_or_bust(sc, proc, sc->with_output_to_file_symbol, args, a_thunk_string, 2)); + if ((is_continuation(proc)) || (is_goto(proc))) + wrong_type_error_nr(sc, sc->with_output_to_file_symbol, 1, proc, a_normal_procedure_string); + + old_output_port = current_output_port(sc); + set_current_output_port(sc, s7_open_output_file(sc, string_value(file), "w")); + push_stack(sc, OP_UNWIND_OUTPUT, old_output_port, current_output_port(sc)); + push_stack(sc, OP_APPLY, sc->nil, proc); + return(sc->F); +} + + +/* -------------------------------- format -------------------------------- */ +static inline s7_pointer copy_proper_list(s7_scheme *sc, s7_pointer lst); + +static noreturn void format_error_nr(s7_scheme *sc, const char *ur_msg, s7_int msg_len, const char *str, s7_pointer ur_args, format_data_t *fdat) +{ + s7_pointer x = NULL; + s7_pointer ctrl_str = (fdat->orig_str) ? fdat->orig_str : wrap_string(sc, str, safe_strlen(str)); + s7_pointer args = (is_elist(ur_args)) ? copy_proper_list(sc, ur_args) : ur_args; + s7_pointer msg = wrap_string(sc, ur_msg, msg_len); + if (fdat->loc == 0) + { + if (is_pair(args)) + x = set_elist_4(sc, format_string_1, ctrl_str, args, msg); + else x = set_elist_3(sc, format_string_2, ctrl_str, msg); + } + else + if (is_pair(args)) + x = set_elist_5(sc, format_string_3, ctrl_str, args, wrap_integer(sc, fdat->loc + 20), msg); + else x = set_elist_4(sc, format_string_4, ctrl_str, wrap_integer(sc, fdat->loc + 20), msg); + if (fdat->port) + { + close_format_port(sc, fdat->port); + fdat->port = NULL; + } + error_nr(sc, sc->format_error_symbol, x); +} + +static void format_append_char(s7_scheme *sc, char c, s7_pointer port) +{ + port_write_character(port)(sc, c, port); + sc->format_column++; +} + +static void format_append_newline(s7_scheme *sc, s7_pointer port) +{ + port_write_character(port)(sc, '\n', port); + sc->format_column = 0; +} + +static void format_append_string(s7_scheme *sc, format_data_t *fdat, const char *str, s7_int len, s7_pointer port) +{ + port_write_string(port)(sc, str, len, port); + fdat->loc += len; + sc->format_column += len; +} + +static void format_append_chars(s7_scheme *sc, format_data_t *fdat, char pad, s7_int chrs, s7_pointer port) +{ + if (is_string_port(port)) + { + if ((port_position(port) + chrs) < port_data_size(port)) + { + local_memset((char *)port_data(port) + port_position(port), pad, chrs); + port_position(port) += chrs; + } + else + { + s7_int new_len = port_position(port) + chrs; + resize_port_data(sc, port, new_len * 2); + local_memset((char *)port_data(port) + port_position(port), pad, chrs); + port_position(port) = new_len; + } + fdat->loc += chrs; + sc->format_column += chrs; + } + else + { + block_t *b = mallocate(sc, chrs + 1); + char *str = (char *)block_data(b); + local_memset((void *)str, pad, chrs); + str[chrs] = '\0'; + format_append_string(sc, fdat, str, chrs, port); + liberate(sc, b); + } +} + +static s7_int format_read_integer(s7_int *cur_i, s7_int str_len, const char *str) +{ + /* we know that str[*cur_i] is a digit */ + s7_int i, lval = 0; + for (i = *cur_i; i < str_len - 1; i++) + { + int32_t dig = digits[(uint8_t)str[i]]; + if (dig < 10) + { +#if HAVE_OVERFLOW_CHECKS + if ((multiply_overflow(lval, 10, &lval)) || + (add_overflow(lval, dig, &lval))) + break; +#else + lval = dig + (lval * 10); +#endif + } + else break; + } + *cur_i = i; + return(lval); +} + +static void format_number(s7_scheme *sc, format_data_t *fdat, int32_t radix, s7_int width, s7_int precision, char float_choice, char pad, s7_pointer port) +{ + char *tmp; + block_t *b = NULL; + s7_int nlen = 0; + if (width < 0) width = 0; + + /* precision choice depends on float_choice if it's -1 */ + if (precision < 0) + { + if ((float_choice == 'e') || + (float_choice == 'f') || + (float_choice == 'g')) + precision = 6; + else + { + int32_t typ = type(car(fdat->args)); /* in the "int" cases, precision depends on the arg type */ + precision = ((typ == T_INTEGER) || (typ == T_RATIO)) ? 0 : 6; + }} + /* should (format #f "~F" 1/3) return "1/3"?? in CL it's "0.33333334" */ + + if (pad != ' ') + { + char *padtmp; +#if (!WITH_GMP) + if (radix == 10) + tmp = number_to_string_base_10(sc, car(fdat->args), width, precision, float_choice, &nlen, P_WRITE); + else +#endif + { + b = number_to_string_with_radix(sc, car(fdat->args), radix, width, precision, float_choice, &nlen); + tmp = (char *)block_data(b); + } + padtmp = tmp; + while (*padtmp == ' ') (*(padtmp++)) = pad; + format_append_string(sc, fdat, tmp, nlen, port); + if ((WITH_GMP) || (radix != 10)) liberate(sc, b); + } + else + { +#if (!WITH_GMP) + if (radix == 10) + tmp = number_to_string_base_10(sc, car(fdat->args), width, precision, float_choice, &nlen, P_WRITE); + else +#endif + { + b = number_to_string_with_radix(sc, car(fdat->args), radix, width, precision, float_choice, &nlen); + tmp = (char *)block_data(b); + } + format_append_string(sc, fdat, tmp, nlen, port); + if ((WITH_GMP) || (radix != 10)) liberate(sc, b); + } + fdat->args = cdr(fdat->args); + fdat->ctr++; +} + +static const char *ordinal[11] = {"zeroth", "first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth", "tenth"}; +static const s7_int ordinal_length[11] = {6, 5, 6, 5, 6, 5, 5, 7, 6, 5, 5}; + +static void format_ordinal_number(s7_scheme *sc, format_data_t *fdat, s7_pointer port) +{ + s7_int num = s7_integer_clamped_if_gmp(sc, car(fdat->args)); + if (num < 11) + format_append_string(sc, fdat, ordinal[num], ordinal_length[num], port); + else + { + s7_int nlen = 0; + const char *tmp = integer_to_string(sc, num, &nlen); + format_append_string(sc, fdat, tmp, nlen, port); + num = num % 100; + if ((num >= 11) && (num <= 13)) + format_append_string(sc, fdat, "th", 2, port); + else + { + num = num % 10; + if (num == 1) format_append_string(sc, fdat, "st", 2, port); + else + if (num == 2) format_append_string(sc, fdat, "nd", 2, port); + else + if (num == 3) format_append_string(sc, fdat, "rd", 2, port); + else format_append_string(sc, fdat, "th", 2, port); + }} + fdat->args = cdr(fdat->args); + fdat->ctr++; +} + +static s7_int format_nesting(const char *str, char opener, char closer, s7_int start, s7_int end) /* start=i, end=str_len-1 */ +{ + s7_int nesting = 1; + for (s7_int k = start + 2; k < end; k++) + if (str[k] == '~') + { + if (str[k + 1] == closer) + { + nesting--; + if (nesting == 0) + return(k - start - 1); + } + else + if (str[k + 1] == opener) + nesting++; + } + return(-1); +} + +static bool format_method(s7_scheme *sc, const char *str, format_data_t *fdat, s7_pointer port) +{ + s7_pointer func, obj = car(fdat->args); + char ctrl_str[3]; + + if ((!has_active_methods(sc, obj)) || + ((func = find_method_with_let(sc, obj, sc->format_symbol)) == sc->undefined)) + return(false); + + ctrl_str[0] = '~'; + ctrl_str[1] = str[0]; + ctrl_str[2] = '\0'; + + if (port == obj) /* a problem! we need the openlet port for format, but that's an infinite loop when it calls format again as obj */ + s7_apply_function(sc, func, set_plist_3(sc, port, s7_make_string_wrapper(sc, ctrl_str), s7_make_string_wrapper(sc, "#"))); + else s7_apply_function(sc, func, set_plist_3(sc, port, s7_make_string_wrapper(sc, ctrl_str), obj)); + + fdat->args = cdr(fdat->args); + fdat->ctr++; + return(true); +} + +static s7_int format_n_arg(s7_scheme *sc, const char *str, format_data_t *fdat, s7_pointer args) +{ + s7_int n; + + if (is_null(fdat->args)) /* (format #f "~nT") */ + format_error_nr(sc, "~~N: missing argument", 21, str, args, fdat); + if (!s7_is_integer(car(fdat->args))) + format_error_nr(sc, "~~N: integer argument required", 30, str, args, fdat); + n = s7_integer_clamped_if_gmp(sc, car(fdat->args)); + + if (n < 0) + format_error_nr(sc, "~~N value is negative?", 22, str, args, fdat); + if (n > sc->max_format_length) + format_error_nr(sc, "~~N value is too big", 20, str, args, fdat); + + fdat->args = cdr(fdat->args); /* I don't think fdat->ctr should be incremented here -- it's for *vector-print-length* etc */ + return(n); +} + +static s7_int format_numeric_arg(s7_scheme *sc, const char *str, s7_int str_len, format_data_t *fdat, s7_int *i) +{ + s7_int old_i = *i; + s7_int width = format_read_integer(i, str_len, str); + if (width < 0) + { + if (str[old_i - 1] != ',') /* need branches here, not if-expr because format_error creates the permanent string */ + format_error_nr(sc, "width is negative?", 18, str, fdat->args, fdat); + format_error_nr(sc, "precision is negative?", 22, str, fdat->args, fdat); + } + if (width > sc->max_format_length) + { + if (str[old_i - 1] != ',') + format_error_nr(sc, "width is too big", 16, str, fdat->args, fdat); + format_error_nr(sc, "precision is too big", 20, str, fdat->args, fdat); + } + return(width); +} + +static format_data_t *open_format_data(s7_scheme *sc) +{ + format_data_t *fdat; + sc->format_depth++; + if (sc->format_depth >= sc->num_fdats) + { + int32_t new_num_fdats = sc->format_depth * 2; + sc->fdats = (format_data_t **)Realloc(sc->fdats, sizeof(format_data_t *) * new_num_fdats); + for (int32_t k = sc->num_fdats; k < new_num_fdats; k++) sc->fdats[k] = NULL; + sc->num_fdats = new_num_fdats; + } + fdat = sc->fdats[sc->format_depth]; + if (!fdat) + { + fdat = (format_data_t *)Malloc(sizeof(format_data_t)); + sc->fdats[sc->format_depth] = fdat; + fdat->curly_len = 0; + fdat->curly_str = NULL; + fdat->ctr = 0; + } + else + { + if (fdat->port) + close_format_port(sc, fdat->port); + if (fdat->strport) + close_format_port(sc, fdat->strport); + } + fdat->port = NULL; + fdat->strport = NULL; + fdat->loc = 0; + fdat->curly_arg = sc->nil; + return(fdat); +} + +#if WITH_GMP +static bool is_one_or_big_one(s7_scheme *sc, s7_pointer p) +{ + if (!is_big_number(p)) return(is_one(p)); + if (is_t_big_integer(p)) return(mpz_cmp_ui(big_integer(p), 1) == 0); + if (is_t_big_real(p)) return(mpfr_cmp_d(big_real(p), 1.0) == 0); + return(false); +} +#else +#define is_one_or_big_one(Sc, Num) is_one(Num) +#endif + +static s7_pointer object_to_list(s7_scheme *sc, s7_pointer obj); + +static s7_pointer format_to_port_1(s7_scheme *sc, s7_pointer port, const char *str, s7_pointer args, + s7_pointer *next_arg, bool with_result, bool columnized, s7_int len, s7_pointer orig_str) +{ + s7_int i, str_len; + format_data_t *fdat; + s7_pointer deferred_port; + + if (len <= 0) + { + str_len = safe_strlen(str); + if (str_len == 0) + { + if (is_not_null(args)) + error_nr(sc, sc->format_error_symbol, + set_elist_2(sc, wrap_string(sc, "format control string is null, but there are arguments: ~S", 58), args)); + return((with_result) ? nil_string : sc->F); + }} + else str_len = len; + + fdat = open_format_data(sc); + fdat->args = args; + fdat->orig_str = orig_str; + + if (with_result) + { + deferred_port = port; + port = open_format_port(sc); + fdat->port = port; + } + else deferred_port = sc->F; + + for (i = 0; i < str_len - 1; i++) + { + if ((uint8_t)(str[i]) == (uint8_t)'~') + { + use_write_t use_write; + switch (str[i + 1]) + { + case '%': /* -------- newline -------- */ + /* sbcl apparently accepts numeric args here (including 0) */ + if ((port_data(port)) && + (port_position(port) < port_data_size(port))) + { + port_data(port)[port_position(port)++] = '\n'; + sc->format_column = 0; + } + else format_append_newline(sc, port); + i++; + break; + + case '&': /* -------- conditional newline -------- */ + /* this only works if all output goes through format -- display/write for example do not update format_column */ + if (sc->format_column > 0) + format_append_newline(sc, port); + i++; + break; + + case '~': /* -------- tilde -------- */ + format_append_char(sc, '~', port); + i++; + break; + + case '\n': /* -------- trim white-space -------- so (format #f "hiho~\n") -> "hiho"! */ + for (i = i + 2; i args)) /* (format #f "~*~A") */ + format_error_nr(sc, "can't skip argument!", 20, str, args, fdat); + fdat->args = cdr(fdat->args); + break; + + case '|': /* -------- exit if args nil or ctr > (*s7* 'print-length) -------- */ + if ((is_pair(fdat->args)) && + (fdat->ctr >= sc->print_length)) + { + format_append_string(sc, fdat, " ...", 4, port); + fdat->args = sc->nil; + } + /* fall through */ + + case '^': /* -------- exit -------- */ + if (is_null(fdat->args)) + { + i = str_len; + goto ALL_DONE; + } + i++; + break; + + case '@': /* -------- plural, 'y' or 'ies' -------- */ + i += 2; + if ((str[i] != 'P') && (str[i] != 'p')) + format_error_nr(sc, "unknown '@' directive", 21, str, args, fdat); + if (!is_pair(fdat->args)) + format_error_nr(sc, "'@' directive argument missing", 30, str, args, fdat); + if (!is_real(car(fdat->args))) /* CL accepts non numbers here */ + format_error_nr(sc, "'@P' directive argument is not a real number", 44, str, args, fdat); + + if (!is_one_or_big_one(sc, car(fdat->args))) + format_append_string(sc, fdat, "ies", 3, port); + else format_append_char(sc, 'y', port); + + fdat->args = cdr(fdat->args); + break; + + case 'P': case 'p': /* -------- plural in 's' -------- */ + if (!is_pair(fdat->args)) + format_error_nr(sc, "'P' directive argument missing", 30, str, args, fdat); + if (!is_real(car(fdat->args))) + format_error_nr(sc, "'P' directive argument is not a real number", 43, str, args, fdat); + if (!is_one_or_big_one(sc, car(fdat->args))) + format_append_char(sc, 's', port); + i++; + fdat->args = cdr(fdat->args); + break; + + case '{': /* -------- iteration -------- */ + { + s7_int curly_len; + + if (is_null(fdat->args)) + format_error_nr(sc, "missing argument", 16, str, args, fdat); + + if ((is_pair(car(fdat->args))) && /* any sequence is possible here */ + (s7_list_length(sc, car(fdat->args)) < 0)) /* (format #f "~{~a~e~}" (cons 1 2)) */ + format_error_nr(sc, "~{ argument is a dotted list", 28, str, args, fdat); + + curly_len = format_nesting(str, '{', '}', i, str_len - 1); + + if (curly_len == -1) + format_error_nr(sc, "'{' directive, but no matching '}'", 34, str, args, fdat); + if (curly_len == 1) + format_error_nr(sc, "~{~}' doesn't consume any arguments!", 36, str, args, fdat); + + /* what about cons's here? I can't see any way to specify the car or cdr of a cons within the format string */ + if (is_not_null(car(fdat->args))) /* (format #f "~{~A ~}" ()) -> "" */ + { + s7_pointer curly_arg = object_to_list(sc, car(fdat->args)); /* if a pair (or non-sequence), this simply returns the original */ + /* perhaps use an iterator here -- rootlet->list is expensive! */ + if (is_pair(curly_arg)) /* (format #f "~{~A ~}" #()) -> "" */ + { + char *curly_str = NULL; /* this is the local (nested) format control string */ + s7_pointer orig_arg, cycle_arg; + + fdat->curly_arg = curly_arg; + orig_arg = (curly_arg != car(fdat->args)) ? curly_arg : sc->nil; + if (curly_len > fdat->curly_len) + { + if (fdat->curly_str) free(fdat->curly_str); + fdat->curly_len = curly_len; + fdat->curly_str = (char *)Malloc(curly_len); + } + curly_str = fdat->curly_str; + memcpy((void *)curly_str, (const void *)(str + i + 2), curly_len - 1); + curly_str[curly_len - 1] = '\0'; + + if ((sc->format_depth < sc->num_fdats - 1) && + (sc->fdats[sc->format_depth + 1])) + sc->fdats[sc->format_depth + 1]->ctr = 0; + + /* it's not easy to use an iterator here instead of a list (so object->list isn't needed above), + * because the curly brackets may enclose multiple arguments -- we would need to use + * iterators throughout this function. + */ + cycle_arg = curly_arg; + while (is_pair(curly_arg)) + { + s7_pointer new_arg = sc->nil; + format_to_port_1(sc, port, curly_str, curly_arg, &new_arg, false, columnized, curly_len - 1, NULL); + if (curly_arg == new_arg) + { + if (cdr(curly_arg) == curly_arg) break; + fdat->curly_arg = sc->nil; + format_error_nr(sc, "'{...}' doesn't consume any arguments!", 38, str, args, fdat); + } + curly_arg = new_arg; + if ((!is_pair(curly_arg)) || (curly_arg == cycle_arg)) + break; + cycle_arg = cdr(cycle_arg); + format_to_port_1(sc, port, curly_str, curly_arg, &new_arg, false, columnized, curly_len - 1, NULL); + curly_arg = new_arg; + } + fdat->curly_arg = sc->nil; + while (is_pair(orig_arg)) /* free_cell below clears the type, so a circular list here is ok */ + { + s7_pointer p = orig_arg; + orig_arg = cdr(orig_arg); + free_cell(sc, p); /* if car(fdar->args) is a hash-table, we could also free_cell(car(p)), but not in any other case */ + }} + else + if (!is_null(curly_arg)) + format_error_nr(sc, "'{' directive argument should be a list or something we can turn into a list", 76, str, args, fdat); + } + i += (curly_len + 2); /* jump past the ending '}' too */ + fdat->args = cdr(fdat->args); + fdat->ctr++; + } + break; + + case '}': + format_error_nr(sc, "unmatched '}'", 13, str, args, fdat); + + case '$': + use_write = P_CODE; /* affects when symbols but not keywords are quoted (symbol_to_port and hash_table_to_port) */ + goto OBJSTR; + + case 'W': case 'w': + use_write = P_READABLE; + goto OBJSTR; + + case 'S': case 's': + use_write = P_WRITE; + goto OBJSTR; + + case 'A': case 'a': + use_write = P_DISPLAY; + OBJSTR: /* object->string */ + { + s7_pointer obj, strport; + if (is_null(fdat->args)) + format_error_nr(sc, "missing argument", 16, str, args, fdat); + i++; + obj = car(fdat->args); + if ((use_write == P_READABLE) || + (!has_active_methods(sc, obj)) || + (!format_method(sc, (const char *)(str + i), fdat, port))) + { + bool old_openlets = sc->has_openlets; + /* for the column check, we need to know the length of the object->string output */ + if (columnized) + { + strport = open_format_port(sc); + fdat->strport = strport; + } + else strport = port; + if (use_write == P_READABLE) + sc->has_openlets = false; + object_out(sc, obj, strport, use_write); + if (use_write == P_READABLE) + sc->has_openlets = old_openlets; + if (columnized) + { + if (port_position(strport) >= port_data_size(strport)) + resize_port_data(sc, strport, port_data_size(strport) * 2); + port_data(strport)[port_position(strport)] = '\0'; + if (port_position(strport) > 0) + format_append_string(sc, fdat, (const char *)port_data(strport), port_position(strport), port); + close_format_port(sc, strport); + fdat->strport = NULL; + } + fdat->args = cdr(fdat->args); + fdat->ctr++; + }} + break; + + /* -------- numeric args -------- */ + case ':': + i += 2; + if ((str[i] != 'D') && (str[i] != 'd')) + format_error_nr(sc, "unknown ':' directive", 21, str, args, fdat); + if (!is_pair(fdat->args)) + format_error_nr(sc, "':D' directive argument missing", 31, str, args, fdat); + if (!s7_is_integer(car(fdat->args))) + format_error_nr(sc, "':D' directive argument is not an integer", 41, str, args, fdat); + if (s7_integer_clamped_if_gmp(sc, car(fdat->args)) < 0) + format_error_nr(sc, "':D' directive argument can't be negative", 41, str, args, fdat); + format_ordinal_number(sc, fdat, port); + break; + + case '0': case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': case ',': + case 'N': case 'n': + + case 'B': case 'b': + case 'D': case 'd': + case 'E': case 'e': + case 'F': case 'f': + case 'G': case 'g': + case 'O': case 'o': + case 'X': case 'x': + + case 'T': case 't': + case 'C': case 'c': + { + s7_int width = -1, precision = -1; + char pad = ' '; + i++; /* str[i] == '~' */ + + if (isdigit((int32_t)(str[i]))) + width = format_numeric_arg(sc, str, str_len, fdat, &i); + else + if ((str[i] == 'N') || (str[i] == 'n')) + { + i++; + width = format_n_arg(sc, str, fdat, args); + } + if (str[i] == ',') + { + i++; /* is (format #f "~12,12D" 1) an error? The precision (or is it the width?) has no use here */ + if (isdigit((int32_t)(str[i]))) + precision = format_numeric_arg(sc, str, str_len, fdat, &i); + else + if ((str[i] == 'N') || (str[i] == 'n')) + { + i++; + precision = format_n_arg(sc, str, fdat, args); + } + else + if (str[i] == '\'') /* (format #f "~12,'xD" 1) -> "xxxxxxxxxxx1" */ + { + pad = str[i + 1]; + i += 2; + if (i >= str_len) /* (format #f "~,'") */ + format_error_nr(sc, "incomplete numeric argument", 27, str, args, fdat); + }} /* is (let ((str "~12,'xD")) (set! (str 5) #\null) (format #f str 1)) an error? */ + + switch (str[i]) + { + /* -------- pad to column -------- + * are columns numbered from 1 or 0? there seems to be disagreement about this directive, does "space over to" mean including? + */ + case 'T': case 't': + if (width == -1) width = 0; + if (precision == -1) precision = 0; + if ((width > 0) || (precision > 0)) /* (format #f "a~8Tb") */ + { + /* (length (substring (format #f "~%~10T.") 1)) == (length (format #f "~10T.")) + * (length (substring (format #f "~%-~10T.~%") 1)) == (length (format #f "-~10T.~%")) + */ + if (precision > 0) + { + int32_t mult = (int32_t)(ceil((s7_double)(sc->format_column + 1 - width) / (s7_double)precision)); /* CLtL2 ("least positive int") */ + if (mult < 1) mult = 1; + width += (precision * mult); + } + width -= (sc->format_column + 1); + if (width > 0) + format_append_chars(sc, fdat, pad, width, port); + } + break; + + case 'C': case 'c': + { + s7_pointer obj; + + if (is_null(fdat->args)) + format_error_nr(sc, "~~C: missing argument", 21, str, args, fdat); + /* the "~~" here and below protects against "~C" being treated as a directive */ + obj = car(fdat->args); + if (!is_character(obj)) + { + if (!format_method(sc, (const char *)(str + i), fdat, port)) /* i stepped forward above */ + format_error_nr(sc, "'C' directive requires a character argument", 43, str, args, fdat); + } + else + { + /* here use_write is false, so we just add the char, not its name */ + if (width == -1) + format_append_char(sc, character(obj), port); + else + if (width > 0) + format_append_chars(sc, fdat, character(obj), width, port); + + fdat->args = cdr(fdat->args); + fdat->ctr++; + }} + break; + + /* -------- numbers -------- */ + case 'F': case 'f': + if (is_null(fdat->args)) + format_error_nr(sc, "~~F: missing argument", 21, str, args, fdat); + if (!(is_number(car(fdat->args)))) + { + if (!format_method(sc, (const char *)(str + i), fdat, port)) + format_error_nr(sc, "~~F: numeric argument required", 30, str, args, fdat); + } + else format_number(sc, fdat, 10, width, precision, 'f', pad, port); + break; + + case 'G': case 'g': + if (is_null(fdat->args)) + format_error_nr(sc, "~~G: missing argument", 21, str, args, fdat); + if (!(is_number(car(fdat->args)))) + { + if (!format_method(sc, (const char *)(str + i), fdat, port)) + format_error_nr(sc, "~~G: numeric argument required", 30, str, args, fdat); + } + else format_number(sc, fdat, 10, width, precision, 'g', pad, port); + break; + + case 'E': case 'e': + if (is_null(fdat->args)) + format_error_nr(sc, "~~E: missing argument", 21, str, args, fdat); + if (!(is_number(car(fdat->args)))) + { + if (!format_method(sc, (const char *)(str + i), fdat, port)) + format_error_nr(sc, "~~E: numeric argument required", 30, str, args, fdat); + } + else format_number(sc, fdat, 10, width, precision, 'e', pad, port); + break; + + /* how to handle non-integer arguments in the next 4 cases? clisp just returns + * the argument: (format nil "~X" 1.25) -> "1.25" which is perverse (ClTl2 p 581: + * "if arg is not an integer, it is printed in ~A format and decimal base")!! + * I think I'll use the type of the number to choose the output format. + */ + case 'D': case 'd': + if (is_null(fdat->args)) + format_error_nr(sc, "~~D: missing argument", 21, str, args, fdat); + if (!(is_number(car(fdat->args)))) + { + /* (let () (require mockery.scm) (format #f "~D" ((*mock-number* 'mock-number) 123))) + * port here is a string-port, str has the width/precision data if the caller wants it, + * args is the current arg. But format_number handles fdat->args and so on, so + * I think I'll pass the format method the current control string (str), the + * current object (car(fdat->args)), and the arglist (args), and assume it will + * return a (scheme) string. + */ + if (!format_method(sc, (const char *)(str + i), fdat, port)) + format_error_nr(sc, "~~D: numeric argument required", 30, str, args, fdat); + } + else format_number(sc, fdat, 10, width, precision, 'd', pad, port); + break; + + case 'O': case 'o': + if (is_null(fdat->args)) + format_error_nr(sc, "~~O: missing argument", 21, str, args, fdat); + if (!(is_number(car(fdat->args)))) + { + if (!format_method(sc, (const char *)(str + i), fdat, port)) + format_error_nr(sc, "~~O: numeric argument required", 30, str, args, fdat); + } + else format_number(sc, fdat, 8, width, precision, 'o', pad, port); + break; + + case 'X': case 'x': + if (is_null(fdat->args)) + format_error_nr(sc, "~~X: missing argument", 21, str, args, fdat); + if (!(is_number(car(fdat->args)))) + { + if (!format_method(sc, (const char *)(str + i), fdat, port)) + format_error_nr(sc, "~~X: numeric argument required", 30, str, args, fdat); + } + else format_number(sc, fdat, 16, width, precision, 'x', pad, port); + break; + + case 'B': case 'b': + if (is_null(fdat->args)) + format_error_nr(sc, "~~B: missing argument", 21, str, args, fdat); + if (!(is_number(car(fdat->args)))) + { + if (!format_method(sc, (const char *)(str + i), fdat, port)) + format_error_nr(sc, "~~B: numeric argument required", 30, str, args, fdat); + } + else format_number(sc, fdat, 2, width, precision, 'b', pad, port); + break; + + default: + if (width > 0) + format_error_nr(sc, "unused numeric argument", 23, str, args, fdat); + format_error_nr(sc, "unimplemented format directive", 30, str, args, fdat); + }} + break; + + default: + format_error_nr(sc, "unimplemented format directive", 30, str, args, fdat); + }} + else /* str[i] is not #\~ */ + { + const char *p = (char *)strchr((const char *)(str + i + 1), (int)'~'); + s7_int j = (p) ? p - str : str_len; + s7_int new_len = j - i; + + if ((port_data(port)) && + ((port_position(port) + new_len) < port_data_size(port))) + { + memcpy((void *)(port_data(port) + port_position(port)), (const void *)(str + i), new_len); + port_position(port) += new_len; + } + else port_write_string(port)(sc, (const char *)(str + i), new_len, port); + fdat->loc += new_len; + sc->format_column += new_len; + i = j - 1; + }} + + ALL_DONE: + if (next_arg) + (*next_arg) = fdat->args; + else + if (is_not_null(fdat->args)) + format_error_nr(sc, "too many arguments", 18, str, args, fdat); + + if (i < str_len) + { + if (str[i] == '~') + format_error_nr(sc, "control string ends in tilde", 28, str, args, fdat); + format_append_char(sc, str[i], port); + } + sc->format_depth--; + if (with_result) + { + s7_pointer result; + if ((is_output_port(deferred_port)) && + (port_position(port) > 0)) + { + if (port_position(port) < port_data_size(port)) + port_data(port)[port_position(port)] = '\0'; + port_write_string(deferred_port)(sc, (const char *)port_data(port), port_position(port), deferred_port); + } + if (port_position(port) < port_data_size(port)) + { + block_t *block = inline_mallocate(sc, FORMAT_PORT_LENGTH); + result = inline_block_to_string(sc, port_data_block(port), port_position(port)); + port_data_size(port) = FORMAT_PORT_LENGTH; + port_data_block(port) = block; + port_data(port) = (uint8_t *)(block_data(block)); + port_data(port)[0] = '\0'; + port_position(port) = 0; + } + else result = make_string_with_length(sc, (char *)port_data(port), port_position(port)); + close_format_port(sc, port); + fdat->port = NULL; + return(result); + } + return(sc->F); +} + +static bool is_columnizing(const char *str) /* look for ~t ~,T ~,t */ +{ + for (const char *p = (const char *)str; (*p);) + if (*p++ == '~') /* this is faster than strchr */ + { + char c = *p++; + if ((c == 't') || (c == 'T')) return(true); + if (!c) return(false); + if ((c == ',') || ((c >= '0') && (c <= '9')) || (c == 'n') || (c == 'N')) + { + while (((c >= '0') && (c <= '9')) || (c == 'n') || (c == 'N')) c = *p++; + if ((c == 't') || (c == 'T')) return(true); + if (!c) return(false); /* ~,1 for example */ + if (c == ',') + { + c = *p++; + while (((c >= '0') && (c <= '9')) || (c == 'n') || (c == 'N')) c = *p++; + if ((c == 't') || (c == 'T')) return(true); + if (!c) return(false); + }}} + return(false); +} + +static s7_pointer g_format(s7_scheme *sc, s7_pointer args) +{ + #define H_format "(format out str . args) substitutes args into str sending the result to out. Most of \ +s7's format directives are taken from CL: ~% = newline, ~& = newline if the preceding output character was \ +no a newline, ~~ = ~, ~ trims white space, ~* skips an argument, ~^ exits {} iteration if the arg list is exhausted, \ +~nT spaces over to column n, ~A prints a representation of any object, ~S is the same, but puts strings in double quotes, \ +~C prints a character, numbers are handled by ~F, ~E, ~G, ~B, ~O, ~D, and ~X with preceding numbers giving \ +spacing (and spacing character) and precision. ~{ starts an embedded format directive which is ended by ~}: \n\ +\n\ + >(format #f \"dashed: ~{~A~^-~}\" '(1 2 3))\n\ + \"dashed: 1-2-3\"\n\ +\n\ +~P inserts \"s\" if the current it is not 1 or 1.0 (use ~@P for \"ies\" or \"y\").\n\ +~B is number->string in base 2, ~O in base 8, ~D base 10, ~X base 16,\n\ +~E: (format #f \"~E\" 100.1) -> \"1.001000e+02\" (%e in C)\n\ +~F: (format #f \"~F\" 100.1) -> \"100.100000\" (%f in C)\n\ +~G: (format #f \"~G\" 100.1) -> \"100.1\" (%g in C)\n\ +\n\ +If the 'out' it is not an output port, the resultant string is returned. If it \ +is #t, the string is also sent to the current-output-port." + + #define Q_format s7_make_circular_signature(sc, 2, 3, \ + s7_make_signature(sc, 2, sc->is_string_symbol, sc->not_symbol), \ + s7_make_signature(sc, 3, sc->is_output_port_symbol, sc->is_boolean_symbol, sc->is_null_symbol), sc->T) + + s7_pointer pt = car(args), str; + if (is_null(pt)) + { + pt = current_output_port(sc); /* () -> (current-output-port) */ + if (pt == sc->F) /* otherwise () -> #f so we get a returned string, which is confusing */ + return(pt); /* but this means some error checks are skipped? */ + } + sc->format_column = 0; + if (!((is_boolean(pt)) || /* #f or #t */ + ((is_output_port(pt)) && /* (current-output-port) or call-with-open-file arg, etc */ + (!port_is_closed(pt))))) + return(method_or_bust(sc, pt, sc->format_symbol, args, an_output_port_string, 1)); + + str = cadr(args); + if (!is_string(str)) + return(method_or_bust(sc, str, sc->format_symbol, args, sc->type_names[T_STRING], 2)); + return(format_to_port_1(sc, (pt == sc->T) ? current_output_port(sc) : pt, + string_value(str), cddr(args), NULL, !is_output_port(pt), true, string_length(str), str)); +} + +const char *s7_format(s7_scheme *sc, s7_pointer args) +{ + s7_pointer result = g_format(sc, args); + return((is_string(result)) ? string_value(result) : NULL); +} + +static s7_pointer g_format_f(s7_scheme *sc, s7_pointer args) /* port == #f, there are other args */ +{ + s7_pointer str = cadr(args); + sc->format_column = 0; + if (!is_string(str)) + return(method_or_bust(sc, str, sc->format_symbol, args, sc->type_names[T_STRING], 2)); + return(format_to_port_1(sc, sc->F, string_value(str), cddr(args), NULL, true, true, string_length(str), str)); +} + +static s7_pointer g_format_just_control_string(s7_scheme *sc, s7_pointer args) +{ + s7_pointer pt = car(args); + s7_pointer str = cadr(args); + if (pt == sc->F) + return(str); + + if (is_null(pt)) + { + pt = current_output_port(sc); + if (pt == sc->F) + return(sc->F); + } + if (pt == sc->T) + { + if ((current_output_port(sc) != sc->F) && (string_length(str) != 0)) + port_write_string(current_output_port(sc))(sc, string_value(str), string_length(str), current_output_port(sc)); + return(str); + } + if ((!is_output_port(pt)) || + (port_is_closed(pt))) + return(method_or_bust(sc, pt, sc->format_symbol, args, a_format_port_string, 1)); + + if (string_length(str) == 0) + return((is_output_port(pt)) ? sc->F : nil_string); + + port_write_string(pt)(sc, string_value(str), string_length(str), pt); + return(sc->F); +} + +static s7_pointer g_format_as_objstr(s7_scheme *sc, s7_pointer args) +{ + s7_pointer func, obj = caddr(args); + if ((!has_active_methods(sc, obj)) || + ((func = find_method_with_let(sc, obj, sc->format_symbol)) == sc->undefined)) + return(s7_object_to_string(sc, obj, false)); + return(s7_apply_function(sc, func, set_plist_3(sc, sc->F, cadr(args), obj))); +} + +static s7_pointer g_format_no_column(s7_scheme *sc, s7_pointer args) +{ + s7_pointer pt = car(args), str; + if (is_null(pt)) + { + pt = current_output_port(sc); + if (pt == sc->F) + return(sc->F); + } + if (!((is_boolean(pt)) || + ((is_output_port(pt)) && /* (current-output-port) or call-with-open-file arg, etc */ + (!port_is_closed(pt))))) + return(method_or_bust(sc, pt, sc->format_symbol, args, a_format_port_string, 1)); + + str = cadr(args); + sc->format_column = 0; + return(format_to_port_1(sc, (pt == sc->T) ? current_output_port(sc) : pt, + string_value(str), cddr(args), NULL, + !is_output_port(pt), /* i.e. is boolean port so we're returning a string */ + false, /* we checked in advance that it is not columnized */ + string_length(str), str)); +} + +static s7_pointer format_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool ops) +{ + if (args > 1) + { + const s7_pointer port = cadr(expr); + s7_pointer str_arg = caddr(expr); + if (is_string(str_arg)) + { + if ((ops) && ((args == 2) || (args == 3))) + { + s7_int len; + char *orig = string_value(str_arg); + const char *p = strchr((const char *)orig, (int)'~'); + if (!p) + return((args == 2) ? sc->format_just_control_string : f); + + len = string_length(str_arg); + if ((args == 2) && + (len > 1) && + (orig[len - 1] == '%') && + ((p - orig) == len - 2)) + { + orig[len - 2] = '\n'; + orig[len - 1] = '\0'; + string_length(str_arg) = len - 1; + return(sc->format_just_control_string); + } + if ((args == 3) && + (len == 2) && + (port == sc->F) && + (orig[0] == '~') && + ((orig[1] == 'A') || (orig[1] == 'a'))) + return(sc->format_as_objstr); + } + /* this used to worry about optimized expr and particular cases -- why? I can't find a broken case */ + if (!is_columnizing(string_value(str_arg))) + return(sc->format_no_column); + } + if (port == sc->F) + return(sc->format_f); + } + return(f); +} + + +#if WITH_SYSTEM_EXTRAS +#include + +/* -------------------------------- directory? -------------------------------- */ +static bool is_directory_b_7p(s7_scheme *sc, s7_pointer p) +{ + if (!is_string(p)) + sole_arg_wrong_type_error_nr(sc, sc->is_directory_symbol, p, sc->type_names[T_STRING]); + if (string_length(p) >= 2) + { + block_t *b = expand_filename(sc, string_value(p)); + if (b) + { + bool result = is_directory((char *)block_data(b)); + liberate(sc, b); + return(result); + }} + return(is_directory(string_value(p))); +} + +static s7_pointer g_is_directory(s7_scheme *sc, s7_pointer args) +{ + #define H_is_directory "(directory? str) returns #t if str is the name of a directory" + #define Q_is_directory s7_make_signature(sc, 2, sc->is_boolean_symbol, sc->is_string_symbol) + return(s7_make_boolean(sc, is_directory_b_7p(sc, car(args)))); +} + +/* -------------------------------- file-exists? -------------------------------- */ +static bool file_probe(const char *arg) +{ +#if (!MS_WINDOWS) + return(access(arg, F_OK) == 0); +#else + int32_t fd = open(arg, O_RDONLY, 0); + if (fd == -1) return(false); + close(fd); + return(true); +#endif +} + +static bool file_exists_b_7p(s7_scheme *sc, s7_pointer p) +{ + if (!is_string(p)) + sole_arg_wrong_type_error_nr(sc, sc->file_exists_symbol, p, sc->type_names[T_STRING]); + if (string_length(p) >= 2) + { + block_t *b = expand_filename(sc, string_value(p)); + if (b) + { + bool result = file_probe((char *)block_data(b)); + liberate(sc, b); + return(result); + }} + return(file_probe(string_value(p))); +} + +static s7_pointer g_file_exists(s7_scheme *sc, s7_pointer args) +{ + #define H_file_exists "(file-exists? filename) returns #t if the file exists" + #define Q_file_exists s7_make_signature(sc, 2, sc->is_boolean_symbol, sc->is_string_symbol) + return(s7_make_boolean(sc, file_exists_b_7p(sc, car(args)))); +} + +/* -------------------------------- delete-file -------------------------------- */ +static s7_pointer g_delete_file(s7_scheme *sc, s7_pointer args) +{ + #define H_delete_file "(delete-file filename) deletes the file filename." + #define Q_delete_file s7_make_signature(sc, 2, sc->is_integer_symbol, sc->is_string_symbol) + + s7_pointer name = car(args); + if (!is_string(name)) + return(sole_arg_method_or_bust(sc, name, sc->delete_file_symbol, args, sc->type_names[T_STRING])); + if (string_length(name) > 2) + { + block_t *b = expand_filename(sc, string_value(name)); + if (b) + { + s7_int result = unlink((char *)block_data(b)); + liberate(sc, b); + return(make_integer(sc, result)); + }} + return(make_integer(sc, unlink(string_value(name)))); +} + +/* -------------------------------- getenv -------------------------------- */ +static s7_pointer g_getenv(s7_scheme *sc, s7_pointer args) /* r7rs says #f if no such variable. this used to return "" in that case, 6-May-22 */ +{ + #define H_getenv "(getenv var) returns the value of an environment variable, or #f if none is found" + #define Q_getenv s7_make_signature(sc, 2, s7_make_signature(sc, 2, sc->is_string_symbol, sc->not_symbol), sc->is_string_symbol) + + char *result; + s7_pointer name = car(args); + if (!is_string(name)) + return(sole_arg_method_or_bust(sc, name, sc->getenv_symbol, args, sc->type_names[T_STRING])); + result = getenv(string_value(name)); + return((result) ? s7_make_string(sc, result) : sc->F); +} + +/* -------------------------------- system -------------------------------- */ +static s7_pointer g_system(s7_scheme *sc, s7_pointer args) +{ + #define H_system "(system command) executes the command. If the optional second argument is #t, \ +system captures the output as a string and returns it." + #define Q_system s7_make_signature(sc, 3, s7_make_signature(sc, 2, sc->is_integer_symbol, sc->is_string_symbol), sc->is_string_symbol, sc->is_boolean_symbol) + +#ifdef __EMSCRIPTEN__ + return s7_nil(sc); +#else + s7_pointer name = car(args); + + if (!is_string(name)) + return(sole_arg_method_or_bust(sc, name, sc->system_symbol, args, sc->type_names[T_STRING])); + + if ((is_pair(cdr(args))) && + (cadr(args) == sc->T)) + { + #define BUF_SIZE 256 + char buf[BUF_SIZE]; + char *str = NULL; + int32_t cur_len = 0, full_len = 0; + FILE *fd = popen(string_value(name), "r"); + while (fgets(buf, BUF_SIZE, fd)) + { + s7_int buf_len = safe_strlen(buf); + if (cur_len + buf_len >= full_len) + { + full_len += BUF_SIZE * 2; + str = (str) ? (char *)Realloc(str, full_len) : (char *)Malloc(full_len); + } + memcpy((void *)(str + cur_len), (void *)buf, buf_len); + cur_len += buf_len; + } + pclose(fd); + if (str) + { + block_t *b = mallocate_block(sc); + block_data(b) = (void *)str; + block_set_index(b, TOP_BLOCK_LIST); + return(block_to_string(sc, b, cur_len)); + } + return(nil_string); + } + return(make_integer(sc, system(string_value(name)))); +#endif +} + + +#if (!MS_WINDOWS) +#include + +/* -------------------------------- directory->list -------------------------------- */ +static s7_pointer directory_to_list_1(s7_scheme *sc, const char *dir_name) +{ + s7_pointer result; + DIR *dpos; + sc->w = sc->nil; + if ((dpos = opendir(dir_name))) + { + struct dirent *dirp; + while ((dirp = readdir(dpos))) + sc->w = cons_unchecked(sc, s7_make_string(sc, dirp->d_name), sc->w); + closedir(dpos); + } + result = sc->w; + sc->w = sc->unused; + return(result); +} + +static s7_pointer g_directory_to_list(s7_scheme *sc, s7_pointer args) +{ + #define H_directory_to_list "(directory->list directory) returns the contents of the directory as a list of strings (filenames)." + #define Q_directory_to_list s7_make_signature(sc, 2, sc->is_list_symbol, sc->is_string_symbol) /* can return nil */ + + s7_pointer name = car(args); + if (!is_string(name)) + return(method_or_bust_p(sc, name, sc->directory_to_list_symbol, sc->type_names[T_STRING])); + if (string_length(name) >= 2) + { + block_t *b = expand_filename(sc, string_value(name)); + if (b) + { + s7_pointer result = directory_to_list_1(sc, (char *)block_data(b)); + liberate(sc, b); + return(result); + }} + return(directory_to_list_1(sc, string_value(name))); +} + +/* -------------------------------- file-mtime -------------------------------- */ +static s7_pointer g_file_mtime(s7_scheme *sc, s7_pointer args) +{ + #define H_file_mtime "(file-mtime file): return the write date of file" + #define Q_file_mtime s7_make_signature(sc, 2, sc->is_integer_symbol, sc->is_string_symbol) + + struct stat statbuf; + int32_t err; + s7_pointer name = car(args); + + if (!is_string(name)) + return(sole_arg_method_or_bust(sc, name, sc->file_mtime_symbol, args, sc->type_names[T_STRING])); + if (string_length(name) >= 2) + { + block_t *b = expand_filename(sc, string_value(name)); + if (b) + { + err = stat((char *)block_data(b), &statbuf); + liberate(sc, b); + if (err < 0) + file_error_nr(sc, "file-mtime", strerror(errno), string_value(name)); + return(make_integer(sc, (s7_int)(statbuf.st_mtime))); + }} + err = stat(string_value(name), &statbuf); + if (err < 0) + file_error_nr(sc, "file-mtime", strerror(errno), string_value(name)); + return(make_integer(sc, (s7_int)(statbuf.st_mtime))); +} +#endif +#endif /* with_system_extras */ + + +/* -------------------------------- lists -------------------------------- */ +s7_pointer s7_cons(s7_scheme *sc, s7_pointer a, s7_pointer b) +{ + s7_pointer x; + new_cell(sc, x, T_PAIR | T_SAFE_PROCEDURE); + set_car(x, a); + set_cdr(x, b); + return(x); +} + +static s7_pointer cons_unchecked(s7_scheme *sc, s7_pointer a, s7_pointer b) +{ + /* apparently slightly faster as a function? */ + s7_pointer x; + new_cell_no_check(sc, x, T_PAIR | T_SAFE_PROCEDURE); + set_car(x, a); + set_cdr(x, b); + return(x); +} + +static s7_pointer semipermanent_cons(s7_scheme *sc, s7_pointer a, s7_pointer b, uint64_t type) +{ + s7_pointer x = alloc_pointer(sc); + set_full_type(x, type | T_UNHEAP); + set_car(x, a); + set_cdr(x, b); + return(x); +} + +static s7_pointer semipermanent_list(s7_scheme *sc, s7_int len) +{ + s7_pointer p = sc->nil; + for (s7_int j = 0; j < len; j++) + p = semipermanent_cons(sc, sc->unused, p, T_PAIR | T_IMMUTABLE); + return(p); +} + +s7_pointer s7_make_signature(s7_scheme *sc, s7_int len, ...) +{ + va_list ap; + s7_int i; + s7_pointer res = sc->nil; + + for (i = 0; i < len; i++) + res = semipermanent_cons(sc, sc->unused, res, T_PAIR | T_IMMUTABLE); + va_start(ap, len); + i = 0; + for (s7_pointer p = res; is_pair(p); p = cdr(p), i++) + { + set_car(p, va_arg(ap, s7_pointer)); + if ((!is_normal_symbol(car(p))) && (!is_boolean(car(p))) && (!is_pair(car(p)))) + s7_warn(sc, 512, "s7_make_signature got an invalid entry %s at position %" ld64, display(car(p)), i); + } + va_end(ap); + return((s7_pointer)res); +} + +s7_pointer s7_make_circular_signature(s7_scheme *sc, s7_int cycle_point, s7_int len, ...) +{ + va_list ap; + s7_int i; + s7_pointer p, res = sc->nil, back = NULL, end = NULL; + + for (i = 0; i < len; i++) + res = semipermanent_cons(sc, sc->nil, res, T_PAIR | T_IMMUTABLE); + va_start(ap, len); + for (p = res, i = 0; is_pair(p); p = cdr(p), i++) + { + set_car(p, va_arg(ap, s7_pointer)); + if ((!is_normal_symbol(car(p))) && (!is_boolean(car(p))) && (!is_pair(car(p)))) + s7_warn(sc, 512, "s7_make_circular_signature got an invalid entry %s at position %" ld64, display(car(p)), i); + if (i == cycle_point) back = p; + if (i == (len - 1)) end = p; + } + va_end(ap); + if (end) set_cdr(end, back); + if (i < len) + s7_warn(sc, 256, "s7_make_circular_signature got too few entries: %s\n", display(res)); + return((s7_pointer)res); +} + + +bool s7_is_pair(s7_pointer p) {return(is_pair(p));} +static s7_pointer is_pair_p_p(s7_scheme *sc, s7_pointer p) {return((is_pair(p)) ? sc->T : sc->F);} + +s7_pointer s7_car(s7_pointer p) {return(car(p));} +s7_pointer s7_cdr(s7_pointer p) {return(cdr(p));} + +s7_pointer s7_cadr(s7_pointer p) {return(cadr(p));} +s7_pointer s7_cddr(s7_pointer p) {return(cddr(p));} +s7_pointer s7_cdar(s7_pointer p) {return(cdar(p));} +s7_pointer s7_caar(s7_pointer p) {return(caar(p));} + +s7_pointer s7_caadr(s7_pointer p) {return(caadr(p));} +s7_pointer s7_caddr(s7_pointer p) {return(caddr(p));} +s7_pointer s7_cadar(s7_pointer p) {return(cadar(p));} +s7_pointer s7_caaar(s7_pointer p) {return(caaar(p));} +s7_pointer s7_cdadr(s7_pointer p) {return(cdadr(p));} +s7_pointer s7_cdddr(s7_pointer p) {return(cdddr(p));} +s7_pointer s7_cddar(s7_pointer p) {return(cddar(p));} +s7_pointer s7_cdaar(s7_pointer p) {return(cdaar(p));} + +s7_pointer s7_caaadr(s7_pointer p) {return(caaadr(p));} +s7_pointer s7_caaddr(s7_pointer p) {return(caaddr(p));} +s7_pointer s7_caadar(s7_pointer p) {return(caadar(p));} +s7_pointer s7_caaaar(s7_pointer p) {return(caaaar(p));} +s7_pointer s7_cadadr(s7_pointer p) {return(cadadr(p));} +s7_pointer s7_cadddr(s7_pointer p) {return(cadddr(p));} +s7_pointer s7_caddar(s7_pointer p) {return(caddar(p));} +s7_pointer s7_cadaar(s7_pointer p) {return(cadaar(p));} + +s7_pointer s7_cdaadr(s7_pointer p) {return(cdaadr(p));} +s7_pointer s7_cdaddr(s7_pointer p) {return(cdaddr(p));} +s7_pointer s7_cdadar(s7_pointer p) {return(cdadar(p));} +s7_pointer s7_cdaaar(s7_pointer p) {return(cdaaar(p));} +s7_pointer s7_cddadr(s7_pointer p) {return(cddadr(p));} +s7_pointer s7_cddddr(s7_pointer p) {return(cddddr(p));} +s7_pointer s7_cdddar(s7_pointer p) {return(cdddar(p));} +s7_pointer s7_cddaar(s7_pointer p) {return(cddaar(p));} + +s7_pointer s7_set_car(s7_pointer p, s7_pointer q) {set_car(p, q); return(q);} +s7_pointer s7_set_cdr(s7_pointer p, s7_pointer q) {set_cdr(p, q); return(q);} + + +/* -------------------------------------------------------------------------------- */ +void s7_list_to_array(s7_scheme *sc, s7_pointer list, s7_pointer *array, int32_t len) +{ + int32_t i = 0; + for (s7_pointer p = list; is_pair(p); p = cdr(p), i++) array[i] = car(p); + for (; i < len; i++) array[i] = sc->undefined; +} + + +/* ---------------- tree-leaves ---------------- */ +static inline s7_int tree_len_1(s7_scheme *sc, s7_pointer p) +{ + s7_int sum; + if ((S7_DEBUGGING) && (tree_is_cyclic(sc, p))) {fprintf(stderr, "%s[%d]: tree is cyclic\n", __func__, __LINE__); abort();} + for (sum = 0; is_pair(p); p = cdr(p)) + { + s7_pointer cp = car(p); + if ((!is_pair(cp)) || + (car(cp) == sc->quote_symbol)) + sum++; + else + { + do { + s7_pointer ccp = car(cp); + if ((!is_pair(ccp)) || + (car(ccp) == sc->quote_symbol)) + sum++; + else + { + do { + s7_pointer cccp = car(ccp); + if ((!is_pair(cccp)) || + (car(cccp) == sc->quote_symbol)) + sum++; + else sum += tree_len_1(sc, cccp); + ccp = cdr(ccp); + } while (is_pair(ccp)); + if (!is_null(ccp)) sum++; + } + cp = cdr(cp); + } while (is_pair(cp)); + if (!is_null(cp)) sum++; + }} + return((is_null(p)) ? sum : sum + 1); +} + +static inline s7_int tree_len(s7_scheme *sc, s7_pointer p) +{ + if (is_null(p)) + return(0); + if ((!is_pair(p)) || + (car(p) == sc->quote_symbol)) + return(1); + return(tree_len_1(sc, p)); +} + +static s7_int tree_leaves_i_7p(s7_scheme *sc, s7_pointer p) +{ + if ((sc->safety > NO_SAFETY) && + (tree_is_cyclic(sc, p))) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "tree-leaves: tree is cyclic: ~S", 31), p)); + return(tree_len(sc, p)); +} + +static s7_pointer tree_leaves_p_p(s7_scheme *sc, s7_pointer tree) +{ + if ((sc->safety > NO_SAFETY) && /* repeat code to avoid extra call overhead */ + (tree_is_cyclic(sc, tree))) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "tree-leaves: tree is cyclic: ~S", 31), tree)); + return(make_integer(sc, tree_len(sc, tree))); +} + +static s7_pointer g_tree_leaves(s7_scheme *sc, s7_pointer args) +{ + #define H_tree_leaves "(tree-leaves tree) returns the number of leaves in the tree" + #define Q_tree_leaves s7_make_signature(sc, 2, sc->is_integer_symbol, sc->T) + return(tree_leaves_p_p(sc, car(args))); +} + + +/* ---------------- tree-memq ---------------- */ +static inline bool tree_memq_1(s7_scheme *sc, s7_pointer sym, s7_pointer tree) /* sym need not be a symbol */ +{ + if (car(tree) == sc->quote_symbol) + return((!is_symbol(sym)) && (!is_pair(sym)) && (is_pair(cdr(tree))) && (sym == cadr(tree))); + do { + if (sym == car(tree)) + return(true); + + if (is_pair(car(tree))) + { + s7_pointer cp = car(tree); + if (car(cp) == sc->quote_symbol) + { + if ((!is_symbol(sym)) && (!is_pair(sym)) && (is_pair(cdr(cp))) && (sym == cadr(cp))) + return(true); + } + else + do { + if (sym == car(cp)) + return(true); + if ((is_pair(car(cp))) && (tree_memq_1(sc, sym, car(cp)))) + return(true); + cp = cdr(cp); + if (sym == cp) + return(true); + } while (is_pair(cp)); + } + tree = cdr(tree); + if (sym == tree) + return(true); + } while (is_pair(tree)); + return(false); +} + +bool s7_tree_memq(s7_scheme *sc, s7_pointer sym, s7_pointer tree) +{ + if (sym == tree) return(true); + if (!is_pair(tree)) return(false); + if ((sc->safety > NO_SAFETY) && + (tree_is_cyclic(sc, tree))) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "tree-memq: tree is cyclic: ~S", 29), tree)); + return(tree_memq_1(sc, sym, tree)); +} + +static s7_pointer g_tree_memq(s7_scheme *sc, s7_pointer args) +{ + #define H_tree_memq "(tree-memq obj tree) is a tree-oriented version of memq, but returning #t if the object is in the tree." + #define Q_tree_memq s7_make_signature(sc, 3, sc->is_boolean_symbol, sc->T, sc->T) + return(make_boolean(sc, s7_tree_memq(sc, car(args), cadr(args)))); +} + + +/* ---------------- tree-set-memq ---------------- */ +static inline bool pair_set_memq(s7_scheme *sc, s7_pointer tree) +{ + while (true) + { + s7_pointer p = car(tree); + if (is_symbol(p)) + { + if (symbol_is_in_list(sc, p)) + return(true); + } + else + if ((is_unquoted_pair(p)) && + (pair_set_memq(sc, p))) + return(true); + tree = cdr(tree); + if (!is_pair(tree)) break; + } + return((is_symbol(tree)) && (symbol_is_in_list(sc, tree))); +} + +static bool tree_set_memq(s7_scheme *sc, s7_pointer tree) +{ + if (is_symbol(tree)) + return(symbol_is_in_list(sc, tree)); + if ((!is_pair(tree)) || + (car(tree) == sc->quote_symbol)) + return(false); + return(pair_set_memq(sc, tree)); +} + +static bool tree_set_memq_b_7pp(s7_scheme *sc, s7_pointer syms, s7_pointer tree) +{ + if (!is_pair(syms)) return(false); + if (sc->safety > NO_SAFETY) + { + if (tree_is_cyclic(sc, syms)) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "tree-set-memq: symbol list is cyclic: ~S", 40), syms)); + if (tree_is_cyclic(sc, tree)) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "tree-set-memq: tree is cyclic: ~S", 33), tree)); + } + clear_symbol_list(sc); + for (s7_pointer p = syms; is_pair(p); p = cdr(p)) + if (is_symbol(car(p))) + add_symbol_to_list(sc, car(p)); + return(tree_set_memq(sc, tree)); +} + +static s7_pointer tree_set_memq_p_pp(s7_scheme *sc, s7_pointer syms, s7_pointer tree) +{ + return(make_boolean(sc, tree_set_memq_b_7pp(sc, syms, tree))); +} + +static s7_pointer g_tree_set_memq(s7_scheme *sc, s7_pointer args) +{ + #define H_tree_set_memq "(tree-set-memq symbols tree) returns #t if any of the list of symbols is in the tree" + #define Q_tree_set_memq s7_make_signature(sc, 3, sc->is_boolean_symbol, sc->T, sc->T) + return(make_boolean(sc, tree_set_memq_b_7pp(sc, car(args), cadr(args)))); +} + +static s7_pointer tree_set_memq_direct(s7_scheme *sc, s7_pointer syms, s7_pointer tree) +{ + if ((sc->safety > NO_SAFETY) && + (tree_is_cyclic(sc, tree))) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "tree-set-memq: tree is cyclic: ~S", 33), tree)); + clear_symbol_list(sc); + for (s7_pointer p = syms; is_pair(p); p = cdr(p)) + add_symbol_to_list(sc, car(p)); + return(make_boolean(sc, tree_set_memq(sc, tree))); +} + +static s7_pointer g_tree_set_memq_1(s7_scheme *sc, s7_pointer args) +{ + return(tree_set_memq_direct(sc, car(args), cadr(args))); +} + +static s7_pointer tree_set_memq_chooser(s7_scheme *sc, s7_pointer f, int32_t unused_args, s7_pointer expr, bool unused_ops) +{ + if ((is_proper_quote(sc, cadr(expr))) && /* not (tree-set-memq (quote) ... */ + (is_pair(cadadr(expr)))) + { + for (s7_pointer p = cadadr(expr); is_pair(p); p = cdr(p)) + if (!is_symbol(car(p))) + return(f); + return(sc->tree_set_memq_syms); /* this is tree_set_memq_1 */ + } + return(f); +} + + +/* ---------------- tree-count ---------------- */ +static s7_int tree_count(s7_scheme *sc, s7_pointer x, s7_pointer p, s7_int count) +{ + if (p == x) return(count + 1); + if ((!is_pair(p)) || (car(p) == sc->quote_symbol)) return(count); + return(tree_count(sc, x, cdr(p), tree_count(sc, x, car(p), count))); +} + +static inline s7_int tree_count_at_least(s7_scheme *sc, s7_pointer x, s7_pointer p, s7_int count, s7_int top) +{ + if (p == x) return(count + 1); + if ((!is_pair(p)) || (car(p) == sc->quote_symbol)) return(count); + do { + count = tree_count_at_least(sc, x, car(p), count, top); + if (count >= top) return(count); + p = cdr(p); + if (p == x) return(count + 1); + } while (is_pair(p)); + return(count); +} + +static s7_pointer g_tree_count(s7_scheme *sc, s7_pointer args) +{ + #define H_tree_count "(tree-count obj tree max-count) returns how many times obj is in tree (using eq?), stopping at max-count (if specified)" + #define Q_tree_count s7_make_signature(sc, 4, sc->is_integer_symbol, sc->T, sc->T, sc->is_integer_symbol) + s7_pointer obj = car(args); + s7_pointer tree = cadr(args), count; + + if (!is_pair(tree)) + { + if ((is_pair(cddr(args))) && + (!s7_is_integer(caddr(args)))) + wrong_type_error_nr(sc, sc->tree_count_symbol, 3, caddr(args), sc->type_names[T_INTEGER]); + /* here we need eqv? not eq? for integers: (tree-count <0-int-zero> <0-not-int-zero>) + * perhaps split tree_count|_at_least into eq?/eqv?/equal?/equivalent? cases? + * this is used primarily for symbol counts in lint.scm + */ + return((obj == tree) ? int_one : int_zero); + } + if ((sc->safety > NO_SAFETY) && + (tree_is_cyclic(sc, tree))) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "tree-count: tree is cyclic: ~S", 30), tree)); + if (is_null(cddr(args))) + return(make_integer(sc, tree_count(sc, obj, tree, 0))); + count = caddr(args); + if (!s7_is_integer(count)) + wrong_type_error_nr(sc, sc->tree_count_symbol, 3, count, sc->type_names[T_INTEGER]); + return(make_integer(sc, tree_count_at_least(sc, obj, tree, 0, s7_integer_clamped_if_gmp(sc, count)))); +} + + +/* -------------------------------- pair? -------------------------------- */ +static s7_pointer g_is_pair(s7_scheme *sc, s7_pointer args) +{ + #define H_is_pair "(pair? obj) returns #t if obj is a pair (a non-empty list)" + #define Q_is_pair sc->pl_bt + check_boolean_method(sc, is_pair, sc->is_pair_symbol, args); +} + + +/* -------------------------------- list? -------------------------------- */ +bool s7_is_list(s7_scheme *sc, s7_pointer p) {return(is_list(p));} + +static bool is_list_b(s7_pointer p) {return((is_pair(p)) || (type(p) == T_NIL));} + +static s7_pointer g_is_list(s7_scheme *sc, s7_pointer args) +{ + #define H_is_list "(list? obj) returns #t if obj is a pair or null" + #define Q_is_list sc->pl_bt + #define is_a_list(p) s7_is_list(sc, p) + check_boolean_method(sc, is_a_list, sc->is_list_symbol, args); +} + +static s7_int proper_list_length(s7_pointer a) +{ + s7_int i = 0; + for (s7_pointer b = a; is_pair(b); i++, b = cdr(b)) {}; + return(i); +} + +static s7_int proper_list_length_with_end(s7_pointer a, s7_pointer *c) +{ + s7_int i = 0; + s7_pointer b; + for (b = a; is_pair(b); i++, b = cdr(b)) {}; + *c = b; + return(i); +} + +s7_int s7_list_length(s7_scheme *sc, s7_pointer a) /* returns -len if list is dotted, 0 if it's (directly) circular */ +{ + s7_pointer slow = a, fast = a; + for (s7_int i = 0; ; i += 2) + { + if (!is_pair(fast)) return((is_null(fast)) ? i : -i); + fast = cdr(fast); + if (!is_pair(fast)) return((is_null(fast)) ? (i + 1) : (-i - 1)); /* if unrolled further, it's a lot slower? */ + fast = cdr(fast); + slow = cdr(slow); + if (fast == slow) return(0); + } + return(0); +} + + +/* -------------------------------- proper-list? -------------------------------- */ +static inline s7_pointer copy_proper_list(s7_scheme *sc, s7_pointer lst) +{ + s7_pointer tp; + if (!is_pair(lst)) return(sc->nil); + sc->temp5 = lst; + tp = list_1(sc, car(lst)); + sc->temp8 = tp; + for (s7_pointer p = cdr(lst), np = tp; is_pair(p); p = cdr(p), np = cdr(np)) + { + set_cdr(np, list_1_unchecked(sc, car(p))); + p = cdr(p); + if (is_pair(p)) {np = cdr(np); set_cdr(np, list_1_unchecked(sc, car(p)));} else break; + p = cdr(p); + if (is_pair(p)) {np = cdr(np); set_cdr(np, list_1(sc, car(p)));} else break; + } + sc->temp8 = sc->unused; + sc->temp5 = sc->unused; + return(tp); +} + +bool s7_is_proper_list(s7_scheme *sc, s7_pointer lst) +{ + /* #t if () or undotted/non-circular pair */ + s7_pointer slow = lst, fast = lst; + while (true) + { + if (!is_pair(fast)) + return(is_null(fast)); /* else it's an improper list */ + LOOP_4(fast = cdr(fast); if (!is_pair(fast)) return(is_null(fast))); + fast = cdr(fast); + slow = cdr(slow); + if (fast == slow) return(false); + } + return(true); +} + +static s7_pointer g_is_proper_list(s7_scheme *sc, s7_pointer args) +{ + #define H_is_proper_list "(proper-list? x) returns #t is x is a list that is neither circular nor dotted." + #define Q_is_proper_list sc->pl_bt + return(make_boolean(sc, s7_is_proper_list(sc, car(args)))); +} + +static s7_pointer is_proper_list_p_p(s7_scheme *sc, s7_pointer arg) {return(make_boolean(sc, s7_is_proper_list(sc, arg)));} + +static bool is_proper_list_1(s7_scheme *sc, s7_pointer p) {return((is_pair(p)) && (is_null(cdr(p))));} +static bool is_proper_list_2(s7_scheme *sc, s7_pointer p) {return((is_pair(p)) && (is_pair(cdr(p))) && (is_null(cddr(p))));} +static bool is_proper_list_3(s7_scheme *sc, s7_pointer p) {return((is_pair(p)) && (is_pair(cdr(p))) && (is_pair(cddr(p))) && (is_null(cdddr(p))));} +static bool is_proper_list_4(s7_scheme *unused_sc, s7_pointer p) {return(proper_list_length(p) == 4);} + + +/* -------------------------------- make-list -------------------------------- */ +static s7_pointer make_big_list(s7_scheme *sc, s7_int len, s7_pointer init) +{ + check_free_heap_size(sc, len + 1); /* using cons_unchecked below, +1 in case we are on the trigger at the end */ + sc->value = sc->nil; + for (s7_int i = 0; i < len; i++) sc->value = cons_unchecked(sc, init, sc->value); + return(sc->value); +} + +static inline s7_pointer make_list(s7_scheme *sc, s7_int len, s7_pointer init) +{ + switch (len) + { + case 0: return(sc->nil); + case 1: return(T_Pair(cons(sc, init, sc->nil))); + case 2: return(T_Pair(cons_unchecked(sc, init, cons(sc, init, sc->nil)))); + case 3: return(T_Pair(cons_unchecked(sc, init, cons_unchecked(sc, init, cons(sc, init, sc->nil))))); + case 4: return(T_Pair(cons_unchecked(sc, init, cons_unchecked(sc, init, cons_unchecked(sc, init, cons(sc, init, sc->nil)))))); + case 5: return(T_Pair(cons_unchecked(sc, init, cons_unchecked(sc, init, cons_unchecked(sc, init, cons_unchecked(sc, init, cons(sc, init, sc->nil))))))); + case 6: return(T_Pair(cons_unchecked(sc, init, cons_unchecked(sc, init, cons_unchecked(sc, init, + cons_unchecked(sc, init, cons_unchecked(sc, init, cons(sc, init, sc->nil)))))))); + case 7: return(T_Pair(cons_unchecked(sc, init, cons_unchecked(sc, init, cons_unchecked(sc, init, + cons_unchecked(sc, init, cons_unchecked(sc, init, cons_unchecked(sc, init, cons(sc, init, sc->nil))))))))); + default: + return(make_big_list(sc, len, init)); + } + return(sc->nil); /* never happens, I hope */ +} + +s7_pointer s7_make_list(s7_scheme *sc, s7_int len, s7_pointer init) {return(make_list(sc, len, init));} + +static s7_pointer make_list_p_pp(s7_scheme *sc, s7_pointer n, s7_pointer init) +{ + s7_int len; + if (!s7_is_integer(n)) + return(method_or_bust(sc, n, sc->make_list_symbol, set_plist_2(sc, n, init), sc->type_names[T_INTEGER], 1)); + + len = s7_integer_clamped_if_gmp(sc, n); +#if WITH_GMP + if ((len == 0) && (!is_zero(n))) + out_of_range_error_nr(sc, sc->make_list_symbol, int_one, n, wrap_string(sc, "big integer is too big for s7_int", 33)); +#endif + if (len == 0) return(sc->nil); /* what about (make-list 0 123)? */ + if ((len < 0) || (len > sc->max_list_length)) + out_of_range_error_nr(sc, sc->make_list_symbol, int_one, n, (len < 0) ? it_is_negative_string : it_is_too_large_string); + return(make_list(sc, len, init)); +} + +static s7_pointer g_make_list(s7_scheme *sc, s7_pointer args) +{ + #define H_make_list "(make-list length (initial-element #f)) returns a list of 'length' elements whose value is 'initial-element'." + #define Q_make_list s7_make_signature(sc, 3, sc->is_proper_list_symbol, sc->is_integer_symbol, sc->T) + return(make_list_p_pp(sc, car(args), (is_pair(cdr(args))) ? cadr(args) : sc->F)); +} + + +/* -------------------------------- list-ref -------------------------------- */ +s7_pointer s7_list_ref(s7_scheme *sc, s7_pointer lst, s7_int num) +{ + s7_int i; + s7_pointer x; + for (x = lst, i = 0; (i < num) && (is_pair(x)); i++, x = cdr(x)) {} + if ((i == num) && (is_pair(x))) + return(car(x)); + return(sc->nil); +} + +static s7_pointer list_ref_1(s7_scheme *sc, s7_pointer lst, s7_pointer ind) +{ + s7_int index; + s7_pointer p = lst; + + if (!s7_is_integer(ind)) + return(method_or_bust_pp(sc, ind, sc->list_ref_symbol, lst, ind, sc->type_names[T_INTEGER], 2)); + index = s7_integer_clamped_if_gmp(sc, ind); + if ((index < 0) || (index > sc->max_list_length)) + out_of_range_error_nr(sc, sc->list_ref_symbol, int_two, ind, (index < 0) ? it_is_negative_string : it_is_too_large_string); + + for (s7_int i = 0; (i < index) && is_pair(p); i++, p = cdr(p)) {} + if (is_pair(p)) return(car(p)); + if (is_null(p)) + out_of_range_error_nr(sc, sc->list_ref_symbol, int_two, ind, it_is_too_large_string); + wrong_type_error_nr(sc, sc->list_ref_symbol, 1, lst, a_proper_list_string); + return(NULL); +} + +static s7_pointer implicit_index(s7_scheme *sc, s7_pointer obj, s7_pointer indices); + +static s7_pointer ref_index_checked(s7_scheme *sc, s7_pointer caller, s7_pointer in_obj, s7_pointer args) +{ + if (!is_applicable(in_obj)) /* let implicit_index shuffle syntax and closures */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_4(sc, wrap_string(sc, "~$ becomes ~$, but ~S can't take arguments", 42), + cons(sc, caller, args), cons(sc, in_obj, cddr(args)), in_obj)); + /* perhaps first $s -> "(~S ~{~$~^ ~})..." and we can pass the symbol rather than the global value as "caller" */ + return(implicit_index(sc, in_obj, cddr(args))); +} + +static s7_pointer g_list_ref(s7_scheme *sc, s7_pointer args) +{ + #define H_list_ref "(list-ref lst i ...) returns the i-th element (0-based) of the list" + #define Q_list_ref s7_make_circular_signature(sc, 2, 3, sc->T, sc->is_pair_symbol, sc->is_integer_symbol) + /* (let ((L '((1 2 3) (4 5 6)))) (list-ref L 1 2)) */ + + s7_pointer lst = car(args); + if (!is_pair(lst)) + return(method_or_bust(sc, lst, sc->list_ref_symbol, args, sc->type_names[T_PAIR], 1)); + + lst = list_ref_1(sc, lst, cadr(args)); + if (is_pair(cddr(args))) + return(ref_index_checked(sc, global_value(sc->list_ref_symbol), lst, args)); + return(lst); +} + +static bool op_implicit_pair_ref_a(s7_scheme *sc) +{ + s7_pointer s = lookup_checked(sc, car(sc->code)); + if (!is_pair(s)) {sc->last_function = s; return(false);} + sc->value = list_ref_1(sc, s, fx_call(sc, cdr(sc->code))); + return(true); +} + +static s7_pointer implicit_pair_index_checked(s7_scheme *sc, s7_pointer obj, s7_pointer in_obj, s7_pointer indices) +{ + if (!is_applicable(in_obj)) + { + s7_pointer safe_indices = copy_proper_list(sc, indices); + error_nr(sc, sc->syntax_error_symbol, + set_elist_4(sc, wrap_string(sc, "~$ becomes ~$, but ~S can't take arguments", 42), + cons(sc, obj, safe_indices), cons(sc, in_obj, cdr(safe_indices)), in_obj)); + } + return(implicit_index(sc, in_obj, cdr(indices))); +} + +static bool op_implicit_pair_ref_aa(s7_scheme *sc) +{ + s7_pointer i1; + s7_pointer s = lookup_checked(sc, car(sc->code)); + if (!is_pair(s)) {sc->last_function = s; return(false);} + sc->args = fx_call(sc, cddr(sc->code)); + i1 = fx_call(sc, cdr(sc->code)); + sc->value = implicit_pair_index_checked(sc, s, list_ref_1(sc, s, i1), set_plist_2(sc, i1, sc->args)); + return(true); +} + +static s7_pointer list_ref_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool unused_ops) +{ + if (args == 2) + { + s7_pointer index = caddr(expr); + if (is_t_integer(index)) + { + if (integer(index) == 0) return(sc->list_ref_at_0); + if (integer(index) == 1) return(sc->list_ref_at_1); + if (integer(index) == 2) return(sc->list_ref_at_2); + }} + return(f); +} + +static inline s7_pointer list_ref_p_pi_unchecked(s7_scheme *sc, s7_pointer p1, s7_int i1) +{ + s7_pointer p = p1; + if ((i1 < 0) || (i1 > sc->max_list_length)) + out_of_range_error_nr(sc, sc->list_ref_symbol, int_two, wrap_integer(sc, i1), (i1 < 0) ? it_is_negative_string : it_is_too_large_string); + for (s7_int i = 0; ((is_pair(p)) && (i < i1)); i++, p = cdr(p)); + if (!is_pair(p)) + { + if (is_null(p)) + out_of_range_error_nr(sc, sc->list_ref_symbol, int_two, wrap_integer(sc, i1), it_is_too_large_string); + wrong_type_error_nr(sc, sc->list_ref_symbol, 1, p1, a_proper_list_string); + } + return(car(p)); +} + +static s7_pointer list_ref_p_pi(s7_scheme *sc, s7_pointer p1, s7_int i1) +{ + if (!is_pair(p1)) + wrong_type_error_nr(sc, sc->list_ref_symbol, 1, p1, sc->type_names[T_PAIR]); + return(list_ref_p_pi_unchecked(sc, p1, i1)); +} + +static s7_pointer list_ref_p_pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + if (!is_pair(p1)) + return(g_list_ref(sc, set_plist_2(sc, p1, p2))); + if (!s7_is_integer(p2)) + wrong_type_error_nr(sc, sc->list_ref_symbol, 1, p2, sc->type_names[T_INTEGER]); + return(list_ref_p_pi_unchecked(sc, p1, s7_integer_clamped_if_gmp(sc, p2))); +} + + +/* -------------------------------- list-set! -------------------------------- */ +s7_pointer s7_list_set(s7_scheme *sc, s7_pointer lst, s7_int num, s7_pointer val) +{ + s7_int i; + s7_pointer x; + for (x = lst, i = 0; (i < num) && (is_pair(x)); i++, x = cdr(x)) {} + if ((i == num) && + (is_pair(x))) + set_car(x, T_Ext(val)); + return(val); +} + +static s7_pointer g_list_set_1(s7_scheme *sc, s7_pointer lst, s7_pointer args, int32_t arg_num) +{ + #define H_list_set "(list-set! lst i ... val) sets the i-th element (0-based) of the list to val" + #define Q_list_set s7_make_circular_signature(sc, 3, 4, sc->T, sc->is_pair_symbol, sc->is_integer_symbol, sc->is_integer_or_any_at_end_symbol) + + s7_int index; + s7_pointer p = lst, ind; + /* (let ((L '((1 2 3) (4 5 6)))) (list-set! L 1 2 32) L) */ + + if (!is_mutable_pair(lst)) + return(mutable_method_or_bust(sc, lst, sc->list_set_symbol, set_ulist_1(sc, lst, args), sc->type_names[T_PAIR], 1)); + ind = car(args); + if ((arg_num > 2) && (is_null(cdr(args)))) + { + set_car(lst, ind); + return(ind); + } + if (!s7_is_integer(ind)) + return(method_or_bust(sc, ind, sc->list_set_symbol, set_ulist_1(sc, lst, args), sc->type_names[T_INTEGER], 2)); + index = s7_integer_clamped_if_gmp(sc, ind); + if ((index < 0) || (index > sc->max_list_length)) + out_of_range_error_nr(sc, sc->list_set_symbol, wrap_integer(sc, arg_num), ind, (index < 0) ? it_is_negative_string : it_is_too_large_string); + + for (s7_int i = 0; (i < index) && is_pair(p); i++, p = cdr(p)) {} + + if (!is_pair(p)) + { + if (is_null(p)) + out_of_range_error_nr(sc, sc->list_set_symbol, wrap_integer(sc, arg_num), ind, it_is_too_large_string); + wrong_type_error_nr(sc, sc->list_set_symbol, 1, lst, a_proper_list_string); + } + if (is_null(cddr(args))) + set_car(p, cadr(args)); + else + { + if (!s7_is_pair(car(p))) + wrong_number_of_arguments_error_nr(sc, "too many arguments for list-set!: ~S", 36, args); + return(g_list_set_1(sc, car(p), cdr(args), arg_num + 1)); + } + return(cadr(args)); +} + +static s7_pointer g_list_set(s7_scheme *sc, s7_pointer args) {return(g_list_set_1(sc, car(args), cdr(args), 2));} + +static inline s7_pointer list_set_p_pip_unchecked(s7_scheme *sc, s7_pointer p1, s7_int i1, s7_pointer p2) +{ + s7_pointer p = p1; + if ((i1 < 0) || (i1 > sc->max_list_length)) + out_of_range_error_nr(sc, sc->list_set_symbol, int_two, wrap_integer(sc, i1), (i1 < 0) ? it_is_negative_string : it_is_too_large_string); + for (s7_int i = 0; ((is_pair(p)) && (i < i1)); i++, p = cdr(p)); + if (!is_pair(p)) + { + if (is_null(p)) + out_of_range_error_nr(sc, sc->list_set_symbol, int_two, wrap_integer(sc, i1), it_is_too_large_string); + wrong_type_error_nr(sc, sc->list_set_symbol, 1, p1, a_proper_list_string); + } + set_car(p, p2); + return(p2); +} + +static s7_pointer list_increment_p_pip_unchecked(opt_info *o) +{ + s7_scheme *sc = o->sc; + s7_pointer p = slot_value(o->v[2].p), p1, p2; + s7_int index = integer(p); + if ((index < 0) || (index > sc->max_list_length)) + out_of_range_error_nr(sc, sc->list_set_symbol, int_two, p, (index < 0) ? it_is_negative_string : it_is_too_large_string); + p1 = slot_value(o->v[1].p); + p = p1; + for (s7_int i = 0; ((is_pair(p)) && (i < index)); i++, p = cdr(p)); + if (!is_pair(p)) + { + if (is_null(p)) + out_of_range_error_nr(sc, sc->list_set_symbol, int_two, wrap_integer(sc, index), it_is_too_large_string); + wrong_type_error_nr(sc, sc->list_set_symbol, 1, p1, a_proper_list_string); + } + p2 = g_add_xi(sc, car(p), integer(o->v[3].p), index); + set_car(p, p2); + return(p2); +} + +static s7_pointer list_set_p_pip(s7_scheme *sc, s7_pointer p1, s7_int i1, s7_pointer p2) /* this may be uncallable now -- opt'd away in every case? */ +{ + if (!is_pair(p1)) + wrong_type_error_nr(sc, sc->list_set_symbol, 1, p1, sc->type_names[T_PAIR]); + return(list_set_p_pip_unchecked(sc, p1, i1, p2)); +} + +static s7_pointer g_list_set_i(s7_scheme *sc, s7_pointer args) +{ + s7_pointer lst = car(args), val; + s7_pointer p = lst; + s7_int index; + if (!is_mutable_pair(lst)) + return(mutable_method_or_bust(sc, lst, sc->list_set_symbol, args, sc->type_names[T_PAIR], 1)); + + index = s7_integer_clamped_if_gmp(sc, cadr(args)); + if ((index < 0) || (index > sc->max_list_length)) + out_of_range_error_nr(sc, sc->list_set_symbol, int_two, wrap_integer(sc, index), (index < 0) ? it_is_negative_string : it_is_too_large_string); + + for (s7_int i = 0; (i < index) && is_pair(p); i++, p = cdr(p)) {} + if (!is_pair(p)) + { + if (is_null(p)) + out_of_range_error_nr(sc, sc->list_set_symbol, int_two, wrap_integer(sc, index), it_is_too_large_string); + wrong_type_error_nr(sc, sc->list_set_symbol, 1, lst, a_proper_list_string); + } + val = caddr(args); + set_car(p, val); + return(val); +} + +static s7_pointer list_set_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool unused_ops) +{ + if ((args == 3) && + (s7_is_integer(caddr(expr))) && + (s7_integer_clamped_if_gmp(sc, caddr(expr)) >= 0) && + (s7_integer_clamped_if_gmp(sc, caddr(expr)) < sc->max_list_length)) + return(sc->list_set_i); + return(f); +} + + +/* -------------------------------- list-tail -------------------------------- */ +static s7_pointer list_tail_p_pp(s7_scheme *sc, s7_pointer lst, s7_pointer p) +{ + s7_int i, index; + if (!s7_is_integer(p)) + return(method_or_bust_pp(sc, p, sc->list_tail_symbol, lst, p, sc->type_names[T_INTEGER], 2)); + index = s7_integer_clamped_if_gmp(sc, p); + + if (!is_list(lst)) /* (list-tail () 0) -> () */ + return(method_or_bust_with_type_pi(sc, lst, sc->list_tail_symbol, lst, index, a_list_string, 1)); + if ((index < 0) || (index > sc->max_list_length)) + out_of_range_error_nr(sc, sc->list_tail_symbol, int_two, wrap_integer(sc, index), (index < 0) ? it_is_negative_string : it_is_too_large_string); + + for (i = 0; (i < index) && (is_pair(lst)); i++, lst = cdr(lst)) {} + if (i < index) + out_of_range_error_nr(sc, sc->list_tail_symbol, int_two, wrap_integer(sc, index), it_is_too_large_string); + return(lst); +} + +static s7_pointer g_list_tail(s7_scheme *sc, s7_pointer args) +{ + #define H_list_tail "(list-tail lst i) returns the list from the i-th element on" + #define Q_list_tail s7_make_signature(sc, 3, sc->T, sc->is_pair_symbol, sc->is_integer_symbol) /* #t: (list-tail '(1 . 2) 1) -> 2 */ + return(list_tail_p_pp(sc, car(args), cadr(args))); +} + + +/* -------------------------------- cons -------------------------------- */ +static s7_pointer g_cons(s7_scheme *sc, s7_pointer args) +{ + #define H_cons "(cons a b) returns a pair containing a and b" + #define Q_cons s7_make_signature(sc, 3, sc->is_pair_symbol, sc->T, sc->T) + + s7_pointer x; + new_cell(sc, x, T_PAIR | T_SAFE_PROCEDURE); + set_car(x, car(args)); + set_cdr(x, cadr(args)); + return(x); +} + +static s7_pointer cons_p_pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + s7_pointer x; + new_cell(sc, x, T_PAIR | T_SAFE_PROCEDURE); + set_car(x, p1); + set_cdr(x, p2); + return(x); +} + + +/* -------- car -------- */ + +static s7_pointer g_car(s7_scheme *sc, s7_pointer args) +{ + #define H_car "(car pair) returns the first element of the pair" + #define Q_car sc->pl_p + + s7_pointer lst = car(args); + if (is_pair(lst)) + return(car(lst)); + return(sole_arg_method_or_bust(sc, lst, sc->car_symbol, args, sc->type_names[T_PAIR])); +} + +static s7_pointer car_p_p(s7_scheme *sc, s7_pointer p) +{ + if (is_pair(p)) + return(car(p)); + return(sole_arg_method_or_bust(sc, p, sc->car_symbol, set_plist_1(sc, p), sc->type_names[T_PAIR])); +} + +static s7_pointer g_list_ref_at_0(s7_scheme *sc, s7_pointer args) +{ + if (is_pair(car(args))) return(caar(args)); + return(method_or_bust(sc, car(args), sc->list_ref_symbol, args, sc->type_names[T_PAIR], 1)); /* 1=arg num if error */ +} + + +static s7_pointer g_set_car(s7_scheme *sc, s7_pointer args) +{ + #define H_set_car "(set-car! pair val) sets the pair's first element to val" + #define Q_set_car s7_make_signature(sc, 3, sc->T, sc->is_pair_symbol, sc->T) + + s7_pointer p = car(args); + if (!is_mutable_pair(p)) return(mutable_method_or_bust(sc, p, sc->set_car_symbol, args, sc->type_names[T_PAIR], 1)); + set_car(p, cadr(args)); + return(car(p)); +} + +static Inline s7_pointer inline_set_car(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + if (!is_mutable_pair(p1)) return(mutable_method_or_bust(sc, p1, sc->set_car_symbol, set_plist_1(sc, p1), sc->type_names[T_PAIR], 1)); + set_car(p1, p2); + return(p2); +} + +static s7_pointer set_car_p_pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) {return(inline_set_car(sc, p1, p2));} + + +/* -------- cdr -------- */ +static s7_pointer g_cdr(s7_scheme *sc, s7_pointer args) +{ + #define H_cdr "(cdr pair) returns the second element of the pair" + #define Q_cdr sc->pl_p + + s7_pointer lst = car(args); + if (is_pair(lst)) + return(cdr(lst)); + return(sole_arg_method_or_bust(sc, lst, sc->cdr_symbol, args, sc->type_names[T_PAIR])); +} + +static s7_pointer cdr_p_p(s7_scheme *sc, s7_pointer p) +{ + if (is_pair(p)) + return(cdr(p)); + return(sole_arg_method_or_bust(sc, p, sc->cdr_symbol, set_plist_1(sc, p), sc->type_names[T_PAIR])); +} + + +static s7_pointer g_set_cdr(s7_scheme *sc, s7_pointer args) +{ + #define H_set_cdr "(set-cdr! pair val) sets the pair's second element to val" + #define Q_set_cdr s7_make_signature(sc, 3, sc->T, sc->is_pair_symbol, sc->T) + + s7_pointer p = car(args); + if (!is_mutable_pair(p)) return(mutable_method_or_bust(sc, p, sc->set_cdr_symbol, args, sc->type_names[T_PAIR], 1)); + set_cdr(p, cadr(args)); + return(cdr(p)); +} + +static Inline s7_pointer inline_set_cdr(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + if (!is_mutable_pair(p1)) return(mutable_method_or_bust(sc, p1, sc->set_cdr_symbol, set_plist_1(sc, p1), sc->type_names[T_PAIR], 1)); + set_cdr(p1, p2); + return(p2); +} + +static s7_pointer set_cdr_p_pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) {return(inline_set_cdr(sc, p1, p2));} + + +/* -------- caar --------*/ +static s7_pointer g_caar(s7_scheme *sc, s7_pointer args) +{ + #define H_caar "(caar lst) returns (car (car lst)): (caar '((1 2))) -> 1" + #define Q_caar sc->pl_p + + s7_pointer lst = car(args); + /* it makes no difference in timing to move lst here or below (i.e. lst=car(lst) then return(car(lst)) and so on) */ + if (!is_pair(lst)) return(sole_arg_method_or_bust(sc, lst, sc->caar_symbol, args, sc->type_names[T_PAIR])); + if (!is_pair(car(lst))) sole_arg_wrong_type_error_nr(sc, sc->caar_symbol, lst, car_a_list_string); + return(caar(lst)); + +} + +static s7_pointer caar_p_p(s7_scheme *sc, s7_pointer p) +{ + if ((is_pair(p)) && (is_pair(car(p)))) return(caar(p)); + if (is_pair(p)) sole_arg_wrong_type_error_nr(sc, sc->caar_symbol, p, car_a_list_string); + return(sole_arg_method_or_bust(sc, p, sc->caar_symbol, set_plist_1(sc, p), sc->type_names[T_PAIR])); +} + + +/* -------- cadr --------*/ +static s7_pointer g_cadr(s7_scheme *sc, s7_pointer args) +{ + #define H_cadr "(cadr lst) returns (car (cdr lst)): (cadr '(1 2 3)) -> 2" + #define Q_cadr sc->pl_p + + s7_pointer lst = car(args); + if (!is_pair(lst)) return(sole_arg_method_or_bust(sc, lst, sc->cadr_symbol, args, sc->type_names[T_PAIR])); + if (!is_pair(cdr(lst))) sole_arg_wrong_type_error_nr(sc, sc->cadr_symbol, lst, cdr_a_list_string); + return(cadr(lst)); +} + +static s7_pointer cadr_p_p(s7_scheme *sc, s7_pointer p) +{ + if ((is_pair(p)) && (is_pair(cdr(p)))) return(cadr(p)); + if (is_pair(p)) sole_arg_wrong_type_error_nr(sc, sc->cadr_symbol, p, cdr_a_list_string); + return(sole_arg_method_or_bust(sc, p, sc->cadr_symbol, set_plist_1(sc, p), sc->type_names[T_PAIR])); +} + +static s7_pointer g_list_ref_at_1(s7_scheme *sc, s7_pointer args) +{ + s7_pointer lst = car(args); + if (!is_pair(lst)) return(method_or_bust(sc, lst, sc->list_ref_symbol, args, sc->type_names[T_PAIR], 1)); + if (!is_pair(cdr(lst))) out_of_range_error_nr(sc, sc->list_ref_symbol, int_two, cadr(args), it_is_too_large_string); + return(cadr(lst)); +} + + +/* -------- cdar -------- */ +static s7_pointer g_cdar(s7_scheme *sc, s7_pointer args) +{ + #define H_cdar "(cdar lst) returns (cdr (car lst)): (cdar '((1 2 3))) -> '(2 3)" + #define Q_cdar sc->pl_p + + s7_pointer lst = car(args); + if (!is_pair(lst)) return(sole_arg_method_or_bust(sc, lst, sc->cdar_symbol, args, sc->type_names[T_PAIR])); + if (!is_pair(car(lst))) sole_arg_wrong_type_error_nr(sc, sc->cdar_symbol, lst, car_a_list_string); + return(cdar(lst)); +} + +static s7_pointer cdar_p_p(s7_scheme *sc, s7_pointer p) +{ + if ((is_pair(p)) && (is_pair(car(p)))) return(cdar(p)); + if (!is_pair(p)) sole_arg_wrong_type_error_nr(sc, sc->cdar_symbol, p, car_a_list_string); + return(sole_arg_method_or_bust(sc, p, sc->cdar_symbol, set_plist_1(sc, p), sc->type_names[T_PAIR])); +} + + +/* -------- cddr -------- */ +static s7_pointer g_cddr(s7_scheme *sc, s7_pointer args) +{ + #define H_cddr "(cddr lst) returns (cdr (cdr lst)): (cddr '(1 2 3 4)) -> '(3 4)" + #define Q_cddr sc->pl_p + + s7_pointer lst = car(args); + if (!is_pair(lst)) return(sole_arg_method_or_bust(sc, lst, sc->cddr_symbol, args, sc->type_names[T_PAIR])); + if (!is_pair(cdr(lst))) sole_arg_wrong_type_error_nr(sc, sc->cddr_symbol, lst, cdr_a_list_string); + return(cddr(lst)); +} + +static s7_pointer cddr_p_p(s7_scheme *sc, s7_pointer p) +{ + if ((is_pair(p)) && (is_pair(cdr(p)))) return(cddr(p)); + if (is_pair(p)) sole_arg_wrong_type_error_nr(sc, sc->cddr_symbol, p, cdr_a_list_string); + return(sole_arg_method_or_bust(sc, p, sc->cddr_symbol, set_plist_1(sc, p), sc->type_names[T_PAIR])); +} + +/* -------- caaar -------- */ +static s7_pointer caaar_p_p(s7_scheme *sc, s7_pointer lst) +{ + if (!is_pair(lst)) return(sole_arg_method_or_bust(sc, lst, sc->caaar_symbol, set_plist_1(sc, lst), sc->type_names[T_PAIR])); + if (!is_pair(car(lst))) sole_arg_wrong_type_error_nr(sc, sc->caaar_symbol, lst, car_a_list_string); + if (!is_pair(caar(lst))) sole_arg_wrong_type_error_nr(sc, sc->caaar_symbol, lst, caar_a_list_string); + if (!is_pair(caar(lst))) sole_arg_wrong_type_error_nr(sc, sc->caaar_symbol, lst, caar_a_list_string); + return(caaar(lst)); +} + +static s7_pointer g_caaar(s7_scheme *sc, s7_pointer args) +{ + #define H_caaar "(caaar lst) returns (car (car (car lst))): (caaar '(((1 2)))) -> 1" + #define Q_caaar sc->pl_p + return(caaar_p_p(sc, car(args))); +} + +/* -------- caadr -------- */ +static s7_pointer caadr_p_p(s7_scheme *sc, s7_pointer p) +{ + if ((is_pair(p)) && (is_pair(cdr(p))) && (is_pair(cadr(p)))) return(caadr(p)); + if (!is_pair(p)) return(sole_arg_method_or_bust(sc, p, sc->caadr_symbol, set_plist_1(sc, p), sc->type_names[T_PAIR])); + if (!is_pair(cdr(p))) sole_arg_wrong_type_error_nr(sc, sc->caadr_symbol, p, cdr_a_list_string); + sole_arg_wrong_type_error_nr(sc, sc->caadr_symbol, p, cadr_a_list_string); + return(NULL); +} + +static s7_pointer g_caadr(s7_scheme *sc, s7_pointer args) +{ + #define H_caadr "(caadr lst) returns (car (car (cdr lst))): (caadr '(1 (2 3))) -> 2" + #define Q_caadr sc->pl_p + s7_pointer lst = car(args); + if (!is_pair(lst)) return(sole_arg_method_or_bust(sc, lst, sc->caadr_symbol, args, sc->type_names[T_PAIR])); + if (!is_pair(cdr(lst))) sole_arg_wrong_type_error_nr(sc, sc->caadr_symbol, lst, cdr_a_list_string); + if (!is_pair(cadr(lst))) sole_arg_wrong_type_error_nr(sc, sc->caadr_symbol, lst, cadr_a_list_string); + return(caadr(lst)); +} + +/* -------- cadar -------- */ +static s7_pointer g_cadar(s7_scheme *sc, s7_pointer args) +{ + #define H_cadar "(cadar lst) returns (car (cdr (car lst))): (cadar '((1 2 3))) -> 2" + #define Q_cadar sc->pl_p + + s7_pointer lst = car(args); + if (!is_pair(lst)) return(sole_arg_method_or_bust(sc, lst, sc->cadar_symbol, args, sc->type_names[T_PAIR])); + if (!is_pair(car(lst))) sole_arg_wrong_type_error_nr(sc, sc->cadar_symbol, lst, car_a_list_string); + if (!is_pair(cdar(lst))) sole_arg_wrong_type_error_nr(sc, sc->cadar_symbol, lst, cdar_a_list_string); + return(cadar(lst)); +} + +static s7_pointer cadar_p_p(s7_scheme *sc, s7_pointer p) +{ + if ((is_pair(p)) && (is_pair(car(p))) && (is_pair(cdar(p)))) return(cadar(p)); + if (!is_pair(p)) return(sole_arg_method_or_bust(sc, p, sc->cadar_symbol, set_plist_1(sc, p), sc->type_names[T_PAIR])); + if (!is_pair(car(p))) sole_arg_wrong_type_error_nr(sc, sc->cadar_symbol, p, car_a_list_string); + sole_arg_wrong_type_error_nr(sc, sc->cadar_symbol, p, cdar_a_list_string); + return(NULL); +} + +/* -------- cdaar -------- */ +static s7_pointer cdaar_p_p(s7_scheme *sc, s7_pointer lst) +{ + if (!is_pair(lst)) return(sole_arg_method_or_bust(sc, lst, sc->cdaar_symbol, set_plist_1(sc, lst), sc->type_names[T_PAIR])); + if (!is_pair(car(lst))) sole_arg_wrong_type_error_nr(sc, sc->cdaar_symbol, lst, car_a_list_string); + if (!is_pair(caar(lst))) sole_arg_wrong_type_error_nr(sc, sc->cdaar_symbol, lst, caar_a_list_string); + return(cdaar(lst)); +} + +static s7_pointer g_cdaar(s7_scheme *sc, s7_pointer args) +{ + #define H_cdaar "(cdaar lst) returns (cdr (car (car lst))): (cdaar '(((1 2 3)))) -> '(2 3)" + #define Q_cdaar sc->pl_p + return(cdaar_p_p(sc, car(args))); +} + +/* -------- caddr -------- */ +static s7_pointer g_caddr(s7_scheme *sc, s7_pointer args) +{ + #define H_caddr "(caddr lst) returns (car (cdr (cdr lst))): (caddr '(1 2 3 4)) -> 3" + #define Q_caddr sc->pl_p + + s7_pointer lst = car(args); + if (!is_pair(lst)) return(sole_arg_method_or_bust(sc, lst, sc->caddr_symbol, args, sc->type_names[T_PAIR])); + if (!is_pair(cdr(lst))) sole_arg_wrong_type_error_nr(sc, sc->caddr_symbol, lst, cdr_a_list_string); + if (!is_pair(cddr(lst))) sole_arg_wrong_type_error_nr(sc, sc->caddr_symbol, lst, cddr_a_list_string); + return(caddr(lst)); +} + +static s7_pointer caddr_p_p(s7_scheme *sc, s7_pointer p) +{ + if ((is_pair(p)) && (is_pair(cdr(p))) && (is_pair(cddr(p)))) return(caddr(p)); + if (!is_pair(p)) return(sole_arg_method_or_bust(sc, p, sc->caddr_symbol, set_plist_1(sc, p), sc->type_names[T_PAIR])); + if (!is_pair(cdr(p))) sole_arg_wrong_type_error_nr(sc, sc->caddr_symbol, p, cdr_a_list_string); + sole_arg_wrong_type_error_nr(sc, sc->caddr_symbol, p, cddr_a_list_string); + return(NULL); +} + +static s7_pointer g_list_ref_at_2(s7_scheme *sc, s7_pointer args) +{ + s7_pointer lst = car(args); + if (!is_pair(lst)) + return(method_or_bust(sc, lst, sc->list_ref_symbol, args, sc->type_names[T_PAIR], 1)); + if ((!is_pair(cdr(lst))) || (!is_pair(cddr(lst)))) + out_of_range_error_nr(sc, sc->list_ref_symbol, int_two, cadr(args), it_is_too_large_string); + return(caddr(lst)); +} + + +/* -------- cdddr -------- */ +static s7_pointer cdddr_p_p(s7_scheme *sc, s7_pointer lst) +{ + if (!is_pair(lst)) return(sole_arg_method_or_bust(sc, lst, sc->cdddr_symbol, set_plist_1(sc, lst), sc->type_names[T_PAIR])); + if (!is_pair(cdr(lst))) sole_arg_wrong_type_error_nr(sc, sc->cdddr_symbol, lst, cdr_a_list_string); + if (!is_pair(cddr(lst))) sole_arg_wrong_type_error_nr(sc, sc->cdddr_symbol, lst, cddr_a_list_string); + return(cdddr(lst)); +} + +static s7_pointer g_cdddr(s7_scheme *sc, s7_pointer args) +{ + #define H_cdddr "(cdddr lst) returns (cdr (cdr (cdr lst))): (cdddr '(1 2 3 4)) -> '(4)" + #define Q_cdddr sc->pl_p + return(cdddr_p_p(sc, car(args))); +} + +/* -------- cdadr -------- */ +static s7_pointer cdadr_p_p(s7_scheme *sc, s7_pointer lst) +{ + if (!is_pair(lst)) return(sole_arg_method_or_bust(sc, lst, sc->cdadr_symbol, set_plist_1(sc, lst), sc->type_names[T_PAIR])); + if (!is_pair(cdr(lst))) sole_arg_wrong_type_error_nr(sc, sc->cdadr_symbol, lst, cdr_a_list_string); + if (!is_pair(cadr(lst))) sole_arg_wrong_type_error_nr(sc, sc->cdadr_symbol, lst, cadr_a_list_string); + return(cdadr(lst)); +} + +static s7_pointer g_cdadr(s7_scheme *sc, s7_pointer args) +{ + #define H_cdadr "(cdadr lst) returns (cdr (car (cdr lst))): (cdadr '(1 (2 3 4))) -> '(3 4)" + #define Q_cdadr sc->pl_p + return(cdadr_p_p(sc, car(args))); +} + +/* -------- cddar -------- */ +static s7_pointer cddar_p_p(s7_scheme *sc, s7_pointer lst) +{ + if (!is_pair(lst)) return(sole_arg_method_or_bust(sc, lst, sc->cddar_symbol, set_plist_1(sc, lst), sc->type_names[T_PAIR])); + if (!is_pair(car(lst))) sole_arg_wrong_type_error_nr(sc, sc->cddar_symbol, lst, car_a_list_string); + if (!is_pair(cdar(lst))) sole_arg_wrong_type_error_nr(sc, sc->cddar_symbol, lst, cdar_a_list_string); + return(cddar(lst)); +} + +static s7_pointer g_cddar(s7_scheme *sc, s7_pointer args) +{ + #define H_cddar "(cddar lst) returns (cdr (cdr (car lst))): (cddar '((1 2 3 4))) -> '(3 4)" + #define Q_cddar sc->pl_p + return(cddar_p_p(sc, car(args))); +} + +/* -------- caaaar -------- */ +static s7_pointer g_caaaar(s7_scheme *sc, s7_pointer args) +{ + #define H_caaaar "(caaaar lst) returns (car (car (car (car lst)))): (caaaar '((((1 2))))) -> 1" + #define Q_caaaar sc->pl_p + + s7_pointer lst = car(args); + if (!is_pair(lst)) return(sole_arg_method_or_bust(sc, lst, sc->caaaar_symbol, args, sc->type_names[T_PAIR])); + if (!is_pair(car(lst))) sole_arg_wrong_type_error_nr(sc, sc->caaaar_symbol, lst, car_a_list_string); + if (!is_pair(caar(lst))) sole_arg_wrong_type_error_nr(sc, sc->caaaar_symbol, lst, caar_a_list_string); + if (!is_pair(caaar(lst))) sole_arg_wrong_type_error_nr(sc, sc->caaaar_symbol, lst, caaar_a_list_string); + return(caaaar(lst)); +} + +/* -------- caaadr -------- */ +static s7_pointer g_caaadr(s7_scheme *sc, s7_pointer args) +{ + #define H_caaadr "(caaadr lst) returns (car (car (car (cdr lst)))): (caaadr '(1 ((2 3)))) -> 2" + #define Q_caaadr sc->pl_p + + s7_pointer lst = car(args); + if (!is_pair(lst)) return(sole_arg_method_or_bust(sc, lst, sc->caaadr_symbol, args, sc->type_names[T_PAIR])); + if (!is_pair(cdr(lst))) sole_arg_wrong_type_error_nr(sc, sc->caaadr_symbol, lst, cdr_a_list_string); + if (!is_pair(cadr(lst))) sole_arg_wrong_type_error_nr(sc, sc->caaadr_symbol, lst, cadr_a_list_string); + if (!is_pair(caadr(lst))) sole_arg_wrong_type_error_nr(sc, sc->caaadr_symbol, lst, caadr_a_list_string); + return(caaadr(lst)); +} + +/* -------- caadar -------- */ +static s7_pointer g_caadar(s7_scheme *sc, s7_pointer args) +{ + #define H_caadar "(caadar lst) returns (car (car (cdr (car lst)))): (caadar '((1 (2 3)))) -> 2" + #define Q_caadar sc->pl_p + + s7_pointer lst = car(args); + if (!is_pair(lst)) return(sole_arg_method_or_bust(sc, lst, sc->caadar_symbol, args, sc->type_names[T_PAIR])); + if (!is_pair(car(lst))) sole_arg_wrong_type_error_nr(sc, sc->caadar_symbol, lst, car_a_list_string); + if (!is_pair(cdar(lst))) sole_arg_wrong_type_error_nr(sc, sc->caadar_symbol, lst, cdar_a_list_string); + if (!is_pair(cadar(lst))) sole_arg_wrong_type_error_nr(sc, sc->caadar_symbol, lst, cadar_a_list_string); + return(caadar(lst)); +} + +/* -------- cadaar -------- */ +static s7_pointer g_cadaar(s7_scheme *sc, s7_pointer args) +{ + #define H_cadaar "(cadaar lst) returns (car (cdr (car (car lst)))): (cadaar '(((1 2 3)))) -> 2" + #define Q_cadaar sc->pl_p + + s7_pointer lst = car(args); + if (!is_pair(lst)) return(sole_arg_method_or_bust(sc, lst, sc->cadaar_symbol, args, sc->type_names[T_PAIR])); + if (!is_pair(car(lst))) sole_arg_wrong_type_error_nr(sc, sc->cadaar_symbol, lst, car_a_list_string); + if (!is_pair(caar(lst))) sole_arg_wrong_type_error_nr(sc, sc->cadaar_symbol, lst, caar_a_list_string); + if (!is_pair(cdaar(lst))) sole_arg_wrong_type_error_nr(sc, sc->cadaar_symbol, lst, cdaar_a_list_string); + return(cadaar(lst)); +} + +/* -------- caaddr -------- */ + +static s7_pointer caaddr_p_p(s7_scheme *sc, s7_pointer lst) +{ + if (!is_pair(lst)) return(sole_arg_method_or_bust(sc, lst, sc->caaddr_symbol, set_plist_1(sc, lst), sc->type_names[T_PAIR])); + if (!is_pair(cdr(lst))) sole_arg_wrong_type_error_nr(sc, sc->caaddr_symbol, lst, cdr_a_list_string); + if (!is_pair(cddr(lst))) sole_arg_wrong_type_error_nr(sc, sc->caaddr_symbol, lst, cddr_a_list_string); + if (!is_pair(caddr(lst))) sole_arg_wrong_type_error_nr(sc, sc->caaddr_symbol, lst, caddr_a_list_string); + return(caaddr(lst)); +} + +static s7_pointer g_caaddr(s7_scheme *sc, s7_pointer args) +{ + #define H_caaddr "(caaddr lst) returns (car (car (cdr (cdr lst)))): (caaddr '(1 2 (3 4))) -> 3" + #define Q_caaddr sc->pl_p + return(caaddr_p_p(sc, car(args))); +} + +/* -------- cadddr -------- */ +static s7_pointer cadddr_p_p(s7_scheme *sc, s7_pointer lst) +{ + if (!is_pair(lst)) return(sole_arg_method_or_bust(sc, lst, sc->cadddr_symbol, set_plist_1(sc, lst), sc->type_names[T_PAIR])); + if (!is_pair(cdr(lst))) sole_arg_wrong_type_error_nr(sc, sc->cadddr_symbol, lst, cdr_a_list_string); + if (!is_pair(cddr(lst))) sole_arg_wrong_type_error_nr(sc, sc->cadddr_symbol, lst, cddr_a_list_string); + if (!is_pair(cdddr(lst))) sole_arg_wrong_type_error_nr(sc, sc->cadddr_symbol, lst, cdddr_a_list_string); + return(cadddr(lst)); +} + +static s7_pointer g_cadddr(s7_scheme *sc, s7_pointer args) +{ + #define H_cadddr "(cadddr lst) returns (car (cdr (cdr (cdr lst)))): (cadddr '(1 2 3 4 5)) -> 4" + #define Q_cadddr sc->pl_p + return(cadddr_p_p(sc, car(args))); +} + +/* -------- cadadr -------- */ +static s7_pointer cadadr_p_p(s7_scheme *sc, s7_pointer lst) +{ + if (!is_pair(lst)) return(sole_arg_method_or_bust(sc, lst, sc->cadadr_symbol, set_plist_1(sc, lst), sc->type_names[T_PAIR])); + if (!is_pair(cdr(lst))) sole_arg_wrong_type_error_nr(sc, sc->cadadr_symbol, lst, cdr_a_list_string); + if (!is_pair(cadr(lst))) sole_arg_wrong_type_error_nr(sc, sc->cadadr_symbol, lst, cadr_a_list_string); + if (!is_pair(cdadr(lst))) sole_arg_wrong_type_error_nr(sc, sc->cadadr_symbol, lst, cdadr_a_list_string); + return(cadadr(lst)); +} + +static s7_pointer g_cadadr(s7_scheme *sc, s7_pointer args) +{ + #define H_cadadr "(cadadr lst) returns (car (cdr (car (cdr lst)))): (cadadr '(1 (2 3 4))) -> 3" + #define Q_cadadr sc->pl_p + return(cadadr_p_p(sc, car(args))); +} + +/* -------- caddar -------- */ +static s7_pointer caddar_p_p(s7_scheme *sc, s7_pointer lst) +{ + if (!is_pair(lst)) return(sole_arg_method_or_bust(sc, lst, sc->caddar_symbol, set_plist_1(sc, lst), sc->type_names[T_PAIR])); + if (!is_pair(car(lst))) sole_arg_wrong_type_error_nr(sc, sc->caddar_symbol, lst, car_a_list_string); + if (!is_pair(cdar(lst))) sole_arg_wrong_type_error_nr(sc, sc->caddar_symbol, lst, cdar_a_list_string); + if (!is_pair(cddar(lst))) sole_arg_wrong_type_error_nr(sc, sc->caddar_symbol, lst, cddar_a_list_string); + return(caddar(lst)); +} + +static s7_pointer g_caddar(s7_scheme *sc, s7_pointer args) +{ + #define H_caddar "(caddar lst) returns (car (cdr (cdr (car lst)))): (caddar '((1 2 3 4))) -> 3" + #define Q_caddar sc->pl_p + return(caddar_p_p(sc, car(args))); +} + +/* -------- cdaaar -------- */ +static s7_pointer g_cdaaar(s7_scheme *sc, s7_pointer args) +{ + #define H_cdaaar "(cdaaar lst) returns (cdr (car (car (car lst)))): (cdaaar '((((1 2 3))))) -> '(2 3)" + #define Q_cdaaar sc->pl_p + + s7_pointer lst = car(args); + if (!is_pair(lst)) return(sole_arg_method_or_bust(sc, lst, sc->cdaaar_symbol, args, sc->type_names[T_PAIR])); + if (!is_pair(car(lst))) sole_arg_wrong_type_error_nr(sc, sc->cdaaar_symbol, lst, car_a_list_string); + if (!is_pair(caar(lst))) sole_arg_wrong_type_error_nr(sc, sc->cdaaar_symbol, lst, caar_a_list_string); + if (!is_pair(caaar(lst))) sole_arg_wrong_type_error_nr(sc, sc->cdaaar_symbol, lst, caaar_a_list_string); + return(cdaaar(lst)); +} + +/* -------- cdaadr -------- */ +static s7_pointer g_cdaadr(s7_scheme *sc, s7_pointer args) +{ + #define H_cdaadr "(cdaadr lst) returns (cdr (car (car (cdr lst)))): (cdaadr '(1 ((2 3 4)))) -> '(3 4)" + #define Q_cdaadr sc->pl_p + + s7_pointer lst = car(args); + if (!is_pair(lst)) return(sole_arg_method_or_bust(sc, lst, sc->cdaadr_symbol, args, sc->type_names[T_PAIR])); + if (!is_pair(cdr(lst))) sole_arg_wrong_type_error_nr(sc, sc->cdaadr_symbol, lst, cdr_a_list_string); + if (!is_pair(cadr(lst))) sole_arg_wrong_type_error_nr(sc, sc->cdaadr_symbol, lst, cadr_a_list_string); + if (!is_pair(caadr(lst))) sole_arg_wrong_type_error_nr(sc, sc->cdaadr_symbol, lst, caadr_a_list_string); + return(cdaadr(lst)); +} + +/* -------- cdadar -------- */ +static s7_pointer g_cdadar(s7_scheme *sc, s7_pointer args) +{ + #define H_cdadar "(cdadar lst) returns (cdr (car (cdr (car lst)))): (cdadar '((1 (2 3 4)))) -> '(3 4)" + #define Q_cdadar sc->pl_p + + s7_pointer lst = car(args); + if (!is_pair(lst)) return(sole_arg_method_or_bust(sc, lst, sc->cdadar_symbol, args, sc->type_names[T_PAIR])); + if (!is_pair(car(lst))) sole_arg_wrong_type_error_nr(sc, sc->cdadar_symbol, lst, car_a_list_string); + if (!is_pair(cdar(lst))) sole_arg_wrong_type_error_nr(sc, sc->cdadar_symbol, lst, cdar_a_list_string); + if (!is_pair(cadar(lst))) sole_arg_wrong_type_error_nr(sc, sc->cdadar_symbol, lst, cadar_a_list_string); + return(cdadar(lst)); +} + +/* -------- cddaar -------- */ +static s7_pointer g_cddaar(s7_scheme *sc, s7_pointer args) +{ + #define H_cddaar "(cddaar lst) returns (cdr (cdr (car (car lst)))): (cddaar '(((1 2 3 4)))) -> '(3 4)" + #define Q_cddaar sc->pl_p + + s7_pointer lst = car(args); + if (!is_pair(lst)) return(sole_arg_method_or_bust(sc, lst, sc->cddaar_symbol, args, sc->type_names[T_PAIR])); + if (!is_pair(car(lst))) sole_arg_wrong_type_error_nr(sc, sc->cddaar_symbol, lst, car_a_list_string); + if (!is_pair(caar(lst))) sole_arg_wrong_type_error_nr(sc, sc->cddaar_symbol, lst, caar_a_list_string); + if (!is_pair(cdaar(lst))) sole_arg_wrong_type_error_nr(sc, sc->cddaar_symbol, lst, cdaar_a_list_string); + return(cddaar(lst)); +} + +/* -------- cdaddr -------- */ +static s7_pointer g_cdaddr(s7_scheme *sc, s7_pointer args) +{ + #define H_cdaddr "(cdaddr lst) returns (cdr (car (cdr (cdr lst)))): (cdaddr '(1 2 (3 4 5))) -> '(4 5)" + #define Q_cdaddr sc->pl_p + + s7_pointer lst = car(args); + if (!is_pair(lst)) return(sole_arg_method_or_bust(sc, lst, sc->cdaddr_symbol, args, sc->type_names[T_PAIR])); + if (!is_pair(cdr(lst))) sole_arg_wrong_type_error_nr(sc, sc->cdaddr_symbol, lst, cdr_a_list_string); + if (!is_pair(cddr(lst))) sole_arg_wrong_type_error_nr(sc, sc->cdaddr_symbol, lst, cddr_a_list_string); + if (!is_pair(caddr(lst))) sole_arg_wrong_type_error_nr(sc, sc->cdaddr_symbol, lst, caddr_a_list_string); + return(cdaddr(lst)); +} + +/* -------- cddddr -------- */ + +static s7_pointer cddddr_p_p(s7_scheme *sc, s7_pointer lst) +{ + if (!is_pair(lst)) return(sole_arg_method_or_bust(sc, lst, sc->cddddr_symbol, set_plist_1(sc, lst), sc->type_names[T_PAIR])); + if (!is_pair(cdr(lst))) sole_arg_wrong_type_error_nr(sc, sc->cddddr_symbol, lst, cdr_a_list_string); + if (!is_pair(cddr(lst))) sole_arg_wrong_type_error_nr(sc, sc->cddddr_symbol, lst, cddr_a_list_string); + if (!is_pair(cdddr(lst))) sole_arg_wrong_type_error_nr(sc, sc->cddddr_symbol, lst, cdddr_a_list_string); + return(cddddr(lst)); +} + +static s7_pointer g_cddddr(s7_scheme *sc, s7_pointer args) +{ + #define H_cddddr "(cddddr lst) returns (cdr (cdr (cdr (cdr lst)))): (cddddr '(1 2 3 4 5)) -> '(5)" + #define Q_cddddr sc->pl_p + return(cddddr_p_p(sc, car(args))); +} + + +/* -------- cddadr -------- */ +static s7_pointer cddadr_p_p(s7_scheme *sc, s7_pointer lst) +{ + if (!is_pair(lst)) return(sole_arg_method_or_bust(sc, lst, sc->cddadr_symbol, set_plist_1(sc, lst), sc->type_names[T_PAIR])); + if (!is_pair(cdr(lst))) sole_arg_wrong_type_error_nr(sc, sc->cddadr_symbol, lst, cdr_a_list_string); + if (!is_pair(cadr(lst))) sole_arg_wrong_type_error_nr(sc, sc->cddadr_symbol, lst, cadr_a_list_string); + if (!is_pair(cdadr(lst))) sole_arg_wrong_type_error_nr(sc, sc->cddadr_symbol, lst, cdadr_a_list_string); + return(cddadr(lst)); +} + +static s7_pointer g_cddadr(s7_scheme *sc, s7_pointer args) +{ + #define H_cddadr "(cddadr lst) returns (cdr (cdr (car (cdr lst)))): (cddadr '(1 (2 3 4 5))) -> '(4 5)" + #define Q_cddadr sc->pl_p + return(cddadr_p_p(sc, car(args))); +} + + +/* -------- cdddar -------- */ + +static s7_pointer cdddar_p_p(s7_scheme *sc, s7_pointer lst) +{ + if (!is_pair(lst)) return(sole_arg_method_or_bust(sc, lst, sc->cdddar_symbol, set_plist_1(sc, lst), sc->type_names[T_PAIR])); + if (!is_pair(car(lst))) sole_arg_wrong_type_error_nr(sc, sc->cdddar_symbol, lst, car_a_list_string); + if (!is_pair(cdar(lst))) sole_arg_wrong_type_error_nr(sc, sc->cdddar_symbol, lst, cdar_a_list_string); + if (!is_pair(cddar(lst))) sole_arg_wrong_type_error_nr(sc, sc->cdddar_symbol, lst, cddar_a_list_string); + return(cdddar(lst)); +} + +static s7_pointer g_cdddar(s7_scheme *sc, s7_pointer args) +{ + #define H_cdddar "(cdddar lst) returns (cdr (cdr (cdr (car lst)))): (cdddar '((1 2 3 4 5))) -> '(4 5)" + #define Q_cdddar sc->pl_p + return(cdddar_p_p(sc, car(args))); +} + +/* -------------------------------- assoc assv assq -------------------------------- */ +s7_pointer s7_assq(s7_scheme *sc, s7_pointer obj, s7_pointer x) +{ + s7_pointer y = x; + while (true) + { + /* we can blithely take the car of anything, since we're not treating it as an object, + * then if we get a bogus match, the following check that caar made sense ought to catch it. + * if car(#) = # (initialization time), then cdr(nil)->unspec + * and subsequent caar(unspec)->unspec so we could forgo half the is_pair checks below. + * This breaks if "x" is a dotted list -- the last cdr is not nil, so we lose. + */ + LOOP_8(if ((obj == unchecked_car(car(x))) && (is_pair(car(x)))) return(car(x)); x = cdr(x); if (!is_pair(x)) return(sc->F)); + y = cdr(y); + if (x == y) return(sc->F); + } + return(sc->F); /* not reached */ +} + +static s7_pointer assq_p_pp(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + return((is_pair(y)) ? s7_assq(sc, x, y) : + ((is_null(y)) ? sc->F : + method_or_bust_pp(sc, y, sc->assq_symbol, x, y, an_association_list_string, 2))); +} + +static s7_pointer g_assq(s7_scheme *sc, s7_pointer args) +{ + #define H_assq "(assq obj alist) returns the key-value pair associated (via eq?) with the key obj in the association list alist" + #define Q_assq s7_make_signature(sc, 3, s7_make_signature(sc, 2, sc->is_pair_symbol, sc->not_symbol), sc->T, sc->is_list_symbol) + return(assq_p_pp(sc, car(args), cadr(args))); + /* we don't check for (pair? (car x)) here (or in assv) so we get some inconsistency with assoc: + * (assq #f '(#f 2 . 3)) -> #f, (assoc #f '(#f 2 . 3)) -> 'error + */ +} + +static s7_pointer assv_p_pp(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + s7_pointer z; + if (!is_pair(y)) + { + if (is_null(y)) return(sc->F); + return(method_or_bust_pp(sc, y, sc->assv_symbol, x, y, an_association_list_string, 2)); + } + if (is_simple(x)) + return(s7_assq(sc, x, y)); + + z = y; + while (true) + { + /* here we can't play the assq == game because s7_is_eqv thinks it's getting a legit s7 object */ + if ((is_pair(car(y))) && (s7_is_eqv(sc, x, caar(y)))) return(car(y)); + y = cdr(y); + if (!is_pair(y)) return(sc->F); + + if ((is_pair(car(y))) && (s7_is_eqv(sc, x, caar(y)))) return(car(y)); + y = cdr(y); + if (!is_pair(y)) return(sc->F); + + z = cdr(z); + if (z == y) return(sc->F); + } + return(sc->F); /* not reached */ +} + +static s7_pointer g_assv(s7_scheme *sc, s7_pointer args) /* g_assv is called by g_assoc below */ +{ + #define H_assv "(assv obj alist) returns the key-value pair associated (via eqv?) with the key obj in the association list alist" + #define Q_assv Q_assq + return(assv_p_pp(sc, car(args), cadr(args))); +} + +s7_pointer s7_assoc(s7_scheme *sc, s7_pointer sym, s7_pointer lst) +{ + s7_pointer x, y; + if (!is_pair(lst)) + return(sc->F); + x = lst; + y = lst; + while (true) + { + if ((is_pair(car(x))) && (s7_is_equal(sc, sym, caar(x)))) return(car(x)); + x = cdr(x); + if (!is_pair(x)) return(sc->F); + + if ((is_pair(car(x))) && (s7_is_equal(sc, sym, caar(x)))) return(car(x)); + x = cdr(x); + if (!is_pair(x)) return(sc->F); + + y = cdr(y); + if (x == y) return(sc->F); + } + return(sc->F); +} + +static s7_pointer assoc_1(s7_scheme *sc, s7_pointer obj, s7_pointer x) +{ + s7_pointer y = x; + if (is_string(obj)) + { + while (true) + { + if (is_pair(car(x))) + { + s7_pointer val = caar(x); + if ((val == obj) || + ((is_string(val)) && + (scheme_strings_are_equal(obj, val)))) + return(car(x)); + } + x = cdr(x); + if (!is_pair(x)) return(sc->F); + + if (is_pair(car(x))) + { + s7_pointer val = caar(x); + if ((val == obj) || + ((is_string(val)) && + (scheme_strings_are_equal(obj, val)))) + return(car(x)); + } + x = cdr(x); + if (!is_pair(x)) return(sc->F); + y = cdr(y); + if (x == y) return(sc->F); + } + return(sc->F); + } + while (true) + { + if ((is_pair(car(x))) && (s7_is_equal(sc, obj, caar(x)))) return(car(x)); + x = cdr(x); + if (!is_pair(x)) return(sc->F); + + if ((is_pair(car(x))) && (s7_is_equal(sc, obj, caar(x)))) return(car(x)); + x = cdr(x); + if (!is_pair(x)) return(sc->F); + y = cdr(y); + if (x == y) return(sc->F); + } + return(sc->F); /* not reached */ +} + +static bool closure_has_two_normal_args(s7_scheme *sc, s7_pointer eq_func) /* sc for is_null */ +{ + return((is_closure(eq_func)) && + (is_pair(closure_args(eq_func))) && + (is_pair(cdr(closure_args(eq_func)))) && /* not dotted arg list */ + (is_null(cddr(closure_args(eq_func))))); /* arity == 2 */ +} + +static s7_pointer g_is_eq(s7_scheme *sc, s7_pointer args); +static s7_pointer g_is_eqv(s7_scheme *sc, s7_pointer args); +static s7_pfunc s7_bool_optimize(s7_scheme *sc, s7_pointer expr); + +static s7_pointer g_assoc(s7_scheme *sc, s7_pointer args) +{ + #define H_assoc "(assoc obj alist (func #f)) returns the key-value pair associated (via equal?) with the key obj in the association list alist.\ +If 'func' is a function of 2 arguments, it is used for the comparison instead of 'equal?" + #define Q_assoc s7_make_signature(sc, 4, \ + s7_make_signature(sc, 2, sc->is_pair_symbol, sc->is_boolean_symbol), \ + sc->T, sc->is_list_symbol, sc->is_procedure_symbol) + s7_pointer x = cadr(args), obj, eq_func = NULL; + + if (!is_null(x)) + { + if (!is_pair(x)) + return(method_or_bust(sc, x, sc->assoc_symbol, args, an_association_list_string, 2)); + if (!is_pair(car(x))) + wrong_type_error_nr(sc, sc->assoc_symbol, 2, x, an_association_list_string); /* we're assuming caar below so it better exist */ + } + if (is_pair(cddr(args))) + { + s7_pointer y; + eq_func = caddr(args); + /* here we know x is a pair, but need to protect against circular lists */ + /* I wonder if the assoc equality function should get the cons, not just caar? */ + + if (is_safe_c_function(eq_func)) + { + s7_function func = c_function_call(eq_func); + if (func == g_is_eq) return(is_null(x) ? sc->F : s7_assq(sc, car(args), x)); + if (func == g_is_eqv) return(assv_p_pp(sc, car(args), x)); + if (!s7_is_aritable(sc, eq_func, 2)) + wrong_type_error_nr(sc, sc->assoc_symbol, 3, eq_func, an_eq_func_string); + set_car(sc->t2_1, car(args)); + for (s7_pointer slow = x; is_pair(x); x = cdr(x), slow = cdr(slow)) + { + if (!is_pair(car(x))) wrong_type_error_nr(sc, sc->assoc_symbol, 2, cadr(args), an_association_list_string); /* not x */ + set_car(sc->t2_2, caar(x)); + if (is_true(sc, func(sc, sc->t2_1))) return(car(x)); + x = cdr(x); + if ((!is_pair(x)) || (x == slow)) return(sc->F); + if (!is_pair(car(x))) wrong_type_error_nr(sc, sc->assoc_symbol, 2, cadr(args), an_association_list_string); + set_car(sc->t2_2, caar(x)); + if (is_true(sc, func(sc, sc->t2_1))) return(car(x)); + } + return(sc->F); + } + if (closure_has_two_normal_args(sc, eq_func)) + { + s7_pointer body = closure_body(eq_func); + if (is_null(x)) return(sc->F); + if (is_null(cdr(body))) + { + s7_pfunc func; + sc->curlet = make_let_with_two_slots(sc, sc->curlet, car(closure_args(eq_func)), car(args), cadr(closure_args(eq_func)), sc->F); + func = s7_bool_optimize(sc, body); + if (func) + { + s7_pointer slowx = x; + opt_info *o = sc->opts[0]; + s7_pointer b = next_slot(let_slots(sc->curlet)); + while (true) + { + if (!is_pair(car(x))) wrong_type_error_nr(sc, sc->assoc_symbol, 2, cadr(args), an_association_list_string); + slot_set_value(b, caar(x)); + if (o->v[0].fb(o)) return(car(x)); + x = cdr(x); + if (!is_pair(x)) return(sc->F); + if (!is_pair(car(x))) wrong_type_error_nr(sc, sc->assoc_symbol, 2, cadr(args), an_association_list_string); + slot_set_value(b, caar(x)); + if (o->v[0].fb(o)) return(car(x)); + x = cdr(x); + if (!is_pair(x)) return(sc->F); + slowx = cdr(slowx); + if (x == slowx) return(sc->F); + } + return(sc->F); + }}} + + /* member_if is similar. Do not call eval here with op_eval_done to return! An error will longjmp past the + * assoc point, leaving the op_eval_done on the stack, causing s7 to quit. + */ + if (type(eq_func) < T_CONTINUATION) + return(method_or_bust_p(sc, eq_func, sc->assoc_symbol, a_procedure_string)); + if (!s7_is_aritable(sc, eq_func, 2)) + wrong_type_error_nr(sc, sc->assoc_symbol, 3, eq_func, an_eq_func_string); + if (is_null(x)) return(sc->F); + if ((is_any_macro(eq_func)) && (!is_c_macro(eq_func))) clear_all_optimizations(sc, closure_body(eq_func)); + y = list_1(sc, copy_proper_list(sc, args)); + set_opt1_fast(y, x); + set_opt2_slow(y, x); + push_stack(sc, OP_ASSOC_IF, list_1_unchecked(sc, y), eq_func); + if (needs_copied_args(eq_func)) + push_stack(sc, OP_APPLY, list_2_unchecked(sc, car(args), caar(x)), eq_func); + else + { + set_car(sc->t2_1, car(args)); + set_car(sc->t2_2, caar(x)); + push_stack(sc, OP_APPLY, sc->t2_1, eq_func); + } + return(sc->unspecified); + } + if (is_null(x)) return(sc->F); + obj = car(args); + if (is_simple(obj)) + return(s7_assq(sc, obj, x)); + return(assoc_1(sc, obj, x)); +} + +static s7_pointer assoc_p_pp(s7_scheme *sc, s7_pointer obj, s7_pointer x) +{ + if (!is_pair(x)) + { + if (is_null(x)) return(sc->F); + return(method_or_bust(sc, x, sc->assoc_symbol, set_plist_2(sc, obj, x), an_association_list_string, 2)); + } + if (!is_pair(car(x))) wrong_type_error_nr(sc, sc->assoc_symbol, 2, x, an_association_list_string); + if (is_simple(obj)) return(s7_assq(sc, obj, x)); + return(assoc_1(sc, obj, x)); +} + +static bool op_assoc_if(s7_scheme *sc) +{ + s7_pointer orig_args = car(sc->args); + /* code=func, args=(list (list args)) with f/opt1_fast=list, value=result of comparison + * (assoc 3 '((1 . a) (2 . b) (3 . c) (4 . d)) =) + */ + if (sc->value != sc->F) /* previous comparison was not #f -- return (car list) */ + { + sc->value = car(opt1_fast(orig_args)); + return(true); + } + if (!is_pair(cdr(opt1_fast(orig_args)))) /* (assoc 3 '((1 . 2) . 3) =) or nil */ + { + sc->value = sc->F; + return(true); + } + set_opt1_fast(orig_args, cdr(opt1_fast(orig_args))); /* cdr down arg list */ + + if (sc->cur_op == OP_ASSOC_IF1) + { + /* circular list check */ + if (opt1_fast(orig_args) == opt2_slow(orig_args)) + { + sc->value = sc->F; + return(true); + } + set_opt2_slow(orig_args, cdr(opt2_slow(orig_args))); /* cdr down the slow list */ + push_stack_direct(sc, OP_ASSOC_IF); + } + else push_stack_direct(sc, OP_ASSOC_IF1); + + if (!is_pair(car(opt1_fast(orig_args)))) /* (assoc 1 '((2 . 2) 3) =) -- we access caaadr below */ + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "assoc: second argument is not an alist: ~S", 42), orig_args)); + /* not sure about this -- we could simply skip the entry both here and in g_assoc + * (assoc 1 '((2 . 2) 3)) -> #f + * (assoc 1 '((2 . 2) 3) =) -> error currently + */ + if (needs_copied_args(sc->code)) + sc->args = list_2(sc, caar(orig_args), caar(opt1_fast(orig_args))); + else sc->args = set_plist_2(sc, caar(orig_args), caar(opt1_fast(orig_args))); + return(false); +} + +static s7_pointer assoc_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool ops) +{ + if (!ops) return(f); + if ((args == 3) && (is_normal_symbol(cadddr(expr)))) + { + if (cadddr(expr) == sc->is_eq_symbol) return(global_value(sc->assq_symbol)); + if (cadddr(expr) == sc->is_eqv_symbol) return(global_value(sc->assv_symbol)); + } + return(f); +} + + +/* ---------------- member, memv, memq ---------------- */ +s7_pointer s7_memq(s7_scheme *sc, s7_pointer obj, s7_pointer x) +{ + s7_pointer y = x; + while (true) + { + LOOP_4(if (obj == car(x)) return(x); x = cdr(x); if (!is_pair(x)) return(sc->F)); + y = cdr(y); + if (x == y) return(sc->F); + } + return(sc->F); +} + +static s7_pointer memq_p_pp(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + return((is_pair(y)) ? s7_memq(sc, x, y) : + ((is_null(y)) ? sc->F : method_or_bust_pp(sc, y, sc->memq_symbol, x, y, a_list_string, 2))); +} + +static s7_pointer g_memq(s7_scheme *sc, s7_pointer args) +{ + #define H_memq "(memq obj list) looks for obj in list and returns the list from that point if it is found, otherwise #f. memq uses eq?" + #define Q_memq sc->pl_tl + + s7_pointer x = car(args); + s7_pointer y = cadr(args); + if (is_pair(y)) + return(s7_memq(sc, x, y)); + if (is_null(y)) + return(sc->F); + return(method_or_bust_pp(sc, y, sc->memq_symbol, x, y, a_list_string, 2)); +} + +/* I think (memq 'c '(a b . c)) should return #f because otherwise (memq () ...) would return the () at the end */ +/* if memq's list is a quoted list, it won't be changing, so we can tell ahead of time that it is a proper list, and what its length is */ + +static s7_pointer g_memq_2(s7_scheme *sc, s7_pointer args) +{ + s7_pointer x = cadr(args); + const s7_pointer obj = car(args); + if (obj == car(x)) return(x); + return((obj == cadr(x)) ? cdr(x) : sc->F); +} + +static s7_pointer memq_2_p_pp(s7_scheme *sc, s7_pointer obj, s7_pointer x) +{ + if (obj == car(x)) return(x); + return((obj == cadr(x)) ? cdr(x) : sc->F); +} + +static s7_pointer memq_3_p_pp(s7_scheme *sc, s7_pointer obj, s7_pointer x) +{ + if (obj == car(x)) return(x); + if (obj == cadr(x)) return(cdr(x)); + return((obj == caddr(x)) ? cddr(x) : sc->F); +} + +static s7_pointer g_memq_3(s7_scheme *sc, s7_pointer args) +{ + s7_pointer x = cadr(args); + const s7_pointer obj = car(args); + while (true) + { + if (obj == car(x)) return(x); + x = cdr(x); + if (obj == car(x)) return(x); + x = cdr(x); + if (obj == car(x)) return(x); + x = cdr(x); + if (!is_pair(x)) return(sc->F); + } + return(sc->F); +} + +static s7_pointer memq_4_p_pp(s7_scheme *sc, s7_pointer obj, s7_pointer x) +{ + while (true) + { + LOOP_4(if (obj == car(x)) return(x); x = cdr(x)); + if (!is_pair(x)) return(sc->F); + } + return(sc->F); +} + +static s7_pointer g_memq_4(s7_scheme *sc, s7_pointer args) {return(memq_4_p_pp(sc, car(args), cadr(args)));} + +static s7_pointer g_memq_any(s7_scheme *sc, s7_pointer args) +{ + /* no circular list check needed in this case */ + s7_pointer x = cadr(args); + const s7_pointer obj = car(args); + while (true) + { + LOOP_4(if (obj == car(x)) return(x); x = cdr(x); if (!is_pair(x)) return(sc->F)); + } + return(sc->F); +} + +static s7_pointer memq_chooser(s7_scheme *sc, s7_pointer f, int32_t unused_args, s7_pointer expr, bool unused_ops) +{ + s7_pointer lst = caddr(expr); + if ((is_proper_quote(sc, lst)) && + (is_pair(cadr(lst)))) + { + s7_int len = s7_list_length(sc, cadr(lst)); + if (len > 0) + { + if (len == 2) /* this used to set opt3_any to cadr, but that doesn't survive call/cc's copy_stack */ + return(sc->memq_2); + if ((len % 4) == 0) + return(sc->memq_4); + return(((len % 3) == 0) ? sc->memq_3 : sc->memq_any); + }} + return(f); +} + +static bool numbers_are_eqv(s7_scheme *sc, s7_pointer a, s7_pointer b) +{ +#if WITH_GMP + if ((is_big_number(a)) || (is_big_number(b))) + return(big_numbers_are_eqv(sc, a, b)); + if (type(a) != type(b)) return(false); +#endif + /* if (type(a) != type(b)) return(false); */ /* (eqv? 1 1.0) -> #f! but assume that we've checked types already */ + + /* switch is apparently as expensive as 3-4 if's! so this only loses if every call involves complex numbers? */ + if (is_t_integer(a)) return(integer(a) == integer(b)); + if (is_t_real(a)) return(real(a) == real(b)); /* NaNs are not equal to anything including themselves */ + if (is_t_ratio(a)) return((numerator(a) == numerator(b)) && (denominator(a) == denominator(b))); + if (!is_t_complex(a)) return(false); + return((real_part(a) == real_part(b)) && (imag_part(a) == imag_part(b))); +} + +static s7_pointer memv_number(s7_scheme *sc, s7_pointer obj, s7_pointer x) +{ + s7_pointer y = x; +#if (!WITH_GMP) + uint8_t obj_type = type(obj); +#endif + while (true) + { +#if WITH_GMP + LOOP_4(if ((is_number(car(x))) && (numbers_are_eqv(sc, obj, car(x)))) return(x); x = cdr(x); if (!is_pair(x)) return(sc->F)); +#else + LOOP_4(if ((type(car(x)) == obj_type) && (numbers_are_eqv(sc, obj, car(x)))) return(x); x = cdr(x); if (!is_pair(x)) return(sc->F)); +#endif + y = cdr(y); + if (x == y) return(sc->F); + } + return(sc->F); +} + +static s7_pointer memv_p_pp(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + s7_pointer z; + if (!is_pair(y)) + { + if (is_null(y)) return(sc->F); + return(method_or_bust_pp(sc, y, sc->memv_symbol, x, y, a_list_string, 2)); + } + if (is_simple(x)) return(s7_memq(sc, x, y)); + if (is_number(x)) return(memv_number(sc, x, y)); + + z = y; + while (true) + { + if (s7_is_eqv(sc, x, car(y))) return(y); + y = cdr(y); + if (!is_pair(y)) return(sc->F); + + if (s7_is_eqv(sc, x, car(y))) return(y); + y = cdr(y); + if (!is_pair(y)) return(sc->F); + + z = cdr(z); + if (z == y) return(sc->F); + } + return(sc->F); /* not reached */ +} + +static s7_pointer g_memv(s7_scheme *sc, s7_pointer args) +{ + #define H_memv "(memv obj list) looks for obj in list and returns the list from that point if it is found, otherwise #f. memv uses eqv?" + #define Q_memv sc->pl_tl + return(memv_p_pp(sc, car(args), cadr(args))); +} + + +s7_pointer s7_member(s7_scheme *sc, s7_pointer sym, s7_pointer lst) +{ + for (s7_pointer x = lst; is_pair(x); x = cdr(x)) + if (s7_is_equal(sc, sym, car(x))) + return(x); + return(sc->F); +} + +static s7_pointer member(s7_scheme *sc, s7_pointer obj, s7_pointer x) +{ + s7_pointer y = x; + if (is_string(obj)) + while (true) + { + if ((obj == car(x)) || + ((is_string(car(x))) && + (scheme_strings_are_equal(obj, car(x))))) + return(x); + x = cdr(x); + if (!is_pair(x)) return(sc->F); + + if ((obj == car(x)) || + ((is_string(car(x))) && + (scheme_strings_are_equal(obj, car(x))))) + return(x); + x = cdr(x); + if (!is_pair(x)) return(sc->F); + y = cdr(y); + if (x == y) return(sc->F); + } + else + while (true) + { + LOOP_4(if (s7_is_equal(sc, obj, car(x))) return(x); x = cdr(x); if (!is_pair(x)) return(sc->F)); + y = cdr(y); + if (x == y) return(sc->F); + } + return(sc->F); +} + +static bool p_to_b(opt_info *p); + +static s7_pointer g_member(s7_scheme *sc, s7_pointer args) +{ + #define H_member "(member obj list (func #f)) looks for obj in list and returns the list from that point if it is found, otherwise #f. \ +member uses equal? If 'func' is a function of 2 arguments, it is used for the comparison instead of 'equal?" + #define Q_member s7_make_signature(sc, 4, s7_make_signature(sc, 2, sc->is_pair_symbol, sc->not_symbol), sc->T, sc->is_list_symbol, sc->is_procedure_symbol) + + /* this could be extended to accept sequences: + * (member #\a "123123abnfc" char=?) -> "abnfc" + * (member "abc" "123abc321" string=?) -> "abc321" but there's the string length complication + * (member 1 #(0 1 2) =) -> #(1 2) etc but what would it do for a hash-table? + * the third arg can be weird: (member #f (list #t) cons) -> (#t) -- cons returns '(#f . #t) which is true, so we get '(#t) + * should this be an error: (member '(1 2 3) () '(1 . 2)) -- the third arg is bogus, but the second is nil + * + * here as in assoc, sort, and make-hash-table we accept macros, but I can't think of a good reason to do so. + */ + s7_pointer x = cadr(args), obj; + + if ((!is_pair(x)) && (!is_null(x))) + return(method_or_bust(sc, x, sc->member_symbol, args, a_list_string, 2)); + + if (is_not_null(cddr(args))) + { + s7_pointer y, eq_func = caddr(args); + + if (is_safe_c_function(eq_func)) + { + s7_function func = c_function_call(eq_func); + if (func == g_is_eq) return(is_null(x) ? sc->F : s7_memq(sc, car(args), x)); + if (func == g_is_eqv) return(g_memv(sc, args)); + if (func == g_less) + func = g_less_2; + else + if (func == g_greater) + func = g_greater_2; + else + if (!s7_is_aritable(sc, eq_func, 2)) + wrong_type_error_nr(sc, sc->member_symbol, 3, eq_func, an_eq_func_string); + set_car(sc->t2_1, car(args)); + for (s7_pointer slow = x; is_pair(x); x = cdr(x), slow = cdr(slow)) + { + set_car(sc->t2_2, car(x)); + if (is_true(sc, func(sc, sc->t2_1))) return(x); + if (!is_pair(cdr(x))) return(sc->F); + x = cdr(x); + if (x == slow) return(sc->F); + set_car(sc->t2_2, car(x)); + if (is_true(sc, func(sc, sc->t2_1))) return(x); + } + return(sc->F); + } + + if (closure_has_two_normal_args(sc, eq_func)) + { + s7_pointer body = closure_body(eq_func); + if (is_null(x)) return(sc->F); + if ((!no_bool_opt(body)) && + (is_null(cdr(body)))) + { + s7_pfunc func; + sc->curlet = make_let_with_two_slots(sc, sc->curlet, car(closure_args(eq_func)), car(args), cadr(closure_args(eq_func)), sc->F); + func = s7_bool_optimize(sc, body); + if (func) + { + opt_info *o = sc->opts[0]; + s7_pointer b = next_slot(let_slots(sc->curlet)); + if (o->v[0].fb == p_to_b) + { + s7_pointer (*fp)(opt_info *o) = o->v[O_WRAP].fp; + for (s7_pointer slow = x; is_pair(x); x = cdr(x), slow = cdr(slow)) + { + slot_set_value(b, car(x)); + if (fp(o) != sc->F) return(x); + if (!is_pair(cdr(x))) return(sc->F); + x = cdr(x); + if (x == slow) return(sc->F); + slot_set_value(b, car(x)); + if (fp(o) != sc->F) return(x); + }} + else + for (s7_pointer slow = x; is_pair(x); x = cdr(x), slow = cdr(slow)) + { + slot_set_value(b, car(x)); + if (o->v[0].fb(o)) return(x); + if (!is_pair(cdr(x))) return(sc->F); + x = cdr(x); + if (x == slow) return(sc->F); + slot_set_value(b, car(x)); + if (o->v[0].fb(o)) return(x); + } + return(sc->F); + } + set_no_bool_opt(body); + }} + if (type(eq_func) < T_CONTINUATION) + return(method_or_bust(sc, eq_func, sc->member_symbol, args, a_procedure_string, 3)); + if (!s7_is_aritable(sc, eq_func, 2)) + wrong_type_error_nr(sc, sc->member_symbol, 3, eq_func, an_eq_func_string); + if (is_null(x)) return(sc->F); + if ((is_any_macro(eq_func)) && (!is_c_macro(eq_func))) clear_all_optimizations(sc, closure_body(eq_func)); + y = list_1(sc, copy_proper_list(sc, args)); /* this could probably be handled with a counter cell (cdr here is unused) */ + set_opt1_fast(y, x); + set_opt2_slow(y, x); + push_stack(sc, OP_MEMBER_IF, list_1(sc, y), eq_func); + if (needs_copied_args(eq_func)) + push_stack(sc, OP_APPLY, list_2_unchecked(sc, car(args), car(x)), eq_func); + else + { + set_car(sc->t2_1, car(args)); + set_car(sc->t2_2, car(x)); + push_stack(sc, OP_APPLY, sc->t2_1, eq_func); + } + return(sc->unspecified); + } + if (is_null(x)) return(sc->F); + obj = car(args); + if (is_simple(obj)) + return(s7_memq(sc, obj, x)); + /* the only things that aren't simply == here are c_object, string, number, vector, hash-table, pair, and c_pointer, but all the other cases are unlikely */ + if (is_number(obj)) + return(memv_number(sc, obj, x)); + return(member(sc, obj, x)); +} + +static s7_pointer member_p_pp(s7_scheme *sc, s7_pointer obj, s7_pointer x) +{ + if (is_null(x)) return(sc->F); + if (!is_pair(x)) return(method_or_bust(sc, x, sc->member_symbol, set_plist_2(sc, obj, x), a_list_string, 2)); + if (is_simple(obj)) return(s7_memq(sc, obj, x)); + if (is_number(obj)) return(memv_number(sc, obj, x)); + return(member(sc, obj, x)); +} + +static s7_pointer member_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool ops) +{ + if (!ops) return(f); + if ((args == 3) && (is_normal_symbol(cadddr(expr)))) + { + if (cadddr(expr) == sc->is_eq_symbol) return(memq_chooser(sc, global_value(sc->memq_symbol), 2, expr, ops)); + if (cadddr(expr) == sc->is_eqv_symbol) return(global_value(sc->memv_symbol)); + } + return(f); +} + +static bool op_member_if(s7_scheme *sc) +{ + s7_pointer orig_args = car(sc->args); + /* code=func, args = (list (list original args)) with opt1_fast->position in cadr (the list), + * the extra indirection (list (list...)) is needed because call/cc copies arg lists + * value = result of comparison + */ + if (sc->value != sc->F) /* previous comparison was not #f -- return list */ + { + sc->value = opt1_fast(orig_args); + return(true); + } + if (!is_pair(cdr(opt1_fast(orig_args)))) /* no more args -- return #f */ + { + sc->value = sc->F; + return(true); + } + set_opt1_fast(orig_args, cdr(opt1_fast(orig_args))); /* cdr down arg list */ + + if (sc->cur_op == OP_MEMBER_IF1) + { + /* circular list check */ + if (opt1_fast(orig_args) == opt2_slow(orig_args)) + { + sc->value = sc->F; + return(true); + } + set_opt2_slow(orig_args, cdr(opt2_slow(orig_args))); /* cdr down the slow list (check for circular list) */ + push_stack_direct(sc, OP_MEMBER_IF); + } + else push_stack_direct(sc, OP_MEMBER_IF1); + + if (needs_copied_args(sc->code)) + sc->args = list_2(sc, caar(orig_args), car(opt1_fast(orig_args))); + else sc->args = set_plist_2(sc, caar(orig_args), car(opt1_fast(orig_args))); + return(false); +} + + +/* -------------------------------- list -------------------------------- */ +static s7_pointer g_list(s7_scheme *sc, s7_pointer args) +{ + #define H_list "(list ...) returns its arguments in a list" + #define Q_list s7_make_circular_signature(sc, 1, 2, sc->is_proper_list_symbol, sc->T) + return(copy_proper_list(sc, args)); +} + +static s7_pointer g_list_0(s7_scheme *sc, s7_pointer args) {return(sc->nil);} +static s7_pointer g_list_1(s7_scheme *sc, s7_pointer args) {return(list_1(sc, car(args)));} +static s7_pointer g_list_2(s7_scheme *sc, s7_pointer args) {return(list_2(sc, car(args), cadr(args)));} +static s7_pointer g_list_3(s7_scheme *sc, s7_pointer args) {return(list_3(sc, car(args), cadr(args), caddr(args)));} +static s7_pointer g_list_4(s7_scheme *sc, s7_pointer args) +{ + s7_pointer p = cddr(args); + return(list_4(sc, car(args), cadr(args), car(p), cadr(p))); +} + +static s7_pointer list_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer unused_expr, bool unused_ops) +{ + if (args == 0) return(sc->list_0); + if (args == 1) return(sc->list_1); + if (args == 2) return(sc->list_2); + if (args == 3) return(sc->list_3); + return((args == 4) ? sc->list_4 : f); +} + +static s7_pointer list_p_p(s7_scheme *sc, s7_pointer p1) {return(list_1(sc, sc->value = p1));} +static s7_pointer list_p_pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) {return(list_2(sc, p1, p2));} +static s7_pointer list_p_ppp(s7_scheme *sc, s7_pointer p1, s7_pointer p2, s7_pointer p3) {return(list_3(sc, p1, p2, p3));} +/* if the GC sees a free cell here, protect it in the caller, not here, but sometimes the GC is called here! */ + +static void check_list_validity(s7_scheme *sc, const char *caller, s7_pointer lst) +{ + s7_pointer p = lst; + for (int32_t i = 1; is_pair(p); p = cdr(p), i++) + if (!s7_is_valid(sc, car(p))) + { + if (i < 11) + s7_warn(sc, 256, "the %s argument to %s: %p, is not an s7 object\n", ordinal[i], caller, car(p)); + else s7_warn(sc, 256, "%s: argument number %d is not an s7 object: %p\n", caller, i, car(p)); + } +} + +s7_pointer s7_list(s7_scheme *sc, s7_int num_values, ...) +{ + va_list ap; + s7_pointer p; + if (num_values == 0) + return(sc->nil); + sc->w = make_list(sc, num_values, sc->unused); + p = sc->w; + va_start(ap, num_values); + for (s7_int i = 0; i < num_values; i++, p = cdr(p)) + set_car(p, va_arg(ap, s7_pointer)); + va_end(ap); + if (sc->safety > NO_SAFETY) + check_list_validity(sc, __func__, sc->w); + p = sc->w; + sc->w = sc->unused; + return(p); +} + +s7_pointer s7_list_nl(s7_scheme *sc, s7_int num_values, ...) /* arglist should be NULL terminated */ +{ + s7_int i = 0; + va_list ap; + s7_pointer p; + + if (num_values == 0) + return(sc->nil); + + sc->w = make_list(sc, num_values, sc->unused); + va_start(ap, num_values); + for (s7_pointer q = sc->w; i < num_values; i++, q = cdr(q)) + { + p = va_arg(ap, s7_pointer); + if (!p) + { + va_end(ap); + wrong_number_of_arguments_error_nr(sc, "not enough arguments for s7_list_nl: ~S", 39, sc->w); /* ideally we'd sublist this and append extra below */ + } + set_car(q, p); + } + p = va_arg(ap, s7_pointer); + va_end(ap); + if (p) wrong_number_of_arguments_error_nr(sc, "too many arguments for s7_list_nl: ~S", 37, sc->w); + + if (sc->safety > NO_SAFETY) + check_list_validity(sc, __func__, sc->w); + + p = sc->w; + sc->w = sc->unused; + return(p); +} + +static s7_pointer safe_list_1(s7_scheme *sc) +{ + if (!list_is_in_use(sc->safe_lists[1])) + { + sc->current_safe_list = 1; + set_list_in_use(sc->safe_lists[1]); + return(sc->safe_lists[1]); + } + return(cons(sc, sc->nil, sc->nil)); +} + +static s7_pointer safe_list_2(s7_scheme *sc) +{ + if (!list_is_in_use(sc->safe_lists[2])) + { + sc->current_safe_list = 2; + set_list_in_use(sc->safe_lists[2]); + return(sc->safe_lists[2]); + } + return(cons_unchecked(sc, sc->nil, list_1(sc, sc->nil))); +} + +static s7_pointer make_safe_list(s7_scheme *sc, s7_int num_args) +{ + if (num_args < NUM_SAFE_LISTS) + { + sc->current_safe_list = num_args; + if (!is_pair(sc->safe_lists[num_args])) + sc->safe_lists[num_args] = semipermanent_list(sc, num_args); + if (!list_is_in_use(sc->safe_lists[num_args])) + { + set_list_in_use(sc->safe_lists[num_args]); + return(sc->safe_lists[num_args]); + }} + return(make_big_list(sc, num_args, sc->nil)); +} + +static inline s7_pointer safe_list_if_possible(s7_scheme *sc, s7_int num_args) +{ + if ((num_args < NUM_SAFE_PRELISTS) && + (!list_is_in_use(sc->safe_lists[num_args]))) + { + sc->current_safe_list = num_args; + set_list_in_use(sc->safe_lists[num_args]); + return(sc->safe_lists[num_args]); + } + return(make_safe_list(sc, num_args)); +} + +static s7_pointer g_list_append(s7_scheme *sc, s7_pointer args) +{ + s7_pointer tp = sc->nil, np = NULL, pp; + + /* we know here that car(args) is a list and cdr(args) is not nil; this function does not check sc->max_list_length; called only in g_append */ + s7_gc_protect_via_stack(sc, args); + for (s7_pointer y = args; is_pair(y); y = cdr(y)) /* arglist so not dotted */ + { + s7_pointer p = car(y), func; + + if ((has_active_methods(sc, p)) && + ((func = find_method_with_let(sc, p, sc->append_symbol)) != sc->undefined)) + { + unstack(sc); + return(s7_apply_function(sc, func, (is_null(tp)) ? y : set_ulist_1(sc, tp, y))); + } + if (is_null(cdr(y))) + { + if (is_null(tp)) + { + /* Guile: (append '() 1): 1, r7rs claims an improper list is the result, yet its own examples contradict that + * (what does "share structure" mean when there are no structures? I assume they mean sequences) + */ + unstack(sc); + return(p); + } + if (is_list(p)) + set_cdr(np, p); + else + { + s7_int len = sequence_length(sc, p); + if (len > 0) + set_cdr(np, s7_copy_1(sc, sc->append_symbol, set_plist_2(sc, p, make_list(sc, len, sc->unused)))); + else + if (len < 0) + set_cdr(np, p); + } + sc->temp8 = sc->unused; + unstack(sc); + return(tp); + } + + if (!is_sequence(p)) + { + unstack(sc); + wrong_type_error_nr(sc, sc->append_symbol, position_of(y, args), p, a_sequence_string); + } + if (!sequence_is_empty(sc, p)) + { + if (is_pair(p)) + { + if (!s7_is_proper_list(sc, p)) + { + sc->temp8 = sc->unused; + unstack(sc); + wrong_type_error_nr(sc, sc->append_symbol, position_of(y, args), p, a_proper_list_string); + } + if (is_null(tp)) + { + tp = list_1(sc, car(p)); + np = tp; + sc->temp8 = tp; /* GC protect? */ + pp = cdr(p); + } + else pp = p; + for (; is_pair(pp); pp = cdr(pp), np = cdr(np)) + set_cdr(np, list_1(sc, car(pp))); + } + else + { + s7_int len = sequence_length(sc, p); + if (len > 0) + { + if (is_null(tp)) + { + tp = s7_copy_1(sc, sc->append_symbol, set_plist_2(sc, p, make_list(sc, len, sc->unused))); + np = tp; + sc->temp8 = tp; + } + else set_cdr(np, s7_copy_1(sc, sc->append_symbol, set_plist_2(sc, p, make_list(sc, len, sc->unused)))); + for (; is_pair(cdr(np)); np = cdr(np)); + } + else + if (len < 0) + { + unstack(sc); + wrong_type_error_nr(sc, sc->append_symbol, position_of(y, args), p, a_sequence_string); + }}}} + unstack(sc); + return(tp); +} + +static s7_pointer append_in_place(s7_scheme *sc, s7_pointer a, s7_pointer b) +{ + /* tack b onto the end of a without copying either -- 'a' is changed! */ + s7_pointer p; + if (is_null(a)) return(b); + p = a; + while (is_not_null(cdr(p))) p = cdr(p); + set_cdr(p, b); + return(a); +} + + +/* -------------------------------- vectors -------------------------------- */ +bool s7_is_vector(s7_pointer p) {return(is_any_vector(p));} +bool s7_is_float_vector(s7_pointer p) {return(is_float_vector(p));} +bool s7_is_int_vector(s7_pointer p) {return(is_int_vector(p));} +bool s7_is_byte_vector(s7_pointer p) {return(is_byte_vector(p));} + +static bool is_byte_vector_b_p(s7_pointer b) {return(is_byte_vector(b));} + +s7_int s7_vector_length(s7_pointer vec) {return(vector_length(vec));} + +static s7_pointer normal_vector_setter(s7_scheme *sc, s7_pointer vec, s7_int loc, s7_pointer val) +{ + vector_element(vec, loc) = val; + return(val); +} + +static s7_pointer typed_vector_typer_symbol(s7_scheme *sc, s7_pointer p) +{ + s7_pointer typer = typed_vector_typer(p); + return((is_c_function(typer)) ? c_function_symbol(typer) : find_closure(sc, typer, closure_let(typer))); +} + +static const char *typed_vector_typer_name(s7_scheme *sc, s7_pointer p) +{ + s7_pointer typer = typed_vector_typer(p); + return((is_c_function(typer)) ? c_function_name(typer) : symbol_name(typed_vector_typer_symbol(sc, p))); +} + +static void port_write_vector_typer(s7_scheme *sc, s7_pointer vect, s7_pointer port) +{ + const char *setter = make_type_name(sc, typed_vector_typer_name(sc, vect), NO_ARTICLE); + port_write_string(port)(sc, setter, safe_strlen(setter), port); +} + +static noreturn void typed_vector_type_error_nr(s7_scheme *sc, s7_pointer vec, s7_pointer val) +{ + const char *descr = typed_vector_typer_name(sc, vec); + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_4(sc, wrap_string(sc, "vector-set! third argument ~$, is ~A, but the vector's element type checker, ~A, rejects it", 91), + val, type_name_string(sc, val), wrap_string(sc, descr, safe_strlen(descr)))); +} + +static inline s7_pointer typed_vector_setter(s7_scheme *sc, s7_pointer vec, s7_int loc, s7_pointer val) /* tstr faster without inline, but tbig slower */ +{ + if ((sc->safety >= NO_SAFETY) && + (typed_vector_typer_call(sc, vec, set_plist_1(sc, val)) == sc->F)) + typed_vector_type_error_nr(sc, vec, val); + vector_element(vec, loc) = val; + return(val); +} + +static s7_pointer normal_vector_getter(s7_scheme *sc, s7_pointer vec, s7_int loc) {return(vector_element(vec, loc));} +static s7_pointer int_vector_getter(s7_scheme *sc, s7_pointer vec, s7_int loc) {return(make_integer(sc, int_vector(vec, loc)));} +static s7_pointer float_vector_getter(s7_scheme *sc, s7_pointer vec, s7_int loc) {return(make_real(sc, float_vector(vec, loc)));} +static s7_pointer byte_vector_getter(s7_scheme *sc, s7_pointer bv, s7_int loc) {return(small_int(byte_vector(bv, loc)));} + +static s7_pointer int_vector_setter(s7_scheme *sc, s7_pointer vec, s7_int loc, s7_pointer val) +{ + if (s7_is_integer(val)) + int_vector(vec, loc) = s7_integer_clamped_if_gmp(sc, val); + else wrong_type_error_nr(sc, sc->int_vector_set_symbol, 3, val, sc->type_names[T_INTEGER]); + return(val); +} + +static s7_pointer float_vector_setter(s7_scheme *sc, s7_pointer vec, s7_int loc, s7_pointer val) +{ + float_vector(vec, loc) = real_to_double(sc, val, "float-vector-set!"); + return(val); +} + +static s7_pointer byte_vector_setter(s7_scheme *sc, s7_pointer str, s7_int loc, s7_pointer val) +{ + s7_int byte; + if (!s7_is_integer(val)) + wrong_type_error_nr(sc, sc->byte_vector_set_symbol, 3, val, sc->type_names[T_INTEGER]); + byte = s7_integer_clamped_if_gmp(sc, val); + if ((byte < 0) || (byte >= 256)) + wrong_type_error_nr(sc, sc->byte_vector_set_symbol, 3, val, wrap_string(sc, "a byte", 6)); + byte_vector(str, loc) = (uint8_t)byte; + return(val); +} + +static block_t *mallocate_empty_block(s7_scheme *sc) +{ + block_t *b; + b = mallocate_block(sc); + block_data(b) = NULL; + block_info(b) = NULL; + return(b); +} + +#define mallocate_vector(Sc, Len) ((Len) > 0) ? inline_mallocate(Sc, Len) : mallocate_empty_block(Sc) + +static inline s7_pointer make_simple_vector(s7_scheme *sc, s7_int len) /* len >= 0 and < max */ +{ + s7_pointer x; + block_t *b = mallocate_vector(sc, len * sizeof(s7_pointer)); + new_cell(sc, x, T_VECTOR | T_SAFE_PROCEDURE); + vector_length(x) = len; + vector_block(x) = b; + vector_elements(x) = (s7_pointer *)block_data(b); + vector_set_dimension_info(x, NULL); + vector_getter(x) = normal_vector_getter; + vector_setter(x) = normal_vector_setter; + add_vector(sc, x); + return(x); +} + +static inline s7_pointer make_simple_float_vector(s7_scheme *sc, s7_int len) /* len >= 0 and < max */ +{ + s7_pointer x; + block_t *b = mallocate_vector(sc, len * sizeof(s7_double)); + new_cell(sc, x, T_FLOAT_VECTOR | T_SAFE_PROCEDURE); + vector_length(x) = len; + vector_block(x) = b; + float_vector_floats(x) = (s7_double *)block_data(b); + vector_set_dimension_info(x, NULL); + vector_getter(x) = float_vector_getter; + vector_setter(x) = float_vector_setter; + add_vector(sc, x); + return(x); +} + +static inline s7_pointer make_simple_int_vector(s7_scheme *sc, s7_int len) /* len >= 0 and < max */ +{ + s7_pointer x; + block_t *b = mallocate_vector(sc, len * sizeof(s7_int)); + new_cell(sc, x, T_INT_VECTOR | T_SAFE_PROCEDURE); + vector_length(x) = len; + vector_block(x) = b; + int_vector_ints(x) = (s7_int *)block_data(b); + vector_set_dimension_info(x, NULL); + vector_getter(x) = int_vector_getter; + vector_setter(x) = int_vector_setter; + add_vector(sc, x); + return(x); +} + +static s7_pointer make_simple_byte_vector(s7_scheme *sc, s7_int len) +{ + s7_pointer x; + block_t *b = inline_mallocate(sc, len); + new_cell(sc, x, T_BYTE_VECTOR | T_SAFE_PROCEDURE); + vector_block(x) = b; + byte_vector_bytes(x) = (uint8_t *)block_data(b); + vector_length(x) = len; + vector_set_dimension_info(x, NULL); + vector_getter(x) = byte_vector_getter; + vector_setter(x) = byte_vector_setter; + add_vector(sc, x); + return(x); +} + +static Vectorized void normal_vector_fill(s7_pointer vec, s7_pointer obj) +{ + s7_pointer *orig = vector_elements(vec); + s7_int len = vector_length(vec), i, left; + if (len == 0) return; + /* splitting out this part made no difference in speed; type check of obj is handled elsewhere */ + left = len - 8; + i = 0; + while (i <= left) + LOOP_8(orig[i++] = obj); + for (; i < len; i++) + orig[i] = obj; +} + +static s7_pointer make_vector_1(s7_scheme *sc, s7_int len, bool filled, uint8_t typ) +{ + s7_pointer x; + + if ((len < 0) || (len > sc->max_vector_length)) + out_of_range_error_nr(sc, sc->make_vector_symbol, int_one, wrap_integer(sc, len), (len < 0) ? it_is_negative_string : it_is_too_large_string); + + /* this has to follow the error checks! (else garbage in free_heap temps portion confuses GC when "vector" is finalized) */ + new_cell(sc, x, typ | T_SAFE_PROCEDURE); + vector_length(x) = len; + if (len == 0) + { + vector_block(x) = mallocate_empty_block(sc); + any_vector_elements(x) = NULL; + if (typ == T_VECTOR) set_has_simple_elements(x); + } + else + if (typ == T_VECTOR) + { + block_t *b = inline_mallocate(sc, len * sizeof(s7_pointer)); + vector_block(x) = b; + vector_elements(x) = (s7_pointer *)block_data(b); + vector_getter(x) = normal_vector_getter; + vector_setter(x) = normal_vector_setter; + if (filled) normal_vector_fill(x, sc->nil); + } + else + if (typ == T_FLOAT_VECTOR) + { + block_t *b = inline_mallocate(sc, len * sizeof(s7_double)); + vector_block(x) = b; + float_vector_floats(x) = (s7_double *)block_data(b); + if (filled) + { + if (STEP_8(len)) + memclr64((void *)float_vector_floats(x), len * sizeof(s7_double)); + else memclr((void *)float_vector_floats(x), len * sizeof(s7_double)); + } + vector_getter(x) = float_vector_getter; + vector_setter(x) = float_vector_setter; + } + else + if (typ == T_INT_VECTOR) + { + block_t *b = inline_mallocate(sc, len * sizeof(s7_int)); + vector_block(x) = b; + int_vector_ints(x) = (s7_int *)block_data(b); + if (filled) + { + if (STEP_8(len)) + memclr64((void *)int_vector_ints(x), len * sizeof(s7_int)); + else memclr((void *)int_vector_ints(x), len * sizeof(s7_int)); + } + vector_getter(x) = int_vector_getter; + vector_setter(x) = int_vector_setter; + } + else /* byte-vector */ + { + block_t *b = mallocate(sc, len); + vector_block(x) = b; + byte_vector_bytes(x) = (uint8_t *)block_data(b); + vector_getter(x) = byte_vector_getter; + vector_setter(x) = byte_vector_setter; + if (filled) + { + if (STEP_64(len)) + memclr64((void *)(byte_vector_bytes(x)), len); + else memclr((void *)(byte_vector_bytes(x)), len); + }} + vector_set_dimension_info(x, NULL); + return(x); +} + +#define FILLED true +#define NOT_FILLED false + +s7_pointer s7_make_vector(s7_scheme *sc, s7_int len) +{ + s7_pointer v = make_vector_1(sc, len, FILLED, T_VECTOR); + add_vector(sc, v); + return(v); +} + +s7_pointer s7_make_and_fill_vector(s7_scheme *sc, s7_int len, s7_pointer fill) +{ + s7_pointer vect = make_simple_vector(sc, len); + normal_vector_fill(vect, fill); + return(vect); +} + +static vdims_t *make_wrap_only(s7_scheme *sc) /* this makes sc->wrap_only */ +{ + vdims_t *v = (vdims_t *)mallocate_block(sc); + vdims_original(v) = sc->F; + vector_elements_should_be_freed(v) = false; + vdims_rank(v) = 1; + vdims_dims(v) = NULL; + vdims_offsets(v) = NULL; + return(v); +} + +static vdims_t *make_vdims(s7_scheme *sc, bool elements_should_be_freed, s7_int dims, const s7_int *dim_info) +{ + vdims_t *v; + if ((dims == 1) && (!elements_should_be_freed)) + return(sc->wrap_only); + + if (dims > 1) + { + s7_int offset = 1; + v = (vdims_t *)mallocate(sc, dims * 2 * sizeof(s7_int)); + vdims_original(v) = sc->F; + vector_elements_should_be_freed(v) = elements_should_be_freed; + vdims_rank(v) = dims; + vdims_offsets(v) = (s7_int *)(vdims_dims(v) + dims); + for (s7_int i = 0; i < dims; i++) + vdims_dims(v)[i] = dim_info[i]; + for (s7_int i = dims - 1; i >= 0; i--) + { + vdims_offsets(v)[i] = offset; + offset *= vdims_dims(v)[i]; + } + return(v); + } + v = (vdims_t *)mallocate_block(sc); + vdims_original(v) = sc->F; + vector_elements_should_be_freed(v) = elements_should_be_freed; + vdims_rank(v) = 1; + vdims_dims(v) = NULL; + vdims_offsets(v) = NULL; + return(v); +} + +static s7_pointer make_any_vector(s7_scheme *sc, int32_t type, s7_int len, s7_int dims, const s7_int *dim_info) +{ + const s7_pointer p = make_vector_1(sc, len, FILLED, type); + if (dim_info) + { + vector_set_dimension_info(p, make_vdims(sc, false, dims, dim_info)); + add_multivector(sc, p); + } + else add_vector(sc, p); + return(p); +} + +s7_pointer s7_make_int_vector(s7_scheme *sc, s7_int len, s7_int dims, s7_int *dim_info) {return(make_any_vector(sc, T_INT_VECTOR, len, dims, dim_info));} +s7_pointer s7_make_byte_vector(s7_scheme *sc, s7_int len, s7_int dims, s7_int *dim_info) {return(make_any_vector(sc, T_BYTE_VECTOR, len, dims, dim_info));} +s7_pointer s7_make_float_vector(s7_scheme *sc, s7_int len, s7_int dims, s7_int *dim_info) {return(make_any_vector(sc, T_FLOAT_VECTOR, len, dims, dim_info));} +s7_pointer s7_make_normal_vector(s7_scheme *sc, s7_int len, s7_int dims, s7_int *dim_info) {return(make_any_vector(sc, T_VECTOR, len, dims, dim_info));} + +s7_pointer s7_make_float_vector_wrapper(s7_scheme *sc, s7_int len, s7_double *data, s7_int dims, s7_int *dim_info, bool free_data) +{ + /* this wraps up a C-allocated/freed double array as an s7 vector */ + s7_pointer x; + block_t *b = mallocate_empty_block(sc); + new_cell(sc, x, T_FLOAT_VECTOR | T_SAFE_PROCEDURE); + vector_block(x) = b; + float_vector_floats(x) = data; + vector_getter(x) = float_vector_getter; + vector_setter(x) = float_vector_setter; + vector_length(x) = len; + if (!dim_info) + { + s7_int di[1]; + di[0] = len; + vector_set_dimension_info(x, make_vdims(sc, free_data, 1, di)); + } + else vector_set_dimension_info(x, make_vdims(sc, free_data, dims, dim_info)); + add_multivector(sc, x); + return(x); +} + + +/* -------------------------------- vector-fill! -------------------------------- */ +static Vectorized void float_vector_fill(s7_pointer vec, s7_double x) +{ + s7_int len = vector_length(vec); + if (len == 0) return; + if (x == 0.0) + { + if (STEP_8(len)) + memclr64((void *)float_vector_floats(vec), len * sizeof(s7_double)); + else memclr((void *)float_vector_floats(vec), len * sizeof(s7_double)); + } + else + { + s7_int i = 0, left = len - 8; + s7_double *orig = float_vector_floats(vec); + while (i <= left) + LOOP_8(orig[i++] = x); + for (; i < len; i++) + orig[i] = x; + } +} + +static Vectorized void int_vector_fill(s7_pointer vec, s7_int k) +{ + s7_int len = vector_length(vec); + if (len == 0) return; + if (k == 0) + { + if (STEP_8(len)) + memclr64((void *)int_vector_ints(vec), len * sizeof(s7_int)); + else memclr((void *)int_vector_ints(vec), len * sizeof(s7_int)); + } + else + { + s7_int i = 0, left = len - 8; + s7_int *orig = int_vector_ints(vec); + while (i <= left) + LOOP_8(orig[i++] = k); + for (; i < len; i++) + orig[i] = k; + } +} + +static void byte_vector_fill(s7_pointer vec, uint8_t byte) +{ + s7_int len = vector_length(vec); + if (len == 0) return; + if (byte > 0) + local_memset((void *)(byte_vector_bytes(vec)), byte, len); + else /* byte == 0 */ + if (STEP_64(len)) + memclr64((void *)(byte_vector_bytes(vec)), len); + else memclr((void *)(byte_vector_bytes(vec)), len); +} + +void s7_vector_fill(s7_scheme *sc, s7_pointer vec, s7_pointer obj) +{ + switch (type(vec)) + { + case T_FLOAT_VECTOR: + if (!is_real(obj)) + wrong_type_error_nr(sc, wrap_string(sc, "float-vector fill!", 18), 2, obj, sc->type_names[T_REAL]); + float_vector_fill(vec, s7_real(obj)); + break; + case T_INT_VECTOR: + if (!s7_is_integer(obj)) /* possibly a bignum */ + wrong_type_error_nr(sc, wrap_string(sc, "int-vector fill!", 16), 2, obj, sc->type_names[T_INTEGER]); + int_vector_fill(vec, s7_integer_clamped_if_gmp(sc, obj)); + break; + case T_BYTE_VECTOR: + if (!is_byte(obj)) + wrong_type_error_nr(sc, wrap_string(sc, "byte-vector fill!", 17), 2, obj, wrap_string(sc, "a byte", 6)); + byte_vector_fill(vec, (uint8_t)s7_integer_clamped_if_gmp(sc, obj)); + break; + case T_VECTOR: + default: + normal_vector_fill(vec, obj); + } +} + +static s7_pointer g_vector_fill_1(s7_scheme *sc, s7_pointer caller, s7_pointer args) +{ + s7_pointer x = car(args), fill; + s7_int start = 0, end; + + if (!is_any_vector(x)) + { + check_method(sc, x, sc->vector_fill_symbol, args); + /* not two_methods (and fill!) here else we get stuff like: + * (let ((e (openlet (inlet 'fill! (lambda (obj val) (string-fill! (obj 'value) val)) 'value "01234")))) (vector-fill! e #\a) (e 'value)) -> "aaaaa" + */ + wrong_type_error_nr(sc, caller, 1, x, sc->type_names[T_VECTOR]); + } + if (is_immutable_vector(x)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, caller, x)); + + fill = cadr(args); + + if ((is_typed_vector(x)) && + (typed_vector_typer_call(sc, x, set_plist_1(sc, fill)) == sc->F)) + { + const char *tstr = make_type_name(sc, typed_vector_typer_name(sc, x), INDEFINITE_ARTICLE); + wrong_type_error_nr(sc, wrap_string(sc, "vector fill!", 12), 2, fill, wrap_string(sc, tstr, safe_strlen(tstr))); + } + if (is_float_vector(x)) + { + if (!is_real(fill)) /* possibly a bignum */ + return(method_or_bust(sc, fill, caller, args, sc->type_names[T_REAL], 2)); + } + else + if ((is_int_vector(x)) || (is_byte_vector(x))) + { + if (!s7_is_integer(fill)) + return(method_or_bust(sc, fill, caller, args, sc->type_names[T_INTEGER], 2)); + if ((is_byte_vector(x)) && + ((s7_integer_clamped_if_gmp(sc, fill) < 0) || (s7_integer_clamped_if_gmp(sc, fill) > 255))) + error_nr(sc, sc->out_of_range_symbol, + set_elist_3(sc, wrap_string(sc, "~S second argument, ~S, should fit in an unsigned byte", 54), caller, fill)); + } + end = vector_length(x); + if (!is_null(cddr(args))) + { + s7_pointer p = start_and_end(sc, caller, args, 3, cddr(args), &start, &end); + if (p != sc->unused) return(p); + if (start == end) return(fill); + } + if (end == 0) return(fill); + + if ((start == 0) && (end == vector_length(x))) + s7_vector_fill(sc, x, fill); + else + if (is_normal_vector(x)) + for (s7_int i = start; i < end; i++) vector_element(x, i) = fill; + else + if (is_int_vector(x)) + { + s7_int k = s7_integer_clamped_if_gmp(sc, fill); + if (k == 0) + memclr((void *)(int_vector_ints(x) + start), (end - start) * sizeof(s7_int)); + else for (s7_int i = start; i < end; i++) int_vector(x, i) = k; + } + else + if (is_float_vector(x)) + { + s7_double y = s7_real(fill); + if (y == 0.0) + memclr((void *)(float_vector_floats(x) + start), (end - start) * sizeof(s7_double)); + else + { + s7_double *orig = float_vector_floats(x); + s7_int left = end - 8; + s7_int i = start; + while (i <= left) + LOOP_8(orig[i++] = y); + for (; i < end; i++) + orig[i] = y; + }} + else + if (is_byte_vector(x)) + { + uint8_t k = (uint8_t)s7_integer_clamped_if_gmp(sc, fill); + if (k == 0) + memclr((void *)(byte_vector_bytes(x) + start), end - start); + else local_memset((void *)(byte_vector_bytes(x) + start), k, end - start); + } + return(fill); +} + +#if (!WITH_PURE_S7) +/* -------------------------------- vector-fill! -------------------------------- */ +static s7_pointer g_vector_fill(s7_scheme *sc, s7_pointer args) +{ + #define H_vector_fill "(vector-fill! v val start end) sets all elements of the vector v between start and end to val" + #define Q_vector_fill s7_make_circular_signature(sc, 3, 4, sc->T, sc->is_vector_symbol, sc->T, sc->is_integer_symbol) + return(g_vector_fill_1(sc, sc->vector_fill_symbol, args)); +} + +/* -------------------------------- vector-append -------------------------------- */ +static s7_pointer vector_append(s7_scheme *sc, s7_pointer args, uint8_t typ, s7_pointer caller); +static s7_pointer copy_source_no_dest(s7_scheme *sc, s7_pointer source, s7_pointer args); + +static s7_pointer g_vector_append(s7_scheme *sc, s7_pointer args) +{ + /* returns a one-dimensional vector. To handle multidimensional vectors, we'd need to + * ensure all the dimensional data matches (rank, size of each dimension except the last etc), + * which is too much trouble. + */ + #define H_vector_append "(vector-append . vectors) returns a new (1-dimensional) vector containing the elements of its vector arguments." + #define Q_vector_append sc->pcl_v + + s7_pointer p = args; + if (is_null(args)) + return(make_simple_vector(sc, 0)); + + if ((is_null(cdr(args))) && + (is_any_vector(car(args)))) + return(copy_source_no_dest(sc, car(args), args)); + + for (int32_t i = 0; is_pair(p); p = cdr(p), i++) + { + s7_pointer x = car(p); + if (!is_any_vector(x)) + { + if (has_active_methods(sc, x)) + { + s7_pointer func = find_method_with_let(sc, x, sc->vector_append_symbol); + if (func != sc->undefined) + { + int32_t k; + s7_pointer v, y; + if (i == 0) + return(s7_apply_function(sc, func, args)); + sc->temp9 = make_list(sc, i, sc->unused); /* we have to copy the arglist here */ + for (k = 0, y = args, v = sc->temp9; k < i; k++, y = cdr(y), v = cdr(v)) + set_car(v, car(y)); + v = g_vector_append(sc, sc->temp9); + y = s7_apply_function(sc, func, set_ulist_1(sc, v, p)); + sc->temp9 = sc->unused; + return(y); + }} + wrong_type_error_nr(sc, sc->vector_append_symbol, i + 1, x, sc->type_names[T_VECTOR]); + }} + return(vector_append(sc, args, type(car(args)), sc->vector_append_symbol)); +} + +static s7_pointer vector_append_p_pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + s7_pointer val; + sc->temp7 = list_2(sc, p1, p2); /* ideally this list would be stack_protected, avoiding temp7 (method call above) */ + val = g_vector_append(sc, sc->temp7); + sc->temp7 = sc->unused; + return(val); +} + +static s7_pointer vector_append_p_ppp(s7_scheme *sc, s7_pointer p1, s7_pointer p2, s7_pointer p3) +{ + s7_pointer val; + sc->temp7 = list_3(sc, p1, p2, p3); + val = g_vector_append(sc, sc->temp7); + sc->temp7 = sc->unused; + return(val); +} +#endif + + +/* -------------------------------- vector-ref|set! -------------------------------- */ +s7_pointer s7_vector_ref(s7_scheme *sc, s7_pointer vec, s7_int index) +{ + if (index >= vector_length(vec)) + out_of_range_error_nr(sc, sc->vector_ref_symbol, int_two, wrap_integer(sc, index), it_is_too_large_string); + return(vector_getter(vec)(sc, vec, index)); +} + +s7_pointer s7_vector_set(s7_scheme *sc, s7_pointer vec, s7_int index, s7_pointer a) +{ + if (index >= vector_length(vec)) + out_of_range_error_nr(sc, sc->vector_set_symbol, int_two, wrap_integer(sc, index), it_is_too_large_string); + if (is_typed_vector(vec)) + return(typed_vector_setter(sc, vec, index, a)); + vector_setter(vec)(sc, vec, index, T_Ext(a)); + return(a); +} + +s7_pointer *s7_vector_elements(s7_pointer vec) {return(vector_elements(vec));} + +/* these are for s7.h */ +s7_int *s7_int_vector_elements(s7_pointer vec) {return(int_vector_ints(vec));} +s7_int s7_int_vector_ref(s7_pointer vec, s7_int index) {return(int_vector(vec, index));} +s7_int s7_int_vector_set(s7_pointer vec, s7_int index, s7_int value) {int_vector(vec, index) = value; return(value);} + +uint8_t *s7_byte_vector_elements(s7_pointer vec) {return(byte_vector_bytes(vec));} +uint8_t s7_byte_vector_ref(s7_pointer vec, s7_int index) {return(byte_vector(vec, index));} +uint8_t s7_byte_vector_set(s7_pointer vec, s7_int index, uint8_t value) {byte_vector(vec, index) = value; return(value);} + +s7_double *s7_float_vector_elements(s7_pointer vec) {return(float_vector_floats(vec));} +s7_double s7_float_vector_ref(s7_pointer vec, s7_int index) {return(float_vector(vec, index));} +s7_double s7_float_vector_set(s7_pointer vec, s7_int index, s7_double value) {float_vector(vec, index) = value; return(value);} + +s7_int s7_vector_dimensions(s7_pointer vec, s7_int *dims, s7_int dims_size) +{ + if (dims_size <= 0) return(0); + if (vector_dimension_info(vec)) + { + s7_int lim = vector_ndims(vec); + if (lim > dims_size) lim = dims_size; + for (s7_int i = 0; i < lim; i++) dims[i] = vector_dimension(vec, i); + return(lim); + } + dims[0] = vector_length(vec); + return(1); +} + +s7_int s7_vector_dimension(s7_pointer vec, s7_int dim) +{ + if (vector_dimension_info(vec)) + return(vector_dimension(vec, dim)); + return((dim == 0) ? vector_length(vec) : -1); +} + +s7_int s7_vector_offsets(s7_pointer vec, s7_int *offs, s7_int offs_size) +{ + if (offs_size <= 0) return(0); + if (vector_dimension_info(vec)) + { + s7_int lim = vector_ndims(vec); + if (lim > offs_size) lim = offs_size; + for (s7_int i = 0; i < lim; i++) offs[i] = vector_offset(vec, i); + return(lim); + } + offs[0] = 1; + return(1); +} + + +static s7_int flatten_multivector_indices(s7_scheme *sc, s7_pointer vector, s7_int indices, va_list ap) +{ + s7_int index, rank = vector_rank(vector); + if (rank != indices) + { + va_end(ap); + wrong_number_of_arguments_error_nr(sc, "s7_vector_ref_n: wrong number of indices: ~A", 44, wrap_integer(sc, indices)); + } + if (rank == 1) + index = va_arg(ap, s7_int); + else + { + s7_int i; + const s7_int *dimensions = vector_dimensions(vector); + const s7_int *offsets = vector_offsets(vector); + for (i = 0, index = 0; i < indices; i++) + { + s7_int ind = va_arg(ap, s7_int); + if ((ind < 0) || (ind >= dimensions[i])) + { + va_end(ap); + out_of_range_error_nr(sc, sc->vector_ref_symbol, wrap_integer(sc, i), wrap_integer(sc, ind), (ind < 0) ? it_is_negative_string : it_is_too_large_string); + return(-1); + } + index += (ind * offsets[i]); + }} + va_end(ap); + return(index); +} + +s7_pointer s7_vector_ref_n(s7_scheme *sc, s7_pointer vector, s7_int indices, ...) +{ + s7_int index; + va_list ap; + va_start(ap, indices); + index = flatten_multivector_indices(sc, vector, indices, ap); + return(vector_getter(vector)(sc, vector, index)); +} + +s7_pointer s7_vector_set_n(s7_scheme *sc, s7_pointer vector, s7_pointer value, s7_int indices, ...) +{ + s7_int index; + va_list ap; + va_start(ap, indices); + index = flatten_multivector_indices(sc, vector, indices, ap); + if (is_typed_vector(vector)) + return(typed_vector_setter(sc, vector, index, value)); + return(vector_setter(vector)(sc, vector, index, value)); +} + + +/* -------------------------------- vector->list -------------------------------- */ +s7_pointer s7_vector_to_list(s7_scheme *sc, s7_pointer vect) +{ + s7_int len = vector_length(vect); + s7_pointer result; + if (len == 0) return(sc->nil); + init_temp(sc->y, sc->nil); + gc_protect_via_stack(sc, vect); + switch (type(vect)) + { + case T_VECTOR: + check_free_heap_size(sc, len); + for (s7_int i = len - 1; i >= 0; i--) + sc->y = cons_unchecked(sc, vector_element(vect, i), sc->y); + break; + case T_BYTE_VECTOR: + check_free_heap_size(sc, len); + for (s7_int i = len - 1; i >= 0; i--) + sc->y = cons_unchecked(sc, small_int(byte_vector(vect, i)), sc->y); + break; + case T_INT_VECTOR: + check_free_heap_size(sc, 2 * len); + for (s7_int i = len - 1; i >= 0; i--) + sc->y = cons_unchecked(sc, make_integer_unchecked(sc, int_vector(vect, i)), sc->y); + break; + case T_FLOAT_VECTOR: + check_free_heap_size(sc, 2 * len); + for (s7_int i = len - 1; i >= 0; i--) + sc->y = cons_unchecked(sc, make_real_unchecked(sc, float_vector(vect, i)), sc->y); + break; + } + unstack(sc); + result = sc->y; + sc->y = sc->unused; + return(result); +} + +s7_pointer s7_array_to_list(s7_scheme *sc, s7_int num_values, s7_pointer *array) +{ + s7_pointer result; + if (num_values == 0) return(sc->nil); + init_temp(sc->y, sc->nil); + check_free_heap_size(sc, num_values); + for (s7_int i = num_values - 1; i >= 0; i--) + sc->y = cons_unchecked(sc, array[i], sc->y); + result = sc->y; + if (sc->safety > NO_SAFETY) + check_list_validity(sc, __func__, result); + sc->y = sc->unused; + return(result); +} + +#if (!WITH_PURE_S7) +static s7_pointer g_vector_to_list(s7_scheme *sc, s7_pointer args) +{ + #define H_vector_to_list "(vector->list v (start 0) end) returns the elements of the vector v as a list; (map values v)" + #define Q_vector_to_list s7_make_signature(sc, 4, sc->is_proper_list_symbol, sc->is_vector_symbol, sc->is_integer_symbol, sc->is_integer_symbol) + + s7_int i, start = 0, end; + s7_pointer p, vec = car(args); + if (!is_any_vector(vec)) + return(sole_arg_method_or_bust(sc, vec, sc->vector_to_list_symbol, args, sc->type_names[T_VECTOR])); + + end = vector_length(vec); + if (!is_null(cdr(args))) + { + p = start_and_end(sc, sc->vector_to_list_symbol, args, 2, cdr(args), &start, &end); + if (p != sc->unused) return(p); + if (start == end) return(sc->nil); + } + if ((end - start) > sc->max_list_length) + out_of_range_error_nr(sc, sc->vector_to_list_symbol, int_one, vec, it_is_too_large_string); + + check_free_heap_size(sc, end - start); + sc->w = sc->nil; + gc_protect_via_stack(sc, vec); + if (is_normal_vector(vec)) + for (i = end - 1; i >= start; i--) sc->w = cons_unchecked(sc, vector_element(vec, i), sc->w); + else for (i = end - 1; i >= start; i--) sc->w = cons_unchecked(sc, vector_getter(vec)(sc, vec, i), sc->w); + unstack(sc); + p = sc->w; + sc->w = sc->unused; + return(p); +} + +static s7_pointer vector_to_list_p_p(s7_scheme *sc, s7_pointer p) +{ + if (!is_any_vector(p)) + return(method_or_bust_p(sc, p, sc->vector_to_list_symbol, sc->type_names[T_VECTOR])); + return(s7_vector_to_list(sc, p)); +} +#endif + + +/* -------------------------------- string->byte-vector -------------------------------- */ +static s7_pointer g_string_to_byte_vector(s7_scheme *sc, s7_pointer args) +{ + #define H_string_to_byte_vector "(string->byte-vector obj) turns a string into a byte-vector." + #define Q_string_to_byte_vector s7_make_signature(sc, 2, sc->is_byte_vector_symbol, sc->is_string_symbol) + s7_pointer str = car(args); + if (!is_string(str)) + return(method_or_bust_p(sc, str, sc->string_to_byte_vector_symbol, sc->type_names[T_STRING])); + return(s7_copy_1(sc, sc->string_to_byte_vector_symbol, set_plist_2(sc, str, make_simple_byte_vector(sc, string_length(str))))); +} + + +/* -------------------------------- byte-vector->string -------------------------------- */ +static s7_pointer g_byte_vector_to_string(s7_scheme *sc, s7_pointer args) +{ + #define H_byte_vector_to_string "(byte-vector->string obj) turns a byte-vector into a string." + #define Q_byte_vector_to_string s7_make_signature(sc, 2, sc->is_string_symbol, sc->is_byte_vector_symbol) + s7_pointer v = car(args); + if (!is_byte_vector(v)) + return(method_or_bust_p(sc, v, sc->byte_vector_to_string_symbol, sc->type_names[T_BYTE_VECTOR])); + return(s7_copy_1(sc, sc->byte_vector_to_string_symbol, set_plist_2(sc, v, make_empty_string(sc, byte_vector_length(v), 0)))); +} + + +/* -------------------------------- vector -------------------------------- */ +static s7_pointer g_vector(s7_scheme *sc, s7_pointer args) +{ + #define H_vector "(vector ...) returns a vector whose elements are the arguments" + #define Q_vector s7_make_circular_signature(sc, 1, 2, sc->is_vector_symbol, sc->T) + + s7_pointer vec, b; + s7_int len = proper_list_length_with_end(args, &b); + if (!is_null(b)) + error_nr(sc, sc->read_error_symbol, set_elist_1(sc, wrap_string(sc, "vector constant data is not a proper list", 41))); + vec = make_simple_vector(sc, len); + if (len > 0) + { + s7_pointer x = args; + for (s7_int i = 0; is_pair(x); x = cdr(x), i++) + vector_element(vec, i) = car(x); + } + return(vec); +} + +static inline s7_pointer vector_p_pp(s7_scheme *sc, s7_pointer p1, s7_pointer p2) +{ + s7_pointer vec = make_simple_vector(sc, 2); + vector_element(vec, 0) = p1; + vector_element(vec, 1) = p2; + return(vec); +} + +static s7_pointer g_vector_2(s7_scheme *sc, s7_pointer args) {return(vector_p_pp(sc, car(args), cadr(args)));} + +static s7_pointer g_vector_3(s7_scheme *sc, s7_pointer args) +{ + s7_pointer vec = make_simple_vector(sc, 3); + vector_element(vec, 0) = car(args); args = cdr(args); + vector_element(vec, 1) = car(args); + vector_element(vec, 2) = cadr(args); + return(vec); +} + +static s7_pointer vector_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer unused_expr, bool unused_ops) +{ + if (args == 2) return(sc->vector_2); + return((args == 3) ? sc->vector_3 : f); +} + + +/* -------------------------------- float-vector? -------------------------------- */ +static s7_pointer g_is_float_vector(s7_scheme *sc, s7_pointer args) +{ + #define H_is_float_vector "(float-vector? obj) returns #t if obj is an homogeneous float vector" + #define Q_is_float_vector sc->pl_bt + check_boolean_method(sc, s7_is_float_vector, sc->is_float_vector_symbol, args); +} + + +/* -------------------------------- float-vector -------------------------------- */ +static s7_pointer g_float_vector(s7_scheme *sc, s7_pointer args) +{ + #define H_float_vector "(float-vector ...) returns an homogeneous float vector whose elements are the arguments" + #define Q_float_vector s7_make_circular_signature(sc, 1, 2, sc->is_float_vector_symbol, sc->is_real_symbol) + + s7_pointer vec, b; + s7_int len = proper_list_length_with_end(args, &b); + if (!is_null(b)) + error_nr(sc, sc->read_error_symbol, set_elist_1(sc, wrap_string(sc, "float-vector constant data is not a proper list", 47))); + + vec = make_simple_float_vector(sc, len); + if (len > 0) + { + s7_int i = 0; + for (s7_pointer x = args; is_pair(x); x = cdr(x), i++) + { /* this used to gc protect vec via sc->w? was that due to very old bignum code in s7_real? */ + s7_pointer p = car(x); + if (is_t_real(p)) + float_vector(vec, i) = real(p); + else + if (is_real(p)) /* bignum is ok here */ + float_vector(vec, i) = s7_real(p); + else return(method_or_bust(sc, p, sc->float_vector_symbol, args, sc->type_names[T_REAL], i + 1)); + }} + return(vec); +} + +static s7_pointer float_vector_p_d(s7_scheme *sc, s7_double x) +{ + s7_pointer vec = make_simple_float_vector(sc, 1); + float_vector(vec, 0) = x; + return(vec); +} + + +/* -------------------------------- int-vector? -------------------------------- */ +static s7_pointer g_is_int_vector(s7_scheme *sc, s7_pointer args) +{ + #define H_is_int_vector "(int-vector? obj) returns #t if obj is an homogeneous s7_int vector" + #define Q_is_int_vector sc->pl_bt + check_boolean_method(sc, is_int_vector, sc->is_int_vector_symbol, args); +} + + +/* -------------------------------- int-vector -------------------------------- */ +static s7_pointer g_int_vector(s7_scheme *sc, s7_pointer args) +{ + #define H_int_vector "(int-vector ...) returns an homogeneous s7_int vector whose elements are the arguments" + #define Q_int_vector s7_make_circular_signature(sc, 1, 2, sc->is_int_vector_symbol, sc->is_integer_symbol) + + s7_int i = 0; + s7_pointer vec, b; + s7_int len = proper_list_length_with_end(args, &b); + if (!is_null(b)) + error_nr(sc, sc->read_error_symbol, set_elist_1(sc, wrap_string(sc, "int-vector constant data is not a proper list", 45))); + + vec = make_simple_int_vector(sc, len); + if (len == 0) return(vec); + for (s7_pointer x = args; is_pair(x); x = cdr(x), i++) + { + s7_pointer p = car(x); + if (!s7_is_integer(p)) + return(method_or_bust(sc, p, sc->int_vector_symbol, args, sc->type_names[T_INTEGER], i + 1)); + int_vector(vec, i) = s7_integer_clamped_if_gmp(sc, p); + } + return(vec); +} + +static s7_pointer int_vector_p_i(s7_scheme *sc, s7_int x) +{ + s7_pointer vec = make_simple_int_vector(sc, 1); + int_vector(vec, 0) = x; + return(vec); +} + + +/* -------------------------------- byte-vector? -------------------------------- */ +static s7_pointer g_is_byte_vector(s7_scheme *sc, s7_pointer args) +{ + #define H_is_byte_vector "(byte-vector? obj) returns #t if obj is a byte-vector" + #define Q_is_byte_vector sc->pl_bt + check_boolean_method(sc, is_byte_vector_b_p, sc->is_byte_vector_symbol, args); +} + + +/* -------------------------------- byte-vector -------------------------------- */ +static s7_pointer g_byte_vector(s7_scheme *sc, s7_pointer args) +{ + #define H_byte_vector "(byte-vector ...) returns a byte-vector whose elements are the arguments" + #define Q_byte_vector s7_make_circular_signature(sc, 1, 2, sc->is_byte_vector_symbol, sc->is_byte_symbol) + + s7_int i = 0; + s7_pointer vec, end; + uint8_t *str; + s7_int len = proper_list_length_with_end(args, &end); + if (!is_null(end)) + error_nr(sc, sc->read_error_symbol, set_elist_1(sc, wrap_string(sc, "byte-vector constant data is not a proper list", 46))); + + vec = make_simple_byte_vector(sc, len); + str = byte_vector_bytes(vec); + for (s7_pointer x = args; is_pair(x); i++, x = cdr(x)) + { + s7_pointer byte = car(x); + s7_int b; + if (is_t_integer(byte)) + b = integer(byte); + else +#if WITH_GMP + if (is_t_big_integer(byte)) + b = big_integer_to_s7_int(sc, big_integer(byte)); + else +#endif + return(method_or_bust(sc, byte, sc->byte_vector_symbol, args, sc->type_names[T_INTEGER], i + 1)); + if ((b < 0) || (b > 255)) + wrong_type_error_nr(sc, sc->byte_vector_symbol, i + 1, byte, an_unsigned_byte_string); + str[i] = (uint8_t)b; + } + return(vec); +} + + +#if (!WITH_PURE_S7) +/* -------------------------------- list->vector -------------------------------- */ +static s7_pointer g_list_to_vector(s7_scheme *sc, s7_pointer args) +{ + #define H_list_to_vector "(list->vector lst) returns a vector containing the elements of lst; (apply vector lst)" + #define Q_list_to_vector s7_make_signature(sc, 2, sc->is_vector_symbol, sc->is_proper_list_symbol) + + s7_pointer p = car(args); + if (is_null(p)) + return(make_simple_vector(sc, 0)); /* was s7_make_vector */ + sc->temp3 = p; + if (!s7_is_proper_list(sc, p)) + return(method_or_bust_p(sc, p, sc->list_to_vector_symbol, a_proper_list_string)); + p = g_vector(sc, p); + sc->temp3 = sc->unused; + return(p); +} + +/* -------------------------------- vector-length -------------------------------- */ +static s7_pointer g_vector_length(s7_scheme *sc, s7_pointer args) +{ + #define H_vector_length "(vector-length v) returns the length of vector v" + #define Q_vector_length s7_make_signature(sc, 2, sc->is_integer_symbol, sc->is_vector_symbol) + + s7_pointer vec = car(args); + if (!is_any_vector(vec)) + return(sole_arg_method_or_bust(sc, vec, sc->vector_length_symbol, args, sc->type_names[T_VECTOR])); + return(make_integer(sc, vector_length(vec))); +} + +static s7_int vector_length_i_7p(s7_scheme *sc, s7_pointer p) +{ + if (!is_any_vector(p)) + return(integer(method_or_bust_p(sc, p, sc->vector_length_symbol, sc->type_names[T_VECTOR]))); + return(vector_length(p)); +} + +static s7_pointer vector_length_p_p(s7_scheme *sc, s7_pointer vec) +{ + if (!is_any_vector(vec)) + return(method_or_bust_p(sc, vec, sc->vector_length_symbol, sc->type_names[T_VECTOR])); + return(make_integer(sc, vector_length(vec))); +} +#endif + + +/* -------------------------------- subvector subvector? subvector-vector subvector-position -------------------------------- */ +static bool s7_is_subvector(s7_pointer g) {return((is_any_vector(g)) && (is_subvector(g)));} + +static s7_pointer g_is_subvector(s7_scheme *sc, s7_pointer args) +{ + #define H_is_subvector "(subvector? obj) returns #t if obj is a subvector" + #define Q_is_subvector sc->pl_bt + check_boolean_method(sc, s7_is_subvector, sc->is_subvector_symbol, args); +} + +static s7_pointer g_subvector_position(s7_scheme *sc, s7_pointer args) +{ + #define H_subvector_position "(subvector-position obj) returns obj's offset" + #define Q_subvector_position s7_make_signature(sc, 2, sc->is_integer_symbol, sc->is_subvector_symbol) + + s7_pointer sv = car(args); + if (s7_is_subvector(sv)) + { + /* we can't use vector_elements(sv) - vector_elements(subvector_vector(sv)) because that assumes we're looking at s7_pointer*, + * so a subvector of a byte_vector gets a bogus position (0 if position is less than 8 etc). + * Since we currently let the user reset s7_int and s7_double, all four cases have to be handled explicitly. + */ + switch (type(sv)) + { + case T_VECTOR: return(make_integer(sc, (s7_int)(vector_elements(sv) - vector_elements(subvector_vector(sv))))); + case T_INT_VECTOR: return(make_integer(sc, (s7_int)(int_vector_ints(sv) - int_vector_ints(subvector_vector(sv))))); + case T_FLOAT_VECTOR: return(make_integer(sc, (s7_int)(float_vector_floats(sv) - float_vector_floats(subvector_vector(sv))))); + case T_BYTE_VECTOR: return(make_integer(sc, (s7_int)(byte_vector_bytes(sv) - byte_vector_bytes(subvector_vector(sv))))); + }} + return(sole_arg_method_or_bust(sc, sv, sc->subvector_position_symbol, args, sc->type_names[T_VECTOR])); +} + +static s7_pointer g_subvector_vector(s7_scheme *sc, s7_pointer args) +{ + #define H_subvector_vector "(subvector-vector obj) returns the vector underlying the subvector obj" + #define Q_subvector_vector s7_make_signature(sc, 2, sc->is_vector_symbol, sc->is_subvector_symbol) + + if (s7_is_subvector(car(args))) + return(subvector_vector(car(args))); + return(sole_arg_method_or_bust(sc, car(args), sc->subvector_vector_symbol, args, sc->type_names[T_VECTOR])); +} + +static s7_pointer subvector(s7_scheme *sc, s7_pointer vect, s7_int skip_dims, s7_int index) +{ + s7_int dims = vector_ndims(vect) - skip_dims; + s7_pointer x; + new_cell(sc, x, (full_type(vect) & (~T_COLLECTED)) | T_SUBVECTOR | T_SAFE_PROCEDURE); + vector_length(x) = 0; + vector_block(x) = mallocate_empty_block(sc); + any_vector_elements(x) = NULL; + vector_getter(x) = vector_getter(vect); + vector_setter(x) = vector_setter(vect); + if (dims > 1) + { + vdims_t *v = (vdims_t *)mallocate_block(sc); + vdims_rank(v) = dims; + vdims_dims(v) = (s7_int *)(vector_dimensions(vect) + skip_dims); + vdims_offsets(v) = (s7_int *)(vector_offsets(vect) + skip_dims); + vdims_original(v) = vect; + vector_elements_should_be_freed(v) = false; + vector_set_dimension_info(x, v); + } + else + { + vector_set_dimension_info(x, NULL); + subvector_set_vector(x, vect); + } + if (is_normal_vector(vect)) + mark_function[T_VECTOR] = mark_vector_possibly_shared; + else mark_function[type(vect)] = mark_int_or_float_vector_possibly_shared; + + vector_length(x) = (skip_dims > 0) ? vector_offset(vect, skip_dims - 1) : vector_length(vect); + if (is_int_vector(vect)) + int_vector_ints(x) = (s7_int *)(int_vector_ints(vect) + index); + else + if (is_float_vector(vect)) + float_vector_floats(x) = (s7_double *)(float_vector_floats(vect) + index); + else + if (is_normal_vector(vect)) + vector_elements(x) = (s7_pointer *)(vector_elements(vect) + index); + else byte_vector_bytes(x) = (uint8_t *)(byte_vector_bytes(vect) + index); + add_multivector(sc, x); + return(x); +} + +static inline vdims_t *list_to_dims(s7_scheme *sc, s7_pointer x) +{ + s7_int i, offset; + s7_pointer y; + s7_int *ds, *os; + s7_int len = proper_list_length(x); + vdims_t *v = (vdims_t *)inline_mallocate(sc, len * 2 * sizeof(s7_int)); + vdims_rank(v) = len; + vdims_offsets(v) = (s7_int *)(vdims_dims(v) + len); + vector_elements_should_be_freed(v) = false; + ds = vdims_dims(v); + os = vdims_offsets(v); + + for (i = 0, y = x; is_not_null(y); i++, y = cdr(y)) + ds[i] = s7_integer_clamped_if_gmp(sc, car(y)); + + for (i = len - 1, offset = 1; i >= 0; i--) + { + os[i] = offset; + offset *= ds[i]; + } + return(v); +} + +static s7_pointer g_subvector(s7_scheme *sc, s7_pointer args) +{ + #define H_subvector "(subvector original-vector (start 0) (end original-vector-len) new-dimensions) returns \ +a vector that points to the same elements as the original-vector but with different starting point, end point, and dimensional info." + #define Q_subvector s7_make_signature(sc, 5, sc->is_subvector_symbol, sc->is_vector_symbol, sc->is_integer_symbol, sc->is_integer_symbol, sc->is_pair_symbol) + + /* (let ((v1 #2d((1 2 3) (4 5 6)))) (let ((v2 (subvector v1 0 6))) v2)) -> #(1 2 3 4 5 6) + * (let ((v1 #(1 2 3 4 5 6))) (let ((v2 (subvector v1 0 6 '(3 2)))) v2)) -> #2D((1 2) (3 4) (5 6)) + */ + s7_pointer orig = car(args), x; + vdims_t *v = NULL; + s7_int new_len, orig_len, offset = 0; + + if (!is_any_vector(orig)) + return(method_or_bust(sc, orig, sc->subvector_symbol, args, sc->type_names[T_VECTOR], 1)); + + orig_len = vector_length(orig); + new_len = orig_len; + + if (is_pair(cdr(args))) /* get start point in vector */ + { + s7_pointer start = cadr(args); + if (!s7_is_integer(start)) + return(method_or_bust(sc, start, sc->subvector_symbol, args, sc->type_names[T_INTEGER], 2)); + offset = s7_integer_clamped_if_gmp(sc, start); + if ((offset < 0) || (offset > orig_len)) /* we need this if, for example, offset == 9223372036854775807 */ + out_of_range_error_nr(sc, sc->subvector_symbol, int_two, start, (offset < 0) ? it_is_negative_string : it_is_too_large_string); + new_len -= offset; + + if (is_pair(cddr(args))) /* get end point in vector */ + { + s7_pointer end = caddr(args); + s7_int new_end; + if (!s7_is_integer(end)) + return(method_or_bust(sc, end, sc->subvector_symbol, args, sc->type_names[T_INTEGER], 3)); + new_end = s7_integer_clamped_if_gmp(sc, end); + if ((new_end < 0) || (new_end > orig_len)) + out_of_range_error_nr(sc, sc->subvector_symbol, int_three, end, (new_end < 0) ? it_is_negative_string : it_is_too_large_string); + if (offset > new_end) + out_of_range_error_nr(sc, sc->subvector_symbol, int_two, start, wrap_string(sc, "start point > end point", 23)); + new_len = new_end - offset; + + if (is_pair(cdddr(args))) + { + s7_pointer dims = cadddr(args); + if ((is_null(dims)) || + (!s7_is_proper_list(sc, dims))) + return(method_or_bust(sc, dims, sc->subvector_symbol, args, sc->type_names[T_PAIR], 4)); + + for (s7_pointer y = dims; is_pair(y); y = cdr(y)) + if ((!s7_is_integer(car(y))) || /* (subvector v '((1 2) (3 4))) */ + (s7_integer_clamped_if_gmp(sc, car(y)) > orig_len) || + (s7_integer_clamped_if_gmp(sc, car(y)) < 0)) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_1(sc, wrap_string(sc, "a subvector must fit in the original vector", 43))); + + v = list_to_dims(sc, dims); + new_len = vdims_dims(v)[0]; + for (s7_int i = 1; i < vdims_rank(v); i++) new_len *= vdims_dims(v)[i]; + if (new_len != new_end - offset) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_4(sc, wrap_string(sc, "subvector dimensional length, ~S, does not match the start and end positions: ~S to ~S~%", 88), + wrap_integer(sc, new_len), start, end)); + vdims_original(v) = orig; + }}} + + if (is_normal_vector(orig)) + mark_function[T_VECTOR] = mark_vector_possibly_shared; + else mark_function[type(orig)] = mark_int_or_float_vector_possibly_shared; /* I think this works for byte-vectors also */ + + new_cell(sc, x, (full_type(orig) & (~T_COLLECTED)) | T_SUBVECTOR | T_SAFE_PROCEDURE); + vector_block(x) = mallocate_empty_block(sc); + vector_set_dimension_info(x, v); + if (!v) subvector_set_vector(x, orig); + vector_length(x) = new_len; /* might be less than original length */ + if ((new_len == 0) && (is_normal_vector(orig))) set_has_simple_elements(x); + vector_getter(x) = vector_getter(orig); + vector_setter(x) = vector_setter(orig); + + if (is_int_vector(orig)) + int_vector_ints(x) = (s7_int *)(int_vector_ints(orig) + offset); + else + if (is_float_vector(orig)) + float_vector_floats(x) = (s7_double *)(float_vector_floats(orig) + offset); + else + if (is_normal_vector(x)) + vector_elements(x) = (s7_pointer *)(vector_elements(orig) + offset); + else byte_vector_bytes(x) = (uint8_t *)(byte_vector_bytes(orig) + offset); + add_multivector(sc, x); + return(x); +} + + +/* -------------------------------- vector-ref -------------------------------- */ +static s7_pointer vector_ref_1(s7_scheme *sc, s7_pointer vect, s7_pointer indices) +{ + s7_int index = 0; + if (vector_length(vect) == 0) + out_of_range_error_nr(sc, sc->vector_ref_symbol, int_one, vect, it_is_too_large_string); + + if (vector_rank(vect) > 1) + { + s7_int i; + s7_pointer x; + for (x = indices, i = 0; (is_not_null(x)) && (i < vector_ndims(vect)); x = cdr(x), i++) + { + s7_int n; + s7_pointer p = car(x); + if (!s7_is_integer(p)) + return(method_or_bust(sc, p, sc->vector_ref_symbol, set_ulist_1(sc, vect, indices), sc->type_names[T_INTEGER], i + 2)); + n = s7_integer_clamped_if_gmp(sc, p); + if ((n < 0) || (n >= vector_dimension(vect, i))) + out_of_range_error_nr(sc, sc->vector_ref_symbol, wrap_integer(sc, i + 2), p, (n < 0) ? it_is_negative_string : it_is_too_large_string); + + index += n * vector_offset(vect, i); + } + if (is_not_null(x)) + { + s7_pointer nv; + if (!is_normal_vector(vect)) + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_3(sc, wrap_string(sc, "~S: too many indices: ~S", 24), sc->vector_ref_symbol, copy_proper_list(sc, indices))); + nv = vector_element(vect, index); + return(implicit_index(sc, nv, x)); + } + /* if not enough indices, return a subvector covering whatever is left */ + if (i < vector_ndims(vect)) + return(subvector(sc, vect, i, index)); + } + else + { + s7_pointer p = car(indices); + /* (let ((hi (make-vector 3 0.0)) (sum 0.0)) (do ((i 0 (+ i 1))) ((= i 3)) (set! sum (+ sum (hi i)))) sum) */ + + if (!s7_is_integer(p)) + return(method_or_bust(sc, p, sc->vector_ref_symbol, set_ulist_1(sc, vect, indices), sc->type_names[T_INTEGER], 2)); + index = s7_integer_clamped_if_gmp(sc, p); + + if ((index < 0) || (index >= vector_length(vect))) + out_of_range_error_nr(sc, sc->vector_ref_symbol, int_two, p, (index < 0) ? it_is_negative_string : it_is_too_large_string); + + if (is_not_null(cdr(indices))) /* (let ((L #(#(1 2 3) #(4 5 6)))) (vector-ref L 1 2)) */ + { + s7_pointer nv; + if (!is_normal_vector(vect)) + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_3(sc, wrap_string(sc, "~S: too many indices: ~S", 24), sc->vector_ref_symbol, copy_proper_list(sc, indices))); + nv = vector_element(vect, index); + return(implicit_pair_index_checked(sc, vect, nv, indices)); + }} + return((vector_getter(vect))(sc, vect, index)); +} + +static s7_pointer g_vector_ref(s7_scheme *sc, s7_pointer args) +{ + #define H_vector_ref "(vector-ref v ... i) returns the i-th element of vector v." + #define Q_vector_ref s7_make_circular_signature(sc, 2, 3, sc->T, sc->is_vector_symbol, sc->is_integer_symbol) + + s7_pointer vec = car(args); + if (!is_any_vector(vec)) + return(method_or_bust(sc, vec, sc->vector_ref_symbol, args, sc->type_names[T_VECTOR], 1)); + return(vector_ref_1(sc, vec, cdr(args))); /* 19-Jan-19 */ +} + +static s7_pointer vector_ref_p_pi(s7_scheme *sc, s7_pointer v, s7_int i) +{ + if ((!is_normal_vector(v)) || + (vector_rank(v) > 1) || + (i < 0) || (i >= vector_length(v))) + return(g_vector_ref(sc, set_plist_2(sc, v, make_integer(sc, i)))); + return(vector_element(v, i)); +} + +static s7_pointer vector_ref_p_pi_unchecked(s7_scheme *sc, s7_pointer v, s7_int i) /* callable but just barely (tgsl.scm) */ +{ + if ((i >= 0) && (i < vector_length(v))) + return(vector_getter(v)(sc, v, i)); + out_of_range_error_nr(sc, sc->vector_ref_symbol, int_two, wrap_integer(sc, i), (i < 0) ? it_is_negative_string : it_is_too_large_string); + return(v); +} + +static s7_pointer normal_vector_ref_p_pi_unchecked(s7_scheme *sc, s7_pointer v, s7_int i) +{ + if ((i >= 0) && (i < vector_length(v))) + return(vector_element(v, i)); + out_of_range_error_nr(sc, sc->vector_ref_symbol, int_two, wrap_integer(sc, i), (i < 0) ? it_is_negative_string : it_is_too_large_string); + return(v); +} + +static s7_pointer vector_ref_p_pii(s7_scheme *sc, s7_pointer v, s7_int i1, s7_int i2) +{ + if ((!is_any_vector(v)) || + (vector_rank(v) != 2) || + (i1 < 0) || (i2 < 0) || + (i1 >= vector_dimension(v, 0)) || (i2 >= vector_dimension(v, 1))) + return(g_vector_ref(sc, set_plist_3(sc, v, make_integer(sc, i1), make_integer_unchecked(sc, i2)))); + return(vector_getter(v)(sc, v, i2 + (i1 * vector_offset(v, 0)))); +} + +static s7_pointer vector_ref_p_pii_direct(s7_scheme *sc, s7_pointer v, s7_int i1, s7_int i2) +{ + if ((i1 < 0) || (i2 < 0) || + (i1 >= vector_dimension(v, 0)) || (i2 >= vector_dimension(v, 1))) + return(g_vector_ref(sc, set_plist_3(sc, v, make_integer(sc, i1), make_integer_unchecked(sc, i2)))); + return(vector_element(v, i2 + (i1 * vector_offset(v, 0)))); +} + +static s7_pointer normal_vector_ref_p_pi_direct(s7_scheme *unused_sc, s7_pointer v, s7_int i) {return(vector_element(v, i));} + +static inline s7_pointer vector_ref_p_pp(s7_scheme *sc, s7_pointer vec, s7_pointer ind) +{ + s7_int index; + if ((!is_normal_vector(vec)) || + (vector_rank(vec) != 1) || + (!s7_is_integer(ind))) + return(g_vector_ref(sc, set_plist_2(sc, vec, ind))); + index = s7_integer_clamped_if_gmp(sc, ind); + if ((index < 0) || (index >= vector_length(vec))) + out_of_range_error_nr(sc, sc->vector_ref_symbol, int_two, ind, (index < 0) ? it_is_negative_string : it_is_too_large_string); + return(vector_element(vec, index)); +} + +static s7_pointer g_vector_ref_2(s7_scheme *sc, s7_pointer args) {return(vector_ref_p_pp(sc, car(args), cadr(args)));} + +static s7_pointer g_vector_ref_3(s7_scheme *sc, s7_pointer args) +{ + s7_pointer vec = car(args), i1, i2; + s7_int ix, iy; + + if (!is_any_vector(vec)) + return(g_vector_ref(sc, args)); + if (vector_rank(vec) != 2) + return(g_vector_ref(sc, args)); + + i1 = cadr(args); + if (!s7_is_integer(i1)) + return(g_vector_ref(sc, args)); + i2 = caddr(args); + if (!s7_is_integer(i2)) + return(g_vector_ref(sc, args)); + ix = s7_integer_clamped_if_gmp(sc, i1); + iy = s7_integer_clamped_if_gmp(sc, i2); + if ((ix >= 0) && (iy >= 0) && + (ix < vector_dimension(vec, 0)) && (iy < vector_dimension(vec, 1))) + { + s7_int index = (ix * vector_offset(vec, 0)) + iy; /* vector_offset(vec, 1) == 1 */ + return(vector_getter(vec)(sc, vec, index)); + } + return(g_vector_ref(sc, args)); +} + +static s7_pointer vector_ref_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer unused_expr, bool unused_ops) +{ + if (args == 2) + return(sc->vector_ref_2); + return((args == 3) ? sc->vector_ref_3 : f); +} + + +/* -------------------------------- vector-set! -------------------------------- */ +static s7_pointer g_vector_set(s7_scheme *sc, s7_pointer args) +{ + #define H_vector_set "(vector-set! v i ... value) sets the i-th element of vector v to value." + #define Q_vector_set s7_make_circular_signature(sc, 3, 4, sc->T, sc->is_vector_symbol, sc->is_integer_symbol, sc->is_integer_or_any_at_end_symbol) + + s7_pointer vec = car(args), val; + s7_int index; + + if (!is_any_vector(vec)) + return(method_or_bust(sc, vec, sc->vector_set_symbol, args, sc->type_names[T_VECTOR], 1)); + if (is_immutable_vector(vec)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->vector_set_symbol, vec)); + if (vector_length(vec) == 0) + out_of_range_error_nr(sc, sc->vector_set_symbol, int_one, vec, it_is_too_large_string); + + if (vector_rank(vec) > 1) + { + s7_int i; + s7_pointer x; + index = 0; + for (x = cdr(args), i = 0; (is_not_null(cdr(x))) && (i < vector_ndims(vec)); x = cdr(x), i++) + { + s7_int n; + s7_pointer p = car(x); + if (!s7_is_integer(p)) + return(method_or_bust(sc, p, sc->vector_set_symbol, args, sc->type_names[T_INTEGER], i + 2)); + n = s7_integer_clamped_if_gmp(sc, p); + if ((n < 0) || (n >= vector_dimension(vec, i))) + out_of_range_error_nr(sc, sc->vector_set_symbol, wrap_integer(sc, i + 2), p, (n < 0) ? it_is_negative_string : it_is_too_large_string); + + index += n * vector_offset(vec, i); + } + if (is_not_null(cdr(x))) + wrong_number_of_arguments_error_nr(sc, "too many arguments for vector-set!: ~S", 38, args); + if (i != vector_ndims(vec)) + wrong_number_of_arguments_error_nr(sc, "not enough arguments for vector-set!: ~S", 40, args); + + /* since vector-ref can return a subvector (if not passed enough args), it might be interesting to + * also set a complete subvector via set!, but would that introduce ambiguity? Only copy the vector + * if at least one index is missing, and the value fits. It also makes error detection harder, + * but so does the current vector-ref handling. Can't decide... + * (define v (make-vector '(2 3) 0)) (vector-set! v 0 #(1 2 3)) -> error, but (vector-ref v 0) -> #(0 0 0) + * Other possible additions: complex-vector and string-vector. + */ + val = car(x); + } + else + { + s7_pointer p = cadr(args); + if (!s7_is_integer(p)) + return(method_or_bust(sc, p, sc->vector_set_symbol, args, sc->type_names[T_INTEGER], 2)); + index = s7_integer_clamped_if_gmp(sc, p); + if ((index < 0) || (index >= vector_length(vec))) + out_of_range_error_nr(sc, sc->vector_set_symbol, int_two, p, (index < 0) ? it_is_negative_string : it_is_too_large_string); + + if (is_not_null(cdddr(args))) + { + s7_pointer v = vector_getter(vec)(sc, vec, index); + if (!is_any_vector(v)) + wrong_number_of_arguments_error_nr(sc, "too many arguments for vector-set!: ~S", 38, args); + return(g_vector_set(sc, set_ulist_1(sc, v, cddr(args)))); + } + val = caddr(args); + } + if (is_typed_vector(vec)) + return(typed_vector_setter(sc, vec, index, val)); + if (is_normal_vector(vec)) + vector_element(vec, index) = val; + else vector_setter(vec)(sc, vec, index, val); + return(val); +} + +static s7_pointer vector_set_p_pip(s7_scheme *sc, s7_pointer v, s7_int i, s7_pointer p) /* this may be uncallable now -- opt'd away in every case? */ +{ + if ((!is_any_vector(v)) || + (vector_rank(v) > 1) || + (i < 0) || (i >= vector_length(v))) + return(g_vector_set(sc, set_plist_3(sc, v, make_integer(sc, i), p))); + + if (is_typed_vector(v)) + return(typed_vector_setter(sc, v, i, p)); + if (is_normal_vector(v)) + vector_element(v, i) = p; + else vector_setter(v)(sc, v, i, p); + return(p); +} + +static s7_pointer vector_set_p_pip_unchecked(s7_scheme *sc, s7_pointer v, s7_int i, s7_pointer p) +{ + if ((i >= 0) && (i < vector_length(v))) + vector_element(v, i) = p; + else out_of_range_error_nr(sc, sc->vector_set_symbol, int_two, wrap_integer(sc, i), (i < 0) ? it_is_negative_string : it_is_too_large_string); + return(p); +} + +static s7_pointer vector_set_p_piip(s7_scheme *sc, s7_pointer v, s7_int i1, s7_int i2, s7_pointer p) +{ + if ((!is_any_vector(v)) || + (vector_rank(v) != 2) || + (i1 < 0) || (i2 < 0) || + (i1 >= vector_dimension(v, 0)) || (i2 >= vector_dimension(v, 1))) + return(g_vector_set(sc, set_elist_4(sc, v, make_integer(sc, i1), make_integer_unchecked(sc, i2), p))); /* someday these should use plist_4 */ + + if (is_typed_vector(v)) + return(typed_vector_setter(sc, v, i2 + (i1 * vector_offset(v, 0)), p)); + + if (is_normal_vector(v)) + vector_element(v, i2 + (i1 * vector_offset(v, 0))) = p; + else vector_setter(v)(sc, v, i2 + (i1 * vector_offset(v, 0)), p); + return(p); +} + +static s7_pointer vector_set_p_piip_direct(s7_scheme *sc, s7_pointer v, s7_int i1, s7_int i2, s7_pointer p) +{ + /* normal untyped vector, rank == 2, uncallable? */ + if ((i1 < 0) || (i2 < 0) || + (i1 >= vector_dimension(v, 0)) || (i2 >= vector_dimension(v, 1))) + return(g_vector_set(sc, set_elist_4(sc, v, make_integer(sc, i1), make_integer_unchecked(sc, i2), p))); + vector_element(v, i2 + (i1 * vector_offset(v, 0))) = p; + return(p); +} + +static s7_pointer typed_vector_set_p_pip_unchecked(s7_scheme *sc, s7_pointer v, s7_int i, s7_pointer p) +{ + if ((i >= 0) && (i < vector_length(v))) + typed_vector_setter(sc, v, i, p); + else out_of_range_error_nr(sc, sc->vector_set_symbol, int_two, wrap_integer(sc, i), (i < 0) ? it_is_negative_string : it_is_too_large_string); + return(p); +} + +static s7_pointer typed_vector_set_p_piip_direct(s7_scheme *sc, s7_pointer v, s7_int i1, s7_int i2, s7_pointer p) +{ + if ((i1 < 0) || (i2 < 0) || + (i1 >= vector_dimension(v, 0)) || (i2 >= vector_dimension(v, 1))) + return(g_vector_set(sc, set_elist_4(sc, v, make_integer(sc, i1), make_integer_unchecked(sc, i2), p))); + return(typed_vector_setter(sc, v, i2 + (i1 * vector_offset(v, 0)), p)); +} + +static s7_pointer normal_vector_set_p_pip_direct(s7_scheme *unused_sc, s7_pointer v, s7_int i, s7_pointer p) {vector_element(v, i) = p; return(p);} + +static s7_pointer typed_normal_vector_set_p_pip_direct(s7_scheme *sc, s7_pointer v, s7_int i, s7_pointer p) +{ + typed_vector_setter(sc, v, i, p); + return(p); +} + +static s7_pointer g_vector_set_3(s7_scheme *sc, s7_pointer args) +{ + /* (vector-set! vector index value) */ + s7_pointer ind, vec = car(args), val; + s7_int index; + + if (!is_any_vector(vec)) + return(g_vector_set(sc, args)); + if (is_immutable_vector(vec)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->vector_set_symbol, vec)); + if (vector_rank(vec) > 1) + return(g_vector_set(sc, args)); + + ind = cadr(args); + if (!s7_is_integer(ind)) + return(g_vector_set(sc, args)); + index = s7_integer_clamped_if_gmp(sc, ind); + if ((index < 0) || (index >= vector_length(vec))) + out_of_range_error_nr(sc, sc->vector_set_symbol, int_two, wrap_integer(sc, index), (index < 0) ? it_is_negative_string : it_is_too_large_string); + + val = caddr(args); + if (is_typed_vector(vec)) + return(typed_vector_setter(sc, vec, index, val)); + if (is_normal_vector(vec)) + vector_element(vec, index) = val; + else vector_setter(vec)(sc, vec, index, val); + return(val); +} + +static s7_pointer vector_set_p_ppp(s7_scheme *sc, s7_pointer vec, s7_pointer ind, s7_pointer val) +{ + s7_int index; + + if ((!is_normal_vector(vec)) || (vector_rank(vec) > 1)) + return(g_vector_set(sc, set_plist_3(sc, vec, ind, val))); + if (is_immutable_vector(vec)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->vector_set_symbol, vec)); + if (!s7_is_integer(ind)) + return(g_vector_set(sc, set_plist_3(sc, vec, ind, val))); + index = s7_integer_clamped_if_gmp(sc, ind); + if ((index < 0) || (index >= vector_length(vec))) + out_of_range_error_nr(sc, sc->vector_set_symbol, int_two, wrap_integer(sc, index), (index < 0) ? it_is_negative_string : it_is_too_large_string); + + if (is_typed_vector(vec)) + return(typed_vector_setter(sc, vec, index, val)); + vector_element(vec, index) = val; + return(val); +} + +static s7_pointer g_vector_set_4(s7_scheme *sc, s7_pointer args) +{ + s7_pointer v = car(args), ip1 = cadr(args), ip2 = caddr(args), val; + s7_int i1, i2; + if ((!is_any_vector(v)) || + (vector_rank(v) != 2) || + (is_immutable_vector(v)) || + (!s7_is_integer(ip1)) || + (!s7_is_integer(ip2))) + return(g_vector_set(sc, args)); + i1 = s7_integer_clamped_if_gmp(sc, ip1); + i2 = s7_integer_clamped_if_gmp(sc, ip2); + if ((i1 < 0) || (i2 < 0) || + (i1 >= vector_dimension(v, 0)) || (i2 >= vector_dimension(v, 1))) + return(g_vector_set(sc, args)); + val = cadddr(args); + if (is_typed_vector(v)) + return(typed_vector_setter(sc, v, i2 + (i1 * vector_offset(v, 0)), val)); + if (is_normal_vector(v)) + vector_element(v, i2 + (i1 * vector_offset(v, 0))) = val; + else vector_setter(v)(sc, v, i2 + (i1 * vector_offset(v, 0)), val); + return(val); +} + +static s7_pointer vector_set_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer unused_expr, bool unused_ops) +{ + if (args == 3) return(sc->vector_set_3); + return((args == 4) ? sc->vector_set_4 : f); +} + + +/* -------------------------------- make-vector -------------------------------- */ +static s7_int multivector_length(s7_scheme *sc, s7_pointer x, s7_pointer caller) +{ + s7_pointer y; + s7_int len, dims = s7_list_length(sc, x); + if (dims <= 0) /* 0 if circular, negative if dotted */ + wrong_type_error_nr(sc, caller, 1, x, a_proper_list_string); + if (dims > sc->max_vector_dimensions) + out_of_range_error_nr(sc, caller, int_one, x, it_is_too_large_string); + + for (y = x, len = 1; is_pair(y); y = cdr(y)) + { + if (!s7_is_integer(car(y))) + wrong_type_error_nr(sc, caller, position_of(y, x), car(y), sc->type_names[T_INTEGER]); +#if HAVE_OVERFLOW_CHECKS + if (multiply_overflow(len, s7_integer_clamped_if_gmp(sc, car(y)), &len)) /* or better perhaps len > sc->max_vector_length */ + out_of_range_error_nr(sc, caller, wrap_integer(sc, position_of(y, x)), car(y), it_is_too_large_string); +#else + len *= s7_integer_clamped_if_gmp(sc, car(y)); +#endif + if (len < 0) + wrong_type_error_nr(sc, caller, position_of(y, x), car(y), a_non_negative_integer_string); + } + return(len); +} + +static void check_vector_typer_c_function(s7_scheme *sc, s7_pointer caller, s7_pointer typf) +{ + s7_pointer sig = c_function_signature(typf); + if ((sig != sc->pl_bt) && + (is_pair(sig)) && + ((car(sig) != sc->is_boolean_symbol) || (cadr(sig) != sc->T) || (!is_null(cddr(sig))))) + wrong_type_error_nr(sc, caller, 2, typf, wrap_string(sc, "a boolean procedure", 19)); + if (!c_function_name(typf)) + wrong_type_error_nr(sc, caller, 2, typf, wrap_string(sc, "a named function", 16)); + if (!c_function_marker(typf)) + c_function_set_marker(typf, mark_vector_1); + if (!c_function_symbol(typf)) + c_function_symbol(typf) = make_symbol(sc, c_function_name(typf), c_function_name_length(typf)); +} + +static inline s7_pointer make_multivector(s7_scheme *sc, s7_pointer vec, s7_pointer x) +{ + vdims_t *v = list_to_dims(sc, x); + vdims_original(v) = sc->F; + vector_set_dimension_info(vec, v); + add_multivector(sc, vec); + return(vec); +} + +static s7_pointer g_make_vector_1(s7_scheme *sc, s7_pointer args, s7_pointer caller) +{ + s7_int len; + s7_pointer x = car(args), fill = sc->unspecified, vec, typf = sc->T; + int32_t result_type = T_VECTOR; + + if (s7_is_integer(x)) + { + len = s7_integer_clamped_if_gmp(sc, x); + if (len < 0) + wrong_type_error_nr(sc, caller, 1, x, a_non_negative_integer_string); + } + else + { + if (!(is_pair(x))) + return(method_or_bust(sc, x, caller, args, wrap_string(sc, "an integer or a list of integers", 32), 1)); + if (!s7_is_integer(car(x))) + wrong_type_error_nr(sc, caller, 1, car(x), sc->type_names[T_INTEGER]); + len = (is_null(cdr(x))) ? s7_integer_clamped_if_gmp(sc, car(x)) : multivector_length(sc, x, caller); + } + + if (is_pair(cdr(args))) + { + fill = cadr(args); + if (caller == sc->make_int_vector_symbol) + result_type = T_INT_VECTOR; + else + if (caller == sc->make_float_vector_symbol) + result_type = T_FLOAT_VECTOR; + else + if (caller == sc->make_byte_vector_symbol) + result_type = T_BYTE_VECTOR; + if (is_pair(cddr(args))) + { + typf = caddr(args); + if ((!is_c_function(typf)) && + (!is_any_closure(typf)) && + (typf != sc->T)) /* default value */ + wrong_type_error_nr(sc, caller, 3, typf, wrap_string(sc, "a built-in procedure, a closure or #t", 37)); + if (is_any_closure(typf)) + { + if (!is_symbol(find_closure(sc, typf, closure_let(typf)))) + wrong_type_error_nr(sc, caller, 3, typf, wrap_string(sc, "a named function", 16)); + /* the name is needed primarily by the error handler: "vector-set! third argument, ..., is a ... but should be a <...>" */ + } + else + if (is_c_function(typf)) + { + if (typf == global_value(sc->is_float_symbol)) + { + if (!is_real(fill)) wrong_type_error_nr(sc, caller, 3, fill, sc->type_names[T_REAL]); + result_type = T_FLOAT_VECTOR; + } + else + if (typf == global_value(sc->is_integer_symbol)) + { + if (!s7_is_integer(fill)) wrong_type_error_nr(sc, caller, 3, fill, sc->type_names[T_INTEGER]); + result_type = (WITH_GMP) ? T_VECTOR : T_INT_VECTOR; + } + else + if (typf == global_value(sc->is_byte_symbol)) + { + if (!is_byte(fill)) wrong_type_error_nr(sc, caller, 2, fill, an_unsigned_byte_string); + result_type = T_BYTE_VECTOR; + } + else check_vector_typer_c_function(sc, caller, typf); + }}} + /* before making the new vector, if fill is specified and the vector is typed, we have to check for a type error. + * otherwise we can end up with a vector whose elements are NULL, causing a segfault in the gc. + */ + if ((result_type == T_VECTOR) && + (typf != sc->T) && /* default value */ + (s7_apply_function(sc, typf, set_plist_1(sc, fill)) == sc->F)) + { + const char *tstr = make_type_name(sc, (is_c_function(typf)) ? c_function_name(typf) : symbol_name(find_closure(sc, typf, closure_let(typf))), INDEFINITE_ARTICLE); + wrong_type_error_nr(sc, sc->make_vector_symbol, 3, fill, wrap_string(sc, tstr, safe_strlen(tstr))); + } + + vec = make_vector_1(sc, len, NOT_FILLED, result_type); + if ((result_type == T_VECTOR) && + (typf != sc->T)) /* default value */ + { + set_typed_vector(vec); + typed_vector_set_typer(vec, typf); + + if ((is_c_function(typf)) && + (c_function_has_simple_elements(typf))) + set_has_simple_elements(vec); + } + s7_vector_fill(sc, vec, fill); + if ((is_pair(x)) && + (is_pair(cdr(x)))) + return(make_multivector(sc, vec, x)); + add_vector(sc, vec); + return(vec); +} + +static s7_pointer g_make_vector(s7_scheme *sc, s7_pointer args) +{ + #define H_make_vector "(make-vector len (value #) type) returns a vector of len elements initialized to value. \ +To create a multidimensional vector, put the dimension bounds in a list (this is to avoid ambiguities such as \ +(make-vector 1 2) where it's not clear whether the '2' is an initial value or a dimension size). (make-vector '(2 3) 1.0) \ +returns a 2 dimensional vector of 6 total elements, all initialized to 1.0." + #define Q_make_vector s7_make_signature(sc, 4, sc->is_vector_symbol, \ + s7_make_signature(sc, 2, sc->is_integer_symbol, sc->is_pair_symbol), sc->T, \ + s7_make_signature(sc, 2, sc->is_procedure_symbol, sc->is_boolean_symbol)) + return(g_make_vector_1(sc, args, sc->make_vector_symbol)); +} + + +/* -------------------------------- make-float-vector -------------------------------- */ +static s7_pointer g_make_float_vector(s7_scheme *sc, s7_pointer args) +{ + #define H_make_float_vector "(make-float-vector len (init 0.0)) returns a float-vector." + #define Q_make_float_vector s7_make_signature(sc, 3, \ + sc->is_float_vector_symbol, s7_make_signature(sc, 2, sc->is_integer_symbol, sc->is_pair_symbol), sc->is_real_symbol) + s7_int len; + s7_pointer x, p = car(args); + block_t *arr; + + if ((is_pair(cdr(args))) || (!s7_is_integer(p))) + { + s7_pointer init; + if (is_pair(cdr(args))) + { + init = cadr(args); + if (!is_real(init)) + return(method_or_bust(sc, init, sc->make_float_vector_symbol, args, sc->type_names[T_REAL], 2)); +#if WITH_GMP + if (s7_is_bignum(init)) + return(g_make_vector_1(sc, set_plist_2(sc, p, wrap_real(sc, s7_real(init))), sc->make_float_vector_symbol)); +#endif + if (is_rational(init)) + return(g_make_vector_1(sc, set_plist_2(sc, p, wrap_real(sc, rational_to_double(sc, init))), sc->make_float_vector_symbol)); + } + else init = real_zero; + if (s7_is_integer(p)) + len = s7_integer_clamped_if_gmp(sc, p); + else + { + if (!is_pair(p)) + return(method_or_bust(sc, p, sc->make_float_vector_symbol, args, wrap_string(sc, "an integer or a list of integers", 32), 1)); + len = multivector_length(sc, p, sc->make_float_vector_symbol); + } + x = make_vector_1(sc, len, NOT_FILLED, T_FLOAT_VECTOR); + float_vector_fill(x, s7_real(init)); + if (!s7_is_integer(p)) + return(make_multivector(sc, x, p)); + add_vector(sc, x); + return(x); + } + + len = s7_integer_clamped_if_gmp(sc, p); + if ((len < 0) || (len > sc->max_vector_length)) + out_of_range_error_nr(sc, sc->make_float_vector_symbol, int_one, p, (len < 0) ? it_is_negative_string : it_is_too_large_string); + + arr = mallocate_vector(sc, len * sizeof(s7_double)); + new_cell(sc, x, T_FLOAT_VECTOR | T_SAFE_PROCEDURE); + vector_length(x) = len; + vector_block(x) = arr; + float_vector_floats(x) = (s7_double *)block_data(arr); + if (len > 0) + { + if (STEP_8(len)) + memclr64((void *)float_vector_floats(x), len * sizeof(s7_double)); + else memclr((void *)float_vector_floats(x), len * sizeof(s7_double)); + } + vector_set_dimension_info(x, NULL); + vector_getter(x) = float_vector_getter; + vector_setter(x) = float_vector_setter; + + add_vector(sc, x); + return(x); +} + +static s7_pointer make_float_vector_p_pp(s7_scheme *sc, s7_pointer len, s7_pointer fill) +{ + if ((is_t_integer(len)) && (is_t_real(fill)) && + (integer(len)>= 0) && (integer(len) < sc->max_vector_length)) + { + s7_pointer fv = make_simple_float_vector(sc, integer(len)); + float_vector_fill(fv, real(fill)); + return(fv); + } + return(g_make_float_vector(sc, set_plist_2(sc, len, fill))); +} + + +/* -------------------------------- make-int-vector -------------------------------- */ +static s7_pointer g_make_int_vector(s7_scheme *sc, s7_pointer args) +{ + #define H_make_int_vector "(make-int-vector len (init 0)) returns an int-vector." + #define Q_make_int_vector s7_make_signature(sc, 3, sc->is_int_vector_symbol, \ + s7_make_signature(sc, 2, sc->is_integer_symbol, sc->is_pair_symbol), sc->is_integer_symbol) + s7_int len; + s7_pointer x, p = car(args); + block_t *arr; + + if ((is_pair(cdr(args))) || + (!s7_is_integer(p))) + { + s7_pointer init; + if (is_pair(cdr(args))) + { + init = cadr(args); + if (!s7_is_integer(init)) + return(method_or_bust(sc, init, sc->make_int_vector_symbol, args, sc->type_names[T_INTEGER], 2)); + } + else init = int_zero; + if (s7_is_integer(p)) + len = s7_integer_clamped_if_gmp(sc, p); + else + { + if (!is_pair(p)) + return(method_or_bust(sc, p, sc->make_int_vector_symbol, args, wrap_string(sc, "an integer or a list of integers", 32), 1)); + len = multivector_length(sc, p, sc->make_int_vector_symbol); + } + x = make_vector_1(sc, len, NOT_FILLED, T_INT_VECTOR); + int_vector_fill(x, s7_integer_clamped_if_gmp(sc, init)); + if (!s7_is_integer(p)) + return(make_multivector(sc, x, p)); + add_vector(sc, x); + return(x); + } + + len = s7_integer_clamped_if_gmp(sc, p); + if ((len < 0) || (len > sc->max_vector_length)) + out_of_range_error_nr(sc, sc->make_int_vector_symbol, int_one, p, (len < 0) ? it_is_negative_string : it_is_too_large_string); + + arr = mallocate_vector(sc, len * sizeof(s7_int)); + new_cell(sc, x, T_INT_VECTOR | T_SAFE_PROCEDURE); + vector_length(x) = len; + vector_block(x) = arr; + int_vector_ints(x) = (s7_int *)block_data(arr); + if (len > 0) + { + if (STEP_8(len)) + memclr64((void *)int_vector_ints(x), len * sizeof(s7_int)); + else memclr((void *)int_vector_ints(x), len * sizeof(s7_int)); + } + vector_set_dimension_info(x, NULL); + vector_getter(x) = int_vector_getter; + vector_setter(x) = int_vector_setter; + + add_vector(sc, x); + return(x); +} + +static s7_pointer make_int_vector_p_ii(s7_scheme *sc, s7_int len, s7_int init) +{ + s7_pointer x = make_vector_1(sc, len, NOT_FILLED, T_INT_VECTOR); + int_vector_fill(x, init); + add_vector(sc, x); + return(x); +} + + +/* -------------------------------- make-byte-vector -------------------------------- */ +static s7_pointer g_make_byte_vector(s7_scheme *sc, s7_pointer args) +{ + #define H_make_byte_vector "(make-byte-vector len (byte 0)) makes a byte-vector of length len filled with byte." + #define Q_make_byte_vector s7_make_signature(sc, 3, sc->is_byte_vector_symbol, \ + s7_make_signature(sc, 2, sc->is_integer_symbol, sc->is_pair_symbol), sc->is_byte_symbol) + s7_int len = 0, ib = 0; + s7_pointer p = car(args), init; + + if (!is_pair(p)) + { + if (!s7_is_integer(p)) + return(method_or_bust(sc, p, sc->make_byte_vector_symbol, args, sc->type_names[T_INTEGER], 1)); + len = s7_integer_clamped_if_gmp(sc, p); + if ((len < 0) || (len > sc->max_vector_length)) + out_of_range_error_nr(sc, sc->make_byte_vector_symbol, int_one, p, (len < 0) ? it_is_negative_string : it_is_too_large_string); + } + if (is_pair(cdr(args))) + { + init = cadr(args); + if (!s7_is_integer(init)) + return(method_or_bust(sc, init, sc->make_byte_vector_symbol, args, sc->type_names[T_INTEGER], 2)); + ib = s7_integer_clamped_if_gmp(sc, init); + if ((ib < 0) || (ib > 255)) + wrong_type_error_nr(sc, sc->make_byte_vector_symbol, 2, init, an_unsigned_byte_string); + } + else init = int_zero; + + if (!s7_is_integer(p)) + return(g_make_vector_1(sc, set_plist_2(sc, p, init), sc->make_byte_vector_symbol)); + + p = make_simple_byte_vector(sc, len); + if (len > 0) /* make-byte-vector 2) should return #u(0 0) so we always need to fill */ + local_memset((void *)(byte_vector_bytes(p)), ib, len); + return(p); +} + +static s7_pointer make_byte_vector_p_ii(s7_scheme *sc, s7_int len, s7_int init) +{ + s7_pointer p; + if ((len < 0) || (len > sc->max_vector_length)) + out_of_range_error_nr(sc, sc->make_byte_vector_symbol, int_one, wrap_integer(sc, len), (len < 0) ? it_is_negative_string : it_is_too_large_string); + if ((init < 0) || (init > 255)) + wrong_type_error_nr(sc, sc->make_byte_vector_symbol, 2, wrap_integer(sc, init), an_unsigned_byte_string); + p = make_simple_byte_vector(sc, len); + if (len > 0) + local_memset((void *)(byte_vector_bytes(p)), init, len); + return(p); +} + + +/* -------------------------------- vector? -------------------------------- */ +static s7_pointer g_is_vector(s7_scheme *sc, s7_pointer args) +{ + #define H_is_vector "(vector? obj) returns #t if obj is a vector" + #define Q_is_vector sc->pl_bt + check_boolean_method(sc, is_any_vector, sc->is_vector_symbol, args); +} + + +/* -------------------------------- vector-rank -------------------------------- */ +s7_int s7_vector_rank(s7_pointer vect) {return((s7_int)(vector_rank(vect)));} + +static s7_pointer g_vector_rank(s7_scheme *sc, s7_pointer args) +{ + #define H_vector_rank "(vector-rank vect) returns the number of dimensions in vect" + #define Q_vector_rank s7_make_signature(sc, 2, sc->is_integer_symbol, sc->is_vector_symbol) + s7_pointer x = car(args); + if (!is_any_vector(x)) + return(sole_arg_method_or_bust(sc, x, sc->vector_rank_symbol, args, sc->type_names[T_VECTOR])); + return(make_integer(sc, vector_rank(x))); +} + + +/* -------------------------------- vector-dimension -------------------------------- */ +static s7_pointer g_vector_dimension(s7_scheme *sc, s7_pointer args) +{ + #define H_vector_dimension "(vector-dimension vect n) returns the size of the n-th dimension (n is 0-based)" + #define Q_vector_dimension s7_make_signature(sc, 3, sc->is_integer_symbol, sc->is_vector_symbol, sc->is_integer_symbol) + s7_pointer v = car(args); + s7_pointer np = cadr(args); + s7_int n; + if (!is_any_vector(v)) + return(method_or_bust(sc, v, sc->vector_dimension_symbol, args, sc->type_names[T_VECTOR], 1)); + if (!s7_is_integer(np)) + return(method_or_bust(sc, v, sc->vector_dimension_symbol, args, sc->type_names[T_INTEGER], 2)); + n = s7_integer_clamped_if_gmp(sc, np); + if (n < 0) + error_nr(sc, sc->out_of_range_symbol, + set_elist_2(sc, wrap_string(sc, "vector-dimension second argument is negative: ~S", 48), np)); + if (n >= vector_rank(v)) + error_nr(sc, sc->out_of_range_symbol, + set_elist_3(sc, wrap_string(sc, "vector-dimension second argument, ~S, should be less than the vector rank, ~S", 77), + np, wrap_integer(sc, vector_rank(v)))); + if (vector_has_dimension_info(v)) + return(make_integer(sc, vector_dimension(v, n))); + return(make_integer(sc, vector_length(v))); +} + + +/* -------------------------------- vector-dimensions -------------------------------- */ +static s7_pointer g_vector_dimensions(s7_scheme *sc, s7_pointer args) +{ + #define H_vector_dimensions "(vector-dimensions vect) returns a list of vect's dimensions" + #define Q_vector_dimensions s7_make_signature(sc, 2, sc->is_pair_symbol, sc->is_vector_symbol) + + s7_pointer x = car(args); + if (!is_any_vector(x)) + return(sole_arg_method_or_bust(sc, x, sc->vector_dimensions_symbol, args, sc->type_names[T_VECTOR])); + if (vector_rank(x) == 1) + return(list_1(sc, make_integer(sc, vector_length(x)))); + + sc->w = sc->nil; + for (s7_int i = vector_ndims(x) - 1; i >= 0; i--) + sc->w = cons(sc, make_integer(sc, vector_dimension(x, i)), sc->w); + x = sc->w; + sc->w = sc->unused; + return(x); +} + + +/* -------------------------------- vector-typer -------------------------------- */ +static s7_pointer g_vector_typer(s7_scheme *sc, s7_pointer args) +{ + #define H_vector_typer "(vector-typer vect) returns the vector's element type checking function" + #define Q_vector_typer s7_make_signature(sc, 2, s7_make_signature(sc, 2, sc->not_symbol, sc->is_procedure_symbol), sc->is_vector_symbol) + + s7_pointer v = car(args); + if (!is_any_vector(v)) + return(sole_arg_method_or_bust(sc, v, sc->vector_typer_symbol, args, sc->type_names[T_VECTOR])); + + if (is_typed_vector(v)) return(typed_vector_typer(v)); + if (is_float_vector(v)) return(global_value(sc->is_float_symbol)); + if (is_int_vector(v)) return(global_value(sc->is_integer_symbol)); + if (is_byte_vector(v)) return(global_value(sc->is_byte_symbol)); + return(sc->F); +} + +static s7_pointer g_set_vector_typer(s7_scheme *sc, s7_pointer args) +{ + s7_pointer v = car(args), typer = cadr(args); + if (!is_any_vector(v)) + wrong_type_error_nr(sc, wrap_string(sc, "set! vector-typer", 17), 1, v, sc->type_names[T_VECTOR]); + if (!is_normal_vector(v)) + { + if (((is_int_vector(v)) && (typer != global_value(sc->is_integer_symbol))) || + ((is_float_vector(v)) && (typer != global_value(sc->is_float_symbol))) || + ((is_byte_vector(v)) && (typer != global_value(sc->is_byte_symbol)))) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_3(sc, wrap_string(sc, "vector-typer can't set ~S typer to ~S", 37), v, typer)); + return(typer); + } + if (is_boolean(typer)) + { + if (is_typed_vector(v)) + { + typed_vector_set_typer(v, sc->F); + clear_typed_vector(v); + }} + else + { + if (is_c_function(typer)) + check_vector_typer_c_function(sc, sc->vector_typer_symbol, typer); + else + { + if (!is_any_closure(typer)) + wrong_type_error_nr(sc, sc->vector_typer_symbol, 2, typer, wrap_string(sc, "a built-in procedure, a closure, #f or #t", 41)); + if (!is_symbol(find_closure(sc, typer, closure_let(typer)))) + wrong_type_error_nr(sc, sc->vector_typer_symbol, 2, typer, wrap_string(sc, "a named function", 16)); + /* the name is needed primarily by the error handler: "vector-set! second argument, ..., is a ... but should be a <...>" */ + } + set_typed_vector(v); + typed_vector_set_typer(v, typer); + if ((is_c_function(typer)) && + (c_function_has_simple_elements(typer))) + set_has_simple_elements(v); + } + return(typer); +} + + +/* -------------------------------- multivector -------------------------------- */ +#define MULTIVECTOR_TOO_MANY_ELEMENTS -1 +#define MULTIVECTOR_NOT_ENOUGH_ELEMENTS -2 + +static int32_t traverse_vector_data(s7_scheme *sc, s7_pointer vec, s7_int flat_ref, s7_int dimension, s7_int dimensions, s7_int *sizes, s7_pointer lst) +{ + /* we're filling vec, we're currently looking for element flat_ref, + * we're at ref in dimension of dimensions, where sizes gives the bounds, and lst is our data + * #3D(((1 2 3) (4 5 6)) ((7 8 9) (10 11 12))) + */ + s7_pointer x = lst; + for (s7_int i = 0; i < sizes[dimension]; i++, x = cdr(x)) + { + if (!is_pair(x)) + return(MULTIVECTOR_NOT_ENOUGH_ELEMENTS); + if (dimension == (dimensions - 1)) + vector_setter(vec)(sc, vec, flat_ref++, car(x)); + else + { + flat_ref = traverse_vector_data(sc, vec, flat_ref, dimension + 1, dimensions, sizes, car(x)); + if (flat_ref < 0) return(flat_ref); + }} + return((is_null(x)) ? flat_ref : MULTIVECTOR_TOO_MANY_ELEMENTS); +} + +static s7_pointer reverse_in_place_unchecked(s7_scheme *sc, s7_pointer term, s7_pointer list) +{ + s7_pointer p = list, result = term; + while (true) + { + s7_pointer q; + LOOP_4(if (is_null(p)) return(result); q = cdr(p); set_cdr(p, result); result = p; p = q); /* return, not break because LOOP_4 is itself a do loop */ + } + return(result); +} + +static s7_pointer proper_list_reverse_in_place(s7_scheme *sc, s7_pointer list) +{ + return(reverse_in_place_unchecked(sc, sc->nil, list)); +} + +static noreturn void multivector_error_nr(s7_scheme *sc, const char *message, s7_pointer data) +{ + error_nr(sc, sc->read_error_symbol, + set_elist_3(sc, wrap_string(sc, "reading constant vector, ~A: ~A", 31), + s7_make_string_wrapper(sc, message), data)); +} + +static s7_pointer g_multivector(s7_scheme *sc, s7_int dims, s7_pointer data) +{ + /* get the dimension bounds from data, make the new vector, fill it from data + * dims needs to be s7_int so we can at least give correct error messages. + */ + s7_pointer vec, x = data; + s7_int err, vec_loc; + s7_int *sizes; + + /* (#2d((1 2 3) (4 5 6)) 0 0) -> 1 + * (#2d((1 2 3) (4 5 6)) 1 1) -> 5 + * (#3d(((1 2) (3 4)) ((5 6) (7 8))) 1 1 0) -> 7 + * #3d(((1 2) (3 4)) ((5 6) (7))) -> error, #3d(((1 2) (3 4)) ((5 6) (7 8 9))), #3d(((1 2) (3 4)) (5 (7 8 9))) etc + * but a special case: #nd() is an n-dimensional empty vector + */ + + if (dims <= 0) /* #0d(...) #2147483649D() [if dims is int32_t this is negative] */ + error_nr(sc, sc->out_of_range_symbol, + set_elist_2(sc, wrap_string(sc, "#nD(...) dimensions, ~A, should be 1 or more", 44), wrap_integer(sc, dims))); + + if (dims > sc->max_vector_dimensions) /* probably can't happen -- caught in read_sharp? */ + error_nr(sc, sc->out_of_range_symbol, + set_elist_3(sc, wrap_string(sc, "#nD(...) dimensions, ~A, should be less that (*s7* 'max-vector-dimensions): ~A", 78), + wrap_integer(sc, dims), wrap_integer(sc, sc->max_vector_dimensions))); + sc->w = sc->nil; + if (is_null(data)) /* dims are already 0 (calloc above) */ + return(g_make_vector(sc, set_plist_1(sc, make_list(sc, dims, int_zero)))); + + sizes = (s7_int *)Calloc(dims, sizeof(s7_int)); + for (s7_int i = 0; i < dims; i++) + { + sizes[i] = proper_list_length(x); + sc->w = cons(sc, make_integer(sc, sizes[i]), sc->w); + x = car(x); + if ((i < (dims - 1)) && + (!is_pair(x))) + { + free(sizes); + multivector_error_nr(sc, "we need a list that fully specifies the vector's elements", data); + }} + + vec = g_make_vector(sc, set_plist_1(sc, sc->w = proper_list_reverse_in_place(sc, sc->w))); + vec_loc = gc_protect_1(sc, vec); + sc->w = sc->unused; + + /* now fill the vector checking that all the lists match */ + err = traverse_vector_data(sc, vec, 0, 0, dims, sizes, data); + + free(sizes); + s7_gc_unprotect_at(sc, vec_loc); + if (err < 0) + multivector_error_nr(sc, (err == MULTIVECTOR_TOO_MANY_ELEMENTS) ? "found too many elements" : "not enough elements found", data); + return(vec); +} + +static s7_pointer g_int_multivector(s7_scheme *sc, s7_int dims, s7_pointer data) +{ + /* dims > 1, sc->value is a pair (not null) */ + s7_pointer *src; + s7_int len; + sc->value = g_multivector(sc, dims, data); + src = (s7_pointer *)vector_elements(sc->value); + len = vector_length(sc->value); + for (s7_int i = 0; i < len; i++) + if (!is_t_integer(src[i])) + wrong_type_error_nr(sc, wrap_string(sc, "#i(...)", 7), i + 1, src[i], sc->type_names[T_INTEGER]); + sc->args = g_make_vector_1(sc, set_plist_2(sc, g_vector_dimensions(sc, set_plist_1(sc, sc->value)), int_zero), sc->make_int_vector_symbol); + return(s7_copy_1(sc, sc->int_vector_symbol, set_plist_2(sc, sc->value, sc->args))); +} + +static s7_pointer g_byte_multivector(s7_scheme *sc, s7_int dims, s7_pointer data) +{ + /* dims > 1, sc->value is a pair (not null) */ + s7_pointer *src; + s7_int len; + sc->value = g_multivector(sc, dims, data); + src = (s7_pointer *)vector_elements(sc->value); + len = vector_length(sc->value); + for (s7_int i = 0; i < len; i++) + if (!is_byte(src[i])) + wrong_type_error_nr(sc, wrap_string(sc, "#u(...)", 7), i + 1, src[i], wrap_string(sc, "a byte", 6)); + sc->args = g_make_vector_1(sc, set_plist_2(sc, g_vector_dimensions(sc, set_plist_1(sc, sc->value)), int_zero), sc->make_byte_vector_symbol); + return(s7_copy_1(sc, sc->byte_vector_symbol, set_plist_2(sc, sc->value, sc->args))); +} + +static s7_pointer g_float_multivector(s7_scheme *sc, s7_int dims, s7_pointer data) +{ + /* dims > 1, sc->value is a pair (not null) */ + s7_pointer *src; + s7_int len; + sc->value = g_multivector(sc, dims, data); + src = (s7_pointer *)vector_elements(sc->value); + len = vector_length(sc->value); + for (s7_int i = 0; i < len; i++) + if (!is_real(src[i])) + wrong_type_error_nr(sc, wrap_string(sc, "#r(...)", 7), i + 1, src[i], sc->type_names[T_REAL]); + sc->args = g_make_vector_1(sc, set_plist_2(sc, g_vector_dimensions(sc, set_plist_1(sc, sc->value)), real_zero), sc->make_float_vector_symbol); + return(s7_copy_1(sc, sc->float_vector_symbol, set_plist_2(sc, sc->value, sc->args))); +} + +static Vectorized s7_pointer s7_vector_copy_1(s7_scheme *sc, s7_pointer old_vect) +{ + s7_int len = vector_length(old_vect); + s7_pointer new_vect; + + if (is_normal_vector(old_vect)) + { + s7_pointer *src = (s7_pointer *)vector_elements(old_vect), *dst; + if ((is_typed_vector(old_vect)) && (len > 0)) /* preserve the type info as well */ + { + if (vector_rank(old_vect) > 1) + new_vect = g_make_vector(sc, set_plist_3(sc, g_vector_dimensions(sc, set_plist_1(sc, old_vect)), + vector_element(old_vect, 0), typed_vector_typer(old_vect))); + else new_vect = g_make_vector(sc, set_plist_3(sc, make_integer(sc, len), + vector_element(old_vect, 0), typed_vector_typer(old_vect))); + } + else + if (vector_rank(old_vect) > 1) + new_vect = g_make_vector(sc, set_plist_1(sc, g_vector_dimensions(sc, set_plist_1(sc, old_vect)))); + else new_vect = make_simple_vector(sc, len); + /* here and in vector-fill! we have a problem with bignums -- should new bignums be allocated? (copy_proper_list also) */ + dst = (s7_pointer *)vector_elements(new_vect); + for (s7_int i = len; i > 0; i--) *dst++ = *src++; + return(new_vect); + } + + if (is_float_vector(old_vect)) + { + const s7_double *src = (s7_double *)float_vector_floats(old_vect); + s7_double *dst; + if (vector_rank(old_vect) > 1) + new_vect = g_make_vector_1(sc, set_plist_2(sc, g_vector_dimensions(sc, set_plist_1(sc, old_vect)), real_zero), sc->make_float_vector_symbol); + else new_vect = make_simple_float_vector(sc, len); + dst = (s7_double *)float_vector_floats(new_vect); + for (s7_int i = len; i > 0; i--) *dst++ = *src++; /* same speed as memcpy(dst, src, len * sizeof(s7_double)); */ + return(new_vect); + } + + if (is_int_vector(old_vect)) + { + const s7_int *src = (s7_int *)int_vector_ints(old_vect); + s7_int *dst; + if (vector_rank(old_vect) > 1) + new_vect = g_make_vector_1(sc, set_plist_2(sc, g_vector_dimensions(sc, set_plist_1(sc, old_vect)), int_zero), sc->make_int_vector_symbol); + else new_vect = make_simple_int_vector(sc, len); + dst = (s7_int *)int_vector_ints(new_vect); + for (s7_int i = len; i > 0; i--) *dst++ = *src++; + return(new_vect); + } + + if (is_byte_vector(old_vect)) + { + const uint8_t *src = (const uint8_t *)byte_vector_bytes(old_vect); + uint8_t *dst; + if (vector_rank(old_vect) > 1) + new_vect = g_make_vector_1(sc, set_plist_2(sc, g_vector_dimensions(sc, set_plist_1(sc, old_vect)), int_zero), sc->make_byte_vector_symbol); + else new_vect = make_simple_byte_vector(sc, len); + dst = (uint8_t *)byte_vector_bytes(new_vect); + for (s7_int i = len; i > 0; i--) *dst++ = *src++; + return(new_vect); + } + return(NULL); +} + +s7_pointer s7_vector_copy(s7_scheme *sc, s7_pointer old_vect) {return(s7_vector_copy_1(sc, old_vect));} /* repeated for Vectorized */ + +static s7_pointer univect_ref(s7_scheme *sc, s7_pointer args, s7_pointer caller, int32_t typ) +{ + s7_pointer v = car(args), index; + s7_int ind; + + if (type(v) != typ) + return(method_or_bust(sc, v, caller, args, sc->type_names[typ], 1)); + + if (vector_rank(v) == 1) + { + index = cadr(args); + if (!s7_is_integer(index)) + return(method_or_bust(sc, index, caller, args, sc->type_names[T_INTEGER], 2)); + ind = s7_integer_clamped_if_gmp(sc, index); + if ((ind < 0) || (ind >= vector_length(v))) + sole_arg_out_of_range_error_nr(sc, caller, index, (ind < 0) ? it_is_negative_string : it_is_too_large_string); + if (!is_null(cddr(args))) + out_of_range_error_nr(sc, caller, int_two, cdr(args), too_many_indices_string); + } + else + { + s7_int i; + s7_pointer x; + ind = 0; + for (x = cdr(args), i = 0; (is_not_null(x)) && (i < vector_ndims(v)); x = cdr(x), i++) + { + s7_int n; + index = car(x); + if (!s7_is_integer(index)) + return(method_or_bust(sc, index, caller, args, sc->type_names[T_INTEGER], i + 2)); + n = s7_integer_clamped_if_gmp(sc, index); + if ((n < 0) || (n >= vector_dimension(v, i))) + out_of_range_error_nr(sc, caller, wrap_integer(sc, i + 2), index, (n < 0) ? it_is_negative_string : it_is_too_large_string); + ind += n * vector_offset(v, i); + } + if (is_not_null(x)) + out_of_range_error_nr(sc, caller, int_two, cdr(args), too_many_indices_string); + + /* if not enough indices, return a subvector covering whatever is left */ + if (i < vector_ndims(v)) + return(subvector(sc, v, i, ind)); + } + if (typ == T_FLOAT_VECTOR) + return(make_real(sc, float_vector(v, ind))); + return((typ == T_INT_VECTOR) ? make_integer(sc, int_vector(v, ind)) : small_int(byte_vector(v, ind))); +} + +static s7_pointer univect_set(s7_scheme *sc, s7_pointer args, s7_pointer caller, int32_t typ) +{ + s7_pointer vec = car(args), val, index; + s7_int ind; + + if (type(vec) != typ) + return(method_or_bust(sc, vec, caller, args, sc->type_names[typ], 1)); + if (is_immutable_vector(vec)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, caller, vec)); + + if (vector_rank(vec) > 1) + { + s7_int i; + s7_pointer x; + ind = 0; + for (x = cdr(args), i = 0; (is_not_null(cdr(x))) && (i < vector_ndims(vec)); x = cdr(x), i++) + { + s7_int n; + index = car(x); + if (!s7_is_integer(index)) + return(method_or_bust(sc, index, caller, args, sc->type_names[T_INTEGER], i + 2)); + n = s7_integer_clamped_if_gmp(sc, index); + if ((n < 0) || (n >= vector_dimension(vec, i))) + out_of_range_error_nr(sc, caller, wrap_integer(sc, i + 2), index, (n < 0) ? it_is_negative_string : it_is_too_large_string); + ind += n * vector_offset(vec, i); + } + if (is_not_null(cdr(x))) + error_nr(sc, sc->wrong_number_of_args_symbol, set_elist_3(sc, wrap_string(sc, "too many arguments for ~A: ~S", 29), caller, args)); + if (i != vector_ndims(vec)) + error_nr(sc, sc->wrong_number_of_args_symbol, set_elist_3(sc, wrap_string(sc, "not enough arguments for ~A: ~S", 31), caller, args)); + val = car(x); + } + else + { + s7_pointer p = cdr(args); + if (is_null(p)) + error_nr(sc, sc->wrong_number_of_args_symbol, set_elist_3(sc, wrap_string(sc, "not enough arguments for ~A: ~S", 31), caller, args)); + /* from (set! (v) val) after optimization into op_set_opsq_a which is completely confused -- set! gets v's setter (float-vector-set!) */ + index = car(p); + if (!s7_is_integer(index)) + return(method_or_bust(sc, index, caller, args, sc->type_names[T_INTEGER], 2)); + ind = s7_integer_clamped_if_gmp(sc, index); + if ((ind < 0) || (ind >= vector_length(vec))) + out_of_range_error_nr(sc, caller, int_two, index, (ind < 0) ? it_is_negative_string : it_is_too_large_string); + if (is_not_null(cddr(p))) + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_3(sc, wrap_string(sc, "too many arguments for ~A: ~S", 29), caller, args)); + val = cadr(p); + } + + if (typ == T_FLOAT_VECTOR) + { + if (!is_real(val)) + return(method_or_bust(sc, val, caller, args, sc->type_names[T_REAL], 3)); + float_vector(vec, ind) = s7_real(val); + } + else + if (typ == T_INT_VECTOR) + { + if (!s7_is_integer(val)) + return(method_or_bust(sc, val, caller, args, sc->type_names[T_INTEGER], 3)); + int_vector(vec, ind) = s7_integer_clamped_if_gmp(sc, val); + } + else + { + if (!is_byte(val)) + return(method_or_bust(sc, val, caller, args, sc->type_names[T_INTEGER], 3)); + byte_vector(vec, ind) = (uint8_t)s7_integer_clamped_if_gmp(sc, val); + } + return(val); +} + + +/* -------------------------------- float-vector-ref -------------------------------- */ +static s7_pointer g_float_vector_ref(s7_scheme *sc, s7_pointer args) +{ + #define H_float_vector_ref "(float-vector-ref v ...) returns an element of the float-vector v." + #define Q_float_vector_ref s7_make_circular_signature(sc, 2, 3, \ + s7_make_signature(sc, 2, sc->is_float_symbol, sc->is_float_vector_symbol), \ + sc->is_float_vector_symbol, sc->is_integer_symbol) + return(univect_ref(sc, args, sc->float_vector_ref_symbol, T_FLOAT_VECTOR)); +} + +static inline s7_pointer float_vector_ref_p_pp(s7_scheme *sc, s7_pointer v, s7_pointer index) +{ + s7_int ind; + if (!is_float_vector(v)) + return(method_or_bust_pp(sc, v, sc->float_vector_ref_symbol, v, index, sc->type_names[T_FLOAT_VECTOR], 1)); + if (vector_rank(v) != 1) + return(univect_ref(sc, set_plist_2(sc, v, index), sc->float_vector_ref_symbol, T_FLOAT_VECTOR)); + if (!s7_is_integer(index)) + return(method_or_bust_pp(sc, index, sc->float_vector_ref_symbol, v, index, sc->type_names[T_INTEGER], 2)); + ind = s7_integer_clamped_if_gmp(sc, index); + if ((ind < 0) || (ind >= vector_length(v))) + out_of_range_error_nr(sc, sc->float_vector_ref_symbol, int_two, index, (ind < 0) ? it_is_negative_string : it_is_too_large_string); + return(make_real(sc, float_vector(v, ind))); +} + +static s7_pointer g_fv_ref_2(s7_scheme *sc, s7_pointer args) {return(float_vector_ref_p_pp(sc, car(args), cadr(args)));} + +static s7_pointer g_fv_ref_3(s7_scheme *sc, s7_pointer args) +{ + s7_pointer fv = car(args), index; + s7_int ind1, ind2; + if (!is_float_vector(fv)) + return(method_or_bust(sc, fv, sc->float_vector_ref_symbol, args, sc->type_names[T_FLOAT_VECTOR], 1)); + if (vector_rank(fv) != 2) + return(univect_ref(sc, args, sc->float_vector_ref_symbol, T_FLOAT_VECTOR)); + index = cadr(args); + if (!s7_is_integer(index)) + return(method_or_bust(sc, index, sc->float_vector_ref_symbol, args, sc->type_names[T_INTEGER], 2)); + ind1 = s7_integer_clamped_if_gmp(sc, index); + if ((ind1 < 0) || (ind1 >= vector_dimension(fv, 0))) + out_of_range_error_nr(sc, sc->float_vector_ref_symbol, int_two, index, (ind1 < 0) ? it_is_negative_string : it_is_too_large_string); + index = caddr(args); + if (!s7_is_integer(index)) + return(method_or_bust(sc, index, sc->float_vector_ref_symbol, args, sc->type_names[T_INTEGER], 3)); + ind2 = s7_integer_clamped_if_gmp(sc, index); + if ((ind2 < 0) || (ind2 >= vector_dimension(fv, 1))) + out_of_range_error_nr(sc, sc->float_vector_ref_symbol, int_three, index, (ind2 < 0) ? it_is_negative_string : it_is_too_large_string); + ind1 = ind1 * vector_offset(fv, 0) + ind2; + return(make_real(sc, float_vector(fv, ind1))); +} + +static inline s7_int ref_check_index(s7_scheme *sc, s7_pointer v, s7_int i) +{ + /* according to callgrind, it is faster to split out the bounds check */ + if ((i < 0) || (i >= vector_length(v))) + out_of_range_error_nr(sc, sc->float_vector_ref_symbol, int_two, wrap_integer(sc, i), (i < 0) ? it_is_negative_string : it_is_too_large_string); + return(i); +} + +static inline s7_double float_vector_ref_d_7pi(s7_scheme *sc, s7_pointer v, s7_int i) {return(float_vector(v, ref_check_index(sc, v, i)));} +static s7_pointer float_vector_ref_p_pi_direct(s7_scheme *sc, s7_pointer v, s7_int i) {return(make_real(sc, float_vector(v, i)));} + +static inline s7_double float_vector_ref_d_7pii(s7_scheme *sc, s7_pointer v, s7_int i1, s7_int i2) +{ + if ((i1 < 0) || (i1 >= vector_dimension(v, 0))) + out_of_range_error_nr(sc, sc->float_vector_ref_symbol, int_two, wrap_integer(sc, i1), (i1 < 0) ? it_is_negative_string : it_is_too_large_string); + if ((i2 < 0) || (i2 >= vector_dimension(v, 1))) + out_of_range_error_nr(sc, sc->float_vector_ref_symbol, int_three, wrap_integer(sc, i2), (i2 < 0) ? it_is_negative_string : it_is_too_large_string); + return(float_vector(v, i2 + (i1 * vector_offset(v, 0)))); +} + +static s7_double float_vector_ref_d_7piii(s7_scheme *sc, s7_pointer v, s7_int i1, s7_int i2, s7_int i3) +{ /* uncallable? */ + if ((i1 < 0) || (i1 >= vector_dimension(v, 0))) + out_of_range_error_nr(sc, sc->float_vector_ref_symbol, int_two, wrap_integer(sc, i1), (i1 < 0) ? it_is_negative_string : it_is_too_large_string); + if ((i2 < 0) || (i2 >= vector_dimension(v, 1))) + out_of_range_error_nr(sc, sc->float_vector_ref_symbol, int_three, wrap_integer(sc, i2), (i2 < 0) ? it_is_negative_string : it_is_too_large_string); + if ((i3 < 0) || (i3 >= vector_dimension(v, 2))) + out_of_range_error_nr(sc, sc->float_vector_ref_symbol, small_int(4), wrap_integer(sc, i3), (i3 < 0) ? it_is_negative_string : it_is_too_large_string); + return(float_vector(v, i3 + (i2 * vector_offset(v, 1)) + (i1 * vector_offset(v, 0)))); +} + +static s7_pointer float_vector_ref_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer unused_expr, bool unused_ops) +{ + return((args == 2) ? sc->fv_ref_2 : ((args == 3) ? sc->fv_ref_3 : f)); +} + + +/* -------------------------------- float-vector-set! -------------------------------- */ +static s7_pointer g_float_vector_set(s7_scheme *sc, s7_pointer args) +{ + #define H_float_vector_set "(float-vector-set! v i ... value) sets the i-th element of the float-vector v to value." + #define Q_float_vector_set s7_make_circular_signature(sc, 3, 4, \ + sc->is_real_symbol, sc->is_float_vector_symbol, sc->is_integer_symbol, sc->is_integer_or_real_at_end_symbol) + return(univect_set(sc, args, sc->float_vector_set_symbol, T_FLOAT_VECTOR)); +} + +static s7_pointer g_fv_set_3(s7_scheme *sc, s7_pointer args) +{ + s7_pointer fv = car(args), index, value; + s7_int ind; + if (!is_float_vector(fv)) + return(method_or_bust(sc, fv, sc->float_vector_set_symbol, args, sc->type_names[T_FLOAT_VECTOR], 1)); + if (vector_rank(fv) != 1) + return(univect_set(sc, args, sc->float_vector_set_symbol, T_FLOAT_VECTOR)); + if (is_immutable_vector(fv)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->float_vector_set_symbol, fv)); + index = cadr(args); + if (!s7_is_integer(index)) + return(method_or_bust(sc, index, sc->float_vector_set_symbol, args, sc->type_names[T_INTEGER], 2)); + ind = s7_integer_clamped_if_gmp(sc, index); + if ((ind < 0) || (ind >= vector_length(fv))) + out_of_range_error_nr(sc, sc->float_vector_set_symbol, int_two, index, (ind < 0) ? it_is_negative_string : it_is_too_large_string); + value = caddr(args); + if (!is_real(value)) + return(method_or_bust(sc, value, sc->float_vector_set_symbol, args, sc->type_names[T_REAL], 3)); + float_vector(fv, ind) = s7_real(value); + return(value); +} + +static s7_pointer g_fv_set_unchecked(s7_scheme *sc, s7_pointer args) +{ + s7_pointer fv, value = caddr(args); + s7_int ind; + if (!is_real(value)) + wrong_type_error_nr(sc, sc->float_vector_set_symbol, 3, value, sc->type_names[T_REAL]); + fv = car(args); + if (is_immutable_vector(fv)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->float_vector_set_symbol, fv)); + ind = s7_integer_clamped_if_gmp(sc, cadr(args)); + float_vector(fv, ind) = s7_real(value); + return(value); +} + +static bool find_matching_ref(s7_scheme *sc, const s7_pointer getter, s7_pointer expr) +{ + /* expr: (*set! v i val), val exists (i.e. args=3, so cddddr is null) */ + s7_pointer v = cadr(expr), ind = caddr(expr); + if ((is_symbol(v)) && (!is_pair(ind))) + { + s7_pointer val = cadddr(expr); + if (is_optimized(val)) /* includes is_pair */ + for (s7_pointer p = val; is_pair(p); p = cdr(p)) + if (is_pair(car(p))) + { + s7_pointer ref = car(p); + if (((car(ref) == getter) && /* (getter v ind) */ + (is_proper_list_2(sc, cdr(ref))) && + (cadr(ref) == v) && + (caddr(ref) == ind)) || + ((car(ref) == v) && /* (v ind) */ + (is_proper_list_1(sc, cdr(ref))) && + (cadr(ref) == ind))) + return(true); + }} + return(false); +} + +static s7_pointer float_vector_set_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool unused_ops) +{ + if (args == 3) + return((find_matching_ref(sc, sc->float_vector_ref_symbol, expr)) ? sc->fv_set_unchecked : sc->fv_set_3); + return(f); +} + +static s7_double float_vector_set_d_7pid_direct(s7_scheme *unused_sc, s7_pointer v, s7_int i, s7_double x) {float_vector(v, i) = x; return(x);} + +static s7_int set_check_index(s7_scheme *sc, s7_pointer v, s7_int i) +{ + if ((i < 0) || (i >= vector_length(v))) + out_of_range_error_nr(sc, sc->float_vector_set_symbol, int_two, wrap_integer(sc, i), (i < 0) ? it_is_negative_string : it_is_too_large_string); + return(i); +} + +static s7_double float_vector_set_d_7pid(s7_scheme *sc, s7_pointer v, s7_int i, s7_double x) {float_vector(v, (set_check_index(sc, v, i))) = x; return(x);} + +static s7_double float_vector_set_d_7piid(s7_scheme *sc, s7_pointer v, s7_int i1, s7_int i2, s7_double x) +{ + if ((i1 < 0) || (i1 >= vector_dimension(v, 0))) + out_of_range_error_nr(sc, sc->float_vector_set_symbol, int_two, wrap_integer(sc, i1), (i1 < 0) ? it_is_negative_string : it_is_too_large_string); + if ((i2 < 0) || (i2 >= vector_dimension(v, 1))) + out_of_range_error_nr(sc, sc->float_vector_set_symbol, int_three, wrap_integer(sc, i2), (i2 < 0) ? it_is_negative_string : it_is_too_large_string); + float_vector(v, i2 + (i1 * vector_offset(v, 0))) = x; + return(x); +} + +static s7_double float_vector_set_d_7piiid(s7_scheme *sc, s7_pointer v, s7_int i1, s7_int i2, s7_int i3, s7_double x) +{ /* uncallable? */ + if ((i1 < 0) || (i1 >= vector_dimension(v, 0))) + out_of_range_error_nr(sc, sc->float_vector_set_symbol, int_two, wrap_integer(sc, i1), (i1 < 0) ? it_is_negative_string : it_is_too_large_string); + if ((i2 < 0) || (i2 >= vector_dimension(v, 1))) + out_of_range_error_nr(sc, sc->float_vector_set_symbol, int_three, wrap_integer(sc, i2), (i2 < 0) ? it_is_negative_string : it_is_too_large_string); + if ((i3 < 0) || (i3 >= vector_dimension(v, 2))) + out_of_range_error_nr(sc, sc->float_vector_set_symbol, small_int(4), wrap_integer(sc, i3), (i3 < 0) ? it_is_negative_string : it_is_too_large_string); + float_vector(v, i3 + (i2 * vector_offset(v, 1)) + (i1 * vector_offset(v, 0))) = x; + return(x); +} + +static s7_pointer float_vector_set_p_pip_direct(s7_scheme *sc, s7_pointer v, s7_int i, s7_pointer p) +{ + float_vector(v, i) = real_to_double(sc, p, "float-vector-set!"); + return(p); +} + + +/* -------------------------------- int-vector-ref -------------------------------- */ +static s7_pointer g_int_vector_ref(s7_scheme *sc, s7_pointer args) +{ + #define H_int_vector_ref "(int-vector-ref v ...) returns an element of the int-vector v." + #define Q_int_vector_ref s7_make_circular_signature(sc, 2, 3, \ + s7_make_signature(sc, 2, sc->is_integer_symbol, sc->is_int_vector_symbol), \ + sc->is_int_vector_symbol, sc->is_integer_symbol) + return(univect_ref(sc, args, sc->int_vector_ref_symbol, T_INT_VECTOR)); +} + +static s7_int int_vector_ref_i_7pi_direct(s7_scheme *unused_sc, s7_pointer v, s7_int i) {return(int_vector(v, i));} +static s7_pointer int_vector_ref_p_pi_direct(s7_scheme *sc, s7_pointer v, s7_int i) {return(make_integer(sc, int_vector(v, i)));} + +static s7_int int_vector_ref_i_7pi(s7_scheme *sc, s7_pointer v, s7_int i) +{ + if ((i < 0) || (i >= vector_length(v))) + out_of_range_error_nr(sc, sc->int_vector_ref_symbol, int_two, wrap_integer(sc, i), (i < 0) ? it_is_negative_string : it_is_too_large_string); + return(int_vector(v, i)); +} + +static s7_int int_vector_ref_i_7pii(s7_scheme *sc, s7_pointer v, s7_int i1, s7_int i2) +{ + if ((i1 < 0) || (i1 >= vector_dimension(v, 0))) + out_of_range_error_nr(sc, sc->int_vector_ref_symbol, int_two, wrap_integer(sc, i1), (i1 < 0) ? it_is_negative_string : it_is_too_large_string); + if ((i2 < 0) || (i2 >= vector_dimension(v, 1))) + out_of_range_error_nr(sc, sc->int_vector_ref_symbol, int_three, wrap_integer(sc, i2), (i2 < 0) ? it_is_negative_string : it_is_too_large_string); + return(int_vector(v, i2 + (i1 * vector_offset(v, 0)))); +} + +static s7_int int_vector_ref_i_7piii(s7_scheme *sc, s7_pointer v, s7_int i1, s7_int i2, s7_int i3) +{ + if ((i1 < 0) || (i1 >= vector_dimension(v, 0))) + out_of_range_error_nr(sc, sc->int_vector_ref_symbol, int_two, wrap_integer(sc, i1), (i1 < 0) ? it_is_negative_string : it_is_too_large_string); + if ((i2 < 0) || (i2 >= vector_dimension(v, 1))) + out_of_range_error_nr(sc, sc->int_vector_ref_symbol, int_three, wrap_integer(sc, i2), (i2 < 0) ? it_is_negative_string : it_is_too_large_string); + if ((i3 < 0) || (i3 >= vector_dimension(v, 2))) + out_of_range_error_nr(sc, sc->int_vector_ref_symbol, small_int(4), wrap_integer(sc, i3), (i3 < 0) ? it_is_negative_string : it_is_too_large_string); + return(int_vector(v, i3 + (i2 * vector_offset(v, 1)) + (i1 * vector_offset(v, 0)))); +} + +static inline s7_pointer int_vector_ref_p_pp(s7_scheme *sc, s7_pointer v, s7_pointer index) +{ + s7_int ind; + if (!is_int_vector(v)) + return(method_or_bust_pp(sc, v, sc->int_vector_ref_symbol, v, index, sc->type_names[T_INT_VECTOR], 1)); + if (vector_rank(v) != 1) + return(univect_ref(sc, set_plist_2(sc, v, index), sc->int_vector_ref_symbol, T_INT_VECTOR)); + if (!s7_is_integer(index)) + return(method_or_bust_pp(sc, index, sc->int_vector_ref_symbol, v, index, sc->type_names[T_INTEGER], 2)); + ind = s7_integer_clamped_if_gmp(sc, index); + if ((ind < 0) || (ind >= vector_length(v))) + out_of_range_error_nr(sc, sc->int_vector_ref_symbol, int_two, index, (ind < 0) ? it_is_negative_string : it_is_too_large_string); + return(make_integer(sc, int_vector(v, ind))); +} + +static s7_pointer g_iv_ref_2(s7_scheme *sc, s7_pointer args) {return(int_vector_ref_p_pp(sc, car(args), cadr(args)));} + +static s7_pointer g_iv_ref_3(s7_scheme *sc, s7_pointer args) +{ + s7_pointer iv = car(args), index; + s7_int ind1, ind2; + if (!is_int_vector(iv)) + return(method_or_bust(sc, iv, sc->int_vector_ref_symbol, args, sc->type_names[T_INT_VECTOR], 1)); + if (vector_rank(iv) != 2) + return(univect_ref(sc, args, sc->int_vector_ref_symbol, T_INT_VECTOR)); + index = cadr(args); + if (!s7_is_integer(index)) + return(method_or_bust(sc, index, sc->int_vector_ref_symbol, args, sc->type_names[T_INTEGER], 2)); + ind1 = s7_integer_clamped_if_gmp(sc, index); + if ((ind1 < 0) || (ind1 >= vector_dimension(iv, 0))) + out_of_range_error_nr(sc, sc->int_vector_ref_symbol, int_two, index, (ind1 < 0) ? it_is_negative_string : it_is_too_large_string); + index = caddr(args); + if (!s7_is_integer(index)) + return(method_or_bust(sc, index, sc->int_vector_ref_symbol, args, sc->type_names[T_INTEGER], 3)); + ind2 = s7_integer_clamped_if_gmp(sc, index); + if ((ind2 < 0) || (ind2 >= vector_dimension(iv, 1))) + out_of_range_error_nr(sc, sc->int_vector_ref_symbol, int_three, index, (ind2 < 0) ? it_is_negative_string : it_is_too_large_string); + ind1 = ind1 * vector_offset(iv, 0) + ind2; + return(make_integer(sc, int_vector(iv, ind1))); +} + +static s7_pointer int_vector_ref_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer unused_expr, bool unused_ops) +{ + return((args == 2) ? sc->iv_ref_2 : ((args == 3) ? sc->iv_ref_3 : f)); +} + + +/* -------------------------------- int-vector-set! -------------------------------- */ +static s7_pointer g_int_vector_set(s7_scheme *sc, s7_pointer args) +{ + #define H_int_vector_set "(int-vector-set! v i ... value) sets the i-th element of the int-vector v to value." + #define Q_int_vector_set s7_make_circular_signature(sc, 2, 3, sc->is_integer_symbol, sc->is_int_vector_symbol, sc->is_integer_symbol) + return(univect_set(sc, args, sc->int_vector_set_symbol, T_INT_VECTOR)); +} + +static s7_int int_vector_set_i_7pii_direct(s7_scheme *unused_sc, s7_pointer v, s7_int i, s7_int x) {int_vector(v, i) = x; return(x);} + +static s7_pointer int_vector_set_p_pip_direct(s7_scheme *sc, s7_pointer v, s7_int i, s7_pointer p) +{ + int_vector(v, i) = s7_integer_clamped_if_gmp(sc, p); + return(p); +} + +static s7_int int_vector_set_i_7pii(s7_scheme *sc, s7_pointer v, s7_int i, s7_int x) +{ + if ((i < 0) || (i >= vector_length(v))) + out_of_range_error_nr(sc, sc->int_vector_set_symbol, int_two, wrap_integer(sc, i), (i < 0) ? it_is_negative_string : it_is_too_large_string); + int_vector(v, i) = x; + return(x); +} + +static s7_int int_vector_set_i_7piii(s7_scheme *sc, s7_pointer v, s7_int i1, s7_int i2, s7_int i3) +{ + if ((i1 < 0) || (i1 >= vector_dimension(v, 0))) + out_of_range_error_nr(sc, sc->int_vector_set_symbol, int_two, wrap_integer(sc, i1), (i1 < 0) ? it_is_negative_string : it_is_too_large_string); + if ((i2 < 0) || (i2 >= vector_dimension(v, 1))) + out_of_range_error_nr(sc, sc->int_vector_set_symbol, int_three, wrap_integer(sc, i2), (i2 < 0) ? it_is_negative_string : it_is_too_large_string); + int_vector(v, i2 + (i1 * vector_offset(v, 0))) = i3; + return(i3); +} + +static s7_pointer int_vector_set_p_ppp(s7_scheme *sc, s7_pointer v, s7_pointer index, s7_pointer val) +{ + if ((is_int_vector(v)) && (vector_rank(v) == 1) && (!is_immutable_vector(v)) && + (is_t_integer(index)) && (is_t_integer(val))) + { + s7_int i = integer(index); + if ((i < 0) || (i >= vector_length(v))) + out_of_range_error_nr(sc, sc->int_vector_set_symbol, int_two, index, (i < 0) ? it_is_negative_string : it_is_too_large_string); + int_vector(v, i) = integer(val); + } + else + { + if (!is_int_vector(v)) + return(method_or_bust_ppp(sc, v, sc->int_vector_set_symbol, v, index, val, sc->type_names[T_INT_VECTOR], 1)); + if (vector_rank(v) != 1) + return(univect_set(sc, set_plist_3(sc, v, index, val), sc->int_vector_set_symbol, T_INT_VECTOR)); + if (is_immutable_vector(v)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->int_vector_set_symbol, v)); + /* (int-vector-set! #i() `(x 1) (abs x)) in a do loop in a function... */ + if (!s7_is_integer(index)) + return(method_or_bust_ppp(sc, index, sc->int_vector_set_symbol, v, index, val, sc->type_names[T_INTEGER], 2)); + if (!s7_is_integer(val)) + return(method_or_bust_ppp(sc, val, sc->int_vector_set_symbol, v, index, val, sc->type_names[T_INTEGER], 3)); +#if WITH_GMP + { + s7_int i = s7_integer_clamped_if_gmp(sc, index); + if ((i < 0) || (i >= vector_length(v))) + out_of_range_error_nr(sc, sc->int_vector_set_symbol, int_two, index, (i < 0) ? it_is_negative_string : it_is_too_large_string); + int_vector(v, i) = s7_integer_clamped_if_gmp(sc, val); + } +#else + if (S7_DEBUGGING) fprintf(stderr, "fell through %s[%d]\n", __func__, __LINE__); +#endif + } + return(val); +} + +static s7_pointer g_iv_set_3(s7_scheme *sc, s7_pointer args) +{ + s7_pointer v = car(args), index, value; + s7_int ind; + if (!is_int_vector(v)) + return(method_or_bust(sc, v, sc->int_vector_set_symbol, args, sc->type_names[T_INT_VECTOR], 1)); + if (vector_rank(v) != 1) + return(univect_set(sc, args, sc->int_vector_set_symbol, T_INT_VECTOR)); + if (is_immutable_vector(v)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->int_vector_set_symbol, v)); + index = cadr(args); + if (!s7_is_integer(index)) + return(method_or_bust(sc, index, sc->int_vector_set_symbol, args, sc->type_names[T_INTEGER], 2)); + ind = s7_integer_clamped_if_gmp(sc, index); + if ((ind < 0) || (ind >= vector_length(v))) + out_of_range_error_nr(sc, sc->int_vector_set_symbol, int_two, index, (ind < 0) ? it_is_negative_string : it_is_too_large_string); + value = caddr(args); + if (!s7_is_integer(value)) + return(method_or_bust(sc, value, sc->int_vector_set_symbol, args, sc->type_names[T_INTEGER], 3)); + int_vector(v, ind) = s7_integer_clamped_if_gmp(sc, value); + return(value); +} + +static s7_pointer int_vector_set_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer unused_expr, bool unused_ops) +{ + return((args == 3) ? sc->iv_set_3 : f); +} + + +/* -------------------------------- byte-vector-ref -------------------------------- */ +static s7_pointer g_byte_vector_ref(s7_scheme *sc, s7_pointer args) +{ + #define H_byte_vector_ref "(byte-vector-ref vect index) returns the byte at the index-th element of the byte-vector vect" + #define Q_byte_vector_ref s7_make_circular_signature(sc, 2, 3, \ + s7_make_signature(sc, 2, sc->is_byte_symbol, sc->is_byte_vector_symbol), \ + sc->is_byte_vector_symbol, sc->is_integer_symbol) + return(univect_ref(sc, args, sc->byte_vector_ref_symbol, T_BYTE_VECTOR)); +} + +static s7_int byte_vector_ref_i_7pi(s7_scheme *sc, s7_pointer p1, s7_int i1) +{ + if ((i1 < 0) || (i1 >= byte_vector_length(p1))) + out_of_range_error_nr(sc, sc->byte_vector_ref_symbol, int_two, wrap_integer(sc, i1), (i1 < 0) ? it_is_negative_string : it_is_too_large_string); + return((s7_int)((byte_vector(p1, i1)))); +} + +static s7_int byte_vector_ref_i_7pii(s7_scheme *sc, s7_pointer v, s7_int i1, s7_int i2) +{ + if ((i1 < 0) || (i1 >= vector_dimension(v, 0))) + out_of_range_error_nr(sc, sc->byte_vector_ref_symbol, int_two, wrap_integer(sc, i1), (i1 < 0) ? it_is_negative_string : it_is_too_large_string); + if ((i2 < 0) || (i2 >= vector_dimension(v, 1))) + out_of_range_error_nr(sc, sc->byte_vector_ref_symbol, int_three, wrap_integer(sc, i2), (i2 < 0) ? it_is_negative_string : it_is_too_large_string); + return((s7_int)byte_vector(v, i2 + (i1 * vector_offset(v, 0)))); +} + +static s7_pointer byte_vector_ref_p_pi_direct(s7_scheme *unused_sc, s7_pointer p1, s7_int i1) {return(small_int((byte_vector(p1, i1))));} +static s7_int byte_vector_ref_i_7pi_direct(s7_scheme *unused_sc, s7_pointer p1, s7_int i1) {return(byte_vector(p1, i1));} + +static s7_pointer g_bv_ref_2(s7_scheme *sc, s7_pointer args) +{ + s7_pointer v = car(args), index; + s7_int ind; + if (!is_byte_vector(v)) + return(method_or_bust(sc, v, sc->byte_vector_ref_symbol, args, sc->type_names[T_BYTE_VECTOR], 1)); + if (vector_rank(v) != 1) + return(univect_ref(sc, args, sc->byte_vector_ref_symbol, T_BYTE_VECTOR)); + index = cadr(args); + if (!s7_is_integer(index)) + return(method_or_bust(sc, index, sc->byte_vector_ref_symbol, args, sc->type_names[T_INTEGER], 2)); + ind = s7_integer_clamped_if_gmp(sc, index); + if ((ind < 0) || (ind >= vector_length(v))) + out_of_range_error_nr(sc, sc->byte_vector_ref_symbol, int_two, index, (ind < 0) ? it_is_negative_string : it_is_too_large_string); + return(small_int(byte_vector(v, ind))); +} + +static s7_pointer g_bv_ref_3(s7_scheme *sc, s7_pointer args) +{ + s7_pointer iv = car(args), index; + s7_int ind1, ind2; + if (!is_byte_vector(iv)) + return(method_or_bust(sc, iv, sc->byte_vector_ref_symbol, args, sc->type_names[T_BYTE_VECTOR], 1)); + if (vector_rank(iv) != 2) + return(univect_ref(sc, args, sc->byte_vector_ref_symbol, T_BYTE_VECTOR)); + index = cadr(args); + if (!s7_is_integer(index)) + return(method_or_bust(sc, index, sc->byte_vector_ref_symbol, args, sc->type_names[T_INTEGER], 2)); + ind1 = s7_integer_clamped_if_gmp(sc, index); + if ((ind1 < 0) || (ind1 >= vector_dimension(iv, 0))) + out_of_range_error_nr(sc, sc->byte_vector_ref_symbol, int_two, index, (ind1 < 0) ? it_is_negative_string : it_is_too_large_string); + index = caddr(args); + if (!s7_is_integer(index)) + return(method_or_bust(sc, index, sc->byte_vector_ref_symbol, args, sc->type_names[T_INTEGER], 3)); + ind2 = s7_integer_clamped_if_gmp(sc, index); + if ((ind2 < 0) || (ind2 >= vector_dimension(iv, 1))) + out_of_range_error_nr(sc, sc->byte_vector_ref_symbol, int_three, index, (ind2 < 0) ? it_is_negative_string : it_is_too_large_string); + ind1 = ind1 * vector_offset(iv, 0) + ind2; + return(small_int(byte_vector(iv, ind1))); +} + +static s7_pointer byte_vector_ref_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer unused_expr, bool unused_ops) +{ + return((args == 2) ? sc->bv_ref_2 : ((args == 3) ? sc->bv_ref_3 : f)); +} + + +/* -------------------------------- byte-vector-set -------------------------------- */ +static s7_pointer g_byte_vector_set(s7_scheme *sc, s7_pointer args) +{ + #define H_byte_vector_set "(byte-vector-set! vect index byte) sets the index-th element of the byte-vector vect to the integer byte" + #define Q_byte_vector_set s7_make_circular_signature(sc, 2, 3, sc->is_byte_symbol, sc->is_byte_vector_symbol, sc->is_integer_symbol) + return(univect_set(sc, args, sc->byte_vector_set_symbol, T_BYTE_VECTOR)); +} + +static s7_int byte_vector_set_i_7pii(s7_scheme *sc, s7_pointer p1, s7_int i1, s7_int i2) +{ + if (!is_byte_vector(p1)) + wrong_type_error_nr(sc, sc->byte_vector_set_symbol, 1, p1, a_byte_vector_string); + if ((i2 < 0) || (i2 > 255)) + wrong_type_error_nr(sc, sc->byte_vector_set_symbol, 3, wrap_integer(sc, i2), an_unsigned_byte_string); + if ((i1 < 0) || (i1 >= byte_vector_length(p1))) + out_of_range_error_nr(sc, sc->byte_vector_set_symbol, int_two, wrap_integer(sc, i1), (i1 < 0) ? it_is_negative_string : it_is_too_large_string); + byte_vector(p1, i1) = (uint8_t)i2; + return(i2); +} + +static s7_int byte_vector_set_i_7pii_direct(s7_scheme *unused_sc, s7_pointer p1, s7_int i1, s7_int i2) +{ + byte_vector(p1, i1) = (uint8_t)i2; return(i2); +} + +static s7_pointer byte_vector_set_p_pip_direct(s7_scheme *unused_sc, s7_pointer p1, s7_int i1, s7_pointer p2) +{ /* uncallable */ + byte_vector(p1, i1) = (uint8_t)s7_integer(p2); return(p2); +} + +static s7_int byte_vector_set_i_7piii(s7_scheme *sc, s7_pointer v, s7_int i1, s7_int i2, s7_int i3) +{ + if ((i3 < 0) || (i3 > 255)) + wrong_type_error_nr(sc, sc->byte_vector_set_symbol, 4, wrap_integer(sc, i3), an_unsigned_byte_string); + if ((i1 < 0) || (i1 >= vector_dimension(v, 0))) + out_of_range_error_nr(sc, sc->int_vector_set_symbol, int_two, wrap_integer(sc, i1), (i1 < 0) ? it_is_negative_string : it_is_too_large_string); + if ((i2 < 0) || (i2 >= vector_dimension(v, 1))) + out_of_range_error_nr(sc, sc->int_vector_set_symbol, int_three, wrap_integer(sc, i2), (i2 < 0) ? it_is_negative_string : it_is_too_large_string); + byte_vector(v, i2 + (i1 * vector_offset(v, 0))) = i3; + return(i3); +} + +static s7_pointer g_bv_set_3(s7_scheme *sc, s7_pointer args) +{ + s7_pointer v = car(args), index, value; + s7_int ind, uval; + if (!is_byte_vector(v)) + return(method_or_bust(sc, v, sc->byte_vector_set_symbol, args, sc->type_names[T_BYTE_VECTOR], 1)); + if (vector_rank(v) != 1) + return(univect_set(sc, args, sc->byte_vector_set_symbol, T_BYTE_VECTOR)); + if (is_immutable_vector(v)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->byte_vector_set_symbol, v)); + index = cadr(args); + if (!s7_is_integer(index)) + return(method_or_bust(sc, index, sc->byte_vector_set_symbol, args, sc->type_names[T_INTEGER], 2)); + ind = s7_integer_clamped_if_gmp(sc, index); + if ((ind < 0) || (ind >= vector_length(v))) + out_of_range_error_nr(sc, sc->byte_vector_set_symbol, int_two, index, (ind < 0) ? it_is_negative_string : it_is_too_large_string); + value = caddr(args); + if (!s7_is_integer(value)) + return(method_or_bust(sc, value, sc->byte_vector_set_symbol, args, sc->type_names[T_INTEGER], 3)); + uval = s7_integer_clamped_if_gmp(sc, value); + if ((uval < 0) || (uval > 255)) + wrong_type_error_nr(sc, sc->byte_vector_set_symbol, 3, value, an_unsigned_byte_string); + byte_vector(v, ind) = (uint8_t)uval; + return(value); +} + +static s7_pointer byte_vector_set_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer unused_expr, bool unused_ops) +{ + return((args == 3) ? sc->bv_set_3 : f); +} + + +/* -------------------------------------------------------------------------------- */ +static bool c_function_is_ok(s7_scheme *sc, s7_pointer x) +{ + s7_pointer p = lookup_unexamined(sc, car(x)); /* lookup_global is usually slower (faster in Snd) */ + if ((p == opt1_cfunc(x)) || + ((p) && (is_any_c_function(p)) && (c_function_class(p) == c_function_class(opt1_cfunc(x))) && (set_opt1_cfunc(x, p)))) + return(true); + sc->last_function = p; + return(false); +} + +static bool cl_function_is_ok(s7_scheme *sc, s7_pointer x) +{ + sc->last_function = lookup_unexamined(sc, car(x)); + return(sc->last_function == opt1_cfunc(x)); +} + +static bool arglist_has_rest(s7_scheme *sc, s7_pointer args) +{ + s7_pointer p; + for (p = args; is_pair(p); p = cdr(p)) + if (car(p) == sc->rest_keyword) + return(true); + return(!is_null(p)); +} + + +/* -------------------------------- sort! -------------------------------- */ +static int32_t dbl_less(const void *f1, const void *f2) +{ + if ((*((const s7_double *)f1)) < (*((const s7_double *)f2))) return(-1); + return(((*((const s7_double *)f1)) > (*((const s7_double *)f2))) ? 1 : 0); +} + +static int32_t int_less(const void *f1, const void *f2) +{ + if ((*((const s7_int *)f1)) < (*((const s7_int *)f2))) return(-1); + return(((*((const s7_int *)f1)) > (*((const s7_int *)f2))) ? 1 : 0); +} + +static int32_t dbl_greater(const void *f1, const void *f2) {return(-dbl_less(f1, f2));} +static int32_t int_greater(const void *f1, const void *f2) {return(-int_less(f1, f2));} + +static int32_t byte_less(const void *f1, const void *f2) +{ + if ((*((const uint8_t *)f1)) < (*((const uint8_t *)f2))) return(-1); + return(((*((const uint8_t *)f1)) > (*((const uint8_t *)f2))) ? 1 : 0); +} + +static int32_t byte_greater(const void *f1, const void *f2) {return(-byte_less(f1, f2));} + +static int32_t dbl_less_2(const void *f1, const void *f2) +{ + s7_double p1 = real(*((const s7_pointer *)f1)); + s7_double p2 = real(*((const s7_pointer *)f2)); + if (p1 < p2) return(-1); + return((p1 > p2) ? 1 : 0); +} + +static int32_t int_less_2(const void *f1, const void *f2) +{ + s7_int p1 = integer(*((const s7_pointer *)f1)); + s7_int p2 = integer(*((const s7_pointer *)f2)); + if (p1 < p2) return(-1); + return((p1 > p2) ? 1 : 0); +} + +static int32_t dbl_greater_2(const void *f1, const void *f2) {return(-dbl_less_2(f1, f2));} +static int32_t int_greater_2(const void *f1, const void *f2) {return(-int_less_2(f1, f2));} + +static int32_t str_less_2(const void *f1, const void *f2) +{ + s7_pointer p1 = (*((const s7_pointer *)f1)); + s7_pointer p2 = (*((const s7_pointer *)f2)); + return(scheme_strcmp(p1, p2)); +} + +static int32_t str_greater_2(const void *f1, const void *f2) {return(-str_less_2(f1, f2));} + +static int32_t chr_less_2(const void *f1, const void *f2) +{ + uint8_t p1 = character(*((const s7_pointer *)f1)); + uint8_t p2 = character(*((const s7_pointer *)f2)); + if (p1 < p2) return(-1); + return((p1 > p2) ? 1 : 0); +} + +static int32_t chr_greater_2(const void *f1, const void *f2) {return(-chr_less_2(f1, f2));} + +#if MS_WINDOWS || defined(__APPLE__) || defined(__FreeBSD__) +struct sort_r_data {void *arg; int32_t (*compar)(const void *a1, const void *a2, void *aarg);}; + +static int32_t sort_r_arg_swap(void *s, const void *aa, const void *bb) +{ + struct sort_r_data *ss = (struct sort_r_data*)s; + return (ss->compar)(aa, bb, ss->arg); +} +#endif + +/* qsort_r in Linux requires _GNU_SOURCE and is different from q_sort_r in FreeBSD, neither matches qsort_s in Windows + * this code tested only in Linux and the mac -- my virtualbox freebsd died, netbsd and openbsd run using fallback code. + */ +static void local_qsort_r(void *base, size_t nmemb, size_t size, int32_t (*compar)(const void *, const void *, void *), void *arg) +{ +#if (defined(__linux__)) && (defined(__GLIBC__)) /* __GLIBC__ because musl does not have qsort_r and has no way to detect it */ + qsort_r(base, nmemb, size, compar, arg); +#else +#if defined(__APPLE__) || defined(__FreeBSD__) /* not in OpenBSD or NetBSD as far as I can tell */ + struct sort_r_data tmp = {arg, compar}; + qsort_r(base, nmemb, size, &tmp, &sort_r_arg_swap); +#else +#if MS_WINDOWS + struct sort_r_data tmp = {arg, compar}; + qsort_s(base, nmemb, size, sort_r_arg_swap, &tmp); +#else + /* from the Net somewhere, by "Pete", about 25 times slower than libc's qsort_r in this context */ + if (nmemb > 1) + { + uint8_t *array = (uint8_t *)base; + uint8_t *after = (uint8_t *)(nmemb * size + array); + size_t h, t; + nmemb /= 4; + h = nmemb + 1; + for (t = 1; nmemb != 0; nmemb /= 4) + t *= 2; + do { + size_t bytes = h * size; + uint8_t *i = (uint8_t *)(array + bytes); + uint8_t *k; + do { + uint8_t *j = (uint8_t *)(i - bytes); + if (compar(j, i, arg) > 0) + { + k = i; + do { + uint8_t *p1 = j, *p2 = k; + uint8_t *end = (uint8_t *)(p2 + size); + do { + uint8_t swap = *p1; + *p1++ = *p2; + *p2++ = swap; + } while (p2 != end); + if (bytes + array > j) + break; + k = j; + j -= bytes; + } while (compar(j, k, arg) > 0); + } + i += size; + } while (i != after); + t /= 2; + h = t * t - t * 3 / 2 + 1; + } while (t != 0); + } +#endif +#endif +#endif +} + +static int32_t vector_sort(const void *v1, const void *v2, void *arg) +{ + s7_scheme *sc = (s7_scheme *)arg; + return(((*(sc->sort_f))(sc, (*(const s7_pointer *)v1), (*(const s7_pointer *)v2))) ? -1 : 1); +} + +static int32_t vector_sort_lt(const void *v1, const void *v2, void *arg) /* for qsort_r */ +{ + s7_pointer s1 = (*(const s7_pointer *)v1); + s7_pointer s2 = (*(const s7_pointer *)v2); + if ((is_t_integer(s1)) && (is_t_integer(s2))) + return((integer(s1) < integer(s2)) ? -1 : 1); + return((lt_b_7pp((s7_scheme *)arg, s1, s2)) ? -1 : 1); +} + +static int32_t vector_car_sort(const void *v1, const void *v2, void *arg) +{ + s7_scheme *sc = (s7_scheme *)arg; + s7_pointer a = (*(const s7_pointer *)v1); + s7_pointer b = (*(const s7_pointer *)v2); + a = (is_pair(a)) ? car(a) : g_car(sc, set_plist_1(sc, a)); + b = (is_pair(b)) ? car(b) : g_car(sc, set_plist_1(sc, b)); + return(((*(sc->sort_f))(sc, a, b)) ? -1 : 1); +} + +static int32_t vector_cdr_sort(const void *v1, const void *v2, void *arg) +{ + s7_scheme *sc = (s7_scheme *)arg; + s7_pointer a = (*(const s7_pointer *)v1); + s7_pointer b = (*(const s7_pointer *)v2); + a = (is_pair(a)) ? cdr(a) : g_cdr(sc, set_plist_1(sc, a)); + b = (is_pair(b)) ? cdr(b) : g_cdr(sc, set_plist_1(sc, b)); + return(((*(sc->sort_f))(sc, a, b)) ? -1 : 1); +} + +static int32_t opt_bool_sort(const void *v1, const void *v2, void *arg) +{ + s7_scheme *sc = (s7_scheme *)arg; + slot_set_value(sc->sort_v1, (*(const s7_pointer *)v1)); /* first slot in curlet */ + slot_set_value(sc->sort_v2, (*(const s7_pointer *)v2)); /* second slot in curlet */ + return((sc->sort_fb(sc->sort_o)) ? -1 : 1); +} + +static int32_t opt_bool_sort_0(const void *v1, const void *v2, void *arg) +{ + s7_scheme *sc = (s7_scheme *)arg; + slot_set_value(sc->sort_v1, (*(const s7_pointer *)v1)); /* first slot in curlet */ + slot_set_value(sc->sort_v2, (*(const s7_pointer *)v2)); /* second slot in curlet */ + return((sc->sort_fb(sc->sort_o)) ? -1 : 1); +} + +static int32_t opt_bool_sort_p(const void *v1, const void *v2, void *arg) +{ + s7_scheme *sc = (s7_scheme *)arg; + slot_set_value(sc->sort_v1, (*(const s7_pointer *)v1)); + slot_set_value(sc->sort_v2, (*(const s7_pointer *)v2)); + return((sc->opts[0]->v[O_WRAP].fp(sc->opts[0]) == sc->F) ? 1 : -1); +} + +#define SORT_O1 1 +static inline int32_t begin_bool_sort_bp(s7_scheme *sc, const void *v1, const void *v2, bool int_expr) +{ + s7_int i; + opt_info *top = sc->opts[0], *o; + slot_set_value(sc->sort_v1, (*(const s7_pointer *)v1)); + slot_set_value(sc->sort_v2, (*(const s7_pointer *)v2)); + for (i = 0; i < sc->sort_body_len - 1; i++) + { + o = top->v[SORT_O1 + i].o1; + o->v[0].fp(o); + } + o = top->v[SORT_O1 + i].o1; + if (int_expr) + return((o->v[0].fb(o)) ? -1 : 1); + return((o->v[0].fp(o) != sc->F) ? -1 : 1); +} + +static int32_t opt_begin_bool_sort_b(const void *v1, const void *v2, void *arg) {return(begin_bool_sort_bp((s7_scheme *)arg, v1, v2, true));} +static int32_t opt_begin_bool_sort_p(const void *v1, const void *v2, void *arg) {return(begin_bool_sort_bp((s7_scheme *)arg, v1, v2, false));} + +static int32_t opt_begin_bool_sort_b2(const void *v1, const void *v2, void *arg) +{ + s7_scheme *sc = (s7_scheme *)arg; + opt_info *top = sc->opts[0], *o; + slot_set_value(sc->sort_v1, (*(const s7_pointer *)v1)); + slot_set_value(sc->sort_v2, (*(const s7_pointer *)v2)); + o = top->v[SORT_O1].o1; + o->v[0].fp(o); + o = top->v[SORT_O1 + 1].o1; + return((o->v[0].fb(o)) ? -1 : 1); +} + +static int32_t closure_sort(const void *v1, const void *v2, void *arg) +{ + s7_scheme *sc = (s7_scheme *)arg; + slot_set_value(sc->sort_v1, (*(const s7_pointer *)v1)); + slot_set_value(sc->sort_v2, (*(const s7_pointer *)v2)); + push_stack(sc, OP_EVAL_DONE, sc->sort_body, sc->code); + sc->code = sc->sort_body; /* this should be ok because we checked in advance that it is a safe closure (no sort! for example) */ + eval(sc, sc->sort_op); + return((sc->value != sc->F) ? -1 : 1); +} + +static int32_t closure_sort_begin(const void *v1, const void *v2, void *arg) +{ + s7_scheme *sc = (s7_scheme *)arg; + slot_set_value(sc->sort_v1, (*(const s7_pointer *)v1)); + slot_set_value(sc->sort_v2, (*(const s7_pointer *)v2)); + push_stack(sc, OP_EVAL_DONE, sc->sort_body, sc->code); + push_stack_no_args(sc, OP_BEGIN_NO_HOOK, T_Pair(sc->sort_begin)); + sc->code = sc->sort_body; + eval(sc, sc->sort_op); + return((sc->value != sc->F) ? -1 : 1); +} + +static s7_b_7pp_t s7_b_7pp_function(s7_pointer f); +static opt_info *alloc_opt_info(s7_scheme *sc); +static bool bool_optimize(s7_scheme *sc, s7_pointer expr); +static bool bool_optimize_nw(s7_scheme *sc, s7_pointer expr); +static bool cell_optimize(s7_scheme *sc, s7_pointer expr); + +static s7_pointer g_sort(s7_scheme *sc, s7_pointer args) +{ + #define H_sort "(sort! sequence less?) sorts a sequence using the function 'less?' to compare elements." + #define Q_sort s7_make_signature(sc, 3, sc->is_sequence_symbol, sc->is_sequence_symbol, sc->is_procedure_symbol) + + s7_pointer data = car(args), lessp, lx; + s7_int len = 0, n, k; + int32_t (*sort_func)(const void *v1, const void *v2, void *arg); + s7_pointer *elements; + + /* both the intermediate vector (if any) and the current args pointer need GC protection, + * but it is a real bother to unprotect args at every return statement, so I'll use temp3 + */ + sc->temp3 = args; /* this is needed but maybe insufficient... if sort is semisafe, we should protect the args, not the list: use OP_GC_PROTECT? */ + if (is_null(data)) + { + /* (apply sort! () #f) should be an error I think */ + lessp = cadr(args); + if (type(lessp) < T_CONTINUATION) + return(method_or_bust(sc, lessp, sc->sort_symbol, args, a_procedure_string, 2)); + if (!s7_is_aritable(sc, lessp, 2)) + wrong_type_error_nr(sc, sc->sort_symbol, 2, lessp, an_eq_func_string); + return(sc->nil); + } + + if (!is_sequence(data)) /* precede immutable because #f (for example) is immutable: "can't sort #f because it is immutable" is a joke */ + wrong_type_error_nr(sc, sc->sort_symbol, 1, data, a_sequence_string); + if (is_immutable(data)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->sort_symbol, data)); + + lessp = cadr(args); + if (type(lessp) <= T_GOTO) + wrong_type_error_nr(sc, sc->sort_symbol, 2, lessp, a_normal_procedure_string); + if (!s7_is_aritable(sc, lessp, 2)) + wrong_type_error_nr(sc, sc->sort_symbol, 2, lessp, an_eq_func_string); + if ((is_any_macro(lessp)) && (!is_c_macro(lessp))) clear_all_optimizations(sc, closure_body(lessp)); + + sort_func = NULL; + sc->sort_f = NULL; + + if (is_safe_c_function(lessp)) /* (sort! a <) */ + { + s7_pointer sig = c_function_signature(lessp); + if ((sig) && + (is_pair(sig)) && + (car(sig) != sc->is_boolean_symbol)) + wrong_type_error_nr(sc, sc->sort_symbol, 2, lessp, + wrap_string(sc, "sort! function should return a boolean", 38)); + sc->sort_f = s7_b_7pp_function(lessp); + if (sc->sort_f) sort_func = (sc->sort_f == lt_b_7pp) ? vector_sort_lt : vector_sort; + } + else + { + if (is_closure(lessp)) + { + s7_pointer expr = car(closure_body(lessp)); + s7_pointer largs = closure_args(lessp); + + if ((is_pair(largs)) && /* closure args not a symbol, etc */ + (!arglist_has_rest(sc, largs))) + { + if ((is_null(cdr(closure_body(lessp)))) && + (is_optimized(expr)) && + (is_safe_c_op(optimize_op(expr))) && + /* since (sort seq (lambda (a b) ...)) can't return a "safe closure" (the hop bit is off in + * optimize in this case, for some arcane reason), the optimized expression won't be hop_safe, + * but that is irrelevant at this point -- if c_function_is_ok, we're good to go. + */ + ((op_has_hop(expr)) || + ((is_global(car(expr))) && /* (sort! x (lambda (car y) (car x)...))! */ + (c_function_is_ok(sc, expr))))) + { + int32_t orig_data = optimize_op(expr); + set_optimize_op(expr, optimize_op(expr) | 1); + if ((optimize_op(expr) == HOP_SAFE_C_SS) && + (car(largs) == cadr(expr)) && + (cadr(largs) == caddr(expr))) + { + s7_pointer lp = lookup(sc, car(expr)); + sc->sort_f = s7_b_7pp_function(lp); + if (sc->sort_f) + { + sort_func = (sc->sort_f == lt_b_7pp) ? vector_sort_lt : vector_sort; + lessp = lp; + }} + else + if ((optimize_op(expr) == HOP_SAFE_C_opSq_opSq) && + ((caadr(expr) == sc->car_symbol) || (caadr(expr) == sc->cdr_symbol)) && + (caadr(expr) == caaddr(expr)) && + (car(largs) == cadadr(expr)) && + (cadr(largs) == cadaddr(expr))) + { + s7_pointer lp = lookup(sc, car(expr)); + sc->sort_f = s7_b_7pp_function(lp); + if (sc->sort_f) + { + sort_func = ((caadr(expr) == sc->car_symbol) ? vector_car_sort : vector_cdr_sort); + lessp = lp; + }} + set_optimize_op(expr, orig_data); + } + + if (!sort_func) + { + s7_pointer init_val, old_e = sc->curlet; + if (is_float_vector(data)) + init_val = real_zero; + else init_val = ((is_int_vector(data)) || (is_byte_vector(data))) ? int_zero : sc->F; + sc->curlet = inline_make_let_with_two_slots(sc, closure_let(lessp), car(largs), init_val, cadr(largs), init_val); + sc->sort_body = expr; + sc->sort_v1 = let_slots(sc->curlet); + sc->sort_v2 = next_slot(let_slots(sc->curlet)); + if (is_null(cdr(closure_body(lessp)))) + { + if (!no_bool_opt(closure_body(lessp))) + { + s7_pfunc sf1 = s7_bool_optimize(sc, closure_body(lessp)); + if (sf1) + { + if (sc->opts[0]->v[0].fb == p_to_b) + sort_func = opt_bool_sort_p; + else + { + sc->sort_o = sc->opts[0]; + sc->sort_fb = sc->sort_o->v[0].fb; + sort_func = (sc->pc == 1) ? opt_bool_sort_0 : opt_bool_sort; + }} + else set_no_bool_opt(closure_body(lessp)); + }} + else + { + sc->sort_body_len = s7_list_length(sc, closure_body(lessp)); + if (sc->sort_body_len < (NUM_VUNIONS - SORT_O1)) + { + s7_pointer p; + int32_t ctr; + opt_info *top; + sc->pc = 0; + top = alloc_opt_info(sc); + for (ctr = SORT_O1, p = closure_body(lessp); is_pair(cdr(p)); ctr++, p = cdr(p)) + { + top->v[ctr].o1 = sc->opts[sc->pc]; + if (!cell_optimize(sc, p)) + break; + } + if (is_null(cdr(p))) + { + int32_t start = sc->pc; + top->v[ctr].o1 = sc->opts[start]; + if (bool_optimize_nw(sc, p)) + sort_func = (sc->sort_body_len == 2) ? opt_begin_bool_sort_b2 : opt_begin_bool_sort_b; + else + { + sc->pc = start; + if (cell_optimize(sc, p)) + sort_func = opt_begin_bool_sort_p; + }}}} + if (!sort_func) + set_curlet(sc, old_e); + } + if ((!sort_func) && + (is_safe_closure(lessp))) /* no embedded sort! or call/cc, etc */ + { + sc->curlet = make_let_with_two_slots(sc, closure_let(lessp), car(largs), sc->F, cadr(largs), sc->F); + sc->sort_body = car(closure_body(lessp)); + sc->sort_begin = cdr(closure_body(lessp)); + sort_func = (is_null(sc->sort_begin)) ? closure_sort : closure_sort_begin; + sc->sort_op = (is_syntactic_pair(sc->sort_body)) ? (opcode_t)optimize_op(sc->sort_body) : (opcode_t)OP_EVAL; + sc->sort_v1 = let_slots(sc->curlet); + sc->sort_v2 = next_slot(let_slots(sc->curlet)); + }}}} + + switch (type(data)) + { + case T_PAIR: + len = s7_list_length(sc, data); /* 0 here == infinite */ + if (len <= 0) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_2(sc, wrap_string(sc, "sort! first argument should be a proper list: ~S", 48), data)); + if (len < 2) + return(data); + if (sort_func) + { + s7_int i = 0; + s7_pointer vec = g_vector(sc, data); + gc_protect_2_via_stack(sc, vec, data); + elements = s7_vector_elements(vec); + local_qsort_r((void *)elements, len, sizeof(s7_pointer), sort_func, (void *)sc); + for (s7_pointer p = data; i < len; i++, p = cdr(p)) + { + if (is_immutable_pair(p)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->sort_symbol, data)); + set_car(p, elements[i]); + } + unstack(sc); /* not pop_stack! */ + return(data); + } + push_stack(sc, OP_SORT_PAIR_END, cons(sc, data, lessp), sc->code); /* save and gc protect the original list and func */ + set_car(args, g_vector(sc, data)); + break; + + case T_BYTE_VECTOR: + case T_STRING: + { + s7_int i; + s7_pointer vec; + uint8_t *chrs; + if (is_string(data)) + { + len = string_length(data); + chrs = (uint8_t *)string_value(data); + } + else + { + len = byte_vector_length(data); + chrs = byte_vector_bytes(data); + } + if (len < 2) return(data); + if (is_c_function(lessp)) + { + if (((is_string(data)) && (sc->sort_f == char_lt_b_7pp)) || + ((is_byte_vector(data)) && (sc->sort_f == lt_b_7pp))) + { + qsort((void *)chrs, len, sizeof(uint8_t), byte_less); + return(data); + } + if (((is_string(data)) && (sc->sort_f == char_gt_b_7pp)) || + ((is_byte_vector(data)) && (sc->sort_f == gt_b_7pp))) + { + qsort((void *)chrs, len, sizeof(uint8_t), byte_greater); + return(data); + }} + vec = make_simple_vector(sc, len); + gc_protect_2_via_stack(sc, vec, data); + elements = s7_vector_elements(vec); + if (is_byte_vector(data)) + for (i = 0; i < len; i++) elements[i] = small_int(chrs[i]); + else for (i = 0; i < len; i++) elements[i] = chars[chrs[i]]; + if (sort_func) + { + local_qsort_r((void *)elements, len, sizeof(s7_pointer), sort_func, (void *)sc); + if (is_byte_vector(data)) + for (i = 0; i < len; i++) chrs[i] = (char)integer(elements[i]); + else for (i = 0; i < len; i++) chrs[i] = character(elements[i]); + unstack(sc); /* not pop_stack! */ + return(data); + } + unstack(sc); /* not pop_stack! */ + push_stack(sc, OP_SORT_STRING_END, cons_unchecked(sc, data, lessp), sc->code); + set_car(args, vec); + } + break; + + case T_INT_VECTOR: + case T_FLOAT_VECTOR: + { + s7_int i; + s7_pointer vec; + len = vector_length(data); + if (len < 2) + return(data); + if (is_c_function(lessp)) + { + if (sc->sort_f == lt_b_7pp) + { + if (is_float_vector(data)) + qsort((void *)float_vector_floats(data), len, sizeof(s7_double), dbl_less); + else qsort((void *)int_vector_ints(data), len, sizeof(s7_int), int_less); + return(data); + } + if (sc->sort_f == gt_b_7pp) + { + if (is_float_vector(data)) + qsort((void *)float_vector_floats(data), len, sizeof(s7_double), dbl_greater); + else qsort((void *)int_vector_ints(data), len, sizeof(s7_int), int_greater); + return(data); + }} + /* currently we have to make the ordinary vector here even if not sf1 + * because the sorter uses vector_element to access sort args (see SORT_DATA in eval). + * This is probably better than passing down getter/setter (fewer allocations). + * get/set macro in eval is SORT_DATA(k) then s7_vector_to_list if pair at start (sort_*_end) + */ + vec = make_vector_1(sc, len, FILLED, T_VECTOR); + gc_protect_2_via_stack(sc, vec, data); + /* we need this vector prefilled because make_real|integer below can cause a GC at any time during that loop, + * and the GC mark process expects the vector to have an s7_pointer at every element. + */ + add_vector(sc, vec); + elements = s7_vector_elements(vec); + check_free_heap_size(sc, len); + if (is_float_vector(data)) + for (i = 0; i < len; i++) elements[i] = make_real_unchecked(sc, float_vector(data, i)); + else for (i = 0; i < len; i++) elements[i] = make_integer_unchecked(sc, int_vector(data, i)); + if (sort_func) + { + local_qsort_r((void *)elements, len, sizeof(s7_pointer), sort_func, (void *)sc); + if (is_float_vector(data)) + for (i = 0; i < len; i++) float_vector(data, i) = real(elements[i]); + else for (i = 0; i < len; i++) int_vector(data, i) = integer(elements[i]); + unstack(sc); + return(data); + } + set_car(args, vec); + init_temp(sc->y, cons(sc, data, lessp)); + unstack(sc); + push_stack(sc, OP_SORT_VECTOR_END, sc->y, sc->code); /* save and gc protect the original homogeneous vector and func */ + sc->y = sc->unused; + } + break; + + case T_VECTOR: + len = vector_length(data); + if (len < 2) + return(data); + if (sort_func) + { + s7_pointer *els = s7_vector_elements(data); + int32_t typ = type(els[0]); + if ((typ == T_INTEGER) || (typ == T_REAL) || (typ == T_STRING) || (typ == T_CHARACTER)) + for (s7_int i = 1; i < len; i++) + if (type(els[i]) != typ) + { + typ = T_FREE; + break; + } + if ((sc->sort_f == lt_b_7pp) || (sc->sort_f == gt_b_7pp)) + { + if (typ == T_INTEGER) + { + qsort((void *)els, len, sizeof(s7_pointer), ((sc->sort_f == lt_b_7pp) ? int_less_2 : int_greater_2)); + return(data); + } + if (typ == T_REAL) + { + qsort((void *)els, len, sizeof(s7_pointer), ((sc->sort_f == lt_b_7pp) ? dbl_less_2 : dbl_greater_2)); + return(data); + }} + if ((typ == T_STRING) && + ((sc->sort_f == string_lt_b_7pp) || (sc->sort_f == string_gt_b_7pp))) + { + qsort((void *)els, len, sizeof(s7_pointer), ((sc->sort_f == string_lt_b_7pp) ? str_less_2 : str_greater_2)); + return(data); + } + if ((typ == T_CHARACTER) && + ((sc->sort_f == char_lt_b_7pp) || (sc->sort_f == char_gt_b_7pp))) + { + qsort((void *)els, len, sizeof(s7_pointer), ((sc->sort_f == char_lt_b_7pp) ? chr_less_2 : chr_greater_2)); + return(data); + } + local_qsort_r((void *)s7_vector_elements(data), len, sizeof(s7_pointer), sort_func, (void *)sc); + return(data); + } + break; + + default: + return(method_or_bust(sc, data, sc->sort_symbol, args, wrap_string(sc, "a sortable sequence", 19), 1)); + } + + n = len - 1; + k = (n / 2) + 1; + lx = make_simple_vector(sc, (sc->safety <= NO_SAFETY) ? 4 : 6); + normal_vector_fill(lx, sc->nil); /* make_mutable_integer below can trigger GC, so all elements of lx must be legit */ + init_temp(sc->y, lx); + vector_element(lx, 0) = make_mutable_integer(sc, n); + vector_element(lx, 1) = make_mutable_integer(sc, k); + vector_element(lx, 2) = make_mutable_integer(sc, 0); + vector_element(lx, 3) = make_mutable_integer(sc, 0); + if (sc->safety > NO_SAFETY) + { + vector_element(lx, 4) = make_mutable_integer(sc, 0); + vector_element(lx, 5) = make_integer_unchecked(sc, n * n); + } + push_stack(sc, OP_SORT, args, lx); + sc->y = sc->unused; + return(sc->F); + /* if the comparison function waffles, sort! can hang: (sort! '(1 2 3) (lambda (a b) (= a b))) + * set 'safety to 1 to add a check for this loop, but the "safe" procedures are direct, so unchecked. + */ +} + +/* these are for the eval sort -- sort a vector, then if necessary put that data into the original sequence */ +static s7_pointer vector_into_list(s7_scheme *sc, s7_pointer vect, s7_pointer lst) +{ + s7_pointer *elements = vector_elements(vect); + s7_int i = 0, len = vector_length(vect); + for (s7_pointer p = lst; i < len; i++, p = cdr(p)) + { + if (is_immutable_pair(p)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->sort_symbol, lst)); + set_car(p, elements[i]); + } + return(lst); +} + +static s7_pointer vector_into_fi_vector(s7_pointer source, s7_pointer dest) +{ + s7_pointer *elements = vector_elements(source); + s7_int len = vector_length(source); + if (is_float_vector(dest)) + { + s7_double *flts = float_vector_floats(dest); + for (s7_int i = 0; i < len; i++) flts[i] = real(elements[i]); + } + else + { + s7_int *ints = int_vector_ints(dest); + for (s7_int i = 0; i < len; i++) ints[i] = integer(elements[i]); + } + return(dest); +} + +static s7_pointer vector_into_string(s7_pointer vect, s7_pointer dest) +{ + s7_pointer *elements = vector_elements(vect); + s7_int len = vector_length(vect); + if (is_byte_vector(dest)) + { + uint8_t *str = (uint8_t *)byte_vector_bytes(dest); + for (s7_int i = 0; i < len; i++) str[i] = (uint8_t)integer(elements[i]); + } + else + { + uint8_t *str = (uint8_t *)string_value(dest); + for (s7_int i = 0; i < len; i++) str[i] = character(elements[i]); + } + return(dest); +} + +#define SORT_N integer(vector_element(sc->code, 0)) +#define SORT_K integer(vector_element(sc->code, 1)) +#define SORT_J integer(vector_element(sc->code, 2)) +#define SORT_K1 integer(vector_element(sc->code, 3)) +#define SORT_CALLS integer(vector_element(sc->code, 4)) +#define SORT_STOP integer(vector_element(sc->code, 5)) +#define SORT_DATA(K) vector_element(car(sc->args), K) +#define SORT_LESSP cadr(sc->args) + +static s7_pointer op_heapsort(s7_scheme *sc) +{ + s7_int n = SORT_N, j, k = SORT_K1; + + if ((n == k) || (k > ((s7_int)(n / 2)))) /* k == n == 0 is the first case */ + return(sc->code); + if (sc->safety > NO_SAFETY) + { + SORT_CALLS++; + if (SORT_CALLS > SORT_STOP) + error_nr(sc, sc->out_of_range_symbol, set_elist_2(sc, wrap_string(sc, "sort! is caught in an infinite loop, comparison: ~S", 51), SORT_LESSP)); + } + j = 2 * k; + SORT_J = j; + if (j < n) + { + s7_pointer lx = SORT_LESSP; /* cadr of sc->args */ + push_stack_direct(sc, OP_SORT1); + if (needs_copied_args(lx)) + sc->args = list_2(sc, SORT_DATA(j), SORT_DATA(j + 1)); + else sc->args = with_list_t2(SORT_DATA(j), SORT_DATA(j + 1)); + sc->code = lx; + sc->value = sc->T; /* for eval */ + } + else sc->value = sc->F; + return(NULL); +} + +static bool op_sort1(s7_scheme *sc) +{ + s7_int j = SORT_J, k = SORT_K1; + s7_pointer lx = SORT_LESSP; + if (is_true(sc, sc->value)) + { + j = j + 1; + SORT_J = j; + } + push_stack_direct(sc, OP_SORT2); + if (needs_copied_args(lx)) + sc->args = list_2(sc, SORT_DATA(k), SORT_DATA(j)); + else sc->args = with_list_t2(SORT_DATA(k), SORT_DATA(j)); + sc->code = lx; + return(false); +} + +static bool op_sort2(s7_scheme *sc) +{ + s7_int j = SORT_J, k = SORT_K1; + if (is_true(sc, sc->value)) + { + s7_pointer lx = SORT_DATA(j); + SORT_DATA(j) = SORT_DATA(k); + SORT_DATA(k) = lx; + } + else return(true); + SORT_K1 = SORT_J; + return(false); +} + +static bool op_sort(s7_scheme *sc) +{ + /* coming in sc->args is sort args (data less?), sc->code = #(n k 0 ...) + * here we call the inner loop until k <= 0 [the local k! -- this is tricky because scheme passes args by value] + */ + s7_int k = SORT_K; + if (k > 0) + { + SORT_K = k - 1; + SORT_K1 = k - 1; + push_stack_direct(sc, OP_SORT); + return(false); + } + return(true); +} + +static bool op_sort3(s7_scheme *sc) +{ + s7_int n = SORT_N; + s7_pointer lx; + if (n <= 0) + { + sc->value = car(sc->args); + return(true); + } + lx = SORT_DATA(0); + SORT_DATA(0) = SORT_DATA(n); + SORT_DATA(n) = lx; + SORT_N = n - 1; + SORT_K1 = 0; + push_stack_direct(sc, OP_SORT3); + return(false); +} + + +/* -------- hash tables -------- */ + +static void free_hash_table(s7_scheme *sc, s7_pointer table) +{ + if (hash_table_entries(table) > 0) + { + hash_entry_t **entries = hash_table_elements(table); + s7_int len = hash_table_mask(table) + 1; + for (s7_int i = 0; i < len; i++) + { + hash_entry_t *n; + for (hash_entry_t *p = entries[i++]; p; p = n) + { + n = hash_entry_next(p); + liberate_block(sc, p); + } + for (hash_entry_t *p = entries[i]; p; p = n) + { + n = hash_entry_next(p); + liberate_block(sc, p); + }}} + liberate(sc, hash_table_block(table)); +} + +static hash_entry_t *make_hash_entry(s7_scheme *sc, s7_pointer key, s7_pointer value, s7_int raw_hash) +{ + hash_entry_t *p = (hash_entry_t *)mallocate_block(sc); + hash_entry_key(p) = key; + hash_entry_set_value(p, value); + hash_entry_set_raw_hash(p, raw_hash); + return(p); +} + + +/* -------------------------------- hash-table? -------------------------------- */ +bool s7_is_hash_table(s7_pointer p) {return(is_hash_table(p));} + +static s7_pointer g_is_hash_table(s7_scheme *sc, s7_pointer args) +{ + #define H_is_hash_table "(hash-table? obj) returns #t if obj is a hash-table" + #define Q_is_hash_table sc->pl_bt + check_boolean_method(sc, is_hash_table, sc->is_hash_table_symbol, args); +} + + +/* -------------------------------- hash-table-entries -------------------------------- */ +static s7_pointer g_hash_table_entries(s7_scheme *sc, s7_pointer args) +{ + #define H_hash_table_entries "(hash-table-entries obj) returns the number of entries in the hash-table obj" + #define Q_hash_table_entries s7_make_signature(sc, 2, sc->is_integer_symbol, sc->is_hash_table_symbol) + + if (!is_hash_table(car(args))) + return(sole_arg_method_or_bust(sc, car(args), sc->hash_table_entries_symbol, args, sc->type_names[T_HASH_TABLE])); + return(make_integer(sc, hash_table_entries(car(args)))); +} + +static s7_int hash_table_entries_i_7p(s7_scheme *sc, s7_pointer p) +{ + if (!is_hash_table(p)) + return(integer(method_or_bust_p(sc, p, sc->hash_table_entries_symbol, sc->type_names[T_HASH_TABLE]))); + return(hash_table_entries(p)); +} + + +/* -------------------------------- hash-table-key|value-typer -------------------------------- */ +static s7_pointer g_hash_table_key_typer(s7_scheme *sc, s7_pointer args) +{ + #define H_hash_table_key_typer "(hash-table-key-typer hash) returns the hash-table's key type checking function" + #define Q_hash_table_key_typer s7_make_signature(sc, 2, s7_make_signature(sc, 2, sc->not_symbol, sc->is_procedure_symbol), sc->is_hash_table_symbol) + + s7_pointer h = car(args); + if (!is_hash_table(h)) return(sole_arg_method_or_bust(sc, h, sc->hash_table_key_typer_symbol, args, sc->type_names[T_HASH_TABLE])); + if (is_typed_hash_table(h)) return(hash_table_key_typer(h)); + return(sc->F); +} + +static s7_pointer g_hash_table_value_typer(s7_scheme *sc, s7_pointer args) +{ + #define H_hash_table_value_typer "(hash-table-value-typer hash) returns the hash-table's value type checking function" + #define Q_hash_table_value_typer s7_make_signature(sc, 2, s7_make_signature(sc, 2, sc->not_symbol, sc->is_procedure_symbol), sc->is_hash_table_symbol) + + s7_pointer h = car(args); + if (!is_hash_table(h)) return(sole_arg_method_or_bust(sc, h, sc->hash_table_value_typer_symbol, args, sc->type_names[T_HASH_TABLE])); + if (is_typed_hash_table(h)) return(hash_table_value_typer(h)); + return(sc->F); +} + +static s7_pointer make_hash_table_procedures(s7_scheme *sc) +{ + s7_pointer x = cons(sc, sc->T, sc->T); /* checker, mapped */ + set_opt1_any(x, sc->T); /* key */ + set_opt2_any(x, sc->T); /* value */ + return(x); +} + +static s7_pointer copy_hash_table_procedures(s7_scheme *sc, s7_pointer table) +{ + if (is_pair(hash_table_procedures(table))) + { + s7_pointer x = cons(sc, hash_table_procedures_checker(table), hash_table_procedures_mapper(table)); + set_opt1_any(x, hash_table_key_typer(table)); + set_opt2_any(x, hash_table_value_typer(table)); + return(x); + } + return(sc->nil); +} + +static void check_hash_table_typer(s7_scheme *sc, s7_pointer caller, s7_pointer h, s7_pointer typer) +{ + if (is_c_function(typer)) + { + s7_pointer sig = c_function_signature(typer); + if ((sig != sc->pl_bt) && + (is_pair(sig)) && + ((car(sig) != sc->is_boolean_symbol) || (cadr(sig) != sc->T) || (!is_null(cddr(sig))))) + wrong_type_error_nr(sc, caller, 2, typer, wrap_string(sc, "a boolean procedure", 19)); + if (!c_function_name(typer)) + wrong_type_error_nr(sc, caller, 2, typer, wrap_string(sc, "a named function", 16)); + } + else + { + if (!is_any_closure(typer)) + wrong_type_error_nr(sc, caller, 2, typer, wrap_string(sc, "a built-in procedure, a closure or #t", 37)); + if (!is_symbol(find_closure(sc, typer, closure_let(typer)))) + wrong_type_error_nr(sc, caller, 2, typer, wrap_string(sc, "a named function", 16)); + } + if (!s7_is_aritable(sc, typer, 1)) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_3(sc, wrap_string(sc, "~A: the second argument, ~S, (the type checker) should accept one argument", 74), caller, typer)); + if (is_c_function(typer)) + { + if (!c_function_symbol(typer)) + c_function_symbol(typer) = make_symbol(sc, c_function_name(typer), c_function_name_length(typer)); + if (c_function_has_simple_elements(typer)) + { + if (caller == sc->hash_table_value_typer_symbol) + set_has_simple_values(h); + else + { + set_has_simple_keys(h); + if (symbol_type(c_function_symbol(typer)) != T_FREE) + set_has_hash_key_type(h); + }}} + if (is_null(hash_table_procedures(h))) + hash_table_set_procedures(h, make_hash_table_procedures(sc)); + set_is_typed_hash_table(h); +} + +static s7_pointer g_set_hash_table_key_typer(s7_scheme *sc, s7_pointer args) +{ + s7_pointer h = car(args), typer = cadr(args); + if (!is_hash_table(h)) + wrong_type_error_nr(sc, wrap_string(sc, "set! hash_table-key-typer", 25), 1, h, sc->type_names[T_HASH_TABLE]); + + if (is_boolean(typer)) /* remove current typer, if any */ + { + if (is_typed_hash_table(h)) + { + hash_table_set_key_typer(h, sc->T); + if (hash_table_value_typer(h) == sc->T) + clear_has_simple_keys(h); /* not clear_is_typed_hash_table(h); and clear_has_hash_key_type?? looks redundant */ + }} + else + { + check_hash_table_typer(sc, sc->hash_table_key_typer_symbol, h, typer); + hash_table_set_key_typer(h, typer); + } + return(typer); +} + +static s7_pointer g_set_hash_table_value_typer(s7_scheme *sc, s7_pointer args) +{ + s7_pointer h = car(args), typer = cadr(args); + if (!is_hash_table(h)) + wrong_type_error_nr(sc, wrap_string(sc, "set! hash_table-value-typer", 27), 1, h, sc->type_names[T_HASH_TABLE]); + + if (is_boolean(typer)) /* remove current typer, if any */ + { + if (is_typed_hash_table(h)) + { + hash_table_set_value_typer(h, sc->T); + if (hash_table_key_typer(h) == sc->T) + clear_has_simple_values(h); /* not clear_is_typed_hash_table(h); */ + }} + else + { + check_hash_table_typer(sc, sc->hash_table_value_typer_symbol, h, typer); + hash_table_set_value_typer(h, typer); + } + return(typer); +} + + +/* ---------------- hash map and equality tables ---------------- */ +/* built in hash loc tables for eq? eqv? equal? equivalent? = string=? string-ci=? char=? char-ci=? (default=equal?) */ +#define hash_loc(Sc, Table, Key) (*(hash_table_mapper(Table)[type(Key)]))(Sc, Table, Key) + +static hash_map_t eq_hash_map[NUM_TYPES]; +static hash_map_t string_eq_hash_map[NUM_TYPES]; +static hash_map_t char_eq_hash_map[NUM_TYPES]; +static hash_map_t closure_hash_map[NUM_TYPES]; +static hash_map_t equivalent_hash_map[NUM_TYPES]; +static hash_map_t c_function_hash_map[NUM_TYPES]; +#if (!WITH_PURE_S7) +static hash_map_t string_ci_eq_hash_map[NUM_TYPES]; +static hash_map_t char_ci_eq_hash_map[NUM_TYPES]; +#endif +/* also default_hash_map */ + + +/* ---------------- hash-code ---------------- */ +/* eqfunc handling which will require other dummy tables */ + +static s7_pointer make_dummy_hash_table(s7_scheme *sc) /* make the absolute minimal hash-table that can support hash-code */ +{ + s7_pointer table = alloc_pointer(sc); + set_type_bit(table, T_IMMUTABLE | T_HASH_TABLE | T_UNHEAP); + hash_table_mapper(table) = default_hash_map; + return(table); +} + +s7_int s7_hash_code(s7_scheme *sc, s7_pointer obj, s7_pointer eqfunc) +{ + return(default_hash_map[type(obj)](sc, sc->dummy_equal_hash_table, obj)); +} + +static s7_pointer g_hash_code(s7_scheme *sc, s7_pointer args) +{ + #define H_hash_code "(hash-code obj (eqfunc)) returns an integer suitable for use as a hash code for obj." + #define Q_hash_code s7_make_signature(sc, 3, sc->is_integer_symbol, sc->T, sc->T) + s7_pointer obj = car(args); + if ((is_pair(cdr(args))) && + (!is_procedure(cadr(args)))) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_2(sc, wrap_string(sc, "hash-code second argument (currently ignored) should be a function: ~S", 70), cadr(args))); + return(make_integer(sc, default_hash_map[type(obj)](sc, sc->dummy_equal_hash_table, obj))); +} + + +static bool (*equals[NUM_TYPES])(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci); +static bool (*equivalents[NUM_TYPES])(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci); + +static hash_entry_t *(*default_hash_checks[NUM_TYPES])(s7_scheme *sc, s7_pointer table, s7_pointer key); +static hash_entry_t *(*equal_hash_checks[NUM_TYPES])(s7_scheme *sc, s7_pointer table, s7_pointer key); + + +/* ---------------- hash empty ---------------- */ +static hash_entry_t *hash_empty(s7_scheme *sc, s7_pointer table, s7_pointer key) {return(sc->unentry);} + +/* ---------------- hash syntax ---------------- */ +static s7_int hash_map_syntax(s7_scheme *sc, s7_pointer table, s7_pointer key) {return(pointer_map(syntax_symbol(key)));} + +static hash_entry_t *hash_equal_syntax(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + s7_int loc = hash_loc(sc, table, key) & hash_table_mask(table); + for (hash_entry_t *x = hash_table_element(table, loc); x; x = hash_entry_next(x)) + if ((is_syntax(hash_entry_key(x))) && + (syntax_symbol(hash_entry_key(x)) == syntax_symbol(key))) /* the opcodes might differ, but the symbols should not */ + return(x); + return(sc->unentry); +} + +/* ---------------- hash symbols ---------------- */ +static s7_int hash_map_symbol(s7_scheme *sc, s7_pointer table, s7_pointer key) {return(pointer_map(key));} + +static hash_entry_t *hash_symbol(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + for (hash_entry_t *x = hash_table_element(table, pointer_map(key) & hash_table_mask(table)); x; x = hash_entry_next(x)) + if (key == hash_entry_key(x)) + return(x); + return(sc->unentry); +} + + +/* ---------------- hash numbers ---------------- */ +static s7_int hash_float_location(s7_double x) {return(((is_NaN(x)) || (is_inf(x)) || (fabs(x) > DOUBLE_TO_INT64_LIMIT)) ? 0 : (s7_int)floor(fabs(x)));} + /* isnormal here in place of is_NaN and is_inf is slower */ + +static s7_int hash_map_int(s7_scheme *sc, s7_pointer table, s7_pointer key) {return(s7_int_abs(integer(key)));} +static s7_int hash_map_real(s7_scheme *sc, s7_pointer table, s7_pointer key) {return(hash_float_location(real(key)));} +static s7_int hash_map_complex(s7_scheme *sc, s7_pointer table, s7_pointer key) {return(hash_float_location(real_part(key)));} + +static s7_int hash_map_ratio(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + /* if numerator is -9223372036854775808, s7_int_abs overflows -- need to divide, then abs: -9223372036854775808/3: -3074457345618258602 3074457345618258602 + * (s7_int)floorl(fabsl(fraction(key))) is no good here, 3441313796169221281/1720656898084610641: 1 2 (in valgrind), + * floor ratio is 1: (- (* 2 1720656898084610641) 3441313796169221281) -> 1 + * or (gmp:) 1.999999999999999999418826611445214136431E0, so the floorl(fabsl) version is wrong + */ + return(s7_int_abs(numerator(key) / denominator(key))); +} + +#if WITH_GMP +static s7_int hash_map_big_int(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + /* may need to use quotient here */ + mpz_abs(sc->mpz_1, big_integer(key)); + return(mpz_get_si(sc->mpz_1)); /* returns the bits that fit */ +} + +static s7_int hash_map_big_ratio(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + mpq_abs(sc->mpq_1, big_ratio(key)); + mpz_fdiv_q(sc->mpz_1, mpq_numref(sc->mpq_1), mpq_denref(sc->mpq_1)); + return(mpz_get_si(sc->mpz_1)); +} + +static s7_int hash_map_big_real_1(s7_scheme *sc, s7_pointer table, mpfr_t key) +{ + if ((mpfr_nan_p(key)) || (mpfr_inf_p(key))) return(0); + mpfr_abs(sc->mpfr_1, key, MPFR_RNDN); + /* mpfr_get_si returns most-positive-int if > 2^63! luckily there aren't any more of these */ + mpfr_get_z(sc->mpz_1, sc->mpfr_1, MPFR_RNDD); /* floor not round */ + return(mpz_get_si(sc->mpz_1)); +} + +static s7_int hash_map_big_real(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + return(hash_map_big_real_1(sc, table, big_real(key))); +} + +static s7_int hash_map_big_complex(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + return(hash_map_big_real_1(sc, table, mpc_realref(big_complex(key)))); +} +#endif + +static hash_entry_t *find_number_in_bin(s7_scheme *sc, hash_entry_t *bin, s7_pointer key) +{ + s7_double old_eps = sc->equivalent_float_epsilon; + bool (*equiv)(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) = equivalents[type(key)]; + sc->equivalent_float_epsilon = sc->hash_table_float_epsilon; + for (; bin; bin = hash_entry_next(bin)) + if (equiv(sc, key, hash_entry_key(bin), NULL)) + { + sc->equivalent_float_epsilon = old_eps; + return(bin); + } + sc->equivalent_float_epsilon = old_eps; + return(NULL); +} + +static hash_entry_t *hash_number_equivalent(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + /* for equivalent? and =, kind of complicated because two bins can be involved if the key is close to an integer */ +#if WITH_GMP + /* first try loc from hash_loc, then get key-floor(key) [with abs], and check against + * epsilon: diff < eps call find big in bin-1, diff > 1.0-eps call same in bin+1 + */ + s7_int loc1, hash_mask = hash_table_mask(table); + s7_int loc = hash_loc(sc, table, key); + s7_int hash_loc = loc & hash_mask; + hash_entry_t *i1 = find_number_in_bin(sc, hash_table_element(table, hash_loc), key); + if (i1) return(i1); + + if (is_real(key)) + { + s7_pointer res = any_real_to_mpfr(sc, key, sc->mpfr_1); + if (res) return(sc->unentry); + } + else + if (is_t_complex(key)) + mpfr_set_d(sc->mpfr_1, real_part(key), MPFR_RNDN); + else mpfr_set(sc->mpfr_1, mpc_realref(big_complex(key)), MPFR_RNDN); + + /* mpfr_1 is big_real, so we can use hash_loc of big_real (and can ignore NaN's): */ + mpfr_abs(sc->mpfr_1, sc->mpfr_1, MPFR_RNDN); + mpfr_add_d(sc->mpfr_2, sc->mpfr_1, sc->hash_table_float_epsilon, MPFR_RNDN); + mpfr_get_z(sc->mpz_1, sc->mpfr_2, MPFR_RNDD); + loc1 = mpz_get_si(sc->mpz_1); + if (loc1 != loc) + { + if (loc1 == hash_table_mask(table)) loc1 = 0; + hash_loc = loc1 & hash_mask; + i1 = find_number_in_bin(sc, hash_table_element(table, hash_loc), key); + return((i1) ? i1 : sc->unentry); + } + mpfr_sub_d(sc->mpfr_2, sc->mpfr_1, sc->hash_table_float_epsilon, MPFR_RNDN); + mpfr_get_z(sc->mpz_1, sc->mpfr_2, MPFR_RNDD); + loc1 = mpz_get_si(sc->mpz_1); + if (loc1 != loc) + { + if (loc1 < 0) loc1 = hash_table_mask(table); + hash_loc = loc1 & hash_mask; + i1 = find_number_in_bin(sc, hash_table_element(table, hash_loc), key); + if (i1) return(i1); + } + return(sc->unentry); +#else + s7_double keyval = (is_real(key)) ? s7_real(key) : real_part(key); + s7_double fprobe = fabs(keyval); + s7_int iprobe = (s7_int)floor(fprobe); + s7_double bin_dist = fprobe - iprobe; + s7_int loc = iprobe & hash_table_mask(table); + hash_entry_t *i1 = find_number_in_bin(sc, hash_table_element(table, loc), key); + if (i1) return(i1); + + if (bin_dist <= sc->hash_table_float_epsilon) /* maybe closest is below iprobe, key+eps>iprobe but key maps to iprobe-1 */ + i1 = find_number_in_bin(sc, hash_table_element(table, (loc > 0) ? loc - 1 : hash_table_mask(table)), key); + else + if (bin_dist >= (1.0 - sc->hash_table_float_epsilon)) + i1 = find_number_in_bin(sc, hash_table_element(table, (loc < hash_table_mask(table)) ? loc + 1 : 0), key); + return((i1) ? i1 : sc->unentry); +#endif +} + +static hash_entry_t *hash_int(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ +#if WITH_GMP + if ((is_t_integer(key)) || (is_t_big_integer(key))) +#else + if (is_t_integer(key)) +#endif + { + s7_int hash_mask = hash_table_mask(table); + hash_entry_t *x; +#if WITH_GMP + s7_int kv = (is_t_integer(key)) ? integer(key) : mpz_get_si(big_integer(key)); +#else + s7_int kv = integer(key); +#endif + s7_int loc = s7_int_abs(kv) & hash_mask; + for (x = hash_table_element(table, loc); x; x = hash_entry_next(x)) +#if WITH_GMP + if (is_t_integer(hash_entry_key(x))) + { + if (integer(hash_entry_key(x)) == kv) + return(x); + } + else + if ((is_t_big_integer(hash_entry_key(x))) && + (mpz_get_si(big_integer(hash_entry_key(x))) == kv)) + return(x); +#else + if (integer(hash_entry_key(x)) == kv) + return(x); +#endif + } + return(sc->unentry); +} + +static hash_entry_t *hash_float(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + /* if a hash-table has only t_real keys, its checker is hash_float, but we might use a t_big_real key */ +#if WITH_GMP + if ((is_t_real(key)) || (is_t_big_real(key))) +#else + if (is_t_real(key)) +#endif + { + s7_double keyval; + s7_int loc, hash_mask; +#if WITH_GMP + if (is_t_real(key)) + { + keyval = real(key); + if (is_NaN(keyval)) return(sc->unentry); + } + else + { + if (mpfr_nan_p(big_real(key))) return(sc->unentry); + keyval = mpfr_get_d(big_real(key), MPFR_RNDN); + } +#else + keyval = real(key); + if (is_NaN(keyval)) return(sc->unentry); +#endif + hash_mask = hash_table_mask(table); + loc = hash_float_location(keyval) & hash_mask; + + for (hash_entry_t *x = hash_table_element(table, loc); x; x = hash_entry_next(x)) + { + if ((is_t_real(hash_entry_key(x))) && + (keyval == real(hash_entry_key(x)))) + return(x); +#if WITH_GMP + if ((is_t_big_real(hash_entry_key(x))) && + (mpfr_cmp_d(big_real(hash_entry_key(x)), keyval) == 0) && + (!mpfr_nan_p(big_real(hash_entry_key(x))))) + return(x); +#endif + }} + return(sc->unentry); +} + +static hash_entry_t *hash_num_eq(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + s7_int hash_mask = hash_table_mask(table); + s7_int loc = hash_loc(sc, table, key) & hash_mask; + for (hash_entry_t *x = hash_table_element(table, loc); x; x = hash_entry_next(x)) + if (num_eq_b_7pp(sc, key, hash_entry_key(x))) + return(x); + return(sc->unentry); +} + +static hash_entry_t *hash_real_num_eq(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ +#if WITH_GMP + if ((is_t_real(key)) && (is_NaN(real(key)))) return(sc->unentry); + if ((is_t_big_real(key)) && (mpfr_nan_p(big_real(key)))) return(sc->unentry); + return(hash_num_eq(sc, table, key)); +#else + return((is_NaN(s7_real(key))) ? sc->unentry : hash_num_eq(sc, table, key)); +#endif +} + +static hash_entry_t *hash_complex_num_eq(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ +#if WITH_GMP + if ((is_t_complex(key)) && ((is_NaN(real_part(key))) || (is_NaN(imag_part(key))))) return(sc->unentry); + if ((is_t_big_complex(key)) && ((mpfr_nan_p(mpc_realref(big_complex(key)))) || (mpfr_nan_p(mpc_imagref(big_complex(key)))))) return(sc->unentry); + return(hash_num_eq(sc, table, key)); +#else + return(((is_NaN(real_part(key))) || (is_NaN(imag_part(key)))) ? sc->unentry : hash_num_eq(sc, table, key)); +#endif +} + +static hash_entry_t *hash_number_num_eq(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + if (is_number(key)) + { +#if (!WITH_GMP) + s7_int hash_mask = hash_table_mask(table); + hash_map_t map = hash_table_mapper(table)[type(key)]; + if (hash_table_checker(table) == hash_int) /* surely by far the most common case? only ints */ + { + s7_int keyi = integer(key); + s7_int loc = map(sc, table, key) & hash_mask; + for (hash_entry_t *x = hash_table_element(table, loc); x; x = hash_entry_next(x)) + if (keyi == integer(hash_entry_key(x))) /* not in gmp, hash_int as eq_func, what else can key be but t_integer? */ + return(x); + } + else +#endif + return((is_real(key)) ? hash_real_num_eq(sc, table, key) : hash_complex_num_eq(sc, table, key)); + } + return(sc->unentry); +} + + +/* ---------------- hash characters ---------------- */ +static s7_int hash_map_char(s7_scheme *sc, s7_pointer table, s7_pointer key) {return(character(key));} + +static hash_entry_t *hash_char(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + if (is_character(key)) + { + /* return(hash_eq(sc, table, key)); + * but I think if we get here at all, we have to be using default_hash_checks|maps -- see hash_symbol above. + */ + s7_int loc = character(key) & hash_table_mask(table); + for (hash_entry_t *x = hash_table_element(table, loc); x; x = hash_entry_next(x)) + if (key == hash_entry_key(x)) + return(x); + } + return(sc->unentry); +} + +#if (!WITH_PURE_S7) +static s7_int hash_map_ci_char(s7_scheme *sc, s7_pointer table, s7_pointer key) {return(upper_character(key));} + +static hash_entry_t *hash_ci_char(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + if (is_character(key)) + { + s7_int hash_mask = hash_table_mask(table); + s7_int loc = hash_loc(sc, table, key) & hash_mask; + for (hash_entry_t *x = hash_table_element(table, loc); x; x = hash_entry_next(x)) + if (upper_character(key) == upper_character(hash_entry_key(x))) + return(x); + } + return(sc->unentry); +} +#endif + + +/* ---------------- hash strings ---------------- */ +static s7_int hash_map_string(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + if (string_hash(key) == 0) + string_hash(key) = raw_string_hash((const uint8_t *)string_value(key), string_length(key)); + return(string_hash(key)); +} + +static hash_entry_t *hash_string(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + if (is_string(key)) + { + hash_entry_t *x; + s7_int key_len = string_length(key), hash_mask = hash_table_mask(table); + uint64_t hash; + const char *key_str = string_value(key); + + if (string_hash(key) == 0) + string_hash(key) = raw_string_hash((const uint8_t *)string_value(key), string_length(key)); + hash = string_hash(key); + if (key_len <= 8) + { + for (x = hash_table_element(table, hash & hash_mask); x; x = hash_entry_next(x)) + if ((hash == string_hash(hash_entry_key(x))) && + (key_len == string_length(hash_entry_key(x)))) + return(x); + } + else + for (x = hash_table_element(table, hash & hash_mask); x; x = hash_entry_next(x)) + if ((hash == string_hash(hash_entry_key(x))) && + (key_len == string_length(hash_entry_key(x))) && /* these are scheme strings, so we can't assume 0=end of string */ + (strings_are_equal_with_length(key_str, string_value(hash_entry_key(x)), key_len))) + return(x); + } + return(sc->unentry); +} + +#if (!WITH_PURE_S7) +static s7_int hash_map_ci_string(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + s7_int len = string_length(key); + return((len == 0) ? 0 : (len + (uppers[(int32_t)(string_value(key)[0])] << 4))); +} + +static hash_entry_t *hash_ci_string(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + if (is_string(key)) + { + s7_int hash_mask = hash_table_mask(table); + s7_int hash = hash_map_ci_string(sc, table, key); + for (hash_entry_t *x = hash_table_element(table, hash & hash_mask); x; x = hash_entry_next(x)) + if (scheme_strequal_ci(key, hash_entry_key(x))) + return(x); + } + return(sc->unentry); +} +#endif + + +/* ---------------- hash eq? ---------------- */ +static s7_int hash_map_nil(s7_scheme *sc, s7_pointer table, s7_pointer key) {return(type(key));} + +static s7_int hash_map_eq(s7_scheme *sc, s7_pointer table, s7_pointer key) {return(pointer_map(key));} + +static hash_entry_t *hash_eq(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + /* explicit eq? as hash equality func or (for example) symbols as keys */ + s7_int hash_mask = hash_table_mask(table); + s7_int loc = pointer_map(key) & hash_mask; /* hash_map_eq */ + for (hash_entry_t *x = hash_table_element(table, loc); x; x = hash_entry_next(x)) + if (key == hash_entry_key(x)) + return(x); + return(sc->unentry); +} + +/* ---------------- hash eqv? ---------------- */ +static hash_entry_t *hash_eqv(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + hash_entry_t *x; + s7_int hash_mask = hash_table_mask(table); + s7_int loc = hash_loc(sc, table, key) & hash_mask; + if (is_number(key)) + { +#if WITH_GMP + for (x = hash_table_element(table, loc); x; x = hash_entry_next(x)) + if (numbers_are_eqv(sc, key, hash_entry_key(x))) + return(x); +#else + uint8_t key_type = type(key); + for (x = hash_table_element(table, loc); x; x = hash_entry_next(x)) + if ((key_type == type(hash_entry_key(x))) && + (numbers_are_eqv(sc, key, hash_entry_key(x)))) + return(x); +#endif + } + else + for (x = hash_table_element(table, loc); x; x = hash_entry_next(x)) + if (s7_is_eqv(sc, key, hash_entry_key(x))) + return(x); + return(sc->unentry); +} + +/* ---------------- hash equal? ---------------- */ +static s7_int hash_map_hash_table(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + /* hash-tables are equal if key/values match independent of table size and entry order. + * if not using equivalent?, hash_table_checker|mapper must also be the same. + * Keys are supposed to be constant while keys, so a hash-table shouldn't be a key of itself. + */ + return(hash_table_entries(key)); +} + +static s7_int hash_map_int_vector(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + if (vector_length(key) == 0) + return(0); + if (vector_length(key) == 1) + return(s7_int_abs(int_vector(key, 0))); + return(vector_length(key) + s7_int_abs(int_vector(key, 0)) + s7_int_abs(int_vector(key, 1))); /* overflow is ok here (in + or abs), as long as it's consistent */ +} + +static s7_int hash_map_byte_vector(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + if (byte_vector_length(key) == 0) + return(0); + if (byte_vector_length(key) == 1) + return((s7_int)byte_vector(key, 0)); + return(byte_vector_length(key) + byte_vector(key, 0) + byte_vector(key, 1)); +} + +static s7_int hash_map_float_vector(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + if (vector_length(key) == 0) + return(0); + if (vector_length(key) == 1) + return(hash_float_location(float_vector(key, 0))); + return(vector_length(key) + hash_float_location(float_vector(key, 0)) + hash_float_location(float_vector(key, 1))); +} + +static s7_int hash_map_vector(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + if ((vector_length(key) == 0) || + (is_sequence(vector_element(key, 0)))) + return(vector_length(key)); + if ((vector_length(key) == 1) || + (is_sequence(vector_element(key, 1)))) + return(hash_loc(sc, table, vector_element(key, 0))); + return(vector_length(key) + hash_loc(sc, table, vector_element(key, 0)) + hash_loc(sc, table, vector_element(key, 1))); /* see above */ +} + + +static s7_int hash_map_closure(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + s7_pointer f = hash_table_procedures_mapper(table); + if (f == sc->unused) + error_nr(sc, make_symbol(sc, "hash-map-recursion", 18), + set_elist_1(sc, wrap_string(sc, "hash-table map function called recursively", 42))); + /* check_stack_size(sc); -- perhaps clear typers as well here or save/restore hash-table-procedures */ + gc_protect_via_stack(sc, f); + hash_table_set_procedures_mapper(table, sc->unused); + sc->value = s7_call(sc, f, set_plist_1(sc, key)); + unstack(sc); + hash_table_set_procedures_mapper(table, f); + if (!s7_is_integer(sc->value)) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_2(sc, wrap_string(sc, "hash-table map function should return an integer: ~S", 52), sc->value)); + return(integer(sc->value)); +} + +static s7_int hash_map_let(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + /* lets are equal if same symbol/value pairs, independent of order, taking into account shadowing + * (length (inlet 'a 1 'a 2)) = 2 + * but this counts as just one entry from equal?'s point of view, so if more than one entry, we have a problem. + * (equal? (inlet 'a 1) (inlet 'a 3 'a 2 'a 1)) = #t + * also currently equal? follows outlet, but that is ridiculous here, so in this case hash equal? + * is not the same as equal? Surely anyone using lets as keys wants eq? + */ + s7_pointer slot; + s7_int slots; + + if ((key == sc->rootlet) || + (!tis_slot(let_slots(key)))) + return(0); + slot = let_slots(key); + if (!tis_slot(next_slot(slot))) + { + if (is_sequence(slot_value(slot))) /* avoid loop if cycles */ + return(pointer_map(slot_symbol(slot))); + return(pointer_map(slot_symbol(slot)) + hash_loc(sc, table, slot_value(slot))); + } + slots = 0; + for (; tis_slot(slot); slot = next_slot(slot)) + if (!is_matched_symbol(slot_symbol(slot))) + { + set_match_symbol(slot_symbol(slot)); + slots++; + } + for (slot = let_slots(key); tis_slot(slot); slot = next_slot(slot)) + clear_match_symbol(slot_symbol(slot)); + + if (slots != 1) + return(slots); + slot = let_slots(key); + if (is_sequence(slot_value(slot))) /* avoid loop if cycles */ + return(pointer_map(slot_symbol(slot))); + return(pointer_map(slot_symbol(slot)) + hash_loc(sc, table, slot_value(slot))); +} + +static hash_entry_t *hash_equal_eq(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + s7_int loc = hash_loc(sc, table, key) & hash_table_mask(table); + for (hash_entry_t *x = hash_table_element(table, loc); x; x = hash_entry_next(x)) + if (hash_entry_key(x) == key) + return(x); + return(sc->unentry); +} + +static hash_entry_t *hash_equal_integer(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + s7_int keyint = integer(key); + s7_int loc = s7_int_abs(keyint) & hash_table_mask(table); /* hash_loc -> hash_map_integer */ + for (hash_entry_t *x = hash_table_element(table, loc); x; x = hash_entry_next(x)) + { + if ((is_t_integer(hash_entry_key(x))) && + (keyint == integer(hash_entry_key(x)))) + return(x); +#if WITH_GMP + if ((is_t_big_integer(hash_entry_key(x))) && + (mpz_cmp_si(big_integer(hash_entry_key(x)), keyint) == 0)) + return(x); +#endif + } + return(sc->unentry); +} + +static hash_entry_t *hash_equal_ratio(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + s7_int keynum = numerator(key), keyden = denominator(key); + s7_int loc = s7_int_abs(keynum / keyden) & hash_table_mask(table); /* hash_loc -> hash_map_ratio */ + for (hash_entry_t *x = hash_table_element(table, loc); x; x = hash_entry_next(x)) + { + if ((is_t_ratio(hash_entry_key(x))) && + (keynum == numerator(hash_entry_key(x))) && + (keyden == denominator(hash_entry_key(x)))) + return(x); +#if WITH_GMP + if ((is_t_big_ratio(hash_entry_key(x))) && + (keynum == mpz_get_si(mpq_numref(big_ratio(hash_entry_key(x))))) && + (keyden == mpz_get_si(mpq_denref(big_ratio(hash_entry_key(x)))))) + return(x); +#endif + } + return(sc->unentry); +} + +static hash_entry_t *hash_equal_real(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + s7_int loc; + s7_double keydbl = real(key); + if (is_NaN(keydbl)) return(sc->unentry); + loc = hash_float_location(keydbl) & hash_table_mask(table); + for (hash_entry_t *x = hash_table_element(table, loc); x; x = hash_entry_next(x)) + { + if ((is_t_real(hash_entry_key(x))) && + (keydbl == real(hash_entry_key(x)))) + return(x); +#if WITH_GMP + if ((is_t_big_real(hash_entry_key(x))) && + (mpfr_cmp_d(big_real(hash_entry_key(x)), keydbl) == 0) && + (!mpfr_nan_p(big_real(hash_entry_key(x))))) + return(x); +#endif + } + return(sc->unentry); +} + +static hash_entry_t *hash_equal_complex(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + s7_int loc; + s7_double keyrl = real_part(key); + s7_double keyim = imag_part(key); +#if WITH_GMP + if ((is_NaN(keyrl)) || (is_NaN(keyim))) return(sc->unentry); +#endif + loc = hash_float_location(keyrl) & hash_table_mask(table); + for (hash_entry_t *x = hash_table_element(table, loc); x; x = hash_entry_next(x)) + { + if ((is_t_complex(hash_entry_key(x))) && + (keyrl == real_part(hash_entry_key(x))) && + (keyim == imag_part(hash_entry_key(x)))) + return(x); +#if WITH_GMP + if ((is_t_big_complex(hash_entry_key(x))) && + (mpfr_cmp_d(mpc_realref(big_complex(hash_entry_key(x))), keyrl) == 0) && + (mpfr_cmp_d(mpc_imagref(big_complex(hash_entry_key(x))), keyim) == 0) && + (!mpfr_nan_p(mpc_realref(big_complex(hash_entry_key(x))))) && + (!mpfr_nan_p(mpc_imagref(big_complex(hash_entry_key(x)))))) + return(x); +#endif + } + return(sc->unentry); +} + +static hash_entry_t *hash_equal_any(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + bool (*equal)(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) = equals[type(key)]; + s7_int hash = hash_loc(sc, table, key); + s7_int loc = hash & hash_table_mask(table); + for (hash_entry_t *x = hash_table_element(table, loc); x; x = hash_entry_next(x)) + if ((hash_entry_raw_hash(x) == hash) && + (equal(sc, key, hash_entry_key(x), NULL))) + return(x); + return(sc->unentry); +} + + +/* ---------------- hash c_functions ---------------- */ +static s7_int hash_map_c_function(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + s7_function f = c_function_call(hash_table_procedures_mapper(table)); + return(integer(f(sc, with_list_t1(key)))); +} + +static hash_entry_t *hash_equal(s7_scheme *sc, s7_pointer table, s7_pointer key); + +static hash_entry_t *hash_c_function(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + if (is_pair(hash_table_procedures(table))) + { + s7_int hash_mask = hash_table_mask(table); + s7_function f = c_function_call(hash_table_procedures_checker(table)); + s7_int hash = hash_loc(sc, table, key); + s7_int loc = hash & hash_mask; + set_car(sc->t2_1, key); + for (hash_entry_t *x = hash_table_element(table, loc); x; x = hash_entry_next(x)) + if (hash_entry_raw_hash(x) == hash) + { + set_car(sc->t2_2, hash_entry_key(x)); + if (is_true(sc, f(sc, sc->t2_1))) + return(x); + } + return(sc->unentry); + } + return(hash_equal(sc, table, key)); +} + +static int32_t len_upto_8(s7_pointer p) +{ + int32_t i = 0; /* unrolling this loop saves 10-15% */ + for (s7_pointer x = p; (is_pair(x)) && (i < 8); i++, x = cdr(x)); + return(i); +} + +static s7_int hash_map_pair(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + /* len+loc(car) is not horrible, but it means (for example) every list '(set! ...) is hashed to the same location, + * so at least we need to take cadr into account if possible. Better would combine the list_length + * with stats like symbols/pairs/constants at top level, then use those to spread it out over all the locs. + */ + s7_pointer p1 = cdr(key); + s7_int loc = 0; + + if (!is_sequence(car(key))) + loc = hash_loc(sc, table, car(key)) + 1; + else + if ((is_pair(car(key))) && + (!is_sequence(caar(key)))) + loc = hash_loc(sc, table, caar(key)) + 1; + if (is_pair(p1)) + { + if (!is_sequence(car(p1))) + loc += hash_loc(sc, table, car(p1)) + 1; + else + if ((is_pair(car(p1))) && + (!is_sequence(caar(p1)))) + loc += hash_loc(sc, table, caar(p1)) + 1; + } + return((loc << 3) | (len_upto_8(key))); +} + +static hash_entry_t *hash_closure(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + if (is_pair(hash_table_procedures(table))) + { + s7_int hash_mask = hash_table_mask(table); + s7_pointer f = hash_table_procedures_checker(table); + s7_int hash = hash_loc(sc, table, key); + s7_int loc = hash & hash_mask; + for (hash_entry_t *x = hash_table_element(table, loc); x; x = hash_entry_next(x)) + if ((hash_entry_raw_hash(x) == hash) && + (is_true(sc, s7_call(sc, f, set_plist_2(sc, key, hash_entry_key(x)))))) + return(x); + return(sc->unentry); + } + return(hash_equal(sc, table, key)); +} + +static hash_entry_t *hash_equal(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + return((*(equal_hash_checks[type(key)]))(sc, table, key)); +} + +/* ---------------- hash equivalent? ---------------- */ +static hash_entry_t *hash_equivalent(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + hash_entry_t *x; + s7_int hash, loc; + + if (is_number(key)) + { +#if WITH_GMP + if (!is_nan_b_7p(sc, key)) + return(hash_number_equivalent(sc, table, key)); +#else + x = hash_number_equivalent(sc, table, key); + if ((x != sc->unentry) || (!is_nan_b_7p(sc, key))) + return(x); +#endif + for (x = hash_table_element(table, 0); x; x = hash_entry_next(x)) /* NaN is mapped to 0 */ + if (is_nan_b_7p(sc, hash_entry_key(x))) /* all NaN's are the same to equivalent? */ + return(x); + return(sc->unentry); + } + hash = hash_loc(sc, table, key); + loc = hash & hash_table_mask(table); + for (x = hash_table_element(table, loc); x; x = hash_entry_next(x)) + if (hash_entry_key(x) == key) + return(x); + + for (x = hash_table_element(table, loc); x; x = hash_entry_next(x)) + if ((hash_entry_raw_hash(x) == hash) && + (s7_is_equivalent(sc, hash_entry_key(x), key))) + return(x); + return(sc->unentry); +} + +static bool hash_keys_not_cyclic(s7_scheme *sc, s7_pointer hash) +{ + return((is_null(hash_table_procedures(hash))) && + (hash_table_mapper(hash) == default_hash_map) && + (hash_table_checker(hash) != hash_equal) && + (hash_table_checker(hash) != hash_equivalent) && + (hash_table_checker(hash) != hash_closure) && + (hash_table_checker(hash) != hash_c_function)); +} + + +/* -------------------------------- make-hash-table -------------------------------- */ +s7_pointer s7_make_hash_table(s7_scheme *sc, s7_int size) +{ + s7_pointer table; + block_t *els; + /* size is rounded up to the next power of 2 */ + + if (size < 2) + size = 2; + else + if ((size & (size - 1)) != 0) /* already 2^n ? */ + { + if ((size & (size + 1)) != 0) /* already 2^n - 1 ? */ + { + size--; + size |= (size >> 1); + size |= (size >> 2); + size |= (size >> 4); + size |= (size >> 8); + size |= (size >> 16); + size |= (size >> 32); + } + size++; + } + els = (block_t *)callocate(sc, size * sizeof(hash_entry_t *)); + new_cell(sc, table, T_HASH_TABLE | T_SAFE_PROCEDURE); + hash_table_mask(table) = size - 1; + hash_table_set_block(table, els); + hash_table_elements(table) = (hash_entry_t **)(block_data(els)); + hash_table_checker(table) = hash_empty; + hash_table_mapper(table) = default_hash_map; + hash_table_entries(table) = 0; + hash_table_set_procedures(table, sc->nil); + add_hash_table(sc, table); + return(table); +} + +static bool compatible_types(s7_scheme *sc, const s7_pointer eq_type, const s7_pointer value_type) +{ + if (eq_type == sc->T) return(true); + if (eq_type == value_type) return(true); + if (eq_type == sc->is_number_symbol) /* only = among built-ins, so other cases aren't needed */ + return((value_type == sc->is_integer_symbol) || + (value_type == sc->is_real_symbol) || + (value_type == sc->is_complex_symbol) || + (value_type == sc->is_rational_symbol)); + return(false); +} + +static s7_pointer g_is_equal(s7_scheme *sc, s7_pointer args); +static s7_pointer g_is_equivalent(s7_scheme *sc, s7_pointer args); + +static s7_pointer g_make_hash_table_1(s7_scheme *sc, s7_pointer args, s7_pointer caller) +{ + #define H_make_hash_table "(make-hash-table (size 8) eq-func typer) returns a new hash table. eq-func is the function \ +used to check equality of keys; it usually defaults to equal?. typer sets the types of the keys and values that are allowed \ +in the table; it is a cons, defaulting to (cons #t #t) which means any types are allowed.\n" + #define Q_make_hash_table s7_make_signature(sc, 4, sc->is_hash_table_symbol, sc->is_integer_symbol, \ + s7_make_signature(sc, 3, sc->is_procedure_symbol, sc->is_pair_symbol, sc->not_symbol), \ + s7_make_signature(sc, 2, sc->is_pair_symbol, sc->not_symbol)) + s7_int size = sc->default_hash_table_length; + + if (is_not_null(args)) + { + s7_pointer p = car(args); + if (!s7_is_integer(p)) + return(method_or_bust(sc, p, caller, args, sc->type_names[T_INTEGER], 1)); + size = s7_integer_clamped_if_gmp(sc, p); + if (size <= 0) /* we need s7_int here to catch (make-hash-table most-negative-fixnum) etc */ + out_of_range_error_nr(sc, caller, int_one, p, wrap_string(sc, "it should be a positive integer", 31)); + if ((size > sc->max_vector_length) || + (size >= (1LL << 32LL))) + out_of_range_error_nr(sc, caller, int_one, p, it_is_too_large_string); + + if (is_not_null(cdr(args))) + { + s7_pointer proc; + s7_pointer ht = s7_make_hash_table(sc, size); + /* check for typers */ + if (is_pair(cddr(args))) + { + s7_pointer typers = caddr(args); + if (is_pair(typers)) + { + s7_pointer keyp = car(typers), valp = cdr(typers); + if ((keyp != sc->T) || (valp != sc->T)) /* one of them is a type checker */ + { + if (((keyp != sc->T) && (!is_c_function(keyp)) && (!is_any_closure(keyp))) || + ((valp != sc->T) && (!is_c_function(valp)) && (!is_any_closure(valp)))) + wrong_type_error_nr(sc, caller, 3, typers, wrap_string(sc, "(key-type . value-type)", 23)); + + if ((keyp != sc->T) && + (!s7_is_aritable(sc, keyp, 1))) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_3(sc, wrap_string(sc, "~A: in the third argument, ~S, (the key/value type checkers) both functions should take one argument", 100), + caller, typers)); + hash_table_set_procedures(ht, make_hash_table_procedures(sc)); + hash_table_set_key_typer(ht, keyp); + hash_table_set_value_typer(ht, valp); + if (is_c_function(keyp)) + { + if (!c_function_name(keyp)) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_3(sc, wrap_string(sc, "~A: in the third argument, ~S, (the key/value type checkers) the first function is anonymous", 92), + caller, typers)); + if (c_function_has_simple_elements(keyp)) + set_has_simple_keys(ht); + if (!c_function_symbol(keyp)) + c_function_symbol(keyp) = make_symbol(sc, c_function_name(keyp), c_function_name_length(keyp)); + if (symbol_type(c_function_symbol(keyp)) != T_FREE) + set_has_hash_key_type(ht); + /* c_function_marker is not currently used in this context */ + + /* now a consistency check for eq-func and key type */ + proc = cadr(args); + if (is_c_function(proc)) + { + s7_pointer eq_sig = c_function_signature(proc); + if ((eq_sig) && + (is_pair(eq_sig)) && + (is_pair(cdr(eq_sig))) && + (!compatible_types(sc, cadr(eq_sig), c_function_symbol(keyp)))) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_3(sc, wrap_string(sc, "~A: in the third argument, the key type function is not compatible with the equality function: ~S", 97), + caller, typers)); + }} + else + if ((is_any_closure(keyp)) && + (!is_symbol(find_closure(sc, keyp, closure_let(keyp))))) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_3(sc, wrap_string(sc, "~A: in the third argument, ~S, (the key/value type checkers) the first function is anonymous", 92), + caller, typers)); + if ((valp != sc->T) && + (!s7_is_aritable(sc, valp, 1))) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_3(sc, wrap_string(sc, "~A: in the third argument, ~S, (the key/value type checkers) both functions should take one argument", 100), + caller, typers)); + if (is_c_function(valp)) + { + if (!c_function_name(valp)) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_3(sc, wrap_string(sc, "~A: in the third argument, ~S, (the key/value type checkers) the second function is anonymous", 93), + caller, typers)); + if (c_function_has_simple_elements(valp)) + set_has_simple_values(ht); + if (!c_function_symbol(valp)) + c_function_symbol(valp) = make_symbol(sc, c_function_name(valp), c_function_name_length(valp)); + if (symbol_type(c_function_symbol(valp)) != T_FREE) + set_has_hash_value_type(ht); + } + else + if ((is_any_closure(valp)) && + (!is_symbol(find_closure(sc, valp, closure_let(valp))))) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_3(sc, wrap_string(sc, "~A: in the third argument, ~S, (the key/value type checkers) the second function is anonymous", 93), + caller, typers)); + set_is_typed_hash_table(ht); + }} + else + if (typers != sc->F) + wrong_type_error_nr(sc, caller, 3, typers, wrap_string(sc, "either #f or (cons key-type-check value-type-check)", 51)); + } + + /* check eq_func */ + proc = cadr(args); + + if (is_c_function(proc)) + { + hash_set_chosen(ht); + + if (!s7_is_aritable(sc, proc, 2)) + wrong_type_error_nr(sc, caller, 2, proc, an_eq_func_string); + + if (c_function_call(proc) == g_is_equal) + { + hash_table_checker(ht) = hash_equal; + return(ht); + } + if (c_function_call(proc) == g_is_equivalent) + { + hash_table_checker(ht) = hash_equivalent; + hash_table_mapper(ht) = equivalent_hash_map; /* needed only by hash_table_equal_1 (checker_locked looks at mapper?!) */ + return(ht); + } + if (c_function_call(proc) == g_is_eq) + { + hash_table_checker(ht) = hash_eq; + hash_table_mapper(ht) = eq_hash_map; + return(ht); + } + if (c_function_call(proc) == g_strings_are_equal) + { + hash_table_checker(ht) = hash_string; + hash_table_mapper(ht) = string_eq_hash_map; + return(ht); + } +#if (!WITH_PURE_S7) + if (c_function_call(proc) == g_strings_are_ci_equal) + { + hash_table_checker(ht) = hash_ci_string; + hash_table_mapper(ht) = string_ci_eq_hash_map; + return(ht); + } + if (c_function_call(proc) == g_chars_are_ci_equal) + { + hash_table_checker(ht) = hash_ci_char; + hash_table_mapper(ht) = char_ci_eq_hash_map; + return(ht); + } +#endif + if (c_function_call(proc) == g_chars_are_equal) + { + hash_table_checker(ht) = hash_char; + hash_table_mapper(ht) = char_eq_hash_map; + return(ht); + } + if (c_function_call(proc) == g_num_eq) + { + if ((is_typed_hash_table(ht)) && + (hash_table_key_typer(ht) == global_value(sc->is_integer_symbol))) + hash_table_checker(ht) = hash_int; + else hash_table_checker(ht) = hash_number_num_eq; + return(ht); + } + if (c_function_call(proc) == g_is_eqv) + { + hash_table_checker(ht) = hash_eqv; + return(ht); + } + error_nr(sc, sc->out_of_range_symbol, + set_elist_3(sc, wrap_string(sc, "~A second argument, ~S, is not a built-in function it can handle", 64), caller, proc)); + } + /* proc not c_function */ + else + { + if (is_pair(proc)) + { + s7_pointer checker = car(proc), mapper = cdr(proc); + + hash_set_chosen(ht); + if (!((is_any_c_function(checker)) || + (is_any_closure(checker)))) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_4(sc, wrap_string(sc, "~A: first entry of type info, ~A, is ~A, but should be a function", 65), + caller, checker, type_name_string(sc, checker))); + if (!((is_any_c_function(mapper)) || + (is_any_closure(mapper)))) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_4(sc, wrap_string(sc, "~A: second entry of type info, ~A, is ~A, but should be a function", 66), + caller, mapper, type_name_string(sc, mapper))); + + if (!(s7_is_aritable(sc, checker, 2))) + wrong_type_error_nr(sc, caller, 2, checker, wrap_string(sc, "a function of two arguments", 27)); + if (!(s7_is_aritable(sc, mapper, 1))) + wrong_type_error_nr(sc, caller, 2, mapper, wrap_string(sc, "a function of one argument", 26)); + + if (is_any_c_function(checker)) + { + s7_pointer sig = c_function_signature(checker); + if ((sig) && + (is_pair(sig)) && + (car(sig) != sc->is_boolean_symbol)) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_3(sc, wrap_string(sc, "~A checker function, ~S, should return a boolean value", 54), caller, checker)); + hash_table_checker(ht) = hash_c_function; + } + else hash_table_checker(ht) = hash_closure; + + if (is_any_c_function(mapper)) + { + s7_pointer sig = c_function_signature(mapper); + if ((sig) && + (is_pair(sig)) && + (car(sig) != sc->is_integer_symbol)) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_3(sc, wrap_string(sc, "~A mapper function, ~S, should return an integer", 48), caller, mapper)); + hash_table_mapper(ht) = c_function_hash_map; + } + else hash_table_mapper(ht) = closure_hash_map; + + if (is_null(hash_table_procedures(ht))) + hash_table_set_procedures(ht, make_hash_table_procedures(sc)); + hash_table_set_procedures_checker(ht, car(proc)); /* proc = cadr(args) */ + hash_table_set_procedures_mapper(ht, cdr(proc)); + return(ht); + } + if (proc != sc->F) wrong_type_error_nr(sc, caller, 2, proc, wrap_string(sc, "a cons of two functions", 23)); + return(ht); + }}} + return(s7_make_hash_table(sc, size)); +} + +static s7_pointer g_make_hash_table(s7_scheme *sc, s7_pointer args) +{ + return(g_make_hash_table_1(sc, args, sc->make_hash_table_symbol)); +} + + +/* -------------------------------- make-weak-hash-table -------------------------------- */ +static s7_pointer g_make_weak_hash_table(s7_scheme *sc, s7_pointer args) +{ + #define H_make_weak_hash_table "(make-weak-hash-table (size 8) eq-func typers) returns a new weak hash table" + #define Q_make_weak_hash_table s7_make_signature(sc, 4, sc->is_weak_hash_table_symbol, sc->is_integer_symbol, \ + s7_make_signature(sc, 3, sc->is_procedure_symbol, sc->is_pair_symbol, sc->not_symbol), \ + s7_make_signature(sc, 2, sc->is_pair_symbol, sc->not_symbol)) + s7_pointer table = g_make_hash_table_1(sc, args, sc->make_weak_hash_table_symbol); + set_weak_hash_table(table); + weak_hash_iters(table) = 0; + return(table); +} + +static const char *hash_table_checker_name(s7_scheme *sc, s7_pointer ht) +{ + if (hash_table_checker(ht) == hash_equal) return("equal?"); + if (hash_table_checker(ht) == hash_equivalent) return("equivalent?"); + if (hash_table_checker(ht) == hash_eq) return("eq?"); + if (hash_table_checker(ht) == hash_eqv) return("eqv?"); + if (hash_table_checker(ht) == hash_string) return("string=?"); +#if (!WITH_PURE_S7) + if (hash_table_checker(ht) == hash_ci_string) return("string-ci=?"); + if (hash_table_checker(ht) == hash_ci_char) return("char-ci=?"); +#endif + if (hash_table_checker(ht) == hash_char) return("char=?"); + if (hash_table_checker(ht) == hash_number_num_eq) return("="); + return("#f"); +} + + +/* -------------------------------- weak-hash-table? -------------------------------- */ +static s7_pointer g_is_weak_hash_table(s7_scheme *sc, s7_pointer args) +{ + #define H_is_weak_hash_table "(weak-hash-table? obj) returns #t if obj is a weak hash-table" + #define Q_is_weak_hash_table sc->pl_bt + #define is_weak_hash(p) ((is_hash_table(p)) && (is_weak_hash_table(p))) + check_boolean_method(sc, is_weak_hash, sc->is_weak_hash_table_symbol, args); +} + +static void init_hash_maps(void) +{ + for (int32_t i = 0; i < NUM_TYPES; i++) + { + default_hash_map[i] = hash_map_nil; + string_eq_hash_map[i] = hash_map_nil; + char_eq_hash_map[i] = hash_map_nil; +#if (!WITH_PURE_S7) + string_ci_eq_hash_map[i] = hash_map_nil; + char_ci_eq_hash_map[i] = hash_map_nil; +#endif + closure_hash_map[i] = hash_map_closure; + c_function_hash_map[i] = hash_map_c_function; + eq_hash_map[i] = hash_map_eq; + + equal_hash_checks[i] = hash_equal_any; + default_hash_checks[i] = hash_equal; + } + default_hash_map[T_CHARACTER] = hash_map_char; + default_hash_map[T_SYMBOL] = hash_map_symbol; + default_hash_map[T_SYNTAX] = hash_map_syntax; + default_hash_map[T_STRING] = hash_map_string; + default_hash_map[T_BYTE_VECTOR] = hash_map_byte_vector; + default_hash_map[T_HASH_TABLE] = hash_map_hash_table; + default_hash_map[T_VECTOR] = hash_map_vector; + default_hash_map[T_INT_VECTOR] = hash_map_int_vector; + default_hash_map[T_FLOAT_VECTOR] = hash_map_float_vector; + default_hash_map[T_LET] = hash_map_let; + default_hash_map[T_PAIR] = hash_map_pair; + + default_hash_map[T_INTEGER] = hash_map_int; + default_hash_map[T_RATIO] = hash_map_ratio; + default_hash_map[T_REAL] = hash_map_real; + default_hash_map[T_COMPLEX] = hash_map_complex; +#if WITH_GMP + default_hash_map[T_BIG_INTEGER] = hash_map_big_int; + default_hash_map[T_BIG_RATIO] = hash_map_big_ratio; + default_hash_map[T_BIG_REAL] = hash_map_big_real; + default_hash_map[T_BIG_COMPLEX] = hash_map_big_complex; +#endif + + string_eq_hash_map[T_STRING] = hash_map_string; + string_eq_hash_map[T_BYTE_VECTOR] = hash_map_byte_vector; + char_eq_hash_map[T_CHARACTER] = hash_map_char; +#if (!WITH_PURE_S7) + string_ci_eq_hash_map[T_STRING] = hash_map_ci_string; + char_ci_eq_hash_map[T_CHARACTER] = hash_map_ci_char; +#endif + + for (int32_t i = 0; i < NUM_TYPES; i++) equivalent_hash_map[i] = default_hash_map[i]; + + equal_hash_checks[T_SYNTAX] = hash_equal_syntax; + equal_hash_checks[T_SYMBOL] = hash_equal_eq; + equal_hash_checks[T_CHARACTER] = hash_equal_eq; + equal_hash_checks[T_INTEGER] = hash_equal_integer; + equal_hash_checks[T_RATIO] = hash_equal_ratio; + equal_hash_checks[T_REAL] = hash_equal_real; + equal_hash_checks[T_COMPLEX] = hash_equal_complex; + + default_hash_checks[T_STRING] = hash_string; + default_hash_checks[T_INTEGER] = hash_int; + default_hash_checks[T_REAL] = hash_float; + default_hash_checks[T_SYMBOL] = hash_symbol; + default_hash_checks[T_CHARACTER] = hash_char; +} + +static void resize_hash_table(s7_scheme *sc, s7_pointer table) +{ + s7_int entries = hash_table_entries(table); + hash_entry_t **old_els = hash_table_elements(table); + s7_pointer dproc = hash_table_procedures(table); /* new block_t so we need to pass this across */ + s7_int old_size = hash_table_mask(table) + 1; + s7_int new_size = old_size * 4; + s7_int hash_mask = new_size - 1; + block_t *np = (block_t *)callocate(sc, new_size * sizeof(hash_entry_t *)); + hash_entry_t **new_els = (hash_entry_t **)(block_data(np)); + + for (s7_int i = 0; i < old_size; i++) + { + hash_entry_t *n; + for (hash_entry_t *x = old_els[i]; x; x = n) + { + s7_int loc = hash_entry_raw_hash(x) & hash_mask; + n = hash_entry_next(x); + hash_entry_next(x) = new_els[loc]; + new_els[loc] = x; + }} + liberate(sc, hash_table_block(table)); + hash_table_set_block(table, np); + hash_table_elements(table) = new_els; + hash_table_mask(table) = hash_mask; /* was new_size - 1 14-Jun-21 */ + hash_table_set_procedures(table, dproc); + hash_table_entries(table) = entries; +} + + +/* -------------------------------- hash-table-ref -------------------------------- */ +s7_pointer s7_hash_table_ref(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + return(hash_entry_value((*hash_table_checker(table))(sc, table, key))); +} + +static s7_pointer g_hash_table_ref(s7_scheme *sc, s7_pointer args) +{ + #define H_hash_table_ref "(hash-table-ref table key) returns the value associated with key in the hash table" + #define Q_hash_table_ref s7_make_circular_signature(sc, 2, 3, sc->T, sc->is_hash_table_symbol, sc->T) + + s7_pointer table = car(args), nt; + if (!is_hash_table(table)) + return(method_or_bust(sc, table, sc->hash_table_ref_symbol, args, sc->type_names[T_HASH_TABLE], 1)); + nt = s7_hash_table_ref(sc, table, cadr(args)); + + if (is_pair(cddr(args))) + return(ref_index_checked(sc, global_value(sc->hash_table_ref_symbol), nt, args)); + return(nt); +} + +static s7_pointer g_hash_table_ref_2(s7_scheme *sc, s7_pointer args) +{ + s7_pointer table = car(args); + if (!is_hash_table(table)) + return(method_or_bust(sc, table, sc->hash_table_ref_symbol, args, sc->type_names[T_HASH_TABLE], 1)); + return(hash_entry_value((*hash_table_checker(table))(sc, table, cadr(args)))); +} + +static s7_pointer hash_table_ref_p_pp(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + if (!is_hash_table(table)) + return(method_or_bust(sc, table, sc->hash_table_ref_symbol, set_plist_2(sc, table, key), sc->type_names[T_HASH_TABLE], 1)); + return(hash_entry_value((*hash_table_checker(table))(sc, table, key))); +} + +static bool op_implicit_hash_table_ref_a(s7_scheme *sc) +{ + s7_pointer s = lookup_checked(sc, car(sc->code)); + if (!is_hash_table(s)) {sc->last_function = s; return(false);} + sc->value = s7_hash_table_ref(sc, s, fx_call(sc, cdr(sc->code))); + return(true); +} + +static bool op_implicit_hash_table_ref_aa(s7_scheme *sc) +{ + s7_pointer in_obj, out_key; + s7_pointer table = lookup_checked(sc, car(sc->code)); + if (!is_hash_table(table)) {sc->last_function = table; return(false);} + out_key = fx_call(sc, cdr(sc->code)); + in_obj = s7_hash_table_ref(sc, table, out_key); + if (is_hash_table(in_obj)) + sc->value = s7_hash_table_ref(sc, in_obj, fx_call(sc, cddr(sc->code))); + else sc->value = implicit_pair_index_checked(sc, table, in_obj, set_plist_2(sc, out_key, fx_call(sc, cddr(sc->code)))); /* -> implicit_index */ + return(true); +} + +static s7_pointer hash_table_ref_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool unused_ops) +{ + if (args == 2) + { + s7_pointer key = caddr(expr); + if ((is_pair(key)) && (car(key) == sc->substring_symbol) && (is_global(sc->substring_symbol))) + set_c_function(key, sc->substring_uncopied); + return(sc->hash_table_ref_2); + } + return(f); +} + + +/* -------------------------------- hash-table-set! -------------------------------- */ +static s7_pointer remove_from_hash_table(s7_scheme *sc, s7_pointer table, hash_entry_t *p) +{ + hash_entry_t *x; + s7_int hash_mask, loc; + + if (p == sc->unentry) return(sc->F); + hash_mask = hash_table_mask(table); + loc = hash_entry_raw_hash(p) & hash_mask; + x = hash_table_element(table, loc); + if (x == p) + hash_table_element(table, loc) = hash_entry_next(x); + else + { + hash_entry_t *y; + for (y = x, x = hash_entry_next(x); x; y = x, x = hash_entry_next(x)) + if (x == p) + { + hash_entry_next(y) = hash_entry_next(x); + break; + }} + hash_table_entries(table)--; + if ((hash_table_entries(table) == 0) && + (hash_table_mapper(table) == default_hash_map)) + { + hash_table_checker(table) = hash_empty; + hash_clear_chosen(table); + } + liberate_block(sc, x); + return(sc->F); +} + +static void cull_weak_hash_table(s7_scheme *sc, s7_pointer table) +{ + s7_int len = hash_table_mask(table) + 1; + hash_entry_t **entries = hash_table_elements(table); + for (s7_int i = 0; i < len; i++) + { + hash_entry_t *nxp, *lxp = entries[i]; + for (hash_entry_t *xp = entries[i]; xp; xp = nxp) + { + nxp = hash_entry_next(xp); + if (is_free_and_clear(hash_entry_key(xp))) + { + if (xp == entries[i]) + { + entries[i] = nxp; + lxp = nxp; + } + else hash_entry_next(lxp) = nxp; + liberate_block(sc, xp); + hash_table_entries(table)--; + if (hash_table_entries(table) == 0) + { + if (hash_table_mapper(table) == default_hash_map) + { + hash_table_checker(table) = hash_empty; + hash_clear_chosen(table); + } + return; + }} + else lxp = xp; + }} +} + +static void hash_table_set_default_checker(s7_pointer table, uint8_t typ) +{ + if (hash_table_checker(table) != default_hash_checks[typ]) + { + if (hash_table_checker(table) == hash_empty) + hash_table_checker(table) = default_hash_checks[typ]; + else + { + hash_table_checker(table) = hash_equal; + hash_set_chosen(table); + }} +} + +static s7_pointer hash_table_typer_symbol(s7_scheme *sc, s7_pointer typer) +{ + if (typer == sc->T) + return(sc->T); + return((is_c_function(typer)) ? c_function_symbol(typer) : find_closure(sc, typer, closure_let(typer))); +} + +static void check_hash_types(s7_scheme *sc, s7_pointer table, s7_pointer key, s7_pointer value) +{ + if (has_hash_key_type(table)) /* symbol_type and c_function_symbol exist and symbol_type is not T_FREE */ + { + if ((uint8_t)symbol_type(c_function_symbol(hash_table_key_typer(table))) != type(key)) + { + const char *tstr = make_type_name(sc, hash_table_typer_name(sc, hash_table_key_typer(table)), INDEFINITE_ARTICLE); + wrong_type_error_nr(sc, wrap_string(sc, "hash-table-set! key", 19), 2, key, wrap_string(sc, tstr, safe_strlen(tstr))); + }} + else + { + s7_pointer kf = hash_table_key_typer(table); + if (kf != sc->T) + { + s7_pointer type_ok; + if (is_c_function(kf)) + type_ok = c_function_call(kf)(sc, set_plist_1(sc, key)); + else type_ok = s7_apply_function(sc, kf, set_plist_1(sc, key)); + if (type_ok == sc->F) + { + const char *descr = hash_table_typer_name(sc, hash_table_key_typer(table)); + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_4(sc, wrap_string(sc, "hash-table-set! second argument ~$, is ~A, but the hash-table's key type checker, ~A, rejects it", 96), + key, type_name_string(sc, key), wrap_string(sc, descr, safe_strlen(descr)))); + }}} + if (has_hash_value_type(table)) + { + if ((uint8_t)symbol_type(c_function_symbol(hash_table_value_typer(table))) != type(value)) + { + const char *tstr = make_type_name(sc, hash_table_typer_name(sc, hash_table_value_typer(table)), INDEFINITE_ARTICLE); + wrong_type_error_nr(sc, sc->hash_table_set_symbol, 3, value, wrap_string(sc, tstr, safe_strlen(tstr))); + }} + else + { + s7_pointer vf = hash_table_value_typer(table); + if (vf != sc->T) + { + s7_pointer type_ok; + if (is_c_function(vf)) + type_ok = c_function_call(vf)(sc, set_plist_1(sc, value)); + else type_ok = s7_apply_function(sc, vf, set_plist_1(sc, value)); + if (type_ok == sc->F) + { + const char *descr = hash_table_typer_name(sc, hash_table_value_typer(table)); + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_4(sc, wrap_string(sc, "hash-table-set! third argument ~$, is ~A, but the hash-table's value type checker, ~A, rejects it", 97), + value, type_name_string(sc, value), wrap_string(sc, descr, safe_strlen(descr)))); + }}} +} + +static void check_hash_table_checker(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + /* check type -- raise error if incompatible with eq func set by make-hash-table */ + if (hash_table_checker(table) == hash_number_num_eq) + { + if (!is_number(key)) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_3(sc, wrap_string(sc, "hash-table-set! key ~S, is ~A, but the hash-table's key function is =", 69), + key, type_name_string(sc, key))); + } + else + if (hash_table_checker(table) == hash_eq) + { + if (is_number(key)) /* (((type(key) >= T_INTEGER) && (type(key) < T_C_MACRO)) || (type(key) == T_PAIR)), but we might want eq? */ + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_3(sc, wrap_string(sc, "hash-table-set! key ~S, is ~A, but the hash-table's key function is eq?", 71), + key, type_name_string(sc, key))); + } + else +#if WITH_PURE_S7 + if (((hash_table_checker(table) == hash_string) && (!is_string(key))) || + ((hash_table_checker(table) == hash_char) && (!is_character(key)))) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_4(sc, wrap_string(sc, "hash-table-set! key ~S, is ~A, but the hash-table's key function is ~A", 70), + key, type_name_string(sc, key), + (hash_table_checker(table) == hash_string) ? sc->string_eq_symbol : sc->char_eq_symbol)); +#else + if ((((hash_table_checker(table) == hash_string) || (hash_table_checker(table) == hash_ci_string)) && + (!is_string(key))) || + (((hash_table_checker(table) == hash_char) || (hash_table_checker(table) == hash_ci_char)) && + (!is_character(key)))) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_4(sc, wrap_string(sc, "hash-table-set! key ~S, is ~A, but the hash-table's key function is ~A", 70), + key, type_name_string(sc, key), + (hash_table_checker(table) == hash_string) ? sc->string_eq_symbol : + ((hash_table_checker(table) == hash_ci_string) ? sc->string_ci_eq_symbol : + ((hash_table_checker(table) == hash_char) ? sc->char_eq_symbol : sc->char_ci_eq_symbol)))); +#endif +} + +s7_pointer s7_hash_table_set(s7_scheme *sc, s7_pointer table, s7_pointer key, s7_pointer value) +{ + s7_int hash_mask, loc; + hash_entry_t *p, *x; + + if (value == sc->F) + return(remove_from_hash_table(sc, table, (*hash_table_checker(table))(sc, table, key))); + + if ((is_typed_hash_table(table)) && + (sc->safety >= NO_SAFETY)) + check_hash_types(sc, table, key, value); + + x = (*hash_table_checker(table))(sc, table, key); + if (x != sc->unentry) + { + hash_entry_set_value(x, T_Ext(value)); + return(value); + } + /* hash_entry_raw_hash(x) can save the hash_loc from the lookup operations, but at some added complexity in + * all the preceding code. This saves about 5% compute time best case in this function. + */ + if (!hash_chosen(table)) + hash_table_set_default_checker(table, type(key)); /* raw_hash value (hash_loc(sc, table, key)) does not change via hash_table_set_default_checker etc */ + else + if (sc->safety > NO_SAFETY) + check_hash_table_checker(sc, table, key); + + p = mallocate_block(sc); + hash_entry_key(p) = key; + hash_entry_set_value(p, T_Ext(value)); + hash_entry_set_raw_hash(p, hash_loc(sc, table, key)); + hash_mask = hash_table_mask(table); + loc = hash_entry_raw_hash(p) & hash_mask; + hash_entry_next(p) = hash_table_element(table, loc); + hash_table_element(table, loc) = p; + hash_table_entries(table)++; + if (hash_table_entries(table) > hash_mask) + resize_hash_table(sc, table); + return(value); +} + +static s7_pointer g_hash_table_set(s7_scheme *sc, s7_pointer args) +{ + #define H_hash_table_set "(hash-table-set! table key value) sets the value associated with key in the hash table to value" + #define Q_hash_table_set s7_make_signature(sc, 4, sc->T, sc->is_hash_table_symbol, sc->T, sc->T) + + s7_pointer table = car(args); + if (!is_mutable_hash_table(table)) + return(mutable_method_or_bust(sc, table, sc->hash_table_set_symbol, args, sc->type_names[T_HASH_TABLE], 1)); + return(s7_hash_table_set(sc, table, cadr(args), caddr(args))); +} + +static s7_pointer hash_table_set_p_ppp(s7_scheme *sc, s7_pointer p1, s7_pointer p2, s7_pointer p3) +{ + if (!is_mutable_hash_table(p1)) /* is_hash_table(p1) is here */ + return(mutable_method_or_bust_ppp(sc, p1, sc->hash_table_set_symbol, p1, p2, p3, sc->type_names[T_HASH_TABLE], 1)); + return(s7_hash_table_set(sc, p1, p2, p3)); +} + +static s7_pointer hash_table_set_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool unused_ops) +{ + if ((args == 3) && (optimize_op(expr) == HOP_SAFE_C_SSA)) + { + s7_pointer val = cadddr(expr); + if ((is_pair(val)) && (car(val) == sc->add_symbol) && (is_proper_list_3(sc, val)) && + ((cadr(val) == int_one) || (caddr(val) == int_one))) + { + s7_pointer add1 = (cadr(val) == int_one) ? caddr(val) : cadr(val); + if ((is_pair(add1)) && (car(add1) == sc->or_symbol) && (is_proper_list_3(sc, add1)) && + (caddr(add1) == int_zero)) + { + s7_pointer or1 = cadr(add1); + if ((is_pair(or1)) && (car(or1) == sc->hash_table_ref_symbol) && (is_proper_list_3(sc, or1)) && + (cadr(or1) == cadr(expr)) && (caddr(or1) == caddr(expr))) + /* (hash-table-set! counts p (+ (or (hash-table-ref counts p) 0) 1)) */ + set_optimize_op(expr, HOP_HASH_TABLE_INCREMENT); + }}} + return(f); +} + + +/* -------------------------------- hash-table -------------------------------- */ +static inline s7_pointer hash_table_add(s7_scheme *sc, s7_pointer table, s7_pointer key, s7_pointer value) +{ + s7_int hash, hash_mask, loc; + hash_entry_t *p; + + if (!hash_chosen(table)) + hash_table_set_default_checker(table, type(key)); /* raw_hash value (hash_loc(sc, table, key)) does not change via hash_table_set_default_checker etc */ + + hash_mask = hash_table_mask(table); + hash = hash_loc(sc, table, key); + loc = hash & hash_mask; + + for (hash_entry_t *x = hash_table_element(table, loc); x; x = hash_entry_next(x)) + if ((hash_entry_raw_hash(x) == hash) && + (s7_is_equal(sc, hash_entry_key(x), key))) + return(value); + + p = mallocate_block(sc); + hash_entry_key(p) = key; + hash_entry_set_value(p, T_Ext(value)); + hash_entry_set_raw_hash(p, hash); + hash_entry_next(p) = hash_table_element(table, loc); + hash_table_element(table, loc) = p; + hash_table_entries(table)++; + if (hash_table_entries(table) > hash_mask) + resize_hash_table(sc, table); + return(value); +} + +static s7_pointer g_hash_table_1(s7_scheme *sc, s7_pointer args, s7_pointer caller) +{ + s7_pointer ht; + s7_int len = proper_list_length(args); + if (len & 1) + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_3(sc, wrap_string(sc, "~A got an odd number of arguments: ~S", 37), caller, args)); + len /= 2; + ht = s7_make_hash_table(sc, (len > sc->default_hash_table_length) ? len : sc->default_hash_table_length); + if (len > 0) + for (s7_pointer x = args, y = cdr(args); is_pair(x); x = cddr(x), y = unchecked_cdr(cdr(y))) + if (car(y) != sc->F) + hash_table_add(sc, ht, car(x), car(y)); + return(ht); +} + +static s7_pointer g_hash_table(s7_scheme *sc, s7_pointer args) +{ + #define H_hash_table "(hash-table ...) returns a hash-table containing the symbol/value pairs passed as its arguments. \ +That is, (hash-table 'a 1 'b 2) returns a new hash-table with the two key/value pairs preinstalled." + #define Q_hash_table s7_make_circular_signature(sc, 1, 2, sc->is_hash_table_symbol, sc->T) + return(g_hash_table_1(sc, args, sc->hash_table_symbol)); +} + +static s7_pointer g_hash_table_2(s7_scheme *sc, s7_pointer args) +{ + s7_pointer ht = s7_make_hash_table(sc, sc->default_hash_table_length); + if (cadr(args) != sc->F) + hash_table_add(sc, ht, car(args), cadr(args)); + return(ht); +} + + +/* -------------------------------- weak-hash-table -------------------------------- */ +static s7_pointer g_weak_hash_table(s7_scheme *sc, s7_pointer args) +{ + #define H_weak_hash_table "(weak-hash-table ...) returns a weak-hash-table containing the symbol/value pairs passed as its arguments. \ +That is, (weak-hash-table 'a 1 'b 2) returns a new weak-hash-table with the two key/value pairs preinstalled." + #define Q_weak_hash_table Q_hash_table + + s7_pointer table = g_hash_table_1(sc, args, sc->weak_hash_table_symbol); + set_weak_hash_table(table); + weak_hash_iters(table) = 0; + return(table); +} + +static s7_pointer hash_table_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer unused_expr, bool unused_ops) +{ + return((args == 2) ? sc->hash_table_2 : f); +} + +static void check_old_hash(s7_scheme *sc, s7_pointer old_hash, s7_pointer new_hash, s7_int start, s7_int end) +{ + s7_int count = 0; + s7_int old_len = hash_table_mask(old_hash) + 1; + hash_entry_t **old_lists = hash_table_elements(old_hash); + for (s7_int i = 0; i < old_len; i++) + for (hash_entry_t *x = old_lists[i]; x; x = hash_entry_next(x)) + { + if (count >= end) + return; + if (count >= start) + check_hash_types(sc, new_hash, hash_entry_key(x), hash_entry_value(x)); + } +} + +static s7_pointer hash_table_copy(s7_scheme *sc, s7_pointer old_hash, s7_pointer new_hash, s7_int start, s7_int end) +{ + s7_int old_len, new_mask, count = 0; + hash_entry_t **old_lists, **new_lists; + + if (is_typed_hash_table(new_hash)) + check_old_hash(sc, old_hash, new_hash, start, end); + + old_len = hash_table_mask(old_hash) + 1; + new_mask = hash_table_mask(new_hash); + old_lists = hash_table_elements(old_hash); + new_lists = hash_table_elements(new_hash); + + if (hash_table_entries(new_hash) == 0) + { + if ((start == 0) && + (end >= hash_table_entries(old_hash))) + { + for (s7_int i = 0; i < old_len; i++) + for (hash_entry_t *x = old_lists[i]; x; x = hash_entry_next(x)) + { + s7_int loc = hash_entry_raw_hash(x) & new_mask; + hash_entry_t *p = make_hash_entry(sc, hash_entry_key(x), hash_entry_value(x), hash_entry_raw_hash(x)); + hash_entry_next(p) = new_lists[loc]; + new_lists[loc] = p; + } + hash_table_entries(new_hash) = hash_table_entries(old_hash); + return(new_hash); + } + for (s7_int i = 0; i < old_len; i++) + for (hash_entry_t *x = old_lists[i]; x; x = hash_entry_next(x)) + { + if (count >= end) + { + hash_table_entries(new_hash) = end - start; + return(new_hash); + } + if (count >= start) + { + s7_int loc = hash_entry_raw_hash(x) & new_mask; + hash_entry_t *p = make_hash_entry(sc, hash_entry_key(x), hash_entry_value(x), hash_entry_raw_hash(x)); + hash_entry_next(p) = new_lists[loc]; + new_lists[loc] = p; + } + count++; + } + hash_table_entries(new_hash) = count - start; + return(new_hash); + } + + /* this can't be optimized much because we have to look for key matches (we're copying old_hash into the existing, non-empty new_hash) */ + for (s7_int i = 0; i < old_len; i++) + for (hash_entry_t *x = old_lists[i]; x; x = hash_entry_next(x)) + { + if (count >= end) + return(new_hash); + if (count >= start) + { + hash_entry_t *y = (*hash_table_checker(new_hash))(sc, new_hash, hash_entry_key(x)); + if (y != sc->unentry) + hash_entry_set_value(y, hash_entry_value(x)); + else + { + s7_int loc = hash_entry_raw_hash(x) & new_mask; + hash_entry_t *p = make_hash_entry(sc, hash_entry_key(x), hash_entry_value(x), hash_entry_raw_hash(x)); + hash_entry_next(p) = new_lists[loc]; + new_lists[loc] = p; + hash_table_entries(new_hash)++; + if (!hash_chosen(new_hash)) + hash_table_set_default_checker(new_hash, type(hash_entry_key(x))); + }} + count++; + } + return(new_hash); +} + +static s7_pointer hash_table_fill(s7_scheme *sc, s7_pointer args) +{ + s7_pointer table = car(args), val = cadr(args); + if (is_immutable_hash_table(table)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->fill_symbol, table)); + + if (hash_table_entries(table) > 0) + { + hash_entry_t **entries = hash_table_elements(table); + s7_int len = hash_table_mask(table) + 1; /* minimum len is 2 (see s7_make_hash_table) */ + if (val == sc->F) /* hash-table-ref returns #f if it can't find a key, so val == #f here means empty the table */ + { + hash_entry_t **hp = entries; + hash_entry_t **hn = (hash_entry_t **)(hp + len); + for (; hp < hn; hp++) + { + if (*hp) + { + hash_entry_t *p = *hp; + while (hash_entry_next(p)) p = hash_entry_next(p); + hash_entry_next(p) = sc->block_lists[BLOCK_LIST]; + sc->block_lists[BLOCK_LIST] = *hp; + } + hp++; + if (*hp) + { + hash_entry_t *p = *hp; + while (hash_entry_next(p)) p = hash_entry_next(p); + hash_entry_next(p) = sc->block_lists[BLOCK_LIST]; + sc->block_lists[BLOCK_LIST] = *hp; + }} + if (len >= 8) + memclr64(entries, len * sizeof(hash_entry_t *)); + else memclr(entries, len * sizeof(hash_entry_t *)); + if (hash_table_mapper(table) == default_hash_map) + { + hash_table_checker(table) = hash_empty; + hash_clear_chosen(table); + } + hash_table_entries(table) = 0; + return(val); + } + if ((is_typed_hash_table(table)) && + (((is_c_function(hash_table_value_typer(table))) && + (c_function_call(hash_table_value_typer(table))(sc, set_plist_1(sc, val)) == sc->F)) || + ((is_any_closure(hash_table_value_typer(table))) && + (s7_apply_function(sc, hash_table_value_typer(table), set_plist_1(sc, val)) == sc->F)))) + { + const char *tstr = make_type_name(sc, hash_table_typer_name(sc, hash_table_value_typer(table)), INDEFINITE_ARTICLE); + wrong_type_error_nr(sc, sc->fill_symbol, 2, val, wrap_string(sc, tstr, safe_strlen(tstr))); + } + for (s7_int i = 0; i < len; i++) + for (hash_entry_t *x = entries[i]; x; x = hash_entry_next(x)) + hash_entry_set_value(x, val); + /* keys haven't changed, so no need to mess with hash_table_checker */ + } + return(val); +} + +static s7_pointer hash_table_reverse(s7_scheme *sc, s7_pointer old_hash) +{ + s7_int len = hash_table_mask(old_hash) + 1; + hash_entry_t **old_lists = hash_table_elements(old_hash); + s7_pointer new_hash = s7_make_hash_table(sc, len); + s7_int gc_loc = gc_protect_1(sc, new_hash); + + /* old_hash checker/mapper functions don't always make sense reversed, although the key/value typers might be ok */ + for (s7_int i = 0; i < len; i++) + for (hash_entry_t *x = old_lists[i]; x; x = hash_entry_next(x)) + s7_hash_table_set(sc, new_hash, hash_entry_value(x), hash_entry_key(x)); + + if (is_weak_hash_table(old_hash)) /* 17-May-23, not sure it makes sense to reverse a weak-hash-table but... */ + { + set_weak_hash_table(new_hash); + weak_hash_iters(new_hash) = 0; + } + s7_gc_unprotect_at(sc, gc_loc); + return(new_hash); +} + + +/* -------------------------------- functions -------------------------------- */ +bool s7_is_function(s7_pointer p) {return(is_c_function(p));} + +static s7_pointer fallback_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool ops) {return(f);} + +static void s7_function_set_class(s7_scheme *sc, s7_pointer f, s7_pointer base_f) +{ + c_function_class(f) = c_function_class(base_f); + c_function_set_base(f, base_f); +} + +static s7_pointer make_function(s7_scheme *sc, const char *name, s7_function f, s7_int req, s7_int opt, bool rst, const char *doc, s7_pointer x, c_proc_t *ptr) +{ + uint32_t ftype = T_C_FUNCTION; + + if ((req == 0) && (rst)) + ftype = T_C_RST_NO_REQ_FUNCTION; + set_full_type(x, ftype); + + c_function_data(x) = ptr; + c_function_call(x) = f; /* f is T_App but needs cast */ + c_function_set_base(x, x); + c_function_set_setter(x, sc->F); + c_function_name(x) = name; /* (procedure-name proc) => (format #f "~A" proc) */ + c_function_name_length(x) = safe_strlen(name); + c_function_documentation(x) = (doc) ? make_semipermanent_c_string(sc, doc) : NULL; + c_function_signature(x) = sc->F; + + c_function_min_args(x) = req; + c_function_optional_args(x) = opt; /* T_C_FUNCTION_STAR type may be set later, so T_Fst not usable here */ + c_function_max_args(x) = (rst) ? MAX_ARITY : req + opt; + + c_function_class(x) = ++sc->f_class; + c_function_chooser(x) = fallback_chooser; + c_function_opt_data(x) = NULL; + c_function_marker(x) = NULL; + c_function_symbol(x) = NULL; + return(x); +} + +static c_proc_t *alloc_semipermanent_function(s7_scheme *sc) +{ + #define ALLOC_FUNCTION_SIZE 256 + if (sc->alloc_function_k == ALLOC_FUNCTION_SIZE) + { + sc->alloc_function_cells = (c_proc_t *)Malloc(ALLOC_FUNCTION_SIZE * sizeof(c_proc_t)); + add_saved_pointer(sc, sc->alloc_function_cells); + sc->alloc_function_k = 0; + } + return(&(sc->alloc_function_cells[sc->alloc_function_k++])); +} + +s7_pointer s7_make_function(s7_scheme *sc, const char *name, s7_function f, + s7_int required_args, s7_int optional_args, bool rest_arg, const char *doc) +{ + s7_pointer x = alloc_pointer(sc); + x = make_function(sc, name, f, required_args, optional_args, rest_arg, doc, x, alloc_semipermanent_function(sc)); + unheap(sc, x); + return(x); +} + +s7_pointer s7_make_safe_function(s7_scheme *sc, const char *name, s7_function f, + s7_int required_args, s7_int optional_args, bool rest_arg, const char *doc) +{ + s7_pointer p = s7_make_function(sc, name, f, required_args, optional_args, rest_arg, doc); + set_type_bit(p, T_SAFE_PROCEDURE); + return(p); +} + +s7_pointer s7_make_typed_function(s7_scheme *sc, const char *name, s7_function f, + s7_int required_args, s7_int optional_args, bool rest_arg, const char *doc, s7_pointer signature) +{ + s7_pointer func = s7_make_function(sc, name, f, required_args, optional_args, rest_arg, doc); + set_type_bit(func, T_SAFE_PROCEDURE); + if (signature) c_function_signature(func) = signature; + return(func); +} + + +/* -------------------------------- procedure? -------------------------------- */ +bool s7_is_procedure(s7_pointer x) {return(is_procedure(x));} + +static s7_pointer g_is_procedure(s7_scheme *sc, s7_pointer args) +{ + #define H_is_procedure "(procedure? obj) returns #t if obj is a procedure" + #define Q_is_procedure sc->pl_bt + return(make_boolean(sc, is_procedure(car(args)))); +} + + +static void s7_function_set_setter(s7_scheme *sc, s7_pointer getter, s7_pointer setter) +{ + /* this is internal, used only with c_function setters, so we don't need to worry about the GC mark choice */ + c_function_set_setter(global_value(getter), global_value(setter)); +} + +s7_pointer s7_closure_body(s7_scheme *sc, s7_pointer p) {return((has_closure_let(p)) ? closure_body(p) : sc->nil);} +s7_pointer s7_closure_let(s7_scheme *sc, s7_pointer p) {return((has_closure_let(p)) ? closure_let(p) : sc->nil);} +s7_pointer s7_closure_args(s7_scheme *sc, s7_pointer p) {return((has_closure_let(p)) ? closure_args(p) : sc->nil);} + + +/* -------------------------------- procedure-source -------------------------------- */ +static s7_pointer procedure_type_to_symbol(s7_scheme *sc, int32_t type) +{ + switch (type) + { + case T_CLOSURE: return(sc->lambda_symbol); + case T_CLOSURE_STAR: return(sc->lambda_star_symbol); + case T_MACRO: return(sc->macro_symbol); + case T_MACRO_STAR: return(sc->macro_star_symbol); + case T_BACRO: return(sc->bacro_symbol); + case T_BACRO_STAR: return(sc->bacro_star_symbol); + default: if (S7_DEBUGGING) fprintf(stderr, "%s[%d] wants %d symbol\n", __func__, __LINE__, type); /* break; ? */ + } + return(sc->lambda_symbol); +} + +static s7_pointer g_procedure_source(s7_scheme *sc, s7_pointer args) +{ + #define H_procedure_source "(procedure-source func) tries to return the definition of func" + #define Q_procedure_source s7_make_signature(sc, 2, sc->is_list_symbol, s7_make_signature(sc, 2, sc->is_procedure_symbol, sc->is_macro_symbol)) + /* make it look like a scheme-level lambda */ + s7_pointer p = car(args); + + if ((is_symbol(p)) && + ((symbol_ctr(p) == 0) || ((p = s7_symbol_value(sc, p)) == sc->undefined))) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_2(sc, wrap_string(sc, "procedure-source arg, '~S, is unbound", 37), car(args))); + if ((is_c_function(p)) || (is_c_macro(p))) + return(sc->nil); + + check_method(sc, p, sc->procedure_source_symbol, set_plist_1(sc, p)); + if (has_closure_let(p)) + { + s7_pointer body = closure_body(p); + /* perhaps if this function has been removed from the heap, it would be better to use copy_body (as in s7_copy)? */ + if (is_safe_closure_body(body)) + clear_safe_closure_body(body); + return(append_in_place(sc, list_2(sc, procedure_type_to_symbol(sc, type(p)), closure_args(p)), body)); + } + if (!is_procedure(p)) + sole_arg_wrong_type_error_nr(sc, sc->procedure_source_symbol, p, a_procedure_or_a_macro_string); + return(sc->nil); + /* perhaps include file/line? perhaps some way to return comments in code -- source code as string exactly as in file? */ +} + + +/* -------------------------------- *current-function* -------------------------------- */ +static s7_pointer let_to_function(s7_scheme *sc, s7_pointer e) +{ + if ((e == sc->rootlet) || (!is_let(e))) + return(sc->F); + if (!((is_funclet(e)) || (is_maclet(e)))) + return(sc->F); + if ((has_let_file(e)) && + (let_file(e) <= (s7_int)sc->file_names_top) && + (let_line(e) > 0)) + return(list_3(sc, funclet_function(e), sc->file_names[let_file(e)], make_integer(sc, let_line(e)))); + return(funclet_function(e)); +} + +static s7_pointer g_function(s7_scheme *sc, s7_pointer args) +{ + #define H_function "(*function* e) returns the current function in e" + #define Q_function s7_make_signature(sc, 3, sc->T, sc->is_let_symbol, sc->is_symbol_symbol) + + s7_pointer e, sym = NULL, fname, fval; + if (is_null(args)) /* (*function*) is akin to __func__ in C */ + { + for (e = sc->curlet; is_let(e); e = let_outlet(e)) + if ((is_funclet(e)) || (is_maclet(e))) + break; + return(let_to_function(sc, e)); + } + e = car(args); + if (!is_let(e)) + sole_arg_wrong_type_error_nr(sc, sc->_function__symbol, e, sc->type_names[T_LET]); + if (is_pair(cdr(args))) + { + sym = cadr(args); + if (!is_symbol(sym)) + wrong_type_error_nr(sc, sc->_function__symbol, 2, sym, sc->type_names[T_SYMBOL]); + } + if (e == sc->rootlet) + return(sc->F); + if (!((is_funclet(e)) || (is_maclet(e)))) + e = let_outlet(e); + if (is_null(cdr(args))) + return(let_to_function(sc, e)); + if ((e == sc->rootlet) || (!is_let(e))) + return(sc->F); + if (!((is_funclet(e)) || (is_maclet(e)))) + return(sc->F); + + if (is_keyword(sym)) + sym = keyword_symbol(sym); + fname = funclet_function(e); + fval = s7_symbol_local_value(sc, fname, e); + + if (sym == sc->name_symbol) return(fname); + if (sym == sc->signature_symbol) return(s7_signature(sc, fval)); + if (sym == sc->arity_symbol) return(s7_arity(sc, fval)); + if (sym == sc->documentation_symbol) return(s7_make_string(sc, s7_documentation(sc, fval))); + if (sym == sc->value_symbol) return(fval); + if ((sym == sc->line_symbol) && (has_let_file(e))) return(make_integer(sc, let_line(e))); + if ((sym == sc->file_symbol) && (has_let_file(e))) return(sc->file_names[let_file(e)]); + if (sym == make_symbol(sc, "funclet", 7)) return(e); + if (sym == make_symbol(sc, "source", 6)) return(g_procedure_source(sc, set_plist_1(sc, fval))); + if ((sym == make_symbol(sc, "arglist", 7)) && ((is_any_closure(fval)) || (is_any_macro(fval)))) return(closure_args(fval)); + return(sc->F); +} + + +/* -------------------------------- funclet -------------------------------- */ +s7_pointer s7_funclet(s7_scheme *sc, s7_pointer p) {return((has_closure_let(p)) ? closure_let(p) : sc->rootlet);} + +static s7_pointer g_funclet(s7_scheme *sc, s7_pointer args) +{ + #define H_funclet "(funclet func) tries to return a function's definition environment" + #define Q_funclet s7_make_signature(sc, 2, s7_make_signature(sc, 2, sc->is_let_symbol, sc->is_null_symbol), \ + s7_make_signature(sc, 3, sc->is_procedure_symbol, sc->is_macro_symbol, sc->is_symbol_symbol)) + s7_pointer p = car(args), e; + if (is_symbol(p)) + { + if ((symbol_ctr(p) == 0) || ((p = s7_symbol_value(sc, p)) == sc->undefined)) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_2(sc, wrap_string(sc, "funclet argument, '~S, is unbound", 33), car(args))); /* not p here */ + } + check_method(sc, p, sc->funclet_symbol, args); + + if (!((is_any_procedure(p)) || (is_c_object(p)))) + sole_arg_wrong_type_error_nr(sc, sc->funclet_symbol, p, a_procedure_or_a_macro_string); + e = find_let(sc, p); + if ((is_null(e)) && + (!is_c_object(p))) /* rootlet is not the c_object let */ + return(sc->rootlet); + return(e); +} + + +/* -------------------------------- s7_define_function and friends -------------------------------- + * + * all c_func* are semipermanent, but they might be local: (let () (load "libm.scm" (curlet)) ...) + * but there's no way to tell in general that the let is not exported. + */ +s7_pointer s7_define_function(s7_scheme *sc, const char *name, s7_function fnc, + s7_int required_args, s7_int optional_args, bool rest_arg, const char *doc) +{ + s7_pointer func = s7_make_function(sc, name, fnc, required_args, optional_args, rest_arg, doc); + s7_pointer sym = make_symbol_with_strlen(sc, name); + s7_define(sc, sc->nil, sym, func); + return(sym); +} + +s7_pointer s7_define_safe_function(s7_scheme *sc, const char *name, s7_function fnc, + s7_int required_args, s7_int optional_args, bool rest_arg, const char *doc) +{ + /* returns (string->symbol name), not the c_proc_t func */ + s7_pointer func = s7_make_safe_function(sc, name, fnc, required_args, optional_args, rest_arg, doc); + s7_pointer sym = make_symbol_with_strlen(sc, name); + s7_define(sc, sc->nil, sym, func); + return(sym); +} + +s7_pointer s7_define_typed_function(s7_scheme *sc, const char *name, s7_function fnc, + s7_int required_args, s7_int optional_args, bool rest_arg, + const char *doc, s7_pointer signature) +{ + /* returns (string->symbol name), not the c_proc_t func */ + s7_pointer func = s7_make_typed_function(sc, name, fnc, required_args, optional_args, rest_arg, doc, signature); + s7_pointer sym = make_symbol_with_strlen(sc, name); + s7_define(sc, sc->nil, sym, func); + c_function_set_marker(func, NULL); + return(sym); +} + +static s7_pointer define_bool_function(s7_scheme *sc, const char *name, s7_function fnc, + s7_int optional_args, const char *doc, s7_pointer signature, int32_t sym_to_type, + void (*marker)(s7_pointer p, s7_int top), + bool simple, s7_function bool_setter) +{ + s7_pointer bfunc; + s7_pointer func = s7_make_typed_function(sc, name, fnc, 1, optional_args, false, doc, signature); + s7_pointer sym = make_symbol_with_strlen(sc, name); + s7_define(sc, sc->nil, sym, func); + if (sym_to_type != T_FREE) + symbol_set_type(sym, sym_to_type); + c_function_symbol(func) = sym; + c_function_set_marker(func, marker); + if (simple) c_function_set_has_simple_elements(func); + c_function_set_bool_setter(func, bfunc = s7_make_safe_function(sc, name, bool_setter, 2, 0, false, NULL)); + c_function_set_has_bool_setter(func); + c_function_set_setter(bfunc, func); + set_is_bool_function(bfunc); + return(sym); +} + +s7_pointer s7_define_unsafe_typed_function(s7_scheme *sc, const char *name, s7_function fnc, + s7_int required_args, s7_int optional_args, bool rest_arg, + const char *doc, s7_pointer signature) +{ + /* returns (string->symbol name), not the c_proc_t func */ + s7_pointer func = s7_make_function(sc, name, fnc, required_args, optional_args, rest_arg, doc); + s7_pointer sym = make_symbol_with_strlen(sc, name); + if (signature) c_function_signature(func) = signature; + s7_define(sc, sc->nil, sym, func); + return(sym); +} + +s7_pointer s7_define_semisafe_typed_function(s7_scheme *sc, const char *name, s7_function fnc, + s7_int required_args, s7_int optional_args, bool rest_arg, + const char *doc, s7_pointer signature) +{ + s7_pointer func = s7_make_function(sc, name, fnc, required_args, optional_args, rest_arg, doc); + s7_pointer sym = make_symbol_with_strlen(sc, name); + if (signature) c_function_signature(func) = signature; + set_is_semisafe(func); + s7_define(sc, sc->nil, sym, func); + return(sym); +} + +s7_pointer s7_make_function_star(s7_scheme *sc, const char *name, s7_function fnc, const char *arglist, const char *doc) +{ + s7_pointer func, local_args; + char *internal_arglist; + s7_int n_args, len = safe_strlen(arglist); + s7_int gc_loc; + block_t *b = inline_mallocate(sc, len + 4); + + internal_arglist = (char *)block_data(b); + internal_arglist[0] = '\''; + internal_arglist[1] = '('; + memcpy((void *)(internal_arglist + 2), (const void *)arglist, len); + internal_arglist[len + 2] = ')'; + internal_arglist[len + 3] = '\0'; + local_args = s7_eval_c_string(sc, internal_arglist); + gc_loc = gc_protect_1(sc, local_args); + liberate(sc, b); + n_args = s7_list_length(sc, local_args); + if (n_args < 0) + { + s7_warn(sc, 256, "%s rest argument is not supported in C-side define*: %s\n", name, arglist); + n_args = -n_args; + } + func = s7_make_function(sc, name, fnc, 0, n_args, false, doc); + + if (n_args > 0) + { + s7_pointer p = local_args; + s7_pointer *names = (s7_pointer *)permalloc(sc, n_args * sizeof(s7_pointer)); + s7_pointer *defaults = (s7_pointer *)permalloc(sc, n_args * sizeof(s7_pointer)); + + set_full_type(func, T_C_FUNCTION_STAR | T_UNHEAP); /* unheap from s7_make_function */ + c_function_call_args(func) = NULL; + c_function_arg_names(func) = names; + c_function_arg_defaults(func) = defaults; + c_func_set_simple_defaults(func); + /* (define* (f :allow-other-keys) 32) -> :allow-other-keys can't be the only parameter: (:allow-other-keys) */ + + for (s7_int i = 0; i < n_args; p = cdr(p), i++) + { + s7_pointer arg = car(p); + if (arg == sc->allow_other_keys_keyword) + { + if (is_not_null(cdr(p))) + s7_warn(sc, 256, "%s :allow-other-keys should be the last parameter: %s\n", name, arglist); + if (p == local_args) + s7_warn(sc, 256, "%s :allow-other-keys can't be the only parameter: %s\n", name, arglist); + c_function_set_allow_other_keys(func); /* local_args is local, so it can't carry the bit */ + n_args--; + c_function_optional_args(func) = n_args; + c_function_max_args(func) = n_args; /* apparently not counting keywords */ + } + else + if (is_pair(arg)) /* there is a default */ + { + names[i] = symbol_to_keyword(sc, car(arg)); + defaults[i] = cadr(arg); + remove_from_heap(sc, cadr(arg)); + if ((is_pair(defaults[i])) || + (is_normal_symbol(defaults[i]))) + { + c_func_clear_simple_defaults(func); + mark_function[T_C_FUNCTION_STAR] = mark_c_proc_star; + }} + else + { + if (arg == sc->rest_keyword) + s7_warn(sc, 256, "%s :rest is not supported in C-side define*: %s\n", name, arglist); + names[i] = symbol_to_keyword(sc, arg); + defaults[i] = sc->F; + }}} + else set_full_type(func, T_C_FUNCTION | T_UNHEAP); + + s7_gc_unprotect_at(sc, gc_loc); + return(func); +} + +s7_pointer s7_make_safe_function_star(s7_scheme *sc, const char *name, s7_function fnc, const char *arglist, const char *doc) +{ + s7_pointer func = s7_make_function_star(sc, name, fnc, arglist, doc); + set_full_type(func, full_type(func) | T_SAFE_PROCEDURE); /* don't step on the c_func_has_simple_defaults flag */ + if (is_c_function_star(func)) /* thunk -> c_function */ + c_function_call_args(func) = semipermanent_list(sc, c_function_optional_args(func)); + return(func); +} + +static void define_function_star_1(s7_scheme *sc, const char *name, s7_function fnc, const char *arglist, const char *doc, bool safe, s7_pointer signature) +{ + s7_pointer func; + if (safe) + func = s7_make_safe_function_star(sc, name, fnc, arglist, doc); + else func = s7_make_function_star(sc, name, fnc, arglist, doc); + s7_define(sc, sc->nil, make_symbol_with_strlen(sc, name), func); + if (signature) c_function_signature(func) = signature; +} + +void s7_define_function_star(s7_scheme *sc, const char *name, s7_function fnc, const char *arglist, const char *doc) +{ + define_function_star_1(sc, name, fnc, arglist, doc, false, NULL); +} + +void s7_define_safe_function_star(s7_scheme *sc, const char *name, s7_function fnc, const char *arglist, const char *doc) +{ + define_function_star_1(sc, name, fnc, arglist, doc, true, NULL); +} + +void s7_define_typed_function_star(s7_scheme *sc, const char *name, s7_function fnc, const char *arglist, const char *doc, s7_pointer signature) +{ + define_function_star_1(sc, name, fnc, arglist, doc, true, signature); +} + + +s7_pointer s7_define_macro(s7_scheme *sc, const char *name, s7_function fnc, + s7_int required_args, s7_int optional_args, bool rest_arg, const char *doc) +{ + s7_pointer func = s7_make_function(sc, name, fnc, required_args, optional_args, rest_arg, doc); + s7_pointer sym = make_symbol_with_strlen(sc, name); + set_full_type(func, T_C_MACRO | T_DONT_EVAL_ARGS | T_UNHEAP); /* s7_make_function includes T_UNHEAP */ + s7_define(sc, sc->nil, sym, func); + return(sym); +} + + +/* -------------------------------- macro? -------------------------------- */ +bool s7_is_macro(s7_scheme *sc, s7_pointer x) {return(is_any_macro(x));} +static bool is_macro_b(s7_pointer x) {return(is_any_macro(x));} + +static s7_pointer g_is_macro(s7_scheme *sc, s7_pointer args) +{ + #define H_is_macro "(macro? arg) returns #t if 'arg' is a macro or a bacro" + #define Q_is_macro sc->pl_bt + check_boolean_method(sc, is_any_macro, sc->is_macro_symbol, args); +} + +static bool closure_is_aritable(s7_scheme *sc, s7_pointer x, s7_pointer x_args, int32_t args); + +static s7_pointer s7_macroexpand(s7_scheme *sc, s7_pointer mac, s7_pointer args) +{ + int32_t arg_len; + if (!s7_is_proper_list(sc, args)) + return(sc->F); + + arg_len = proper_list_length(args); + if (!closure_is_aritable(sc, mac, closure_args(mac), arg_len)) + return(sc->F); + + push_stack_direct(sc, OP_EVAL_DONE); + sc->code = mac; + sc->args = args; + sc->curlet = make_let(sc, closure_let(sc->code)); + eval(sc, OP_APPLY_LAMBDA); + return(sc->value); +} + + +/* -------------------------------- documentation -------------------------------- */ +const char *s7_documentation(s7_scheme *sc, s7_pointer x) +{ + s7_pointer val; + if (is_symbol(x)) + { + if (is_keyword(x)) return(NULL); + if (symbol_has_help(x)) + return(symbol_help(x)); + x = s7_symbol_value(sc, x); /* this is needed by Snd */ + } + if ((is_any_c_function(x)) || + (is_c_macro(x))) + return((const char *)c_function_documentation(x)); + + if (is_syntax(x)) + return(syntax_documentation(x)); + + val = funclet_entry(sc, x, sc->local_documentation_symbol); + if ((val) && (is_string(val))) + return(string_value(val)); + + if (has_closure_let(x)) + { + val = closure_body(x); + if ((is_pair(val)) && (is_string(car(val)))) + return((char *)string_value(car(val))); + } + return(NULL); +} + +static s7_pointer g_documentation(s7_scheme *sc, s7_pointer args) +{ + #define H_documentation "(documentation obj) returns obj's documentation string" + #define Q_documentation s7_make_signature(sc, 2, sc->is_string_symbol, sc->T) /* should (documentation 1) be an error? */ + + s7_pointer p = car(args); + if (is_symbol(p)) + { + if ((symbol_has_help(p)) && + (is_global(p))) + return(s7_make_string(sc, symbol_help(p))); + p = s7_symbol_value(sc, p); + } + /* (documentation func) should act like (documentation abs) -- available without (openlet (funclet func)) or (openlet func) + * so we check that case ahead of time here, rather than going through check_method which does not + * call find_let unless has_active_methods(sc, func). Adding T_HAS_METHODS to all closures causes other troubles. + */ + if (has_closure_let(p)) + { + s7_pointer func = funclet_entry(sc, p, sc->documentation_symbol); + if (func) + return(s7_apply_function(sc, func, args)); + func = closure_body(p); + if ((is_pair(func)) && (is_string(car(func)))) + return(car(func)); + } + /* it would be neat if this would work (define x (let ((+documentation+ "hio")) (vector 1 2 3))) (documentation x) */ + check_method(sc, p, sc->documentation_symbol, args); + return(s7_make_string(sc, s7_documentation(sc, p))); +} + +const char *s7_set_documentation(s7_scheme *sc, s7_pointer sym, const char *new_doc) +{ + if (is_keyword(sym)) return(NULL); + if (is_symbol(sym)) + { + symbol_set_has_help(sym); + symbol_set_help(sym, copy_string(new_doc)); + add_saved_pointer(sc, symbol_help(sym)); + } + return(new_doc); +} + + +/* -------------------------------- help -------------------------------- */ +const char *s7_help(s7_scheme *sc, s7_pointer obj) +{ + if (is_syntax(obj)) + return(syntax_documentation(obj)); + + if (is_symbol(obj)) + { + /* here look for name */ + if (s7_documentation(sc, obj)) + return(s7_documentation(sc, obj)); + obj = s7_symbol_value(sc, obj); + } + if (is_any_procedure(obj)) + return(s7_documentation(sc, obj)); + + if (obj == sc->s7_starlet) + return("*s7* is a let that gives access to s7's internal state: e.g. (*s7* 'print-length)"); + + /* if is string, apropos? (can scan symbol table) */ + return(NULL); +} + +static s7_pointer g_help(s7_scheme *sc, s7_pointer args) +{ + #define H_help "(help obj) returns obj's documentation" + #define Q_help s7_make_signature(sc, 2, s7_make_signature(sc, 2, sc->is_string_symbol, sc->is_boolean_symbol), sc->T) + const char *doc; + check_method(sc, car(args), sc->help_symbol, args); + doc = s7_help(sc, car(args)); + return((doc) ? s7_make_string(sc, doc) : sc->F); +} + + +/* -------------------------------- signature -------------------------------- */ +static void init_signatures(s7_scheme *sc) +{ + sc->string_signature = s7_make_signature(sc, 3, sc->is_char_symbol, sc->is_string_symbol, sc->is_integer_symbol); + sc->byte_vector_signature = s7_make_circular_signature(sc, 2, 3, sc->is_byte_symbol, sc->is_byte_vector_symbol, sc->is_integer_symbol); + sc->vector_signature = s7_make_circular_signature(sc, 2, 3, sc->T, sc->is_vector_symbol, sc->is_integer_symbol); + sc->float_vector_signature = s7_make_circular_signature(sc, 2, 3, sc->is_float_symbol, sc->is_float_vector_symbol, sc->is_integer_symbol); + sc->int_vector_signature = s7_make_circular_signature(sc, 2, 3, sc->is_integer_symbol, sc->is_int_vector_symbol, sc->is_integer_symbol); + sc->c_object_signature = s7_make_circular_signature(sc, 2, 3, sc->T, sc->is_c_object_symbol, sc->T); + sc->let_signature = s7_make_circular_signature(sc, 2, 3, sc->T, sc->is_let_symbol, sc->is_symbol_symbol); + sc->hash_table_signature = s7_make_circular_signature(sc, 2, 3, sc->T, sc->is_hash_table_symbol, sc->T); + sc->pair_signature = s7_make_circular_signature(sc, 2, 3, sc->T, sc->is_pair_symbol, sc->is_integer_symbol); +} + +static s7_pointer g_signature(s7_scheme *sc, s7_pointer args) +{ + #define H_signature "(signature obj) returns obj's signature" + #define Q_signature s7_make_signature(sc, 2, s7_make_signature(sc, 2, sc->is_pair_symbol, sc->is_boolean_symbol), sc->T) + + s7_pointer p = car(args); + switch (type(p)) + { + case T_C_FUNCTION: case T_C_RST_NO_REQ_FUNCTION: + case T_C_FUNCTION_STAR: case T_C_MACRO: + return((s7_pointer)c_function_signature(p)); + + case T_MACRO: case T_MACRO_STAR: + case T_BACRO: case T_BACRO_STAR: + case T_CLOSURE: case T_CLOSURE_STAR: + { + s7_pointer func = funclet_entry(sc, p, sc->local_signature_symbol); + if (func) return(func); + func = funclet_entry(sc, p, sc->signature_symbol); + return((func) ? s7_apply_function(sc, func, args) : sc->F); + } + + case T_VECTOR: + if (vector_length(p) == 0) return(sc->F); /* sig () is #f so sig #() should be #f */ + if (!is_typed_vector(p)) + return(sc->vector_signature); + { + s7_pointer lst = list_3(sc, typed_vector_typer_symbol(sc, p), sc->is_vector_symbol, sc->is_integer_symbol); + set_cdddr(lst, cddr(lst)); + return(lst); + } + + case T_FLOAT_VECTOR: return((vector_length(p) == 0) ? sc->F : sc->float_vector_signature); + case T_INT_VECTOR: return((vector_length(p) == 0) ? sc->F : sc->int_vector_signature); + case T_BYTE_VECTOR: return((vector_length(p) == 0) ? sc->F : sc->byte_vector_signature); + case T_PAIR: return(sc->pair_signature); + case T_STRING: return(sc->string_signature); + + case T_HASH_TABLE: + if (is_typed_hash_table(p)) + return(list_3(sc, + hash_table_typer_symbol(sc, hash_table_value_typer(p)), + sc->is_hash_table_symbol, + hash_table_typer_symbol(sc, hash_table_key_typer(p)))); + return(sc->hash_table_signature); + + case T_ITERATOR: + p = iterator_sequence(p); + if ((is_hash_table(p)) || (is_let(p))) /* cons returned -- would be nice to include the car/cdr types if known */ + return(list_1(sc, sc->is_pair_symbol)); + p = g_signature(sc, set_plist_1(sc, p)); + return(list_1(sc, (is_pair(p)) ? car(p) : sc->T)); + + case T_C_OBJECT: + check_method(sc, p, sc->signature_symbol, args); + return(sc->c_object_signature); + + case T_LET: + check_method(sc, p, sc->signature_symbol, args); + return(sc->let_signature); + + case T_SYMBOL: + /* this used to get the symbol's value and call g_signature on that */ + { + s7_pointer slot = s7_slot(sc, p); + if ((is_slot(slot)) && (slot_has_setter(slot))) + { + s7_pointer setter = slot_setter(slot); + p = g_signature(sc, set_plist_1(sc, setter)); + if (is_pair(p)) + return(list_1(sc, car(p))); + }} + break; + + default: break; + } + return(sc->F); +} + +s7_pointer s7_signature(s7_scheme *sc, s7_pointer func) {return(g_signature(sc, set_plist_1(sc, func)));} + + +/* -------------------------------- dynamic-wind -------------------------------- */ +static s7_pointer closure_or_f(s7_scheme *sc, s7_pointer p) +{ + s7_pointer body; + if (!is_closure(p)) return(p); + body = closure_body(p); + if (is_pair(cdr(body))) return(p); + if (!is_pair(car(body))) return(sc->F); + return((caar(body) == sc->quote_symbol) ? sc->F : p); +} + +static s7_pointer make_baffled_closure(s7_scheme *sc, s7_pointer inp) +{ + /* for dynamic-wind to protect initial and final functions from call/cc */ + s7_pointer nclo = make_closure_unchecked(sc, sc->nil, closure_body(inp), type(inp), 0); /* always preceded by new dw cell */ + s7_pointer let = make_let(sc, closure_let(inp)); /* let_outlet(let) = closure_let(inp) */ + set_baffle_let(let); + set_let_baffle_key(let, sc->baffle_ctr++); + closure_set_let(nclo, let); + return(nclo); +} + +static bool is_dwind_thunk(s7_scheme *sc, s7_pointer x) +{ + switch (type(x)) + { + case T_MACRO: case T_BACRO: case T_CLOSURE: + case T_MACRO_STAR: case T_BACRO_STAR: case T_CLOSURE_STAR: + return(is_null(closure_args(x))); /* this case does not match is_aritable -- it could be loosened -- arity=0 below would need fixup */ + case T_C_FUNCTION: + return(c_function_is_aritable(x, 0)); + case T_C_FUNCTION_STAR: + return(c_function_max_args(x) >= 0); + case T_C_MACRO: + return((c_macro_min_args(x) <= 0) && (c_macro_max_args(x) >= 0)); + case T_GOTO: case T_CONTINUATION: case T_C_RST_NO_REQ_FUNCTION: + return(true); + } + return(x == sc->F); /* (dynamic-wind #f (lambda () 3) #f) */ +} + +static s7_pointer g_dynamic_wind_unchecked(s7_scheme *sc, s7_pointer args) +{ + s7_pointer p, inp, outp; + + new_cell(sc, p, T_DYNAMIC_WIND); /* don't mark car/cdr, don't copy */ + dynamic_wind_in(p) = closure_or_f(sc, car(args)); + dynamic_wind_body(p) = cadr(args); + dynamic_wind_out(p) = closure_or_f(sc, caddr(args)); + + inp = dynamic_wind_in(p); + if ((is_any_closure(inp)) && (!is_safe_closure(inp))) /* wrap this use of inp in a with-baffle */ + dynamic_wind_in(p) = make_baffled_closure(sc, inp); + + outp = dynamic_wind_out(p); + if ((is_any_closure(outp)) && (!is_safe_closure(outp))) + dynamic_wind_out(p) = make_baffled_closure(sc, outp); + + /* since we don't care about the in and out results, and they are thunks, if the body is not a pair, + * or is a quoted thing, we just ignore that function. + */ + push_stack(sc, OP_DYNAMIC_WIND, sc->nil, p); /* args will be the saved result, code = s7_dynwind_t obj */ + if (inp != sc->F) + { + dynamic_wind_state(p) = DWIND_INIT; + push_stack(sc, OP_APPLY, sc->nil, dynamic_wind_in(p)); + } + else + { + dynamic_wind_state(p) = DWIND_BODY; + push_stack(sc, OP_APPLY, sc->nil, dynamic_wind_body(p)); + } + return(sc->F); +} + +static s7_pointer g_dynamic_wind_init(s7_scheme *sc, s7_pointer args) +{ + s7_pointer p, inp = closure_or_f(sc, car(args)); + new_cell(sc, p, T_DYNAMIC_WIND); /* don't mark car/cdr, don't copy */ + dynamic_wind_in(p) = inp; + dynamic_wind_body(p) = cadr(args); + dynamic_wind_out(p) = sc->F; + if ((is_any_closure(inp)) && (!is_safe_closure(inp))) /* wrap this use of inp in a with-baffle */ + dynamic_wind_in(p) = make_baffled_closure(sc, inp); + push_stack(sc, OP_DYNAMIC_WIND, sc->nil, p); /* args will be the saved result, code = s7_dynwind_t obj */ + dynamic_wind_state(p) = DWIND_INIT; + push_stack(sc, OP_APPLY, sc->nil, dynamic_wind_in(p)); + return(sc->F); +} + +static s7_pointer g_dynamic_wind_body(s7_scheme *sc, s7_pointer args) +{ + push_stack(sc, OP_APPLY, sc->nil, cadr(args)); + return(sc->F); +} + +static s7_pointer g_dynamic_wind(s7_scheme *sc, s7_pointer args) +{ + #define H_dynamic_wind "(dynamic-wind init body finish) calls init, then body, then finish, \ +each a function of no arguments, guaranteeing that finish is called even if body is exited" + #define Q_dynamic_wind s7_make_signature(sc, 4, sc->values_symbol, \ + s7_make_signature(sc, 2, sc->is_procedure_symbol, sc->not_symbol), \ + sc->is_procedure_symbol, \ + s7_make_signature(sc, 2, sc->is_procedure_symbol, sc->not_symbol)) + + if (!is_dwind_thunk(sc, car(args))) + return(method_or_bust(sc, car(args), sc->dynamic_wind_symbol, args, wrap_string(sc, "a thunk or #f", 13), 1)); + if (!is_thunk(sc, cadr(args))) + return(method_or_bust(sc, cadr(args), sc->dynamic_wind_symbol, args, a_thunk_string, 2)); + if (!is_dwind_thunk(sc, caddr(args))) + return(method_or_bust(sc, caddr(args), sc->dynamic_wind_symbol, args, wrap_string(sc, "a thunk or #f", 13), 3)); + + /* this won't work: + (let ((final (lambda (a b c) (list a b c)))) + (dynamic-wind + (lambda () #f) + (lambda () (set! final (lambda () (display "in final")))) + final)) + * but why not? 'final' is a thunk by the time it is evaluated. catch (the error handler) is similar. + * It can't work here because we set up the dynamic_wind_out slot below and + * even if the thunk check was removed, we'd still be trying to apply the original function. + */ + return(g_dynamic_wind_unchecked(sc, args)); +} + +static bool is_lambda(s7_scheme *sc, s7_pointer sym) +{ + return((sym == sc->lambda_symbol) && (symbol_id(sym) == 0)); /* do we need (!sc->in_with_let) ? */ + /* symbol_id=0 means it has never been rebound (T_GLOBAL might not be set for initial stuff) */ +} + +static int32_t is_ok_thunk(s7_scheme *sc, s7_pointer arg) /* used only in dynamic_wind_chooser */ +{ + /* 0 = not ok, 1 = ok but not simple, 2 = ok body is just #f, 3 = #f */ + if (arg == sc->F) return(3); + if ((is_pair(arg)) && + (is_lambda(sc, car(arg))) && + (is_pair(cdr(arg))) && + (is_null(cadr(arg))) && /* (lambda () ...) */ + (is_pair(cddr(arg))) && + (s7_is_proper_list(sc, cddr(arg)))) + return(((is_null(cdddr(arg))) && (caddr(arg) == sc->F)) ? 2 : 1); /* 2: (lambda () #f) */ + return(0); +} + +static s7_pointer dynamic_wind_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool unused_ops) +{ + if ((args == 3) && + (is_ok_thunk(sc, caddr(expr)))) + { + int32_t init = is_ok_thunk(sc, cadr(expr)); + int32_t end = is_ok_thunk(sc, cadddr(expr)); + if ((init > 1) && (end > 1)) return(sc->dynamic_wind_body); + if ((init > 0) && (end > 1)) return(sc->dynamic_wind_init); + if ((init > 0) && (end > 0)) return(sc->dynamic_wind_unchecked); + } + return(f); +} + +s7_pointer s7_dynamic_wind(s7_scheme *sc, s7_pointer init, s7_pointer body, s7_pointer finish) +{ + /* this is essentially s7_call with a dynamic-wind wrapper around "body" */ + declare_jump_info(); + store_jump_info(sc); + set_jump_info(sc, DYNAMIC_WIND_SET_JUMP); + if (jump_loc != NO_JUMP) + { + if (jump_loc != ERROR_JUMP) + eval(sc, sc->cur_op); + } + else + { + s7_pointer p; + push_stack_direct(sc, OP_EVAL_DONE); /* this is ok because we have called setjmp etc */ + sc->args = sc->nil; + new_cell(sc, p, T_DYNAMIC_WIND); + dynamic_wind_in(p) = T_Ext(init); + dynamic_wind_body(p) = T_Ext(body); + dynamic_wind_out(p) = T_Ext(finish); + push_stack(sc, OP_DYNAMIC_WIND, sc->nil, p); + if (init != sc->F) + { + dynamic_wind_state(p) = DWIND_INIT; + sc->code = init; + } + else + { + dynamic_wind_state(p) = DWIND_BODY; + sc->code = body; + } + eval(sc, OP_APPLY); + } + restore_jump_info(sc); + + if (is_multiple_value(sc->value)) + sc->value = splice_in_values(sc, multiple_value(sc->value)); + return(sc->value); +} + +static void op_unwind_output(s7_scheme *sc) +{ + bool is_file = is_file_port(sc->code); + + if ((is_output_port(sc->code)) && + (!port_is_closed(sc->code))) + s7_close_output_port(sc, sc->code); /* may call fflush */ + if (((is_output_port(sc->args)) && + (!port_is_closed(sc->args))) || + (sc->args == sc->F)) + set_current_output_port(sc, sc->args); + if ((is_file) && + (is_multiple_value(sc->value))) + sc->value = splice_in_values(sc, multiple_value(sc->value)); +} + +static void op_unwind_input(s7_scheme *sc) +{ + /* sc->code is an input port */ + if (!port_is_closed(sc->code)) + s7_close_input_port(sc, sc->code); + if ((is_input_port(sc->args)) && + (!port_is_closed(sc->args))) + set_current_input_port(sc, sc->args); + if (is_multiple_value(sc->value)) + sc->value = splice_in_values(sc, multiple_value(sc->value)); +} + +static bool op_dynamic_wind(s7_scheme *sc) +{ + if (dynamic_wind_state(sc->code) == DWIND_INIT) + { + dynamic_wind_state(sc->code) = DWIND_BODY; + push_stack(sc, OP_DYNAMIC_WIND, sc->nil, sc->code); + sc->code = dynamic_wind_body(sc->code); + sc->args = sc->nil; + return(true); /* goto apply */ + } + if (dynamic_wind_state(sc->code) == DWIND_BODY) + { + dynamic_wind_state(sc->code) = DWIND_FINISH; + if (dynamic_wind_out(sc->code) != sc->F) + { + push_stack(sc, OP_DYNAMIC_WIND, sc->value, sc->code); + sc->code = dynamic_wind_out(sc->code); + sc->args = sc->nil; + return(true); + } + if (is_multiple_value(sc->value)) + sc->value = splice_in_values(sc, multiple_value(sc->value)); + return(false); /* goto start */ + } + if (is_multiple_value(sc->args)) /* (+ 1 (dynamic-wind (lambda () #f) (lambda () (values 2 3 4)) (lambda () #f)) 5) */ + sc->value = splice_in_values(sc, multiple_value(sc->args)); + else sc->value = sc->args; /* value saved above */ + return(false); +} + + +/* -------------------------------- c-object? -------------------------------- */ +bool s7_is_c_object(s7_pointer p) {return(is_c_object(p));} + +static s7_pointer g_is_c_object(s7_scheme *sc, s7_pointer args) +{ + #define H_is_c_object "(c-object? obj) returns #t is obj is a c-object." + #define Q_is_c_object sc->pl_bt + s7_pointer obj = car(args); + if (is_c_object(obj)) return(sc->T); + if (!has_active_methods(sc, obj)) return(sc->F); + return(apply_boolean_method(sc, obj, sc->is_c_object_symbol)); +} + + +/* -------------------------------- c-object-type -------------------------------- */ +static noreturn void apply_error_nr(s7_scheme *sc, s7_pointer obj, s7_pointer args) +{ + /* the operator type is needed here else the error message is confusing: + * (apply '+ (list 1 2))) -> ;attempt to apply + to (1 2)? + * but using current_code(sc) to get the context is not optimal: + * (1 (bignum 4))) -> ;attempt to apply an integer 1 to (4) in (bignum 4)? + */ + if (is_null(obj)) + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "attempt to apply nil to ~S in ~S?", 33), + args, current_code(sc))); + error_nr(sc, sc->syntax_error_symbol, + set_elist_5(sc, wrap_string(sc, "attempt to apply ~A ~S to ~S in ~S?", 35), + type_name_string(sc, obj), obj, args, current_code(sc))); +} + +static void fallback_free(void *value) {} +static void fallback_mark(void *value) {} + +static s7_pointer fallback_ref(s7_scheme *sc, s7_pointer args) {apply_error_nr(sc, car(args), cdr(args)); return(NULL);} +static s7_pointer fallback_set(s7_scheme *sc, s7_pointer args) {syntax_error_nr(sc, "attempt to set ~S?", 18, car(args)); return(NULL);} +static s7_pointer fallback_length(s7_scheme *sc, s7_pointer obj) {return(sc->F);} + +s7_int s7_c_object_type(s7_pointer obj) {return((is_c_object(obj)) ? c_object_type(obj) : -1);} + +static s7_pointer g_c_object_type(s7_scheme *sc, s7_pointer args) +{ + #define H_c_object_type "(c-object-type obj) returns the c_object's type tag." + #define Q_c_object_type s7_make_signature(sc, 2, sc->is_integer_symbol, sc->is_c_object_symbol) + + s7_pointer p = car(args); + if (is_c_object(p)) + return(make_integer(sc, c_object_type(p))); /* this is the c_object_types table index = tag */ + + /* method or bust with only one arg -- sole_arg_method_or_bust? */ + if (!has_active_methods(sc, p)) + sole_arg_wrong_type_error_nr(sc, sc->c_object_type_symbol, p, sc->type_names[T_C_OBJECT]); + return(find_and_apply_method(sc, p, sc->c_object_type_symbol, args)); +} + +static s7_pointer g_c_object_set(s7_scheme *sc, s7_pointer args) /* called in c_object_set_function */ +{ + s7_pointer obj = car(args); + if (!is_c_object(obj)) /* (call/cc (setter (block))) will call c-object-set! with the continuation as the argument! */ + wrong_type_error_nr(sc, make_symbol(sc, "c-object-set!", 13), 1, obj, sc->type_names[T_C_OBJECT]); + return((*(c_object_set(sc, obj)))(sc, args)); +} + +s7_int s7_make_c_type(s7_scheme *sc, const char *name) +{ + c_object_t *c_type; + s7_int tag = sc->num_c_object_types++; + + if (tag >= sc->c_object_types_size) + { + if (sc->c_object_types_size == 0) + { + sc->c_object_types_size = 8; + sc->c_object_types = (c_object_t **)Calloc(sc->c_object_types_size, sizeof(c_object_t *)); + } + else + { + sc->c_object_types_size = tag * 2; + sc->c_object_types = (c_object_t **)Realloc((void *)(sc->c_object_types), sc->c_object_types_size * sizeof(c_object_t *)); + }} + c_type = (c_object_t *)Calloc(1, sizeof(c_object_t)); /* Malloc+field=NULL is slightly faster here */ + sc->c_object_types[tag] = c_type; + c_type->type = tag; + c_type->scheme_name = make_permanent_string(name); + c_type->getter = sc->F; + c_type->setter = sc->F; + c_type->free = fallback_free; + c_type->mark = fallback_mark; + c_type->ref = fallback_ref; + c_type->set = fallback_set; + c_type->outer_type = T_C_OBJECT; + c_type->length = fallback_length; + /* all other fields are NULL */ + return(tag); +} + +void s7_c_type_set_gc_free(s7_scheme *sc, s7_int tag, s7_pointer (*gc_free)(s7_scheme *sc, s7_pointer obj)) {sc->c_object_types[tag]->gc_free = gc_free;} +void s7_c_type_set_gc_mark(s7_scheme *sc, s7_int tag, s7_pointer (*marker)(s7_scheme *sc, s7_pointer obj)) {sc->c_object_types[tag]->gc_mark = marker;} +void s7_c_type_set_equal(s7_scheme *sc, s7_int tag, bool (*equal)(void *value1, void *value2)) {sc->c_object_types[tag]->eql = equal;} +void s7_c_type_set_is_equal(s7_scheme *sc, s7_int tag, s7_pointer (*is_equal)(s7_scheme *sc, s7_pointer args)) {sc->c_object_types[tag]->equal = is_equal;} +void s7_c_type_set_copy(s7_scheme *sc, s7_int tag, s7_pointer (*copy)(s7_scheme *sc, s7_pointer args)) {sc->c_object_types[tag]->copy = copy;} +void s7_c_type_set_fill(s7_scheme *sc, s7_int tag, s7_pointer (*fill)(s7_scheme *sc, s7_pointer args)) {sc->c_object_types[tag]->fill = fill;} +void s7_c_type_set_reverse(s7_scheme *sc, s7_int tag, s7_pointer (*reverse)(s7_scheme *sc, s7_pointer args)) {sc->c_object_types[tag]->reverse = reverse;} +void s7_c_type_set_to_list(s7_scheme *sc, s7_int tag, s7_pointer (*to_list)(s7_scheme *sc, s7_pointer args)) {sc->c_object_types[tag]->to_list = to_list;} +void s7_c_type_set_to_string(s7_scheme *sc, s7_int tag, s7_pointer (*to_string)(s7_scheme *sc, s7_pointer args)) {sc->c_object_types[tag]->to_string = to_string;} + +void s7_c_type_set_length(s7_scheme *sc, s7_int tag, s7_pointer (*length)(s7_scheme *sc, s7_pointer args)) +{ + sc->c_object_types[tag]->length = (length) ? length : fallback_length; /* is_sequence(c_obj) is #t so we need a length method */ +} + +void s7_c_type_set_is_equivalent(s7_scheme *sc, s7_int tag, s7_pointer (*is_equivalent)(s7_scheme *sc, s7_pointer args)) +{ + sc->c_object_types[tag]->equivalent = is_equivalent; +} + +void s7_c_type_set_free(s7_scheme *sc, s7_int tag, void (*gc_free)(void *value)) +{ + sc->c_object_types[tag]->free = (gc_free) ? gc_free : fallback_free; +} + +void s7_c_type_set_mark(s7_scheme *sc, s7_int tag, void (*mark)(void *value)) +{ + sc->c_object_types[tag]->mark = (mark) ? mark : fallback_mark; +} + +void s7_c_type_set_ref(s7_scheme *sc, s7_int tag, s7_pointer (*ref)(s7_scheme *sc, s7_pointer args)) +{ + sc->c_object_types[tag]->ref = (ref) ? ref : fallback_ref; + sc->c_object_types[tag]->outer_type = (sc->c_object_types[tag]->ref == fallback_ref) ? T_C_OBJECT : (T_C_OBJECT | T_SAFE_PROCEDURE); +} + +void s7_c_type_set_getter(s7_scheme *sc, s7_int tag, s7_pointer getter) +{ + if ((S7_DEBUGGING) && (getter) && (!is_c_function(getter))) fprintf(stderr, "%s[%d]: %p is not a c_function\n", __func__, __LINE__, getter); + sc->c_object_types[tag]->getter = (getter) ? getter : sc->F; +} + +void s7_c_type_set_set(s7_scheme *sc, s7_int tag, s7_pointer (*set)(s7_scheme *sc, s7_pointer args)) +{ + sc->c_object_types[tag]->set = (set) ? set : fallback_set; +} + +void s7_c_type_set_setter(s7_scheme *sc, s7_int tag, s7_pointer setter) +{ + if ((S7_DEBUGGING) && (setter) && (!is_c_function(setter))) fprintf(stderr, "%s[%d]: %p is not a c_function\n", __func__, __LINE__, setter); + sc->c_object_types[tag]->setter = (setter) ? setter : sc->F; +} + +void *s7_c_object_value(s7_pointer obj) {return(c_object_value(obj));} + +void *s7_c_object_value_checked(s7_pointer obj, s7_int type) +{ + if ((is_c_object(obj)) && (c_object_type(obj) == type)) + return(c_object_value(obj)); + return(NULL); +} + +static s7_pointer make_c_object_with_let(s7_scheme *sc, s7_int type, void *value, s7_pointer let, bool with_gc) +{ + s7_pointer x; + new_cell(sc, x, sc->c_object_types[type]->outer_type); + + /* c_object_info(x) = &(sc->c_object_types[type]); */ + /* that won't work because c_object_types can move when it is realloc'd and the old stuff is freed by realloc + * and since we're checking (for example) ref_2 existence as not null, we can't use a table of c_object_t's! + * Using mallocate (s7_make_c_object_with_data) is faster, but not enough to warrant the code. + */ + c_object_type(x) = type; + c_object_value(x) = value; + c_object_set_let(x, (let == sc->rootlet) ? sc->nil : let); + c_object_s7(x) = sc; + if (with_gc) add_c_object(sc, x); + return(x); +} + +s7_pointer s7_make_c_object_with_let(s7_scheme *sc, s7_int type, void *value, s7_pointer let) +{ + return(make_c_object_with_let(sc, type, value, let, true)); +} + +s7_pointer s7_make_c_object(s7_scheme *sc, s7_int type, void *value) +{ + return(make_c_object_with_let(sc, type, value, sc->nil, true)); +} + +s7_pointer s7_make_c_object_without_gc(s7_scheme *sc, s7_int type, void *value) +{ + return(make_c_object_with_let(sc, type, value, sc->nil, false)); +} + +s7_pointer s7_c_object_let(s7_pointer obj) {return(c_object_let(obj));} + +s7_pointer s7_c_object_set_let(s7_scheme *sc, s7_pointer obj, s7_pointer e) +{ + if ((!is_immutable(obj)) && + (is_let(e))) + c_object_set_let(obj, (e == sc->rootlet) ? sc->nil : e); + return(e); +} + +static s7_pointer c_object_length(s7_scheme *sc, s7_pointer obj) +{ + return((*(c_object_len(sc, obj)))(sc, set_clist_1(sc, obj))); +} + +static s7_int c_object_length_to_int(s7_scheme *sc, s7_pointer obj) +{ + s7_pointer res = (*(c_object_len(sc, obj)))(sc, set_clist_1(sc, obj)); + if (s7_is_integer(res)) + return(s7_integer_clamped_if_gmp(sc, res)); + return(-1); +} + +static s7_pointer copy_c_object(s7_scheme *sc, s7_pointer args) +{ + s7_pointer obj = car(args); + check_method(sc, obj, sc->copy_symbol, args); + if (!c_object_copy(sc, obj)) + missing_method_error_nr(sc, sc->copy_symbol, obj); + return((*(c_object_copy(sc, obj)))(sc, args)); +} + +static s7_pointer c_object_type_to_let(s7_scheme *sc, s7_pointer cobj) +{ + return(internal_inlet(sc, 4, + sc->name_symbol, c_object_scheme_name(sc, cobj), + sc->setter_symbol, (c_object_set(sc, cobj) != fallback_set) ? sc->c_object_set_function : sc->F)); + /* should we make new wrappers every time this is called? or save the let somewhere and reuse it? */ +} + +static void apply_c_object(s7_scheme *sc) /* -------- applicable c_object -------- */ +{ + sc->value = (*(c_object_ref(sc, sc->code)))(sc, set_ulist_1(sc, sc->code, sc->args)); + set_car(sc->u1_1, sc->F); +} + +static bool op_implicit_c_object_ref_a(s7_scheme *sc) +{ + s7_pointer c = lookup_checked(sc, car(sc->code)); + if (!is_c_object(c)) {sc->last_function = c; return(false);} + set_car(sc->t2_2, fx_call(sc, cdr(sc->code))); + set_car(sc->t2_1, c); /* fx_call above might use sc->t2* */ + sc->value = (*(c_object_ref(sc, c)))(sc, sc->t2_1); + return(true); +} + + +/* -------- dilambda -------- */ + +s7_pointer s7_dilambda_with_environment(s7_scheme *sc, s7_pointer envir, + const char *name, + s7_pointer (*getter)(s7_scheme *sc, s7_pointer args), s7_int get_req_args, s7_int get_opt_args, + s7_pointer (*setter)(s7_scheme *sc, s7_pointer args), s7_int set_req_args, s7_int set_opt_args, + const char *documentation) +{ + s7_pointer get_func, set_func; + char *internal_set_name; + s7_int len, name_len; + + if (!name) return(sc->F); + name_len = safe_strlen(name); + len = 16 + name_len; + internal_set_name = (char *)permalloc(sc, len); + internal_set_name[0] = '\0'; + catstrs_direct(internal_set_name, "[set-", name, "]", (const char *)NULL); + get_func = s7_make_safe_function(sc, name, getter, get_req_args, get_opt_args, false, documentation); + s7_define(sc, envir, make_symbol(sc, name, name_len), get_func); + set_func = s7_make_safe_function(sc, internal_set_name, setter, set_req_args, set_opt_args, false, documentation); + c_function_set_setter(get_func, set_func); + return(get_func); +} + +s7_pointer s7_dilambda(s7_scheme *sc, + const char *name, + s7_pointer (*getter)(s7_scheme *sc, s7_pointer args), s7_int get_req_args, s7_int get_opt_args, + s7_pointer (*setter)(s7_scheme *sc, s7_pointer args), s7_int set_req_args, s7_int set_opt_args, + const char *documentation) +{ + return(s7_dilambda_with_environment(sc, sc->nil, name, getter, get_req_args, get_opt_args, setter, set_req_args, set_opt_args, documentation)); +} + +s7_pointer s7_typed_dilambda(s7_scheme *sc, + const char *name, + s7_pointer (*getter)(s7_scheme *sc, s7_pointer args), s7_int get_req_args, s7_int get_opt_args, + s7_pointer (*setter)(s7_scheme *sc, s7_pointer args), s7_int set_req_args, s7_int set_opt_args, + const char *documentation, + s7_pointer get_sig, s7_pointer set_sig) +{ + s7_pointer get_func = s7_dilambda(sc, name, getter, get_req_args, get_opt_args, setter, set_req_args, set_opt_args, documentation); + s7_pointer set_func = c_function_setter(get_func); + if (get_sig) c_function_signature(get_func) = get_sig; + if (set_sig) c_function_signature(set_func) = set_sig; + return(get_func); +} + + +/* -------------------------------- dilambda? -------------------------------- */ +bool s7_is_dilambda(s7_pointer obj) +{ + switch (type(obj)) + { + case T_MACRO: case T_MACRO_STAR: + case T_BACRO: case T_BACRO_STAR: + case T_CLOSURE: case T_CLOSURE_STAR: + return(is_any_procedure(closure_setter_or_map_list(obj))); /* type >= T_CLOSURE (excludes goto/continuation) */ + case T_C_FUNCTION: case T_C_RST_NO_REQ_FUNCTION: case T_C_FUNCTION_STAR: + return(is_any_procedure(c_function_setter(obj))); + case T_C_MACRO: + return(is_any_procedure(c_macro_setter(obj))); + } + return(false); +} + +static s7_pointer g_is_dilambda(s7_scheme *sc, s7_pointer args) +{ + #define H_is_dilambda "(dilambda? obj) returns #t if obj is a procedure with setter." + #define Q_is_dilambda sc->pl_bt + check_boolean_method(sc, s7_is_dilambda, sc->is_dilambda_symbol, args); +} + + +/* -------------------------------- dilambda -------------------------------- */ +static s7_pointer g_dilambda(s7_scheme *sc, s7_pointer args) +{ + #define H_dilambda "(dilambda getter setter) sets getter's setter to be setter." + #define Q_dilambda s7_make_signature(sc, 3, sc->is_procedure_symbol, sc->is_procedure_symbol, sc->is_procedure_symbol) + + s7_pointer getter = car(args), setter; + if (!is_any_procedure(getter)) + wrong_type_error_nr(sc, sc->dilambda_symbol, 1, getter, a_procedure_or_a_macro_string); + + setter = cadr(args); + if (!is_any_procedure(setter)) + wrong_type_error_nr(sc, sc->dilambda_symbol, 2, setter, a_procedure_or_a_macro_string); + + s7_set_setter(sc, getter, setter); + return(getter); +} + + +/* -------------------------------- arity -------------------------------- */ +static s7_pointer closure_arity_to_cons(s7_scheme *sc, s7_pointer x, s7_pointer x_args) +{ + /* x_args is unprocessed -- it is exactly the list as used in the closure[*] definition */ + int32_t len; + + if (is_symbol(x_args)) /* any number of args is ok */ + return(cons(sc, int_zero, max_arity)); + if (closure_arity_unknown(x)) + closure_set_arity(x, s7_list_length(sc, x_args)); + len = closure_arity(x); + if (len < 0) /* dotted list => rest arg, (length '(a b . c)) is -2 */ + return(cons(sc, make_integer(sc, -len), max_arity)); + return(cons(sc, make_integer(sc, len), make_integer_unchecked(sc, len))); +} + +static void closure_star_arity_1(s7_scheme *sc, s7_pointer x, s7_pointer args) +{ + if (closure_arity_unknown(x)) + { + if (is_null(args)) + closure_set_arity(x, 0); + else + if ((is_symbol(args)) || (allows_other_keys(args))) + closure_set_arity(x, -1); + else + { + s7_pointer p; + int32_t i; + for (i = 0, p = args; is_pair(p); i++, p = cdr(p)) /* is_pair(p) so (f1 a . b) will end with b not null */ + { + s7_pointer arg = car(p); + if (arg == sc->rest_keyword) + break; + } + closure_set_arity(x, ((is_null(p)) ? i : -1)); /* see below */ + }} +} + +static s7_pointer closure_star_arity_to_cons(s7_scheme *sc, s7_pointer x, s7_pointer x_args) +{ + closure_star_arity_1(sc, x, x_args); + return((closure_arity(x) == -1) ? cons(sc, int_zero, max_arity) : cons(sc, int_zero, make_integer(sc, closure_arity(x)))); +} + +static int32_t closure_arity_to_int(s7_scheme *sc, s7_pointer x) +{ + /* not lambda* here */ + if (closure_arity_unknown(x)) + { + int32_t i; + s7_pointer b; + for (i = 0, b = closure_args(x); is_pair(b); i++, b = cdr(b)) {}; + if (is_null(b)) + closure_set_arity(x, i); + else + { + if (i == 0) + return(-1); + closure_set_arity(x, -i); + }} + return(closure_arity(x)); +} + +static int32_t closure_star_arity_to_int(s7_scheme *sc, s7_pointer x) +{ + /* not lambda here */ + closure_star_arity_1(sc, x, closure_args(x)); + return(closure_arity(x)); +} + +s7_pointer s7_arity(s7_scheme *sc, s7_pointer x) +{ + switch (type(x)) + { + case T_C_FUNCTION: + return(cons(sc, make_integer(sc, c_function_min_args(x)), make_integer_unchecked(sc, c_function_max_args(x)))); + case T_C_RST_NO_REQ_FUNCTION: case T_C_FUNCTION_STAR: + return(cons(sc, int_zero, make_integer(sc, c_function_max_args(x)))); + case T_MACRO: case T_BACRO: case T_CLOSURE: + return(closure_arity_to_cons(sc, x, closure_args(x))); + case T_MACRO_STAR: case T_BACRO_STAR: case T_CLOSURE_STAR: + return(closure_star_arity_to_cons(sc, x, closure_args(x))); + case T_C_MACRO: + return(cons(sc, make_integer(sc, c_macro_min_args(x)), make_integer_unchecked(sc, c_macro_max_args(x)))); + case T_GOTO: case T_CONTINUATION: + return(cons(sc, int_zero, max_arity)); + case T_STRING: + return((string_length(x) == 0) ? sc->F : cons(sc, int_one, int_one)); + case T_LET: + return(cons(sc, int_one, int_one)); + case T_C_OBJECT: + check_method(sc, x, sc->arity_symbol, set_plist_1(sc, x)); + return((is_safe_procedure(x)) ? cons(sc, int_zero, max_arity) : sc->F); + case T_VECTOR: + if (vector_length(x) == 0) return(sc->F); + if (has_simple_elements(x)) return(cons(sc, int_one, make_integer(sc, vector_rank(x)))); + return(cons(sc, int_one, max_arity)); + case T_INT_VECTOR: case T_FLOAT_VECTOR: case T_BYTE_VECTOR: + return((vector_length(x) == 0) ? sc->F : cons(sc, int_one, make_integer(sc, vector_rank(x)))); + case T_PAIR: case T_HASH_TABLE: + return(cons(sc, int_one, max_arity)); + case T_ITERATOR: + return(cons(sc, int_zero, int_zero)); + case T_SYNTAX: + return(cons(sc, small_int(syntax_min_args(x)), (syntax_max_args(x) == -1) ? max_arity : small_int(syntax_max_args(x)))); + } + return(sc->F); +} + +static s7_pointer g_arity(s7_scheme *sc, s7_pointer args) +{ + #define H_arity "(arity obj) the min and max acceptable args for obj if it is applicable, otherwise #f." + #define Q_arity s7_make_signature(sc, 2, s7_make_signature(sc, 2, sc->is_pair_symbol, sc->not_symbol), sc->T) + /* check_method(sc, p, sc->arity_symbol, args); */ + return(s7_arity(sc, car(args))); +} + + +/* -------------------------------- aritable? -------------------------------- */ +static bool closure_is_aritable(s7_scheme *sc, s7_pointer x, s7_pointer x_args, int32_t args) +{ + /* x_args is unprocessed -- it is exactly the list as used in the closure definition */ + s7_int len; + + if (args == 0) + return(!is_pair(x_args)); + if (is_symbol(x_args)) /* any number of args is ok */ + return(true); + + len = closure_arity(x); + if (len == CLOSURE_ARITY_NOT_SET) + { + len = s7_list_length(sc, x_args); + closure_set_arity(x, len); + } + if (len < 0) /* dotted list => rest arg, (length '(a b . c)) is -2 */ + return((-len) <= args); /* so we have enough to take care of the required args */ + return(args == len); /* in a normal lambda list, there are no other possibilities */ +} + +static bool closure_star_is_aritable(s7_scheme *sc, s7_pointer x, s7_pointer x_args, int32_t args) +{ + if (is_symbol(x_args)) + return(true); + closure_star_arity_1(sc, x, x_args); + return((closure_arity(x) == -1) || + (args <= closure_arity(x))); +} + +bool s7_is_aritable(s7_scheme *sc, s7_pointer x, s7_int args) +{ + switch (type(x)) + { + case T_C_FUNCTION: + return(c_function_is_aritable(x, args)); + case T_C_RST_NO_REQ_FUNCTION: + if ((x == initial_value(sc->hash_table_symbol)) || /* these two need a value for each key */ + (x == initial_value(sc->weak_hash_table_symbol))) + return((args & 1) == 0); + case T_C_FUNCTION_STAR: + return(c_function_max_args(x) >= args); + + case T_MACRO: case T_BACRO: case T_CLOSURE: + return(closure_is_aritable(sc, x, closure_args(x), args)); + + case T_MACRO_STAR: case T_BACRO_STAR: case T_CLOSURE_STAR: + return(closure_star_is_aritable(sc, x, closure_args(x), args)); + + case T_C_MACRO: + return((c_macro_min_args(x) <= args) && + (c_macro_max_args(x) >= args)); + + case T_GOTO: case T_CONTINUATION: + return(true); + + case T_STRING: + return((args == 1) && (string_length(x) > 0)); /* ("" 0) -> error */ + + case T_C_OBJECT: + { + s7_pointer func; + if ((has_active_methods(sc, x)) && + ((func = find_method_with_let(sc, x, sc->is_aritable_symbol)) != sc->undefined)) + return(s7_apply_function(sc, func, set_plist_2(sc, x, make_integer(sc, args))) != sc->F); + return(is_safe_procedure(x)); + } + + case T_VECTOR: case T_INT_VECTOR: case T_FLOAT_VECTOR: case T_BYTE_VECTOR: + return((args > 0) && + (vector_length(x) > 0) && /* (#() 0) -> error */ + (args <= vector_rank(x))); + + case T_LET: case T_HASH_TABLE: case T_PAIR: /* for hash-table, this refers to (table 'key) */ + return(args == 1); + + case T_ITERATOR: + return(args == 0); + + case T_SYNTAX: + return((args >= syntax_min_args(x)) && ((args <= syntax_max_args(x)) || (syntax_max_args(x) == -1))); + } + return(false); +} + +static s7_pointer g_is_aritable(s7_scheme *sc, s7_pointer args) +{ + #define H_is_aritable "(aritable? obj num-args) returns #t if 'obj can be applied to 'num-args arguments." + #define Q_is_aritable s7_make_signature(sc, 3, sc->is_boolean_symbol, sc->T, sc->is_integer_symbol) + + s7_pointer n = cadr(args); + s7_int num; + + if (!s7_is_integer(n)) /* remember gmp case! */ + return(method_or_bust(sc, n, sc->is_aritable_symbol, args, sc->type_names[T_INTEGER], 2)); + + num = s7_integer_clamped_if_gmp(sc, n); + if (num < 0) + out_of_range_error_nr(sc, sc->is_aritable_symbol, int_two, n, it_is_negative_string); + if (num > MAX_ARITY) num = MAX_ARITY; + return(make_boolean(sc, s7_is_aritable(sc, car(args), num))); +} + +static bool is_aritable_b_7pp(s7_scheme *sc, s7_pointer f, s7_pointer i) {return(g_is_aritable(sc, set_plist_2(sc, f, i)) != sc->F);} + +static int32_t arity_to_int(s7_scheme *sc, s7_pointer x) +{ + int32_t args; + switch (type(x)) + { + case T_C_FUNCTION: case T_C_RST_NO_REQ_FUNCTION: case T_C_FUNCTION_STAR: + return(c_function_max_args(x)); + + case T_MACRO: case T_BACRO: case T_CLOSURE: + args = closure_arity_to_int(sc, x); + return((args < 0) ? MAX_ARITY : args); + + case T_MACRO_STAR: case T_BACRO_STAR: case T_CLOSURE_STAR: + args = closure_star_arity_to_int(sc, x); + return((args < 0) ? MAX_ARITY : args); + + case T_C_MACRO: return(c_macro_max_args(x)); + /* case T_C_OBJECT: return(MAX_ARITY); */ /* this currently can't be called */ + /* vectors et al don't make sense here -- this is called only in g_set_setter below where it is restricted to is_any_procedure (type>=T_CLOSURE) */ + } + if (S7_DEBUGGING) fprintf(stderr, "%s -1\n", __func__); + return(-1); /* unreachable I think */ +} + + +/* -------------------------------- sequence? -------------------------------- */ +static s7_pointer g_is_sequence(s7_scheme *sc, s7_pointer args) +{ + #define H_is_sequence "(sequence? obj) returns #t if obj is a sequence (vector, string, pair, etc)" + #define Q_is_sequence sc->pl_bt + check_boolean_method(sc, is_simple_sequence, sc->is_sequence_symbol, args); +} + +static bool is_sequence_b(s7_pointer p) {return(is_simple_sequence(p));} + + +/* -------------------------------- setter ------------------------------------------------ */ +static s7_pointer b_simple_setter(s7_scheme *sc, int32_t typer, s7_pointer args) +{ + if (type(cadr(args)) != typer) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_5(sc, wrap_string(sc, "set! ~S, ~S is ~A but should be ~A", 34), + car(args), cadr(args), sc->type_names[type(cadr(args))], sc->type_names[typer])); + return(cadr(args)); +} + +static s7_pointer b_is_symbol_setter(s7_scheme *sc, s7_pointer args) {return(b_simple_setter(sc, T_SYMBOL, args));} +static s7_pointer b_is_syntax_setter(s7_scheme *sc, s7_pointer args) {return(b_simple_setter(sc, T_SYNTAX, args));} +static s7_pointer b_is_let_setter(s7_scheme *sc, s7_pointer args) {return(b_simple_setter(sc, T_LET, args));} +static s7_pointer b_is_iterator_setter(s7_scheme *sc, s7_pointer args) {return(b_simple_setter(sc, T_ITERATOR, args));} +static s7_pointer b_is_c_pointer_setter(s7_scheme *sc, s7_pointer args) {return(b_simple_setter(sc, T_C_POINTER, args));} +static s7_pointer b_is_input_port_setter(s7_scheme *sc, s7_pointer args) {return(b_simple_setter(sc, T_INPUT_PORT, args));} +static s7_pointer b_is_output_port_setter(s7_scheme *sc, s7_pointer args) {return(b_simple_setter(sc, T_OUTPUT_PORT, args));} +static s7_pointer b_is_eof_object_setter(s7_scheme *sc, s7_pointer args) {return(b_simple_setter(sc, T_EOF, args));} +static s7_pointer b_is_random_state_setter(s7_scheme *sc, s7_pointer args) {return(b_simple_setter(sc, T_RANDOM_STATE, args));} +static s7_pointer b_is_char_setter(s7_scheme *sc, s7_pointer args) {return(b_simple_setter(sc, T_CHARACTER, args));} +static s7_pointer b_is_string_setter(s7_scheme *sc, s7_pointer args) {return(b_simple_setter(sc, T_STRING, args));} +static s7_pointer b_is_float_vector_setter(s7_scheme *sc, s7_pointer args) {return(b_simple_setter(sc, T_FLOAT_VECTOR, args));} +static s7_pointer b_is_int_vector_setter(s7_scheme *sc, s7_pointer args) {return(b_simple_setter(sc, T_INT_VECTOR, args));} +static s7_pointer b_is_byte_vector_setter(s7_scheme *sc, s7_pointer args) {return(b_simple_setter(sc, T_BYTE_VECTOR, args));} +static s7_pointer b_is_hash_table_setter(s7_scheme *sc, s7_pointer args) {return(b_simple_setter(sc, T_HASH_TABLE, args));} +static s7_pointer b_is_continuation_setter(s7_scheme *sc, s7_pointer args) {return(b_simple_setter(sc, T_CONTINUATION, args));} +static s7_pointer b_is_null_setter(s7_scheme *sc, s7_pointer args) {return(b_simple_setter(sc, T_NIL, args));} +static s7_pointer b_is_pair_setter(s7_scheme *sc, s7_pointer args) {return(b_simple_setter(sc, T_PAIR, args));} +static s7_pointer b_is_boolean_setter(s7_scheme *sc, s7_pointer args) {return(b_simple_setter(sc, T_BOOLEAN, args));} +static s7_pointer b_is_undefined_setter(s7_scheme *sc, s7_pointer args) {return(b_simple_setter(sc, T_UNDEFINED, args));} +static s7_pointer b_is_unspecified_setter(s7_scheme *sc, s7_pointer args) {return(b_simple_setter(sc, T_UNSPECIFIED, args));} +static s7_pointer b_is_c_object_setter(s7_scheme *sc, s7_pointer args) {return(b_simple_setter(sc, T_C_OBJECT, args));} +static s7_pointer b_is_goto_setter(s7_scheme *sc, s7_pointer args) {return(b_simple_setter(sc, T_GOTO, args));} + +#define b_setter(sc, typer, args, str, len) \ + do { \ + if (!typer(cadr(args))) \ + error_nr(sc, sc->wrong_type_arg_symbol, \ + set_elist_5(sc, wrap_string(sc, "set! ~S, ~S is ~A but should be ~A", 34), \ + car(args), cadr(args), sc->type_names[type(cadr(args))], wrap_string(sc, str, len))); \ + return(cadr(args)); \ + } while (0) + +static s7_pointer b_is_number_setter(s7_scheme *sc, s7_pointer args) {b_setter(sc, s7_is_complex, args, "a number", 8);} +static s7_pointer b_is_complex_setter(s7_scheme *sc, s7_pointer args) {b_setter(sc, s7_is_complex, args, "a number", 8);} +static s7_pointer b_is_gensym_setter(s7_scheme *sc, s7_pointer args) {b_setter(sc, is_gensym, args, "a gensym", 8);} +static s7_pointer b_is_keyword_setter(s7_scheme *sc, s7_pointer args) {b_setter(sc, is_symbol_and_keyword, args, "a keyword", 9);} +static s7_pointer b_is_openlet_setter(s7_scheme *sc, s7_pointer args) {b_setter(sc, has_methods, args, "an open let", 11);} +static s7_pointer b_is_macro_setter(s7_scheme *sc, s7_pointer args) {b_setter(sc, is_any_macro, args, "a macro", 7);} +static s7_pointer b_is_integer_setter(s7_scheme *sc, s7_pointer args) {b_setter(sc, s7_is_integer, args, "an integer", 10);} +static s7_pointer b_is_byte_setter(s7_scheme *sc, s7_pointer args) {b_setter(sc, is_byte, args, "an unsigned byte", 16);} +static s7_pointer b_is_real_setter(s7_scheme *sc, s7_pointer args) {b_setter(sc, is_real, args, "a real", 6);} +static s7_pointer b_is_float_setter(s7_scheme *sc, s7_pointer args) {b_setter(sc, is_t_real, args, "a float", 7);} +static s7_pointer b_is_rational_setter(s7_scheme *sc, s7_pointer args) {b_setter(sc, is_rational, args, "a rational", 10);} +static s7_pointer b_is_list_setter(s7_scheme *sc, s7_pointer args) {b_setter(sc, is_list, args, "a list", 6);} +static s7_pointer b_is_vector_setter(s7_scheme *sc, s7_pointer args) {b_setter(sc, is_any_vector, args, "a vector", 8);} +static s7_pointer b_is_procedure_setter(s7_scheme *sc, s7_pointer args) {b_setter(sc, is_any_procedure, args, "a procedure", 11);} +static s7_pointer b_is_dilambda_setter(s7_scheme *sc, s7_pointer args) {b_setter(sc, s7_is_dilambda, args, "a dilambda", 10);} +static s7_pointer b_is_sequence_setter(s7_scheme *sc, s7_pointer args) {b_setter(sc, is_sequence, args, "a sequence", 10);} +static s7_pointer b_is_subvector_setter(s7_scheme *sc, s7_pointer args) {b_setter(sc, is_subvector, args, "a subvector", 11);} +static s7_pointer b_is_weak_hash_table_setter(s7_scheme *sc, s7_pointer args) {b_setter(sc, is_weak_hash_table, args, "a weak hash-table", 17);} + +static s7_pointer b_is_proper_list_setter(s7_scheme *sc, s7_pointer args) +{ + if (!s7_is_proper_list(sc, car(args))) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_5(sc, wrap_string(sc, "set! ~S, ~S is ~A but should be ~A", 34), + car(args), cadr(args), sc->type_names[type(cadr(args))], wrap_string(sc, "a proper list", 13))); + return(cadr(args)); +} + +static s7_pointer lambda_setter(s7_scheme *sc, s7_pointer p) +{ + if (is_any_procedure(closure_setter(p))) /* setter already known */ + return(closure_setter(p)); + if (!closure_no_setter(p)) + { + s7_pointer f = funclet_entry(sc, p, sc->local_setter_symbol); /* look for +setter+, save value as closure_setter(p) */ + if (f) + { + if (f == sc->F) + { + closure_set_no_setter(p); + return(sc->F); + } + if (!is_any_procedure(f)) + sole_arg_wrong_type_error_nr(sc, sc->setter_symbol, p, wrap_string(sc, "a procedure or a reasonable facsimile thereof", 45)); + closure_set_setter(p, f); + return(f); + } + /* we used to search for setter here, but that can find the built-in setter causing an infinite loop (maybe check for that??) */ + closure_set_no_setter(p); + } + return(sc->F); +} + +static s7_pointer symbol_setter(s7_scheme *sc, s7_pointer sym, s7_pointer e) +{ + s7_pointer slot, setter; + if (is_keyword(sym)) + return(sc->F); + if ((e == sc->rootlet) || (e == sc->nil)) + slot = global_slot(sym); + else + { + s7_pointer old_e = sc->curlet; + set_curlet(sc, e); + slot = s7_slot(sc, sym); + set_curlet(sc, old_e); + } + if ((!is_slot(slot)) || (!slot_has_setter(slot))) return(sc->F); + setter = slot_setter(slot); + if (is_bool_function(setter)) return(c_function_setter(setter)); + return(setter); +} + +static s7_pointer setter_p_pp(s7_scheme *sc, s7_pointer p, s7_pointer e) +{ + if (!((is_let(e)) || (e == sc->rootlet) || (e == sc->nil))) + wrong_type_error_nr(sc, sc->setter_symbol, 2, e, sc->type_names[T_LET]); /* need to check this in case let arg is bogus */ + + switch (type(p)) + { + case T_MACRO: case T_MACRO_STAR: + case T_BACRO: case T_BACRO_STAR: + case T_CLOSURE: case T_CLOSURE_STAR: + return(lambda_setter(sc, p)); + + case T_C_FUNCTION: case T_C_FUNCTION_STAR: case T_C_RST_NO_REQ_FUNCTION: + return(c_function_setter(p)); + + case T_C_MACRO: + return(c_macro_setter(p)); + + case T_C_OBJECT: + check_method(sc, p, sc->setter_symbol, set_plist_2(sc, p, e)); + return((c_object_set(sc, p) == fallback_set) ? sc->F : sc->c_object_set_function); /* for example ((setter obj) obj 0 1.0) if s7test block */ + /* this could wrap the setter as an s7_function giving p's class-name etc */ + + case T_LET: + check_method(sc, p, sc->setter_symbol, set_plist_2(sc, p, e)); + return(global_value(sc->let_set_symbol)); + + case T_ITERATOR: /* (set! (iter) val) doesn't fit the other setters */ + return((is_any_closure(iterator_sequence(p))) ? closure_setter(iterator_sequence(p)) : sc->F); + + case T_PAIR: return(global_value(sc->list_set_symbol)); /* or maybe initial-value? */ + case T_HASH_TABLE: return(global_value(sc->hash_table_set_symbol)); + case T_STRING: return(global_value(sc->string_set_symbol)); + case T_BYTE_VECTOR: return(global_value(sc->byte_vector_set_symbol)); + case T_VECTOR: return(global_value(sc->vector_set_symbol)); + case T_INT_VECTOR: return(global_value(sc->int_vector_set_symbol)); + case T_FLOAT_VECTOR: return(global_value(sc->float_vector_set_symbol)); + case T_SLOT: return((slot_has_setter(p)) ? slot_setter(p) : sc->F); + + case T_SYMBOL: /* (setter symbol let) */ + return(symbol_setter(sc, p, e)); + } + /* wrong_type_error_nr(sc, sc->setter_symbol, 1, p, wrap_string(sc, "something that might have a setter", 34)); */ /* this seems unfriendly */ + return(sc->F); +} + +static s7_pointer g_setter(s7_scheme *sc, s7_pointer args) +{ + #define H_setter "(setter obj let) returns the setter associated with obj" + #define Q_setter s7_make_signature(sc, 3, s7_make_signature(sc, 2, \ + sc->not_symbol, sc->is_procedure_symbol), sc->T, s7_make_signature(sc, 2, sc->is_let_symbol, sc->is_null_symbol)) + return(setter_p_pp(sc, car(args), (is_pair(cdr(args))) ? cadr(args) : sc->curlet)); +} + +s7_pointer s7_setter(s7_scheme *sc, s7_pointer obj) {return(setter_p_pp(sc, obj, sc->curlet));} + + +/* -------------------------------- set-setter -------------------------------- */ +static void protect_setter(s7_scheme *sc, s7_pointer sym, s7_pointer acc) +{ + s7_int loc; + if (sc->protected_setters_size == sc->protected_setters_loc) + { + s7_int size = sc->protected_setters_size; + s7_int new_size = 2 * size; + block_t *ob = vector_block(sc->protected_setters); + block_t *nb = reallocate(sc, ob, new_size * sizeof(s7_pointer)); + block_info(nb) = NULL; + vector_block(sc->protected_setters) = nb; + vector_elements(sc->protected_setters) = (s7_pointer *)block_data(nb); + vector_length(sc->protected_setters) = new_size; + + ob = vector_block(sc->protected_setter_symbols); + nb = reallocate(sc, ob, new_size * sizeof(s7_pointer)); + vector_block(sc->protected_setter_symbols) = nb; + vector_elements(sc->protected_setter_symbols) = (s7_pointer *)block_data(nb); + vector_length(sc->protected_setter_symbols) = new_size; + + for (s7_int i = size; i < new_size; i++) + { + vector_element(sc->protected_setters, i) = sc->unused; + vector_element(sc->protected_setter_symbols, i) = sc->unused; + } + sc->protected_setters_size = new_size; + } + loc = sc->protected_setters_loc++; + vector_element(sc->protected_setters, loc) = acc; + vector_element(sc->protected_setter_symbols, loc) = sym; +} + +static s7_pointer symbol_set_setter(s7_scheme *sc, s7_pointer sym, s7_pointer args) +{ + s7_pointer func, slot; + if (is_keyword(sym)) + wrong_type_error_nr(sc, wrap_string(sc, "set! setter", 11), 1, sym, wrap_string(sc, "a normal symbol (a keyword can't be set)", 40)); + + if (is_pair(cddr(args))) + { + s7_pointer e = cadr(args); /* (let ((x 1)) (set! (setter 'x (curlet)) (lambda (s v e) ...))) */ + func = caddr(args); + if ((e == sc->rootlet) || (e == sc->nil)) + slot = global_slot(sym); + else + { + if (!is_let(e)) + wrong_type_error_nr(sc, wrap_string(sc, "set! setter", 11), 2, e, sc->type_names[T_LET]); + slot = lookup_slot_with_let(sym, e); + }} + else + { + slot = s7_slot(sc, sym); /* (set! (setter 'x) (lambda (s v) ...)) */ + func = cadr(args); + } + if (!is_slot(slot)) + return(sc->F); + + if (func != sc->F) + { + if (sym == sc->setter_symbol) + immutable_object_error_nr(sc, set_elist_2(sc, wrap_string(sc, "can't set (setter 'setter) to ~S", 32), func)); + if (is_syntax_or_qq(slot_value(slot))) /* (set! (setter 'begin) ...), qq is syntax sez r7rs */ + immutable_object_error_nr(sc, set_elist_3(sc, wrap_string(sc, "can't set (setter '~S) to ~S", 28), sym, func)); + if (!is_any_procedure(func)) /* disallow continuation/goto here */ + wrong_type_error_nr(sc, wrap_string(sc, "set! setter", 11), 3, func, wrap_string(sc, "a function or #f", 16)); + if ((!is_c_function(func)) || (!c_function_has_bool_setter(func))) + { + if (s7_is_aritable(sc, func, 3)) + set_has_let_arg(func); + else + if (!s7_is_aritable(sc, func, 2)) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_2(sc, wrap_string(sc, "symbol setter function, ~A, should take 2 or 3 arguments", 56), func)); + }} + if (slot == global_slot(sym)) + s7_set_setter(sc, sym, func); /* special GC protection for global vars */ + else slot_set_setter(slot, func); /* func might be #f */ + if (func != sc->F) + slot_set_has_setter(slot); + return(func); +} + +static s7_pointer g_set_setter(s7_scheme *sc, s7_pointer args) +{ + s7_pointer p = car(args), setter; + if (is_symbol(p)) + return(symbol_set_setter(sc, p, args)); + if (p == sc->s7_starlet) + wrong_type_error_nr(sc, wrap_string(sc, "set! setter", 11), 1, p, wrap_string(sc, "something other than *s7*", 25)); + + setter = cadr(args); + if (setter != sc->F) + { + if (!is_any_procedure(setter)) + wrong_type_error_nr(sc, wrap_string(sc, "set! setter", 11), 2, setter, wrap_string(sc, "a procedure or #f", 17)); + if (arity_to_int(sc, setter) < 1) /* we need at least an arg for the set! value */ + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_2(sc, wrap_string(sc, "setter function, ~A, should take at least one argument", 54), setter)); + } + switch (type(p)) + { + case T_MACRO: case T_MACRO_STAR: + case T_BACRO: case T_BACRO_STAR: + case T_CLOSURE: case T_CLOSURE_STAR: + closure_set_setter(p, setter); + if (setter == sc->F) + closure_set_no_setter(p); + break; + + case T_C_FUNCTION: case T_C_FUNCTION_STAR: case T_C_RST_NO_REQ_FUNCTION: + if (p == global_value(sc->setter_symbol)) /* (immutable? (setter setter)) is #t, but we aren't checking immutable? here -- maybe we should? */ + immutable_object_error_nr(sc, set_elist_2(sc, wrap_string(sc, "can't set (setter setter) to ~S", 31), setter)); + c_function_set_setter(p, setter); + if ((is_any_closure(setter)) || + (is_any_macro(setter))) + add_setter(sc, p, setter); + break; + + case T_C_MACRO: + c_macro_set_setter(p, setter); + if ((is_any_closure(setter)) || + (is_any_macro(setter))) + add_setter(sc, p, setter); + break; + + default: /* (set! (setter 4) ...) or p==continuation etc */ + wrong_type_error_nr(sc, wrap_string(sc, "set! setter", 11), 1, p, wrap_string(sc, "a symbol, a procedure, or a macro", 33)); + } + return(setter); +} + +s7_pointer s7_set_setter(s7_scheme *sc, s7_pointer p, s7_pointer setter) +{ + if (is_symbol(p)) + { + if (slot_has_setter(global_slot(p))) + for (s7_int index = 0; index < sc->protected_setters_loc; index++) + if (vector_element(sc->protected_setter_symbols, index) == p) + { + s7_pointer old_func = vector_element(sc->protected_setters, index); + if ((is_any_procedure(old_func)) && /* i.e. not #f! */ + (is_immutable(old_func))) + return(setter); + vector_element(sc->protected_setters, index) = setter; + slot_set_setter(global_slot(p), setter); + if ((setter != sc->F) && (s7_is_aritable(sc, setter, 3))) + set_has_let_arg(setter); + return(setter); + } + if (setter != sc->F) + { + slot_set_has_setter(global_slot(p)); + protect_setter(sc, p, setter); + slot_set_setter(global_slot(p), setter); + if (s7_is_aritable(sc, setter, 3)) + set_has_let_arg(setter); + return(setter); + } + slot_set_setter(global_slot(p), setter); + return(setter); + } + return(g_set_setter(sc, set_plist_2(sc, p, setter))); +} + +/* (let () (define xxx 23) (define (hix) (set! xxx 24)) (hix) (set! (setter 'xxx) (lambda (sym val) (format *stderr* "val: ~A~%" val) val)) (hix)) + * so set setter before use! + */ + +static s7_pointer call_c_function_setter(s7_scheme *sc, s7_pointer func, s7_pointer symbol, s7_pointer new_value) +{ + if (has_let_arg(func)) + return(c_function_call(func)(sc, with_list_t3(symbol, new_value, sc->curlet))); + return(c_function_call(func)(sc, with_list_t2(symbol, new_value))); +} + +static s7_pointer call_setter(s7_scheme *sc, s7_pointer slot, s7_pointer new_value) /* see also op_set1 */ +{ + s7_pointer func = slot_setter(slot), result; + if (is_c_function(func)) + return(call_c_function_setter(sc, func, slot_symbol(slot), new_value)); + if (!is_any_procedure(func)) + return(new_value); + sc->temp10 = (has_let_arg(func)) ? list_3(sc, slot_symbol(slot), new_value, sc->curlet) : list_2(sc, slot_symbol(slot), new_value); + /* safe lists here are much slower -- the setters are called more often for some reason (see tset.scm) */ + result = s7_call(sc, func, sc->temp10); + sc->temp10 = sc->unused; + return(result); +} + +static s7_pointer bind_symbol_with_setter(s7_scheme *sc, opcode_t op, s7_pointer symbol, s7_pointer new_value) +{ + s7_pointer func = setter_p_pp(sc, symbol, sc->curlet); + if (is_c_function(func)) + return(call_c_function_setter(sc, func, symbol, new_value)); + if (!is_any_procedure(func)) + return(new_value); + sc->args = (has_let_arg(func)) ? list_3(sc, symbol, new_value, sc->curlet) : list_2(sc, symbol, new_value); + push_stack_direct(sc, op); + sc->code = func; + return(sc->no_value); /* this means the setter in set! needs to goto APPLY to get the new value */ +} + + +/* -------------------------------- hooks -------------------------------- */ +s7_pointer s7_hook_functions(s7_scheme *sc, s7_pointer hook) +{ + return(s7_symbol_local_value(sc, sc->body_symbol, closure_let(hook))); +} + +s7_pointer s7_hook_set_functions(s7_scheme *sc, s7_pointer hook, s7_pointer functions) +{ + if (is_list(functions)) + let_set(sc, closure_let(hook), sc->body_symbol, functions); + return(functions); +} + + +/* -------------------------------- eq? eqv? equal? equivalent? -------------------------------- */ +bool s7_is_eq(s7_pointer obj1, s7_pointer obj2) +{ + return((obj1 == obj2) || /* so floats and NaNs might be eq? but not eqv? */ + ((is_unspecified(obj1)) && (is_unspecified(obj2)))); /* this is needed because this function is used by s7_b_pp */ +} + +static s7_pointer is_eq_p_pp(s7_scheme *sc, s7_pointer obj1, s7_pointer obj2) +{ + return(make_boolean(sc, ((obj1 == obj2) || ((is_unspecified(obj1)) && (is_unspecified(obj2)))))); +} + +static s7_pointer g_is_eq(s7_scheme *sc, s7_pointer args) +{ + #define H_is_eq "(eq? obj1 obj2) returns #t if obj1 is eq to (the same object as) obj2" + #define Q_is_eq sc->pcl_bt + return(make_boolean(sc, ((car(args) == cadr(args)) || + ((is_unspecified(car(args))) && (is_unspecified(cadr(args))))))); + /* (eq? (apply apply apply values '(())) #) should return #t */ +} + +bool s7_is_eqv(s7_scheme *sc, s7_pointer a, s7_pointer b) +{ +#if WITH_GMP + if ((is_big_number(a)) || (is_big_number(b))) return(big_numbers_are_eqv(sc, a, b)); +#endif + if (type(a) != type(b)) return(false); + if ((a == b) && (!is_number(a))) /* if a is NaN, a == b doesn't mean (eqv? a b) */ + return(true); /* a == b means (let ((x "a")) (let ((y x)) (eqv? x y))) is #t */ + if (is_number(a)) return(numbers_are_eqv(sc, a, b)); + if (is_unspecified(a)) return(true); /* types are the same so we know b is also unspecified */ + return(false); +} + +static s7_pointer g_is_eqv(s7_scheme *sc, s7_pointer args) +{ + #define H_is_eqv "(eqv? obj1 obj2) returns #t if obj1 is equivalent to obj2" + #define Q_is_eqv sc->pcl_bt + return(make_boolean(sc, s7_is_eqv(sc, car(args), cadr(args)))); +} + +static s7_pointer is_eqv_p_pp(s7_scheme *sc, s7_pointer obj1, s7_pointer obj2) {return(make_boolean(sc, s7_is_eqv(sc, obj1, obj2)));} + +static bool floats_are_equivalent(s7_scheme *sc, s7_double x, s7_double y) +{ + s7_double diff; + if (x == y) return(true); + diff = fabs(x - y); + if (diff <= sc->equivalent_float_epsilon) return(true); + return((is_NaN(x)) && (is_NaN(y))); +} + +#if WITH_GMP +static bool big_floats_are_equivalent(s7_scheme *sc, mpfr_t x, mpfr_t y) +{ + /* protect mpfr_1 */ + if ((mpfr_nan_p(x)) || (mpfr_nan_p(y))) + return((mpfr_nan_p(x)) && (mpfr_nan_p(y))); + mpfr_sub(sc->mpfr_3, x, y, MPFR_RNDN); + mpfr_abs(sc->mpfr_3, sc->mpfr_3, MPFR_RNDN); + return(mpfr_cmp_d(sc->mpfr_3, sc->equivalent_float_epsilon) <= 0); +} +#endif + +static bool eq_equal(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *unused_ci) {return(x == y);} + +static bool symbol_equivalent(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + if (x == y) return(true); + if (!is_normal_symbol(y)) return(false); /* (equivalent? ''(1) '(1)) */ + return((is_slot(global_slot(x))) && /* the optimizer can replace the original symbol with its own */ + (is_syntax(global_value(x))) && + (is_slot(global_slot(y))) && + (is_syntax(global_value(y))) && + (syntax_symbol(global_value(x)) == syntax_symbol(global_value(y)))); +} + +static bool unspecified_equal(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + return(is_unspecified(y)); +} + +static bool undefined_equal(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + return((x == y) || + ((is_undefined(y)) && (undefined_name_length(x) == undefined_name_length(y)) && + (safe_strcmp(undefined_name(x), undefined_name(y))))); +} + +static bool is_equal_1(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + return((*(equals[type(x)]))(sc, x, y, ci)); +} + +static bool is_equivalent_1(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + return((*(equivalents[type(x)]))(sc, x, y, ci)); +} + +static bool c_pointer_equivalent(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + shared_info_t *nci = ci; + if (x == y) return(true); + if (!s7_is_c_pointer(y)) return(false); + if (c_pointer(x) != c_pointer(y)) return(false); + if (c_pointer_type(x) != c_pointer_type(y)) + { + if (!nci) nci = new_shared_info(sc); + if (!is_equivalent_1(sc, c_pointer_type(x), c_pointer_type(y), nci)) + return(false); + } + if (c_pointer_info(x) != c_pointer_info(y)) + { + if (!nci) nci = new_shared_info(sc); + if (!is_equivalent_1(sc, c_pointer_info(x), c_pointer_info(y), nci)) + return(false); + } + return(true); +} + +static bool c_pointer_equal(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + shared_info_t *nci = ci; + if (x == y) return(true); + if (!s7_is_c_pointer(y)) return(false); + if (c_pointer(x) != c_pointer(y)) return(false); + if (c_pointer_type(x) != c_pointer_type(y)) + { + if (!nci) nci = new_shared_info(sc); + if (!is_equal_1(sc, c_pointer_type(x), c_pointer_type(y), nci)) + return(false); + } + if (c_pointer_info(x) != c_pointer_info(y)) + { + if (!nci) nci = new_shared_info(sc); + if (!is_equal_1(sc, c_pointer_info(x), c_pointer_info(y), nci)) + return(false); + } + return(true); +} + +static bool string_equal(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + return((is_string(y)) && (scheme_strings_are_equal(x, y))); +} + +static bool syntax_equal(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + return((is_syntax(y)) && (syntax_symbol(x) == syntax_symbol(y))); +} + +static bool port_equal(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) {return(x == y);} + +static bool port_equivalent(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + if (x == y) return(true); + if (type(x) != type(y)) return(false); + if ((port_is_closed(x)) && (port_is_closed(y))) return(true); + if ((port_is_closed(x)) || (port_is_closed(y))) return(false); /* if either is closed, port_port (below) might be null */ + if (port_type(x) != port_type(y)) return(false); + switch (port_type(x)) + { + case STRING_PORT: + return((port_position(x) == port_position(y)) && + (port_data_size(x) == port_data_size(y)) && + (local_strncmp((const char *)port_data(x), (const char *)port_data(y), (is_input_port(x)) ? port_data_size(x) : port_position(x)))); + case FILE_PORT: + return((is_input_port(x)) && + (port_position(x) == port_position(y)) && + (local_strncmp((const char *)port_filename(x), (const char *)port_filename(y), port_filename_length(x)))); + case FUNCTION_PORT: + if (is_input_port(x)) + return(port_input_function(x) == port_input_function(y)); + return(port_output_function(x) == port_output_function(y)); + } + return(false); +} + +static void add_shared_ref(shared_info_t *ci, s7_pointer x, int32_t ref_x) +{ + /* called only in equality check, not printer */ + if (ci->top == ci->size) + enlarge_shared_info(ci); + set_collected(x); + ci->objs[ci->top] = x; + ci->refs[ci->top++] = ref_x; +} + +static Inline bool inline_equal_ref(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) /* pair_equal:lg/list/io, [read] */ +{ + /* here we know x and y are pointers to the same type of structure */ + int32_t ref_y = (is_collected(y)) ? peek_shared_ref_1(ci, y) : 0; + if (is_collected(x)) + { + int32_t ref_x = peek_shared_ref_1(ci, x); + if (ref_y != 0) + return(ref_x == ref_y); /* this is a change from the macro version 16-Jan-20 -- only true returns from the caller */ + /* try to harmonize the new guy -- there can be more than one structure equal to the current one */ + if (ref_x != 0) + add_shared_ref(ci, y, ref_x); + } + else + if (ref_y != 0) + add_shared_ref(ci, x, ref_y); + else + { + /* assume neither x nor y is in the table, and that they should share a ref value, called only in equality check, not printer */ + if (ci->top >= ci->size2) enlarge_shared_info(ci); + set_collected(x); + set_collected(y); + ci->objs[ci->top] = x; + ci->refs[ci->top++] = ++ci->ref; + ci->objs[ci->top] = y; + ci->refs[ci->top++] = ci->ref; + } + return(false); +} + +static bool equal_ref(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) {return(inline_equal_ref(sc, x, y, ci));} + +static bool c_objects_are_equal(s7_scheme *sc, s7_pointer a, s7_pointer b, shared_info_t *ci) +{ + s7_pointer (*to_list)(s7_scheme *sc, s7_pointer args); + shared_info_t *nci = ci; + s7_pointer pa, pb; + + if (a == b) + return(true); + if (!is_c_object(b)) + return(false); + if (c_object_type(a) != c_object_type(b)) + return(false); + + if (c_object_equal(sc, a)) + return(((*(c_object_equal(sc, a)))(sc, set_clist_2(sc, a, b))) != sc->F); + if (c_object_eql(sc, a)) + return((*(c_object_eql(sc, a)))(c_object_value(a), c_object_value(b))); + + to_list = c_object_to_list(sc, a); + if (!to_list) + return(false); + if (ci) + { + if (equal_ref(sc, a, b, ci)) return(true); /* and nci == ci above */ + } + else nci = new_shared_info(sc); + + for (pa = to_list(sc, set_plist_1(sc, a)), pb = to_list(sc, set_plist_1(sc, b)); is_pair(pa) && (is_pair(pb)); pa = cdr(pa), pb = cdr(pb)) + if (!(is_equal_1(sc, car(pa), car(pb), nci))) + return(false); + return(pa == pb); /* presumably both are nil if successful */ +} + +#define check_equivalent_method(Sc, X, Y) \ + do { \ + if (has_active_methods(sc, X)) \ + { \ + s7_pointer equal_func = find_method_with_let(Sc, X, Sc->is_equivalent_symbol); \ + if (equal_func != Sc->undefined) \ + return(s7_boolean(Sc, s7_apply_function(Sc, equal_func, set_plist_2(Sc, X, Y)))); \ + }} \ + while (0) + +static bool c_objects_are_equivalent(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + check_equivalent_method(sc, x, y); + if (c_object_equivalent(sc, x)) + return(((*(c_object_equivalent(sc, x)))(sc, set_plist_2(sc, x, y))) != sc->F); + return(c_objects_are_equal(sc, x, y, ci)); +} + +static bool hash_table_equal_1(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci, bool equivalent) +{ + hash_entry_t **lists; + s7_int len; + shared_info_t *nci = ci; + hash_check_t hf; + bool (*eqf)(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci); + + if (x == y) + return(true); + if (!is_hash_table(y)) + { + if (equivalent) + check_equivalent_method(sc, y, x); + return(false); + } + if ((ci) && (equal_ref(sc, x, y, ci))) return(true); + + if (hash_table_entries(x) != hash_table_entries(y)) + return(false); + if (hash_table_entries(x) == 0) + return(true); + if ((!equivalent) && ((hash_table_mapper(x) != default_hash_map) || (hash_table_mapper(y) != default_hash_map))) + { + if (hash_table_checker(x) != hash_table_checker(y)) + return(false); + if (hash_table_mapper(x) != hash_table_mapper(y)) + return(false); + } + + len = hash_table_mask(x) + 1; + lists = hash_table_elements(x); + if (!nci) nci = new_shared_info(sc); + eqf = (equivalent) ? is_equivalent_1 : is_equal_1; + + hf = hash_table_checker(y); + if ((hf != hash_equal) && (hf != hash_equivalent)) + { + for (s7_int i = 0; i < len; i++) + for (hash_entry_t *p = lists[i]; p; p = hash_entry_next(p)) + { + hash_entry_t *y_val = hf(sc, y, hash_entry_key(p)); + if (y_val == sc->unentry) + return(false); + if (!eqf(sc, hash_entry_value(p), hash_entry_value(y_val), nci)) + return(false); + } + /* if we get here, every key/value in x has a corresponding key/value in y, and the number of entries match, so surely the tables are equal?? + * if ci not null or hash-table-checker is equal/eqivalent, can't use hf? + */ + return(true); + } + + /* we need to protect the current shared_info data (nci) here so the current hash_table_checker won't work -- + * outside equal?/eqivalent? they can safely assume that they can start a new shared_info process. + */ + for (s7_int i = 0; i < len; i++) + for (hash_entry_t *p = lists[i]; p; p = hash_entry_next(p)) + { + s7_pointer key = hash_entry_key(p); + s7_int hash = hash_loc(sc, y, key); + s7_int loc = hash & hash_table_mask(y); + hash_entry_t *xe; + + for (xe = hash_table_element(y, loc); xe; xe = hash_entry_next(xe)) + if ((hash_entry_raw_hash(xe) == hash) && + (eqf(sc, hash_entry_key(xe), key, nci))) + break; + if (!xe) + return(false); + if (!eqf(sc, hash_entry_value(p), hash_entry_value(xe), nci)) + return(false); + } + return(true); +} + +static bool hash_table_equal(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) {return(hash_table_equal_1(sc, x, y, ci, false));} +static bool hash_table_equivalent(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) {return(hash_table_equal_1(sc, x, y, ci, true));} + +static bool slots_match(s7_scheme *sc, s7_pointer px, s7_pointer y, shared_info_t *nci) +{ + for (s7_pointer ey = y; is_let(T_Lid(ey)); ey = let_outlet(ey)) + for (s7_pointer py = let_slots(ey); tis_slot(py); py = next_slot(py)) + if (slot_symbol(px) == slot_symbol(py)) /* we know something will match */ + return(is_equal_1(sc, slot_value(px), slot_value(py), nci)); + return(false); +} + +static bool slots_equivalent_match(s7_scheme *sc, s7_pointer px, s7_pointer y, shared_info_t *nci) +{ + for (s7_pointer ey = y; is_let(T_Lid(ey)); ey = let_outlet(ey)) + for (s7_pointer py = let_slots(ey); tis_slot(py); py = next_slot(py)) + if (slot_symbol(px) == slot_symbol(py)) /* we know something will match */ + return(is_equivalent_1(sc, slot_value(px), slot_value(py), nci)); + return(false); +} + +static bool let_equal_1(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci, bool equivalent) +{ + s7_pointer ex, ey, px, py; + shared_info_t *nci = ci; + int32_t x_len, y_len; + + if ((!is_let(y)) || (x == sc->rootlet) || (y == sc->rootlet)) + return(false); + + if ((ci) && (equal_ref(sc, x, y, ci))) return(true); + + clear_symbol_list(sc); + for (x_len = 0, ex = x; is_let(T_Lid(ex)); ex = let_outlet(ex)) + for (px = let_slots(ex); tis_slot(px); px = next_slot(px)) + if (!symbol_is_in_list(sc, slot_symbol(px))) + { + add_symbol_to_list(sc, slot_symbol(px)); + x_len++; + } + + for (ey = y; is_let(T_Lid(ey)); ey = let_outlet(ey)) + for (py = let_slots(ey); tis_slot(py); py = next_slot(py)) + if (!symbol_is_in_list(sc, slot_symbol(py))) /* symbol in y, not in x */ + return(false); + + for (y_len = 0, ey = y; is_let(T_Lid(ey)); ey = let_outlet(ey)) + for (py = let_slots(ey); tis_slot(py); py = next_slot(py)) + if (symbol_tag(slot_symbol(py)) != 0) + { + y_len++; + symbol_set_tag(slot_symbol(py), 0); + } + + if (x_len != y_len) /* symbol in x, not in y */ + return(false); + + if (!nci) nci = new_shared_info(sc); + + for (ex = x; is_let(T_Lid(ex)); ex = let_outlet(ex)) + for (px = let_slots(ex); tis_slot(px); px = next_slot(px)) + if (symbol_tag(slot_symbol(px)) == 0) /* unshadowed */ + { + symbol_set_tag(slot_symbol(px), sc->syms_tag); /* values don't match */ + if (((!equivalent) && (!slots_match(sc, px, y, nci))) || + ((equivalent) && (!slots_equivalent_match(sc, px, y, nci)))) + return(false); + } + return(true); +} + +static bool let_equal(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + /* x == y if all unshadowed vars match, leaving aside the rootlet, so that for any local variable, we get the same value in either x or y */ + return((x == y) || (let_equal_1(sc, x, y, ci, false))); +} + +/* what should these do if there are setters? */ +static bool let_equivalent(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + if (x == y) return(true); + if (!is_global(sc->is_equivalent_symbol)) + { + check_equivalent_method(sc, x, y); + check_equivalent_method(sc, y, x); + } + return(let_equal_1(sc, x, y, ci, true)); +} + +static bool closure_equal(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + if (x == y) + return(true); + if (type(x) != type(y)) + return(false); + if ((has_active_methods(sc, x)) && + (has_active_methods(sc, y))) + { + s7_pointer equal_func = find_method(sc, closure_let(x), sc->is_equal_symbol); + if (equal_func != sc->undefined) + return(s7_boolean(sc, s7_apply_function(sc, equal_func, set_plist_2(sc, x, y)))); + } + return(false); +} + +static bool closure_equivalent(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + if (x == y) + return(true); + if (type(x) != type(y)) + return(false); + if (has_active_methods(sc, y)) + check_equivalent_method(sc, x, y); + /* not sure about this -- we can't simply check let_equal(closure_let(x), closure_let(y)) + * because locally defined constant functions on the second pass find the outer let. + */ + return((is_equivalent_1(sc, closure_args(x), closure_args(y), ci)) && + (is_equivalent_1(sc, closure_body(x), closure_body(y), ci))); +} + +static bool pair_equal(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + s7_pointer px, py; + if (x == y) + return(true); + if (!is_pair(y)) + return(false); + if (!ci) + ci = new_shared_info(sc); + else + if (inline_equal_ref(sc, x, y, ci)) + return(true); + + if (!is_equal_1(sc, car(x), car(y), ci)) return(false); + for (px = cdr(x), py = cdr(y); (is_pair(px)) && (is_pair(py)); px = cdr(px), py = cdr(py)) + { + if (!is_equal_1(sc, car(px), car(py), ci)) return(false); + if (inline_equal_ref(sc, px, py, ci)) return(true); + } + return((px == py) || (is_equal_1(sc, px, py, ci))); +} + +static bool pair_equivalent(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + s7_pointer px, py; + if (x == y) + return(true); + if (!is_pair(y)) + { + check_equivalent_method(sc, y, x); + return(false); + } + if (!ci) + ci = new_shared_info(sc); + else + if (inline_equal_ref(sc, x, y, ci)) + return(true); + + if (!is_equivalent_1(sc, car(x), car(y), ci)) return(false); + for (px = cdr(x), py = cdr(y); (is_pair(px)) && (is_pair(py)); px = cdr(px), py = cdr(py)) + { + if (!is_equivalent_1(sc, car(px), car(py), ci)) return(false); + if (inline_equal_ref(sc, px, py, ci)) return(true); + } + return((px == py) || ((is_equivalent_1(sc, px, py, ci)))); +} + +static bool vector_rank_match(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + s7_int x_dims; + + if (!vector_has_dimension_info(x)) + return((!vector_has_dimension_info(y)) || (vector_ndims(y) == 1)); + x_dims = vector_ndims(x); + if (x_dims == 1) + return((!vector_has_dimension_info(y)) || (vector_ndims(y) == 1)); + if ((!vector_has_dimension_info(y)) || + (x_dims != vector_ndims(y))) + return(false); + + for (s7_int j = 0; j < x_dims; j++) + if (vector_dimension(x, j) != vector_dimension(y, j)) + return(false); + return(true); +} + +static bool iv_meq(const s7_int *ex, const s7_int *ey, s7_int len) +{ + s7_int i = 0, left = len - 8; + while (i <= left) + LOOP_8(if (ex[i] != ey[i]) return(false); i++); + for (; i < len; i++) + if (ex[i] != ey[i]) + return(false); + return(true); +} + +static bool byte_vector_equal_1(s7_scheme *sc, s7_pointer x, s7_pointer y) +{ + s7_int len = vector_length(x); + const uint8_t *xp = byte_vector_bytes(x); + const uint8_t *yp = byte_vector_bytes(y); + for (s7_int i = 0; i < len; i++) + if (xp[i] != yp[i]) + return(false); + return(true); +} + +static bool biv_meq(s7_pointer x, s7_pointer y) +{ + s7_int len = vector_length(x); + const uint8_t *xp = byte_vector_bytes(x); + const s7_int *yp = int_vector_ints(y); + for (s7_int i = 0; i < len; i++) + if ((s7_int)(xp[i]) != yp[i]) + return(false); + return(true); +} + +#define base_vector_equal(sc, x, y) \ + do { \ + if (x == y) return(true); \ + len = vector_length(x); \ + if (len != vector_length(y)) return(false); \ + if (!vector_rank_match(sc, x, y)) return(false); \ + if (len == 0) return(true); \ + } while (0) + +static bool vector_equal(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + s7_int len; + shared_info_t *nci = ci; + + if (!is_any_vector(y)) return(false); + base_vector_equal(sc, x, y); /* sets len */ + if (type(x) != type(y)) + { + if ((is_int_vector(x)) && (is_byte_vector(y))) + return(biv_meq(y, x)); + if ((is_byte_vector(x)) && (is_int_vector(y))) + return(biv_meq(x, y)); + for (s7_int i = 0; i < len; i++) + if (!is_equal_1(sc, vector_getter(x)(sc, x, i), vector_getter(y)(sc, y, i), NULL)) /* this could be greatly optimized */ + return(false); + return(true); + } + if (!has_simple_elements(x)) + { + if (ci) + { + if (equal_ref(sc, x, y, ci)) return(true); + } + else nci = new_shared_info(sc); + } + for (s7_int i = 0; i < len; i++) + if (!(is_equal_1(sc, vector_element(x, i), vector_element(y, i), nci))) + return(false); + return(true); +} + +static bool byte_vector_equal(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + s7_int len; + if (!is_byte_vector(y)) + return(vector_equal(sc, x, y, ci)); + base_vector_equal(sc, x, y); + return(byte_vector_equal_1(sc, x, y)); +} + +static bool int_vector_equal(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + s7_int len; + if (!is_int_vector(y)) + return(vector_equal(sc, x, y, ci)); + base_vector_equal(sc, x, y); + return(iv_meq(int_vector_ints(x), int_vector_ints(y), len)); +} + +static bool float_vector_equal(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + s7_int len; + if (!is_float_vector(y)) + return(vector_equal(sc, x, y, ci)); + base_vector_equal(sc, x, y); + for (s7_int i = 0; i < len; i++) + if (float_vector(x, i) != float_vector(y, i)) + return(false); + return(true); +} + +static bool vector_equivalent(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + /* if this is split like vector_equal above, remember it is called by iterator_equal_1 below */ + s7_int i, len; + shared_info_t *nci = ci; + + if (x == y) + return(true); + if (!is_any_vector(y)) + { + check_equivalent_method(sc, y, x); + return(false); + } + len = vector_length(x); + if (len != vector_length(y)) return(false); + if (len == 0) return(true); + if (!vector_rank_match(sc, x, y)) return(false); + + if (type(x) != type(y)) + { + /* (equivalent? (make-int-vector 3 0) (make-vector 3 0)) -> #t + * (equivalent? (make-float-vector 3 1.0) (vector 1 1 1)) -> #t + */ + if ((is_int_vector(x)) && (is_byte_vector(y))) + return(biv_meq(y, x)); + if ((is_byte_vector(x)) && (is_int_vector(y))) + return(biv_meq(x, y)); + for (i = 0; i < len; i++) + if (!is_equivalent_1(sc, vector_getter(x)(sc, x, i), vector_getter(y)(sc, y, i), NULL)) /* this could be greatly optimized */ + return(false); + return(true); + } + + if (is_float_vector(x)) + { + s7_double *arr1 = float_vector_floats(x), *arr2 = float_vector_floats(y); + s7_double fudge = sc->equivalent_float_epsilon; + if (fudge == 0.0) + { + for (i = 0; i < len; i++) + if ((arr1[i] != arr2[i]) && + ((!is_NaN(arr1[i])) || (!is_NaN(arr2[i])))) + return(false); + } + else + if ((len & 0x3) == 0) + for (i = 0; i < len; ) + LOOP_4(if (!floats_are_equivalent(sc, arr1[i], arr2[i])) return(false); i++); + else + for (i = 0; i < len; i++) + if (!floats_are_equivalent(sc, arr1[i], arr2[i])) + return(false); + return(true); + } + if (is_int_vector(x)) + return(iv_meq(int_vector_ints(x), int_vector_ints(y), len)); + if (is_byte_vector(x)) + return(byte_vector_equal_1(sc, x, y)); + + if (!has_simple_elements(x)) + { + if (ci) + { + if (equal_ref(sc, x, y, ci)) return(true); + } + else nci = new_shared_info(sc); + } + for (i = 0; i < len; i++) + if (!(is_equivalent_1(sc, vector_element(x, i), vector_element(y, i), nci))) + return(false); + return(true); +} + +static bool iterator_equal_1(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci, bool equivalent) +{ + s7_pointer x_seq, y_seq, xs, ys; + + if (x == y) return(true); + if (!is_iterator(y)) return(false); + + x_seq = iterator_sequence(x); + y_seq = iterator_sequence(y); + + switch (type(x_seq)) + { + case T_STRING: + return((is_string(y_seq)) && + (iterator_position(x) == iterator_position(y)) && + (iterator_length(x) == iterator_length(y)) && + (string_equal(sc, x_seq, y_seq, ci))); + + case T_VECTOR: case T_INT_VECTOR: case T_BYTE_VECTOR: case T_FLOAT_VECTOR: + return((is_any_vector(y_seq)) && + (iterator_position(x) == iterator_position(y)) && + (iterator_length(x) == iterator_length(y)) && + ((equivalent) ? (vector_equivalent(sc, x_seq, y_seq, ci)) : + ((is_normal_vector(x_seq)) ? (vector_equal(sc, x_seq, y_seq, ci)) : + ((is_float_vector(x_seq)) ? (float_vector_equal(sc, x_seq, y_seq, ci)) : + ((is_int_vector(x_seq)) ? (int_vector_equal(sc, x_seq, y_seq, ci)) : + (byte_vector_equal(sc, x_seq, y_seq, ci))))))); + + /* iterator_next is a function (pair_iterate, iterator_finished etc) */ + case T_PAIR: + if (iterator_next(x) != iterator_next(y)) return(false); /* even if seqs are equal, one might be at end */ + if (equivalent) + { + if (!pair_equivalent(sc, x_seq, y_seq, ci)) + return(false); + } + else + if (!pair_equal(sc, x_seq, y_seq, ci)) + return(false); + + for (xs = x_seq, ys = y_seq; is_pair(xs) && is_pair(ys); xs = cdr(xs), ys = cdr(ys)) + if (xs == iterator_current(x)) + return(ys == iterator_current(y)); + return(is_null(xs) && is_null(ys)); + + case T_NIL: /* (make-iterator #()) works, so () should too */ + return(is_null(y_seq)); /* perhaps for equivalent case, check position in y as well as pair(seq(y))? */ + + case T_C_OBJECT: + if ((is_c_object(y_seq)) && + (iterator_position(x) == iterator_position(y)) && + (iterator_length(x) == iterator_length(y))) + { + if (equivalent) + return(c_objects_are_equivalent(sc, x_seq, y_seq, ci)); + return(c_objects_are_equal(sc, x_seq, y_seq, ci)); + } + return(false); + + case T_LET: + if (!is_let(y_seq)) return(false); + if (iterator_next(x) != iterator_next(y)) return(false); + if (x_seq == sc->rootlet) + return(iterator_position(x) == iterator_position(y)); /* y_seq must also be sc->rootlet since nexts are the same (rootlet_iterate) */ + if (equivalent) + { + if (!let_equivalent(sc, x_seq, y_seq, ci)) + return(false); + } + else + if (!let_equal(sc, x_seq, y_seq, ci)) + return(false); + + for (xs = let_slots(x_seq), ys = let_slots(y_seq); tis_slot(xs) && tis_slot(ys); xs = next_slot(xs), ys = next_slot(ys)) + if (xs == iterator_current_slot(x)) + return(ys == iterator_current_slot(y)); + return(is_slot_end(xs) && is_slot_end(ys)); + + case T_HASH_TABLE: + if (!is_hash_table(y_seq)) return(false); + if (hash_table_entries(x_seq) != hash_table_entries(y_seq)) return(false); + if (hash_table_entries(x_seq) == 0) return(true); + if (iterator_position(x) != iterator_position(y)) return(false); + if (!equivalent) + return(hash_table_equal(sc, x_seq, y_seq, ci)); + return(hash_table_equivalent(sc, x_seq, y_seq, ci)); + + case T_CLOSURE: case T_CLOSURE_STAR: + return(x_seq == y_seq); /* or closure_equal/equivalent? */ + + default: break; + } + return(false); +} + +static bool iterator_equal(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) {return(iterator_equal_1(sc, x, y, ci, false));} +static bool iterator_equivalent(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) {return(iterator_equal_1(sc, x, y, ci, true));} + +#if WITH_GMP +static bool big_integer_equal(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + /* (equal? 1 1.0) -> #f */ + if (is_t_big_integer(y)) + return(mpz_cmp(big_integer(x), big_integer(y)) == 0); + return((is_t_integer(y)) && (mpz_cmp_si(big_integer(x), integer(y)) == 0)); +} + +static bool big_ratio_equal(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + if (is_t_big_ratio(y)) + return(mpq_equal(big_ratio(x), big_ratio(y))); + if (is_t_ratio(y)) + return((numerator(y) == mpz_get_si(mpq_numref(big_ratio(x)))) && + (denominator(y) == mpz_get_si(mpq_denref(big_ratio(x))))); + return(false); +} + +static bool big_real_equal(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + if (is_t_big_real(y)) + return(mpfr_equal_p(big_real(x), big_real(y))); + if (is_t_real(y)) + { + if (mpfr_nan_p(big_real(x))) return(false); + return((!is_NaN(real(y))) && + (mpfr_cmp_d(big_real(x), real(y)) == 0)); + } + return(false); +} + +static bool big_complex_equal(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + if ((mpfr_nan_p(mpc_realref(big_complex(x)))) || (mpfr_nan_p(mpc_imagref(big_complex(x))))) + return(false); + if (is_t_big_complex(y)) + return((!mpfr_nan_p(mpc_realref(big_complex(y)))) && + (!mpfr_nan_p(mpc_imagref(big_complex(y)))) && + (mpc_cmp(big_complex(x), big_complex(y)) == 0)); + if (is_t_complex(y)) + return((!is_NaN(real_part(y))) && + (!is_NaN(imag_part(y))) && + (mpfr_cmp_d(mpc_realref(big_complex(x)), real_part(y)) == 0) && + (mpfr_cmp_d(mpc_imagref(big_complex(x)), imag_part(y)) == 0)); + return(false); +} +#endif + +static bool integer_equal(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + if (is_t_integer(y)) + return(integer(x) == integer(y)); +#if WITH_GMP + if (is_t_big_integer(y)) + return(mpz_cmp_si(big_integer(y), integer(x)) == 0); +#endif + return(false); +} + +/* apparently ratio_equal is predefined in g++ -- name collision on mac */ +static bool fraction_equal(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + if (is_t_ratio(y)) + return((numerator(x) == numerator(y)) && + (denominator(x) == denominator(y))); +#if WITH_GMP + if (is_t_big_ratio(y)) + return((numerator(x) == mpz_get_si(mpq_numref(big_ratio(y)))) && + (denominator(x) == mpz_get_si(mpq_denref(big_ratio(y))))); +#endif + return(false); +} + +static bool real_equal(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + if (is_t_real(y)) + return(real(x) == real(y)); +#if WITH_GMP + if (is_t_big_real(y)) + return((!is_NaN(real(x))) && + (!mpfr_nan_p(big_real(y))) && + (mpfr_cmp_d(big_real(y), real(x)) == 0)); +#endif + return(false); +} + +static bool complex_equal(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + if (is_t_complex(y)) + return((real_part(x) == real_part(y)) && (imag_part(x) == imag_part(y))); +#if WITH_GMP + if (is_t_big_complex(y)) + { + if ((is_NaN(real_part(x))) || (is_NaN(imag_part(x))) || + (mpfr_nan_p(mpc_realref(big_complex(y)))) || (mpfr_nan_p(mpc_imagref(big_complex(y))))) + return(false); + return((mpfr_cmp_d(mpc_realref(big_complex(y)), real_part(x)) == 0) && + (mpfr_cmp_d(mpc_imagref(big_complex(y)), imag_part(x)) == 0)); + } +#endif + return(false); +} + +#if WITH_GMP +static bool big_integer_or_ratio_equivalent(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci, bool int_case) +{ + if (int_case) + mpfr_set_z(sc->mpfr_1, big_integer(x), MPFR_RNDN); + else mpfr_set_q(sc->mpfr_1, big_ratio(x), MPFR_RNDN); + + switch (type(y)) + { + case T_INTEGER: + if (int_case) + return(mpz_cmp_si(big_integer(x), integer(y)) == 0); + mpfr_set_si(sc->mpfr_2, integer(y), MPFR_RNDN); + return(big_floats_are_equivalent(sc, sc->mpfr_1, sc->mpfr_2)); + case T_RATIO: + mpfr_set_d(sc->mpfr_2, fraction(y), MPFR_RNDN); + return(big_floats_are_equivalent(sc, sc->mpfr_1, sc->mpfr_2)); + case T_REAL: + mpfr_set_d(sc->mpfr_2, real(y), MPFR_RNDN); + return(big_floats_are_equivalent(sc, sc->mpfr_1, sc->mpfr_2)); + case T_COMPLEX: + mpfr_set_d(sc->mpfr_2, real_part(y), MPFR_RNDN); + if (!big_floats_are_equivalent(sc, sc->mpfr_1, sc->mpfr_2)) + return(false); + if (is_NaN(imag_part(y))) return(false); + mpfr_set_d(sc->mpfr_1, sc->equivalent_float_epsilon, MPFR_RNDN); + mpfr_set_d(sc->mpfr_2, imag_part(y), MPFR_RNDN); + return(mpfr_cmpabs(sc->mpfr_2, sc->mpfr_1) <= 0); + case T_BIG_INTEGER: + if (int_case) + return(mpz_cmp(big_integer(x), big_integer(y)) == 0); + mpfr_set_z(sc->mpfr_2, big_integer(y), MPFR_RNDN); + return(big_floats_are_equivalent(sc, sc->mpfr_1, sc->mpfr_2)); + case T_BIG_RATIO: + mpfr_set_q(sc->mpfr_2, big_ratio(y), MPFR_RNDN); + return(big_floats_are_equivalent(sc, sc->mpfr_1, sc->mpfr_2)); + case T_BIG_REAL: + return(big_floats_are_equivalent(sc, sc->mpfr_1, big_real(y))); + case T_BIG_COMPLEX: + if (big_floats_are_equivalent(sc, sc->mpfr_1, mpc_realref(big_complex(y)))) + { + if (mpfr_nan_p(mpc_imagref(big_complex(y)))) return(false); + mpfr_set_d(sc->mpfr_1, sc->equivalent_float_epsilon, MPFR_RNDN); + return(mpfr_cmpabs(mpc_imagref(big_complex(y)), sc->mpfr_1) <= 0); + }} + return(false); +} + +static bool big_integer_equivalent(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + return(big_integer_or_ratio_equivalent(sc, x, y, ci, true)); +} + +static bool big_ratio_equivalent(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + return(big_integer_or_ratio_equivalent(sc, x, y, ci, false)); +} + + +static bool big_real_equivalent(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + switch (type(y)) + { + case T_INTEGER: + mpfr_set_si(sc->mpfr_2, integer(y), MPFR_RNDN); + return(big_floats_are_equivalent(sc, big_real(x), sc->mpfr_2)); + case T_RATIO: + mpfr_set_d(sc->mpfr_2, fraction(y), MPFR_RNDN); + return(big_floats_are_equivalent(sc, big_real(x), sc->mpfr_2)); + case T_REAL: + mpfr_set_d(sc->mpfr_2, real(y), MPFR_RNDN); + return(big_floats_are_equivalent(sc, big_real(x), sc->mpfr_2)); + case T_COMPLEX: + mpfr_set_d(sc->mpfr_2, real_part(y), MPFR_RNDN); + if (!big_floats_are_equivalent(sc, big_real(x), sc->mpfr_2)) + return(false); + if (is_NaN(imag_part(y))) return(false); + mpfr_set_d(sc->mpfr_1, sc->equivalent_float_epsilon, MPFR_RNDN); + mpfr_set_d(sc->mpfr_2, imag_part(y), MPFR_RNDN); + return(mpfr_cmpabs(sc->mpfr_2, sc->mpfr_1) <= 0); + case T_BIG_INTEGER: + mpfr_set_z(sc->mpfr_2, big_integer(y), MPFR_RNDN); + return(big_floats_are_equivalent(sc, big_real(x), sc->mpfr_2)); + case T_BIG_RATIO: + mpfr_set_q(sc->mpfr_2, big_ratio(y), MPFR_RNDN); + return(big_floats_are_equivalent(sc, big_real(x), sc->mpfr_2)); + case T_BIG_REAL: + return(big_floats_are_equivalent(sc, big_real(x), big_real(y))); + case T_BIG_COMPLEX: + if (big_floats_are_equivalent(sc, big_real(x), mpc_realref(big_complex(y)))) + { + if (mpfr_nan_p(mpc_imagref(big_complex(y)))) return(false); + mpfr_set_d(sc->mpfr_1, sc->equivalent_float_epsilon, MPFR_RNDN); + return(mpfr_cmpabs(mpc_imagref(big_complex(y)), sc->mpfr_1) <= 0); + }} + return(false); +} + +static bool big_complex_equivalent(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + mpfr_set_d(sc->mpfr_1, sc->equivalent_float_epsilon, MPFR_RNDN); + switch (type(y)) + { + case T_INTEGER: + mpfr_set_si(sc->mpfr_2, integer(y), MPFR_RNDN); + return((big_floats_are_equivalent(sc, mpc_realref(big_complex(x)), sc->mpfr_2)) && + (big_floats_are_equivalent(sc, mpc_imagref(big_complex(x)), sc->mpfr_1))); + case T_RATIO: + mpfr_set_d(sc->mpfr_2, fraction(y), MPFR_RNDN); + return((big_floats_are_equivalent(sc, mpc_realref(big_complex(x)), sc->mpfr_2)) && + (big_floats_are_equivalent(sc, mpc_imagref(big_complex(x)), sc->mpfr_1))); + case T_REAL: + mpfr_set_d(sc->mpfr_2, real(y), MPFR_RNDN); + return((big_floats_are_equivalent(sc, mpc_realref(big_complex(x)), sc->mpfr_2)) && + (big_floats_are_equivalent(sc, mpc_imagref(big_complex(x)), sc->mpfr_1))); + case T_COMPLEX: + mpfr_set_d(sc->mpfr_1, imag_part(y), MPFR_RNDN); + mpfr_set_d(sc->mpfr_2, real_part(y), MPFR_RNDN); + return((big_floats_are_equivalent(sc, mpc_realref(big_complex(x)), sc->mpfr_2)) && + (big_floats_are_equivalent(sc, mpc_imagref(big_complex(x)), sc->mpfr_1))); + case T_BIG_INTEGER: + mpfr_set_z(sc->mpfr_2, big_integer(y), MPFR_RNDN); + return((big_floats_are_equivalent(sc, mpc_realref(big_complex(x)), sc->mpfr_2)) && + (big_floats_are_equivalent(sc, mpc_imagref(big_complex(x)), sc->mpfr_1))); + case T_BIG_RATIO: + mpfr_set_q(sc->mpfr_2, big_ratio(y), MPFR_RNDN); + return((big_floats_are_equivalent(sc, mpc_realref(big_complex(x)), sc->mpfr_2)) && + (big_floats_are_equivalent(sc, mpc_imagref(big_complex(x)), sc->mpfr_1))); + case T_BIG_REAL: + return((big_floats_are_equivalent(sc, mpc_realref(big_complex(x)), big_real(y))) && + (big_floats_are_equivalent(sc, mpc_imagref(big_complex(x)), sc->mpfr_1))); + case T_BIG_COMPLEX: + return((big_floats_are_equivalent(sc, mpc_realref(big_complex(x)), mpc_realref(big_complex(y)))) && + (big_floats_are_equivalent(sc, mpc_imagref(big_complex(x)), mpc_imagref(big_complex(y))))); + } + return(false); +} + +static bool both_floats_are_equivalent(s7_scheme *sc, s7_pointer y) +{ + if (!big_floats_are_equivalent(sc, sc->mpfr_1, mpc_realref(big_complex(y)))) + return(false); + if (mpfr_nan_p(mpc_imagref(big_complex(y)))) return(false); + mpfr_set_d(sc->mpfr_1, sc->equivalent_float_epsilon, MPFR_RNDN); + return(mpfr_cmpabs(mpc_imagref(big_complex(y)), sc->mpfr_1) <= 0); +} +#endif + +static bool integer_equivalent(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + switch (type(y)) + { + case T_INTEGER: + return(integer(x) == integer(y)); + case T_RATIO: + return(floats_are_equivalent(sc, (double)integer(x), fraction(y))); + case T_REAL: + return(floats_are_equivalent(sc, (double)integer(x), real(y))); + case T_COMPLEX: + return((fabs(imag_part(y)) <= sc->equivalent_float_epsilon) && + (floats_are_equivalent(sc, (double)integer(x), real_part(y)))); +#if WITH_GMP + case T_BIG_INTEGER: + return(mpz_cmp_si(big_integer(y), integer(x)) == 0); + case T_BIG_RATIO: + mpfr_set_si(sc->mpfr_1, integer(x), MPFR_RNDN); + mpfr_set_q(sc->mpfr_2, big_ratio(y), MPFR_RNDN); + return(big_floats_are_equivalent(sc, sc->mpfr_1, sc->mpfr_2)); + case T_BIG_REAL: + mpfr_set_si(sc->mpfr_1, integer(x), MPFR_RNDN); + return(big_floats_are_equivalent(sc, sc->mpfr_1, big_real(y))); + case T_BIG_COMPLEX: + mpfr_set_si(sc->mpfr_1, integer(x), MPFR_RNDN); + return(both_floats_are_equivalent(sc, y)); +#endif + } + return(false); +} + +static bool fraction_equivalent(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + switch (type(y)) + { + case T_INTEGER: + return(floats_are_equivalent(sc, (double)fraction(x), integer(y))); + case T_RATIO: + return(floats_are_equivalent(sc, (double)fraction(x), fraction(y))); + case T_REAL: + return(floats_are_equivalent(sc, (double)fraction(x), real(y))); + case T_COMPLEX: + return((fabs(imag_part(y)) <= sc->equivalent_float_epsilon) && + (floats_are_equivalent(sc, fraction(x), real_part(y)))); +#if WITH_GMP + case T_BIG_INTEGER: + mpfr_set_z(sc->mpfr_1, big_integer(y), MPFR_RNDN); + mpfr_set_d(sc->mpfr_2, fraction(x), MPFR_RNDN); + return(big_floats_are_equivalent(sc, sc->mpfr_1, sc->mpfr_2)); + case T_BIG_RATIO: + mpfr_set_d(sc->mpfr_1, fraction(x), MPFR_RNDN); + mpfr_set_q(sc->mpfr_2, big_ratio(y), MPFR_RNDN); + return(big_floats_are_equivalent(sc, sc->mpfr_1, sc->mpfr_2)); + case T_BIG_REAL: + mpfr_set_d(sc->mpfr_1, fraction(x), MPFR_RNDN); + return(big_floats_are_equivalent(sc, sc->mpfr_1, big_real(y))); + case T_BIG_COMPLEX: + mpfr_set_d(sc->mpfr_1, fraction(x), MPFR_RNDN); + return(both_floats_are_equivalent(sc, y)); +#endif + } + return(false); +} + +static bool real_equivalent(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + switch (type(y)) + { + case T_INTEGER: + return(floats_are_equivalent(sc, real(x), integer(y))); + case T_RATIO: + return(floats_are_equivalent(sc, real(x), fraction(y))); + case T_REAL: + return(floats_are_equivalent(sc, real(x), real(y))); + case T_COMPLEX: + return((fabs(imag_part(y)) <= sc->equivalent_float_epsilon) && + (floats_are_equivalent(sc, real(x), real_part(y)))); +#if WITH_GMP + case T_BIG_INTEGER: + mpfr_set_z(sc->mpfr_1, big_integer(y), MPFR_RNDN); + mpfr_set_d(sc->mpfr_2, real(x), MPFR_RNDN); + return(big_floats_are_equivalent(sc, sc->mpfr_1, sc->mpfr_2)); + case T_BIG_RATIO: + mpfr_set_d(sc->mpfr_1, real(x), MPFR_RNDN); + mpfr_set_q(sc->mpfr_2, big_ratio(y), MPFR_RNDN); + return(big_floats_are_equivalent(sc, sc->mpfr_1, sc->mpfr_2)); + case T_BIG_REAL: + mpfr_set_d(sc->mpfr_1, real(x), MPFR_RNDN); + return(big_floats_are_equivalent(sc, sc->mpfr_1, big_real(y))); + case T_BIG_COMPLEX: + mpfr_set_d(sc->mpfr_1, real(x), MPFR_RNDN); + return(both_floats_are_equivalent(sc, y)); +#endif + } + return(false); +} + +static bool complex_equivalent(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ + switch (type(y)) + { + case T_INTEGER: + return((floats_are_equivalent(sc, real_part(x), integer(y))) && + (floats_are_equivalent(sc, imag_part(x), 0.0))); + case T_RATIO: + return((floats_are_equivalent(sc, real_part(x), fraction(y))) && + (floats_are_equivalent(sc, imag_part(x), 0.0))); + case T_REAL: + return((floats_are_equivalent(sc, real_part(x), real(y))) && + (floats_are_equivalent(sc, imag_part(x), 0.0))); + case T_COMPLEX: + return((floats_are_equivalent(sc, real_part(x), real_part(y))) && + (floats_are_equivalent(sc, imag_part(x), imag_part(y)))); +#if WITH_GMP + case T_BIG_INTEGER: + mpfr_set_z(sc->mpfr_1, big_integer(y), MPFR_RNDN); + mpfr_set_d(sc->mpfr_2, real_part(x), MPFR_RNDN); + return((big_floats_are_equivalent(sc, sc->mpfr_1, sc->mpfr_2)) && + (floats_are_equivalent(sc, imag_part(x), 0.0))); + case T_BIG_RATIO: + mpfr_set_d(sc->mpfr_1, real_part(x), MPFR_RNDN); + mpfr_set_q(sc->mpfr_2, big_ratio(y), MPFR_RNDN); + return((big_floats_are_equivalent(sc, sc->mpfr_1, sc->mpfr_2)) && + (floats_are_equivalent(sc, imag_part(x), 0.0))); + case T_BIG_REAL: + mpfr_set_d(sc->mpfr_1, real_part(x), MPFR_RNDN); + return((big_floats_are_equivalent(sc, sc->mpfr_1, big_real(y))) && + (floats_are_equivalent(sc, imag_part(x), 0.0))); + case T_BIG_COMPLEX: + mpfr_set_d(sc->mpfr_1, real_part(x), MPFR_RNDN); + mpfr_set_d(sc->mpfr_2, imag_part(x), MPFR_RNDN); + return((big_floats_are_equivalent(sc, sc->mpfr_1, mpc_realref(big_complex(y)))) && + (big_floats_are_equivalent(sc, sc->mpfr_2, mpc_imagref(big_complex(y))))); +#endif + } + return(false); +} + +static bool random_state_equal(s7_scheme *sc, s7_pointer x, s7_pointer y, shared_info_t *ci) +{ +#if WITH_GMP + return(x == y); +#else + return((x == y) || + ((is_random_state(y)) && + (random_seed(x) == random_seed(y)) && + (random_carry(x) == random_carry(y)))); +#endif +} + +static void init_equals(void) +{ + for (int32_t i = 0; i < NUM_TYPES; i++) {equals[i] = eq_equal; equivalents[i] = eq_equal;} + equals[T_SYMBOL] = eq_equal; + equals[T_C_POINTER] = c_pointer_equal; + equals[T_UNSPECIFIED] = unspecified_equal; + equals[T_UNDEFINED] = undefined_equal; + equals[T_STRING] = string_equal; + equals[T_SYNTAX] = syntax_equal; + equals[T_C_OBJECT] = c_objects_are_equal; + equals[T_RANDOM_STATE] = random_state_equal; + equals[T_ITERATOR] = iterator_equal; + equals[T_INPUT_PORT] = port_equal; + equals[T_OUTPUT_PORT] = port_equal; + equals[T_MACRO] = closure_equal; + equals[T_MACRO_STAR] = closure_equal; + equals[T_BACRO] = closure_equal; + equals[T_BACRO_STAR] = closure_equal; + equals[T_CLOSURE] = closure_equal; + equals[T_CLOSURE_STAR] = closure_equal; + equals[T_HASH_TABLE] = hash_table_equal; + equals[T_LET] = let_equal; + equals[T_PAIR] = pair_equal; + equals[T_VECTOR] = vector_equal; + equals[T_INT_VECTOR] = int_vector_equal; + equals[T_BYTE_VECTOR] = byte_vector_equal; + equals[T_FLOAT_VECTOR] = float_vector_equal; + equals[T_INTEGER] = integer_equal; + equals[T_RATIO] = fraction_equal; + equals[T_REAL] = real_equal; + equals[T_COMPLEX] = complex_equal; +#if WITH_GMP + equals[T_BIG_INTEGER] = big_integer_equal; + equals[T_BIG_RATIO] = big_ratio_equal; + equals[T_BIG_REAL] = big_real_equal; + equals[T_BIG_COMPLEX] = big_complex_equal; +#endif + equivalents[T_SYMBOL] = symbol_equivalent; + equivalents[T_C_POINTER] = c_pointer_equivalent; + equivalents[T_UNSPECIFIED] = unspecified_equal; + equivalents[T_UNDEFINED] = undefined_equal; + equivalents[T_STRING] = string_equal; + equivalents[T_SYNTAX] = syntax_equal; + equivalents[T_C_OBJECT] = c_objects_are_equivalent; + equivalents[T_RANDOM_STATE] = random_state_equal; + equivalents[T_ITERATOR] = iterator_equivalent; + equivalents[T_INPUT_PORT] = port_equivalent; + equivalents[T_OUTPUT_PORT] = port_equivalent; + equivalents[T_MACRO] = closure_equivalent; + equivalents[T_MACRO_STAR] = closure_equivalent; + equivalents[T_BACRO] = closure_equivalent; + equivalents[T_BACRO_STAR] = closure_equivalent; + equivalents[T_CLOSURE] = closure_equivalent; + equivalents[T_CLOSURE_STAR] = closure_equivalent; + equivalents[T_HASH_TABLE] = hash_table_equivalent; + equivalents[T_LET] = let_equivalent; + equivalents[T_PAIR] = pair_equivalent; + equivalents[T_VECTOR] = vector_equivalent; + equivalents[T_INT_VECTOR] = vector_equivalent; + equivalents[T_FLOAT_VECTOR] = vector_equivalent; + equivalents[T_BYTE_VECTOR] = vector_equivalent; + equivalents[T_INTEGER] = integer_equivalent; + equivalents[T_RATIO] = fraction_equivalent; + equivalents[T_REAL] = real_equivalent; + equivalents[T_COMPLEX] = complex_equivalent; +#if WITH_GMP + equivalents[T_BIG_INTEGER] = big_integer_equivalent; + equivalents[T_BIG_RATIO] = big_ratio_equivalent; + equivalents[T_BIG_REAL] = big_real_equivalent; + equivalents[T_BIG_COMPLEX] = big_complex_equivalent; +#endif +} + +bool s7_is_equal(s7_scheme *sc, s7_pointer x, s7_pointer y) {return((*(equals[type(x)]))(sc, x, y, NULL));} +bool s7_is_equivalent(s7_scheme *sc, s7_pointer x, s7_pointer y) {return((*(equivalents[type(x)]))(sc, x, y, NULL));} + +static s7_pointer g_is_equal(s7_scheme *sc, s7_pointer args) +{ + #define H_is_equal "(equal? obj1 obj2) returns #t if obj1 is equal to obj2" + #define Q_is_equal sc->pcl_bt + return(make_boolean(sc, is_equal_1(sc, car(args), cadr(args), NULL))); +} + +static s7_pointer g_is_equivalent(s7_scheme *sc, s7_pointer args) +{ + #define H_is_equivalent "(equivalent? obj1 obj2) returns #t if obj1 is close enough to obj2." + #define Q_is_equivalent sc->pcl_bt + return(make_boolean(sc, is_equivalent_1(sc, car(args), cadr(args), NULL))); +} + +static s7_pointer is_equal_p_pp(s7_scheme *sc, s7_pointer a, s7_pointer b) {return((is_equal_1(sc, a, b, NULL)) ? sc->T : sc->F);} +static s7_pointer is_equivalent_p_pp(s7_scheme *sc, s7_pointer a, s7_pointer b) {return((is_equivalent_1(sc, a, b, NULL)) ? sc->T : sc->F);} + + +/* ---------------------------------------- length, copy, fill ---------------------------------------- */ +static s7_pointer s7_length(s7_scheme *sc, s7_pointer lst); /* why isn't this in s7.h? */ + +static s7_pointer (*length_functions[256])(s7_scheme *sc, s7_pointer obj); +static s7_pointer any_length(s7_scheme *sc, s7_pointer obj) {return(sc->F);} + +static s7_pointer pair_length(s7_scheme *sc, s7_pointer a) +{ + s7_int i = 0; + s7_pointer slow = a, fast = a; /* we know a is a pair, don't start with fast = cdr(a)! else if a len = 3, we never match */ + while (true) + { + LOOP_4(fast = cdr(fast); i++; if (!is_pair(fast)) return(make_integer(sc, (is_null(fast)) ? i : -i))); + slow = cdr(slow); + if (fast == slow) return(real_infinity); + } + return(real_infinity); +} + +static s7_pointer nil_length(s7_scheme *sc, s7_pointer lst) {return(int_zero);} +static s7_pointer v_length(s7_scheme *sc, s7_pointer v) {return(make_integer(sc, vector_length(v)));} +static s7_pointer str_length(s7_scheme *sc, s7_pointer v) {return(make_integer(sc, string_length(v)));} +static s7_pointer bv_length(s7_scheme *sc, s7_pointer v) {return(make_integer(sc, byte_vector_length(v)));} +static s7_pointer h_length(s7_scheme *sc, s7_pointer lst) {return(make_integer(sc, hash_table_mask(lst) + 1));} +static s7_pointer iter_length(s7_scheme *sc, s7_pointer lst) {return(s7_length(sc, iterator_sequence(lst)));} + +static s7_pointer c_obj_length(s7_scheme *sc, s7_pointer lst) +{ + if (!is_global(sc->length_symbol)) + check_method(sc, lst, sc->length_symbol, set_plist_1(sc, lst)); + return(c_object_length(sc, lst)); +} + +static s7_pointer lt_length(s7_scheme *sc, s7_pointer lst) +{ + if (!is_global(sc->length_symbol)) + check_method(sc, lst, sc->length_symbol, set_plist_1(sc, lst)); + return(make_integer(sc, let_length(sc, lst))); +} + +static s7_pointer fnc_length(s7_scheme *sc, s7_pointer lst) +{ + return((has_active_methods(sc, lst)) ? make_integer(sc, closure_length(sc, lst)) : sc->F); +} + +static s7_pointer ip_length(s7_scheme *sc, s7_pointer port) +{ + if (port_is_closed(port)) + return(sc->F); /* or 0? */ + if (is_string_port(port)) + return(make_integer(sc, port_data_size(port))); /* length of string we're reading */ +#if (!MS_WINDOWS) + if (is_file_port(port)) + { + long len; + long cur_pos = ftell(port_file(port)); + fseek(port_file(port), 0, SEEK_END); + len = ftell(port_file(port)); + rewind(port_file(port)); + fseek(port_file(port), cur_pos, SEEK_SET); + return(make_integer(sc, len)); + } +#endif + return(sc->F); +} + +static s7_pointer op_length(s7_scheme *sc, s7_pointer port) +{ + if (port_is_closed(port)) + return(sc->F); /* or 0? */ + return((is_string_port(port)) ? make_integer(sc, port_position(port)) : sc->F); /* length of string we've written */ +} + +static s7_pointer rs_length(s7_scheme *sc, s7_pointer port) {return((WITH_GMP) ? sc->F : int_two);} + +static void init_length_functions(void) +{ + for (int32_t i = 0; i < 256; i++) length_functions[i] = any_length; + length_functions[T_NIL] = nil_length; + length_functions[T_PAIR] = pair_length; + length_functions[T_VECTOR] = v_length; + length_functions[T_FLOAT_VECTOR] = v_length; + length_functions[T_INT_VECTOR] = v_length; + length_functions[T_STRING] = str_length; + length_functions[T_BYTE_VECTOR] = bv_length; + length_functions[T_ITERATOR] = iter_length; + length_functions[T_HASH_TABLE] = h_length; + length_functions[T_C_OBJECT] = c_obj_length; + length_functions[T_LET] = lt_length; + length_functions[T_CLOSURE] = fnc_length; + length_functions[T_CLOSURE_STAR] = fnc_length; + length_functions[T_INPUT_PORT] = ip_length; + length_functions[T_OUTPUT_PORT] = op_length; + length_functions[T_RANDOM_STATE] = rs_length; +} + +static s7_pointer s7_length(s7_scheme *sc, s7_pointer lst) {return((*length_functions[unchecked_type(lst)])(sc, lst));} + +static s7_pointer g_length(s7_scheme *sc, s7_pointer args) +{ + #define H_length "(length obj) returns the length of obj, which can be a list, vector, string, input-port, or hash-table. \ +The length of a dotted list does not include the final cdr, and is returned as a negative number. A circular \ +list has infinite length. Length of anything else returns #f." + #define Q_length s7_make_signature(sc, 2, s7_make_signature(sc, 3, sc->is_integer_symbol, sc->is_infinite_symbol, sc->not_symbol), sc->T) + return((*length_functions[unchecked_type(car(args))])(sc, car(args))); +} + +/* length_p_p = s7_length */ + + +/* -------------------------------- copy -------------------------------- */ +static s7_pointer string_setter(s7_scheme *sc, s7_pointer str, s7_int loc, s7_pointer val) +{ + if (is_character(val)) + { + string_value(str)[loc] = s7_character(val); + return(val); + } + set_car(sc->elist_3, wrap_string(sc, "~S: ~S is not a character", 25)); + set_caddr(sc->elist_3, val); + error_nr(sc, sc->wrong_type_arg_symbol, sc->elist_3); + return(NULL); +} + +static s7_pointer string_getter(s7_scheme *sc, s7_pointer str, s7_int loc) +{ + return(chars[(uint8_t)(string_value(str)[loc])]); /* cast needed else (copy (string (integer->char 255))...) is trouble */ +} + +static s7_pointer c_object_setter(s7_scheme *sc, s7_pointer obj, s7_int loc, s7_pointer val) +{ + return((*(c_object_set(sc, obj)))(sc, with_list_t3(obj, make_integer(sc, loc), val))); +} + +static s7_pointer c_object_getter(s7_scheme *sc, s7_pointer obj, s7_int loc) +{ + return((*(c_object_ref(sc, obj)))(sc, set_plist_2(sc, obj, make_integer(sc, loc)))); +} + +static s7_pointer let_setter(s7_scheme *sc, s7_pointer e, s7_int loc, s7_pointer val) +{ + /* loc is irrelevant here, val has to be of the form (cons symbol value) + * if symbol is already in e, its value is changed, otherwise a new slot is added to e + */ + if (is_pair(val)) + { + s7_pointer sym = car(val); + if (is_symbol(sym)) + { + s7_pointer slot; + if (is_keyword(sym)) sym = keyword_symbol(sym); /* else make_slot will mark the keyword as local confusing odd_bits etc */ + slot = slot_in_let(sc, e, sym); + if (is_slot(slot)) + checked_slot_set_value(sc, slot, cdr(val)); + else add_slot_checked_with_id(sc, e, sym, cdr(val)); + return(cdr(val)); + }} + set_car(sc->elist_3, wrap_string(sc, "~S: ~S is not (cons symbol value)", 33)); + set_caddr(sc->elist_3, val); + error_nr(sc, sc->wrong_type_arg_symbol, sc->elist_3); + return(sc->wrong_type_arg_symbol); +} + +static s7_pointer hash_table_setter(s7_scheme *sc, s7_pointer e, s7_int loc, s7_pointer val) +{ + /* loc is irrelevant here, e is the hash-table, val has to be of the form (cons key value) + * if key is already in e, its value is changed, otherwise a new slot is added to e, cadr(elist_3) is caller + */ + if (!is_pair(val)) + { + set_car(sc->elist_3, wrap_string(sc, "~S: ~S is not (cons key value)", 30)); + set_caddr(sc->elist_3, val); + error_nr(sc, sc->wrong_type_arg_symbol, sc->elist_3); + } + return(s7_hash_table_set(sc, e, car(val), cdr(val))); +} + +static s7_pointer copy_hash_table(s7_scheme *sc, s7_pointer source) +{ + s7_pointer new_hash = s7_make_hash_table(sc, hash_table_mask(source) + 1); + s7_int gc_loc = gc_protect_1(sc, new_hash); + hash_table_checker(new_hash) = hash_table_checker(source); + if (hash_chosen(source)) hash_set_chosen(new_hash); + hash_table_mapper(new_hash) = hash_table_mapper(source); + hash_table_set_procedures(new_hash, copy_hash_table_procedures(sc, source)); + hash_table_copy(sc, source, new_hash, 0, hash_table_entries(source)); + if (is_typed_hash_table(source)) + { + set_is_typed_hash_table(new_hash); + if (has_hash_key_type(source)) set_has_hash_key_type(new_hash); + if (has_hash_value_type(source)) set_has_hash_value_type(new_hash); + if (has_simple_keys(source)) set_has_simple_keys(new_hash); + if (has_simple_values(source)) set_has_simple_values(new_hash); + } + if (is_weak_hash_table(source)) /* 16-May-23 */ + { + set_weak_hash_table(new_hash); + weak_hash_iters(new_hash) = 0; + } + s7_gc_unprotect_at(sc, gc_loc); + return(new_hash); +} + +static s7_pointer copy_vector(s7_scheme *sc, s7_pointer source) +{ + s7_int len = vector_length(source); + s7_pointer vec; + if (!is_typed_vector(source)) + return(s7_vector_copy(sc, source)); + if (len == 0) + return(make_simple_vector(sc, 0)); + vec = make_vector_1(sc, len, NOT_FILLED, T_VECTOR); + set_typed_vector(vec); + typed_vector_set_typer(vec, typed_vector_typer(source)); + if (has_simple_elements(source)) set_has_simple_elements(vec); + s7_vector_fill(sc, vec, vector_element(source, 0)); + if (vector_rank(source) > 1) + return(make_multivector(sc, vec, g_vector_dimensions(sc, set_plist_1(sc, source)))); /* see g_subvector to avoid g_vector_dimensions */ + add_vector(sc, vec); + return(vec); +} + +static s7_pointer copy_source_no_dest(s7_scheme *sc, s7_pointer source, s7_pointer args) +{ + s7_pointer dest; + switch (type(source)) + { + case T_STRING: + return(make_string_with_length(sc, string_value(source), string_length(source))); + + case T_C_OBJECT: + return(copy_c_object(sc, args)); + + case T_RANDOM_STATE: + return(random_state_copy(sc, args)); + + case T_HASH_TABLE: /* this has to copy nearly everything */ + return(copy_hash_table(sc, source)); + + case T_ITERATOR: + return(iterator_copy(sc, source)); + + case T_LET: + check_method(sc, source, sc->copy_symbol, args); + return(let_copy(sc, source)); /* this copies only the local let and points to outer lets */ + + case T_CLOSURE: case T_CLOSURE_STAR: + case T_MACRO: case T_MACRO_STAR: + case T_BACRO: case T_BACRO_STAR: + check_method(sc, source, sc->copy_symbol, args); + return(copy_closure(sc, source)); + + case T_INT_VECTOR: case T_FLOAT_VECTOR: case T_BYTE_VECTOR: + return(s7_vector_copy(sc, source)); /* "shallow" copy */ + + case T_VECTOR: + return(copy_vector(sc, source)); + + case T_PAIR: /* top level only, as in the other cases, checks for circles */ + return(copy_any_list(sc, source)); + + case T_INTEGER: + new_cell(sc, dest, T_INTEGER); + integer(dest) = integer(source); + return(dest); + case T_RATIO: + new_cell(sc, dest, T_RATIO); + numerator(dest) = numerator(source); + denominator(dest) = denominator(source); + return(dest); + case T_REAL: + new_cell(sc, dest, T_REAL); + set_real(dest, real(source)); + return(dest); + case T_COMPLEX: + new_cell(sc, dest, T_COMPLEX); + set_real_part(dest, real_part(source)); + set_imag_part(dest, imag_part(source)); + return(dest); +#if WITH_GMP + case T_BIG_INTEGER: return(mpz_to_big_integer(sc, big_integer(source))); + case T_BIG_RATIO: return(mpq_to_big_ratio(sc, big_ratio(source))); + case T_BIG_REAL: return(mpfr_to_big_real(sc, big_real(source))); + case T_BIG_COMPLEX: return(mpc_to_number(sc, big_complex(source))); +#endif + + case T_C_POINTER: + dest = s7_make_c_pointer_with_type(sc, c_pointer(source), c_pointer_type(source), c_pointer_info(source)); + c_pointer_weak1(dest) = c_pointer_weak1(source); + c_pointer_weak2(dest) = c_pointer_weak2(source); + return(dest); + } + return(source); +} + +static s7_pointer copy_p_p(s7_scheme *sc, s7_pointer source) {return(copy_source_no_dest(sc, source, set_plist_1(sc, source)));} + +static s7_pointer copy_c_object_to_same_type(s7_scheme *sc, s7_pointer dest, s7_pointer source, s7_int dest_start, s7_int dest_end, s7_int source_start) +{ + s7_pointer (*cref)(s7_scheme *sc, s7_pointer args) = c_object_ref(sc, source); + s7_pointer (*cset)(s7_scheme *sc, s7_pointer args) = c_object_set(sc, dest); + s7_pointer mi = make_mutable_integer(sc, 0); + s7_int gc_loc1 = gc_protect_1(sc, mi); + s7_pointer mj = make_mutable_integer(sc, 0); + s7_int gc_loc2 = gc_protect_1(sc, mj); + + for (s7_int i = source_start, j = dest_start; i < dest_end; i++, j++) + { + integer(mi) = i; + integer(mj) = j; + set_car(sc->t3_3, cref(sc, with_list_t2(source, mi))); + set_car(sc->t3_1, dest); + set_car(sc->t3_2, mj); + cset(sc, sc->t3_1); + } + s7_gc_unprotect_at(sc, gc_loc1); + s7_gc_unprotect_at(sc, gc_loc2); + free_cell(sc, mi); + free_cell(sc, mj); + return(dest); +} + +static s7_pointer copy_to_same_type(s7_scheme *sc, s7_pointer dest, s7_pointer source, s7_int dest_start, s7_int dest_end, s7_int source_start) +{ + /* types equal, but not a let (handled in s7_copy_1), returns NULL if not copied here */ + s7_int source_len = dest_end - dest_start; + switch (type(source)) + { + case T_PAIR: + { + s7_pointer pd = dest, ps = source; + s7_int i; + for (i = 0; i < source_start; i++) + ps = cdr(ps); + for (i = 0; i < dest_start; i++) + pd = cdr(pd); + for (; (i < dest_end) && is_pair(ps) && is_pair(pd); i++, ps = cdr(ps), pd = cdr(pd)) + set_car(pd, car(ps)); + return(dest); + } + + case T_VECTOR: + if (is_typed_vector(dest)) + { + s7_pointer *els = vector_elements(source); + for (s7_int i = source_start, j = dest_start; j < dest_end; i++, j++) + typed_vector_setter(sc, dest, j, els[i]); /* types are equal, so source is a normal vector */ + } + else memcpy((void *)((vector_elements(dest)) + dest_start), (void *)((vector_elements(source)) + source_start), source_len * sizeof(s7_pointer)); + return(dest); + + case T_INT_VECTOR: + memcpy((void *)((int_vector_ints(dest)) + dest_start), (void *)((int_vector_ints(source)) + source_start), source_len * sizeof(s7_int)); + return(dest); + case T_FLOAT_VECTOR: + memcpy((void *)((float_vector_floats(dest)) + dest_start), (void *)((float_vector_floats(source)) + source_start), source_len * sizeof(s7_double)); + return(dest); + case T_BYTE_VECTOR: + if (is_string(dest)) + memcpy((void *)(string_value(dest) + dest_start), (void *)((byte_vector_bytes(source)) + source_start), source_len * sizeof(uint8_t)); + else memcpy((void *)(byte_vector_bytes(dest) + dest_start), (void *)((byte_vector_bytes(source)) + source_start), source_len * sizeof(uint8_t)); + return(dest); + + case T_STRING: + if (is_string(dest)) + memcpy((void *)(string_value(dest) + dest_start), (void *)((string_value(source)) + source_start), source_len); + else memcpy((void *)(byte_vector_bytes(dest) + dest_start), (void *)((string_value(source)) + source_start), source_len); + return(dest); + + case T_RANDOM_STATE: +#if (!WITH_GMP) + random_seed(dest) = random_seed(source); + random_carry(dest) = random_carry(source); +#endif + return(dest); + + case T_C_OBJECT: + return(copy_c_object_to_same_type(sc, dest, source, dest_start, dest_end, source_start)); + + case T_LET: + return(NULL); + + case T_HASH_TABLE: + { + s7_pointer p; + gc_protect_via_stack(sc, source); + p = hash_table_copy(sc, source, dest, source_start, source_start + source_len); + unstack(sc); + if ((hash_table_checker(source) != hash_table_checker(dest)) && + (hash_table_mapper(dest) == default_hash_map)) + { + if (hash_table_checker(dest) == hash_empty) + hash_table_checker(dest) = hash_table_checker(source); /* copy hash_table_procedures also? what about the mapper? see hash_table_copy */ + else + { + hash_table_checker(dest) = hash_equal; + hash_set_chosen(dest); + }} + return(p); + } + + default: + return(dest); + } + return(NULL); +} + +static noreturn void copy_element_error_nr(s7_scheme *sc, s7_pointer caller, s7_int num, s7_pointer element, int32_t desired_type) +{ + set_elist_6(sc, wrap_string(sc, "~A ~:D element, ~S, is ~A but should be ~A", 42), + caller, wrap_integer(sc, num), element, type_name_string(sc, element), sc->type_names[desired_type]); + error_nr(sc, sc->wrong_type_arg_symbol, sc->elist_6); +} + +static noreturn void copy_element_error_with_type_nr(s7_scheme *sc, s7_pointer caller, s7_int num, s7_pointer element, s7_pointer desired_type) +{ + set_elist_6(sc, wrap_string(sc, "~A ~:D element, ~S, is ~A but should be ~A", 42), + caller, wrap_integer(sc, num), element, type_name_string(sc, element), desired_type); + error_nr(sc, sc->wrong_type_arg_symbol, sc->elist_6); +} + +static s7_pointer s7_copy_1(s7_scheme *sc, s7_pointer caller, s7_pointer args) +{ + #define H_copy "(copy obj) returns a copy of obj, (copy src dest) copies src into dest, (copy src dest start end) copies src from start to end." + /* #define Q_copy s7_make_circular_signature(sc, 3, 4, sc->T, sc->is_sequence_symbol, sc->is_sequence_symbol, sc->is_integer_symbol) */ + /* this is not right when c-object types are handled in lint -- a generator or Snd object need not consider itself a sequence, + * but it can provide a copy method. So, I think I'll just use #t + */ + #define Q_copy s7_make_circular_signature(sc, 3, 4, sc->T, sc->T, sc->T, sc->is_integer_symbol) + + s7_pointer source = car(args), dest; + s7_int i, j, dest_len, start, end, source_len; + s7_pointer (*set)(s7_scheme *sc, s7_pointer obj, s7_int loc, s7_pointer val) = NULL; + s7_pointer (*get)(s7_scheme *sc, s7_pointer obj, s7_int loc) = NULL; + bool have_indices; + + if (is_null(cdr(args))) /* (copy obj) */ + return(copy_source_no_dest(sc, source, args)); + + dest = T_Ext(cadr(args)); + if ((dest == sc->readable_keyword) && (!is_pair(source))) + error_nr(sc, sc->out_of_range_symbol, + set_elist_1(sc, wrap_string(sc, "copy argument 2, :readable, only works if the source is a pair", 62))); + + if ((is_immutable(dest)) && + (dest != sc->readable_keyword) && + (dest != sc->nil)) /* error_hook copies with cadr(args) :readable, so it's currently NULL */ + wrong_type_error_nr(sc, caller, 2, dest, wrap_string(sc, "a mutable object", 16)); /* so this segfaults if not checking for :readable */ + + have_indices = (is_pair(cddr(args))); + if ((source == dest) && (!have_indices)) + return(dest); + + switch (type(source)) + { + case T_PAIR: + if (dest == sc->readable_keyword) /* a kludge, but I can't think of anything less stupid */ + { + if (have_indices) /* it seems to me that the start/end args here don't make any sense so... */ + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_3(sc, wrap_string(sc, "~S: start/end indices make no sense with :readable: ~S", 54), caller, args)); + return(copy_body(sc, source)); + } + end = s7_list_length(sc, source); + if (end == 0) + end = circular_list_entries(source); + else + if (end < 0) end = -end; + break; + + case T_INT_VECTOR: case T_FLOAT_VECTOR: case T_VECTOR: case T_BYTE_VECTOR: + get = vector_getter(source); + end = vector_length(source); + break; + + case T_STRING: + get = string_getter; + end = string_length(source); + break; + + case T_HASH_TABLE: + if (source == dest) return(dest); + end = hash_table_entries(source); + break; + + case T_RANDOM_STATE: + get = random_state_getter; + end = 2; + break; + + case T_C_OBJECT: + if (c_object_copy(sc, source)) + { + s7_pointer x = (*(c_object_copy(sc, source)))(sc, args); + if (x == dest) + return(dest); + } + check_method(sc, source, sc->copy_symbol, args); + get = c_object_getter; + end = c_object_length_to_int(sc, source); + break; + + case T_LET: + if (source == dest) return(dest); + check_method(sc, source, sc->copy_symbol, args); + if (source == sc->rootlet) + wrong_type_error_nr(sc, caller, 1, source, wrap_string(sc, "a sequence other than the rootlet", 33)); + if ((!have_indices) && (is_let(dest)) && (dest != sc->s7_starlet)) + { + s7_pointer slot; + if (dest == sc->rootlet) /* (copy (inlet 'a 1) (rootlet)) */ + for (slot = let_slots(source); tis_slot(slot); slot = next_slot(slot)) + s7_make_slot(sc, dest, slot_symbol(slot), slot_value(slot)); + else + if ((has_let_fallback(source)) && + (has_let_fallback(dest))) + { + for (slot = let_slots(source); tis_slot(slot); slot = next_slot(slot)) + if ((slot_symbol(slot) != sc->let_ref_fallback_symbol) && + (slot_symbol(slot) != sc->let_set_fallback_symbol)) + add_slot_checked_with_id(sc, dest, slot_symbol(slot), slot_value(slot)); + } + else + /* this copies reversing the order -- if shadowing, this unshadows, tmp has in-order copy code, but it's too much effort */ + for (slot = let_slots(source); tis_slot(slot); slot = next_slot(slot)) + add_slot_checked_with_id(sc, dest, slot_symbol(slot), slot_value(slot)); + return(dest); + } + end = let_length(sc, source); + break; + + case T_NIL: + end = 0; + if (is_sequence(dest)) + break; + + default: + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_4(sc, wrap_string(sc, "can't ~S ~S to ~S", 17), caller, source, dest)); + } + + start = 0; + if (have_indices) + { + s7_pointer p = start_and_end(sc, caller, args, 3, cddr(args), &start, &end); + if (p != sc->unused) return(p); + } + if ((start == 0) && (source == dest)) + return(dest); + + source_len = end - start; + if (source_len == 0) + { + if (!is_sequence(dest)) + wrong_type_error_nr(sc, caller, 2, dest, a_sequence_string); + return(dest); + } + + switch (type(dest)) + { + case T_PAIR: + dest_len = source_len; + break; + + case T_INT_VECTOR: + case T_BYTE_VECTOR: + if (is_float_vector(source)) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_4(sc, wrap_string(sc, "can't ~S ~S to ~A", 17), caller, source, sc->type_names[type(dest)])); + case T_FLOAT_VECTOR: + set = vector_setter(dest); + dest_len = vector_length(dest); + break; + + case T_VECTOR: + set = (is_typed_vector(dest)) ? typed_vector_setter : vector_setter(dest); + dest_len = vector_length(dest); + break; + + case T_STRING: + set = string_setter; + dest_len = string_length(dest); + set_cadr(sc->elist_3, caller); /* for possible error handling in string_setter */ + break; + + case T_HASH_TABLE: + set = hash_table_setter; + dest_len = source_len; + set_cadr(sc->elist_3, caller); /* for possible error handling in hash_table_setter */ + break; + + case T_C_OBJECT: + /* if source or dest is c_object, call its copy function before falling back on the get/set functions */ + if (c_object_copy(sc, dest)) + { + s7_pointer x = (*(c_object_copy(sc, dest)))(sc, args); + if (x == dest) + return(dest); + } + set = c_object_setter; + dest_len = c_object_length_to_int(sc, dest); + break; + + case T_LET: + if (dest == sc->rootlet) + wrong_type_error_nr(sc, caller, 2, dest, wrap_string(sc, "a sequence other rootlet", 24)); + if (dest == sc->s7_starlet) + wrong_type_error_nr(sc, caller, 2, dest, wrap_string(sc, "a sequence other than *s7*", 26)); + set = let_setter; + dest_len = source_len; /* grows via set, so dest_len isn't relevant */ + set_cadr(sc->elist_3, caller); /* for possible error handling in let_setter */ + break; + + case T_NIL: + return(sc->nil); + + case T_RANDOM_STATE: + set = random_state_setter; + dest_len = 2; + break; + + default: + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_4(sc, wrap_string(sc, "can't ~S ~S to ~S", 17), caller, source, dest)); + } + + if (dest_len == 0) + return(dest); + + /* end is source_len if not set explicitly */ + if (dest_len < source_len) + { + end = dest_len + start; + source_len = dest_len; + } + + if ((source != dest) && + ((type(source) == type(dest)) || + ((is_string_or_byte_vector(source)) && + (is_string_or_byte_vector(dest))))) + { + s7_pointer res = copy_to_same_type(sc, dest, source, 0, source_len, start); + if (res) return(res); + } + + switch (type(source)) + { + case T_PAIR: + { + s7_pointer p = source; + i = 0; + if (start > 0) + for (i = 0; i < start; i++) + p = cdr(p); + /* dest won't be a pair here if source != dest -- the pair->pair case was caught above */ + if (source == dest) /* here start != 0 (see above) */ + for (s7_pointer dp = source /* i = start */; i < end; i++, p = cdr(p), dp = cdr(dp)) + set_car(dp, car(p)); + else + if (is_string(dest)) + { + char *dst = string_value(dest); + for (/* i = start */ j = 0; i < end; i++, j++, p = cdr(p)) + { + if (!is_character(car(p))) + copy_element_error_nr(sc, caller, i + 1, car(p), T_CHARACTER); + dst[j] = character(car(p)); + }} + else + if ((is_normal_vector(dest)) && (set != typed_vector_setter)) + { + s7_pointer *els = vector_elements(dest); + for (/* i = start */ j = 0; i < end; i++, j++, p = cdr(p)) + els[j] = car(p); + } + else + for (/* i = start */ j = 0; i < end; i++, j++, p = cdr(p)) + set(sc, dest, j, car(p)); + return(dest); + } + + case T_LET: + if (source == sc->s7_starlet) /* *s7* */ + { + s7_pointer iter = s7_make_iterator(sc, sc->s7_starlet); + s7_int gc_loc = s7_gc_protect(sc, iter); + for (i = 0; i < start; i++) + { + s7_iterate(sc, iter); + if (iterator_is_at_end(iter)) + { + s7_gc_unprotect_at(sc, gc_loc); + return(dest); + }} + if (is_pair(dest)) /* (append '(1) *s7* ()) */ + { + s7_pointer p; + for (i = start, p = dest; (i < end) && (is_pair(p)); i++, p = cdr(p)) + { + s7_pointer val = s7_iterate(sc, iter); + if (iterator_is_at_end(iter)) break; + set_car(p, val); + }} + else + for (i = start, j = 0; i < end; i++, j++) + { + s7_pointer val = s7_iterate(sc, iter); + if (iterator_is_at_end(iter)) break; + set(sc, dest, j, val); + } + s7_gc_unprotect_at(sc, gc_loc); + } + else + { + /* source and dest can't be rootlet (checked above), dest also can't be *s7* */ + s7_pointer slot = let_slots(source); + for (i = 0; i < start; i++) slot = next_slot(slot); + if (is_pair(dest)) + { + s7_pointer p; + check_free_heap_size(sc, end - start); + for (i = start, p = dest; (i < end) && (is_pair(p)); i++, p = cdr(p), slot = next_slot(slot)) + set_car(p, cons_unchecked(sc, slot_symbol(slot), slot_value(slot))); + } + else + if (is_let(dest)) + { + if ((has_let_fallback(source)) && + (has_let_fallback(dest))) + { + for (slot = let_slots(source); tis_slot(slot); slot = next_slot(slot)) + if ((slot_symbol(slot) != sc->let_ref_fallback_symbol) && + (slot_symbol(slot) != sc->let_set_fallback_symbol)) + add_slot_checked_with_id(sc, dest, slot_symbol(slot), slot_value(slot)); + } + else + for (i = start; i < end; i++, slot = next_slot(slot)) + add_slot_checked_with_id(sc, dest, slot_symbol(slot), slot_value(slot)); + } + else + if (is_hash_table(dest)) + for (i = start; i < end; i++, slot = next_slot(slot)) + s7_hash_table_set(sc, dest, slot_symbol(slot), slot_value(slot)); /* if value=#f, dest will not contain symbol */ + else + if ((is_normal_vector(dest)) && (set != typed_vector_setter)) + { + s7_pointer *els = vector_elements(dest); + check_free_heap_size(sc, end - start); + for (i = start, j = 0; i < end; i++, j++, slot = next_slot(slot)) + els[j] = cons_unchecked(sc, slot_symbol(slot), slot_value(slot)); + } + else + for (i = start, j = 0; i < end; i++, j++, slot = next_slot(slot)) + set(sc, dest, j, cons(sc, slot_symbol(slot), slot_value(slot))); + } + return(dest); + + case T_HASH_TABLE: + { + s7_int loc = -1, skip = start; + hash_entry_t **elements = hash_table_elements(source); + hash_entry_t *x = NULL; + + while (skip > 0) + { + while (!x) x = elements[++loc]; + skip--; + x = hash_entry_next(x); + } + if (is_pair(dest)) + { + s7_pointer p; + check_free_heap_size(sc, end - start); + for (i = start, p = dest; (i < end) && (is_pair(p)); i++, p = cdr(p)) + { + while (!x) x = elements[++loc]; + set_car(p, cons_unchecked(sc, hash_entry_key(x), hash_entry_value(x))); + x = hash_entry_next(x); + }} + else + if (is_let(dest)) + { + for (i = start; i < end; i++) + { + s7_pointer symbol; + while (!x) x = elements[++loc]; + symbol = hash_entry_key(x); + if (!is_symbol(symbol)) + copy_element_error_nr(sc, caller, i + 1, symbol, T_SYMBOL); + if (is_constant_symbol(sc, symbol)) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_4(sc, wrap_string(sc, "~A into ~A: ~A is a constant", 28), caller, dest, symbol)); + if ((symbol != sc->let_ref_fallback_symbol) && + (symbol != sc->let_set_fallback_symbol)) + add_slot_checked_with_id(sc, dest, symbol, hash_entry_value(x)); /* ...unchecked... if size ok */ + x = hash_entry_next(x); + }} + else + { + check_free_heap_size(sc, end - start); + for (i = start, j = 0; i < end; i++, j++) + { + while (!x) x = elements[++loc]; + set(sc, dest, j, cons_unchecked(sc, hash_entry_key(x), hash_entry_value(x))); + x = hash_entry_next(x); + }} + return(dest); + } + + case T_VECTOR: + { + s7_pointer *vals = vector_elements(source); + if (is_float_vector(dest)) + { + s7_double *dst = float_vector_floats(dest); + for (i = start, j = 0; i < end; i++, j++) + dst[j] = real_to_double(sc, vals[i], symbol_name(caller)); + return(dest); + } + if (is_int_vector(dest)) + { + s7_int *dst = int_vector_ints(dest); + for (i = start, j = 0; i < end; i++, j++) + { + if (!s7_is_integer(vals[i])) + copy_element_error_nr(sc, caller, i + 1, vals[i], T_INTEGER); + dst[j] = s7_integer_clamped_if_gmp(sc, vals[i]); + } + return(dest); + } + if (is_string(dest)) + { + char *dst = string_value(dest); + for (i = start, j = 0; i < end; i++, j++) + { + if (!is_character(vals[i])) + copy_element_error_nr(sc, caller, i + 1, vals[i], T_CHARACTER); + dst[j] = character(vals[i]); + } + return(dest); + } + if (is_byte_vector(dest)) + { + uint8_t *dst = (uint8_t *)byte_vector_bytes(dest); + for (i = start, j = 0; i < end; i++, j++) + { + s7_int byte; + if (!s7_is_integer(vals[i])) + copy_element_error_with_type_nr(sc, caller, i + 1, vals[i], an_unsigned_byte_string); + byte = s7_integer_clamped_if_gmp(sc, vals[i]); + if ((byte >= 0) && (byte < 256)) + dst[j] = (uint8_t)byte; + else copy_element_error_with_type_nr(sc, caller, i + 1, vals[i], an_unsigned_byte_string); + } + return(dest); + }} + break; + + case T_FLOAT_VECTOR: + { + s7_double *src = float_vector_floats(source); + /* int-vector destination can't normally work, fractional parts get rounded away */ + if ((is_normal_vector(dest)) && (!is_typed_vector(dest))) + { + s7_pointer *dst = vector_elements(dest); + check_free_heap_size(sc, end - start); + for (i = start, j = 0; i < end; i++, j++) + dst[j] = make_real_unchecked(sc, src[i]); + return(dest); + }} + break; + + case T_INT_VECTOR: + { + s7_int *src = int_vector_ints(source); + if (is_float_vector(dest)) + { + s7_double *dst = float_vector_floats(dest); + for (i = start, j = 0; i < end; i++, j++) + dst[j] = (s7_double)(src[i]); + return(dest); + } + if ((is_normal_vector(dest)) && (!is_typed_vector(dest))) + /* this could check that the typer is integer? (similarly elsewhere): + * (typed_vector_typer(dest) != global_value(sc->is_integer_symbol)) ? + */ + { + s7_pointer *dst = vector_elements(dest); + check_free_heap_size(sc, end - start); + for (i = start, j = 0; i < end; i++, j++) + dst[j] = make_integer_unchecked(sc, src[i]); + return(dest); + } + if (is_string(dest)) + { + for (i = start, j = 0; i < end; i++, j++) + { + if ((src[i] < 0) || (src[i] > 255)) + copy_element_error_with_type_nr(sc, caller, i + 1, wrap_integer(sc, src[i]), an_unsigned_byte_string); + string_value(dest)[j] = (uint8_t)(src[i]); + } + return(dest); + } + if (is_byte_vector(dest)) + { + for (i = start, j = 0; i < end; i++, j++) + { + if ((src[i] < 0) || (src[i] > 255)) + copy_element_error_with_type_nr(sc, caller, i + 1, wrap_integer(sc, src[i]), an_unsigned_byte_string); + byte_vector(dest, j) = (uint8_t)(src[i]); + } + return(dest); + }} + break; + + case T_BYTE_VECTOR: + if ((is_normal_vector(dest)) && (!is_typed_vector(dest))) + { + s7_pointer *dst = vector_elements(dest); + check_free_heap_size(sc, end - start); + for (i = start, j = 0; i < end; i++, j++) + dst[j] = small_int(byte_vector(source, i)); + return(dest); + } + if (is_int_vector(dest)) + { + s7_int *els = int_vector_ints(dest); + for (i = start, j = 0; i < end; i++, j++) + els[j] = (s7_int)((uint8_t)(byte_vector(source, i))); + return(dest); + } + if (is_float_vector(dest)) + { + s7_double *els = float_vector_floats(dest); + for (i = start, j = 0; i < end; i++, j++) + els[j] = (s7_double)((uint8_t)(byte_vector(source, i))); + return(dest); + } + break; + + case T_STRING: + if ((is_normal_vector(dest)) && (!is_typed_vector(dest))) + { + s7_pointer *dst = vector_elements(dest); + for (i = start, j = 0; i < end; i++, j++) + dst[j] = chars[(uint8_t)string_value(source)[i]]; + return(dest); + } + if (is_int_vector(dest)) + { + s7_int *els = int_vector_ints(dest); + for (i = start, j = 0; i < end; i++, j++) + els[j] = (s7_int)((uint8_t)(string_value(source)[i])); + return(dest); + } + if (is_float_vector(dest)) + { + s7_double *els = float_vector_floats(dest); + for (i = start, j = 0; i < end; i++, j++) + els[j] = (s7_double)((uint8_t)(string_value(source)[i])); + return(dest); + } + break; + } + + if (is_pair(dest)) + { + s7_pointer p; + if (is_float_vector(source)) + { + s7_double *els = float_vector_floats(source); + check_free_heap_size(sc, end - start); + for (i = start, p = dest; (i < end) && (is_pair(p)); i++, p = cdr(p)) + set_car(p, make_real_unchecked(sc, els[i])); + } + else + if (is_int_vector(source)) + { + s7_int *els = int_vector_ints(source); + check_free_heap_size(sc, end - start); + for (i = start, p = dest; (i < end) && (is_pair(p)); i++, p = cdr(p)) + set_car(p, make_integer_unchecked(sc, els[i])); + } + else + for (i = start, p = dest; (i < end) && (is_pair(p)); i++, p = cdr(p)) + set_car(p, get(sc, source, i)); + } + else /* if source == dest here, we're moving data backwards, so this is safe in either case */ + for (i = start, j = 0; i < end; i++, j++) + set(sc, dest, j, get(sc, source, i)); + /* some choices probably should raise an error, but don't: + * (copy (make-hash-table) "1") ; nothing to copy (empty hash table), so no error + */ + return(dest); +} + +s7_pointer s7_copy(s7_scheme *sc, s7_pointer args) {return(s7_copy_1(sc, sc->copy_symbol, args));} +#define g_copy s7_copy + + +/* -------------------------------- reverse -------------------------------- */ +s7_pointer s7_reverse(s7_scheme *sc, s7_pointer a) /* just pairs */ +{ + /* reverse list -- produce new list (other code assumes this function does not return the original!) */ + s7_pointer x, p; + + if (is_null(a)) return(a); + if (!is_pair(cdr(a))) + return((is_null(cdr(a))) ? list_1(sc, car(a)) : cons(sc, cdr(a), car(a))); /* don't return 'a' itself */ + sc->w = list_1(sc, car(a)); + for (x = cdr(a), p = a; is_pair(x); x = cdr(x), p = cdr(p)) + { + sc->w = cons(sc, car(x), sc->w); + if (is_pair(cdr(x))) + { + x = cdr(x); + sc->w = cons_unchecked(sc, car(x), sc->w); + } + if (x == p) /* this can take awhile to notice there's a cycle, but what does the caller expect? */ + break; + } + p = (is_null(x)) ? sc->w : cons(sc, x, sc->w); /* ?? this means that (reverse '(1 2 . 3)) returns '(3 2 1) -- we used to return () here */ + sc->w = sc->unused; + return(p); +} + +/* s7_reverse sometimes tacks extra nodes on the end of a reversed circular list (it detects the cycle too late) + * (let ((lst (list 0))) (set! (cdr lst) lst) (reverse lst)) -> (#1=(0 . #1#) 0 0 0) + */ + +static s7_pointer string_reverse(s7_scheme *sc, s7_pointer p) +{ + s7_pointer np; + char *dest, *source = string_value(p); + s7_int len = string_length(p); + char *end = (char *)(source + len); + np = make_empty_string(sc, len, '\0'); + dest = (char *)(string_value(np) + len); + while (source < end) *(--dest) = *source++; + return(np); +} + +static s7_pointer byte_vector_reverse(s7_scheme *sc, s7_pointer p) +{ + s7_pointer np; + uint8_t *dest; + const uint8_t *source = byte_vector_bytes(p); + s7_int len = byte_vector_length(p); + const uint8_t *end = (const uint8_t *)(source + len); + np = make_simple_byte_vector(sc, len); + dest = (uint8_t *)(byte_vector_bytes(np) + len); + while (source < end) *(--dest) = *source++; + return(np); +} + +static s7_pointer int_vector_reverse(s7_scheme *sc, s7_pointer p) +{ + s7_pointer np; + s7_int *dest, *source = int_vector_ints(p); + s7_int len = vector_length(p); + s7_int *end = (s7_int *)(source + len); + if (vector_rank(p) > 1) + np = g_make_vector_1(sc, set_plist_2(sc, g_vector_dimensions(sc, set_plist_1(sc, p)), int_zero), sc->make_int_vector_symbol); + else np = make_simple_int_vector(sc, len); + dest = (s7_int *)(int_vector_ints(np) + len); + while (source < end) *(--dest) = *source++; + return(np); +} + +static s7_pointer float_vector_reverse(s7_scheme *sc, s7_pointer p) +{ + s7_pointer np; + s7_double *dest, *source = float_vector_floats(p); + s7_int len = vector_length(p); + s7_double *end = (s7_double *)(source + len); + if (vector_rank(p) > 1) + np = g_make_vector_1(sc, set_plist_2(sc, g_vector_dimensions(sc, set_plist_1(sc, p)), real_zero), sc->make_float_vector_symbol); + else np = make_simple_float_vector(sc, len); + dest = (s7_double *)(float_vector_floats(np) + len); + while (source < end) *(--dest) = *source++; + return(np); +} + +static s7_pointer vector_reverse(s7_scheme *sc, s7_pointer p) +{ + s7_pointer np; + s7_pointer *dest, *source = vector_elements(p); + s7_int len = vector_length(p); + s7_pointer *end = (s7_pointer *)(source + len); + if (vector_rank(p) > 1) + np = g_make_vector(sc, set_plist_1(sc, g_vector_dimensions(sc, set_plist_1(sc, p)))); + else np = make_simple_vector(sc, len); + dest = (s7_pointer *)(vector_elements(np) + len); + while (source < end) *(--dest) = *source++; + if (is_typed_vector(p)) + { + set_typed_vector(np); + typed_vector_set_typer(np, typed_vector_typer(p)); + if (has_simple_elements(p)) set_has_simple_elements(np); + } + return(np); +} + +static s7_pointer reverse_p_p(s7_scheme *sc, s7_pointer p) +{ + sc->temp3 = p; + switch (type(p)) + { + case T_NIL: return(sc->nil); + case T_PAIR: return(s7_reverse(sc, p)); + case T_STRING: return(string_reverse(sc, p)); + case T_BYTE_VECTOR: return(byte_vector_reverse(sc, p)); + case T_INT_VECTOR: return(int_vector_reverse(sc, p)); + case T_FLOAT_VECTOR: return(float_vector_reverse(sc, p)); + case T_VECTOR: return(vector_reverse(sc, p)); + case T_HASH_TABLE: return(hash_table_reverse(sc, p)); + case T_C_OBJECT: + check_method(sc, p, sc->reverse_symbol, set_plist_1(sc, p)); + if (!c_object_reverse(sc, p)) + syntax_error_nr(sc, "attempt to reverse ~S?", 22, p); + return((*(c_object_reverse(sc, p)))(sc, set_plist_1(sc, p))); + case T_LET: + check_method(sc, p, sc->reverse_symbol, set_plist_1(sc, p)); + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "can't reverse let: ~S", 21), p)); + default: + return(method_or_bust_p(sc, p, sc->reverse_symbol, a_sequence_string)); + } + return(sc->nil); +} + +static s7_pointer g_reverse(s7_scheme *sc, s7_pointer args) +{ + #define H_reverse "(reverse lst) returns a list with the elements of lst in reverse order. reverse \ +also accepts a string or vector argument." + #define Q_reverse s7_make_signature(sc, 2, sc->is_sequence_symbol, sc->is_sequence_symbol) + return(reverse_p_p(sc, car(args))); +} + +static s7_pointer any_list_reverse_in_place(s7_scheme *sc, s7_pointer term, s7_pointer list) +{ + s7_pointer p, result; + if (is_null(list)) return(term); + p = list; + result = term; + while (true) + { + s7_pointer q = cdr(p); + if (is_null(q)) + { + set_cdr(p, result); + return(p); + } + if ((is_pair(q)) && (!is_immutable_pair(q))) + { + set_cdr(p, result); + result = p; + p = q; + } + else return(sc->nil); /* improper or immutable */ + } + return(result); +} + +static s7_pointer string_or_byte_vector_reverse_in_place(s7_scheme *sc, s7_pointer p) +{ + s7_int len; + uint8_t *bytes; + + if (is_immutable(p)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->reverseb_symbol, p)); + + if (is_string(p)) + { + len = string_length(p); + bytes = (uint8_t *)string_value(p); + } + else + { + len = byte_vector_length(p); + bytes = byte_vector_bytes(p); + } + if (len < 2) return(p); + +#if (defined(__linux__)) && (defined(__GLIBC__)) /* need byteswp.h */ + /* this code (from StackOverflow with changes) is much faster: */ +#include + if ((len & 0x7f) == 0) + { + uint32_t *dst = (uint32_t *)(bytes + len - 4); + uint32_t *src = (uint32_t *)bytes; + while (src < dst) + { + uint32_t a, b; + LOOP_4(a = *src; b = *dst; *src++ = bswap_32(b); *dst-- = bswap_32(a)); + LOOP_4(a = *src; b = *dst; *src++ = bswap_32(b); *dst-- = bswap_32(a)); + LOOP_4(a = *src; b = *dst; *src++ = bswap_32(b); *dst-- = bswap_32(a)); + LOOP_4(a = *src; b = *dst; *src++ = bswap_32(b); *dst-- = bswap_32(a)); + }} + else + if ((len & 0x1f) == 0) /* 4-bytes at a time, 4 times per loop == 16 */ + { + uint32_t *dst = (uint32_t *)(bytes + len - 4); + uint32_t *src = (uint32_t *)bytes; + while (src < dst) + { + uint32_t a, b; + LOOP_4(a = *src; b = *dst; *src++ = bswap_32(b); *dst-- = bswap_32(a)); + }} + else +#endif + { + char *s1 = (char *)bytes; + char *s2 = (char *)(s1 + len - 1); + while (s1 < s2) {char c; c = *s1; *s1++ = *s2; *s2-- = c;} + } + return(p); +} + +static s7_pointer int_vector_reverse_in_place(s7_scheme *sc, s7_pointer p) +{ + if (is_immutable_vector(p)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->reverseb_symbol, p)); + { + s7_int len = vector_length(p); + s7_int *s1 = int_vector_ints(p), *s2; + if (len < 2) return(p); + s2 = (s7_int *)(s1 + len - 1); + if ((len & 0x3f) == 0) /* 63 for 2 32's */ + while (s1 < s2) + { + s7_int c; + LOOP_8(c = *s1; *s1++ = *s2; *s2-- = c); + LOOP_8(c = *s1; *s1++ = *s2; *s2-- = c); + LOOP_8(c = *s1; *s1++ = *s2; *s2-- = c); + LOOP_8(c = *s1; *s1++ = *s2; *s2-- = c); + } + else + if ((len & 0xf) == 0) /* not 0x7 -- odd multiple of 8 will leave center ints unreversed (we're moving 2 at a time) */ + while (s1 < s2) + { + s7_int c; + LOOP_8(c = *s1; *s1++ = *s2; *s2-- = c); + } + else while (s1 < s2) {s7_int c; c = *s1; *s1++ = *s2; *s2-- = c;} + } + return(p); +} + +static s7_pointer float_vector_reverse_in_place(s7_scheme *sc, s7_pointer p) +{ + if (is_immutable_vector(p)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->reverseb_symbol, p)); + { + s7_int len = vector_length(p); + s7_double *s1 = float_vector_floats(p), *s2; + if (len < 2) return(p); + s2 = (s7_double *)(s1 + len - 1); + if ((len & 0x3f) == 0) /* 63 for 2 32's */ + while (s1 < s2) + { + s7_double c; + LOOP_8(c = *s1; *s1++ = *s2; *s2-- = c); + LOOP_8(c = *s1; *s1++ = *s2; *s2-- = c); + LOOP_8(c = *s1; *s1++ = *s2; *s2-- = c); + LOOP_8(c = *s1; *s1++ = *s2; *s2-- = c); + } + else + if ((len & 0xf) == 0) + while (s1 < s2) + { + s7_double c; + LOOP_8(c = *s1; *s1++ = *s2; *s2-- = c); + } + else while (s1 < s2) {s7_double c; c = *s1; *s1++ = *s2; *s2-- = c;} + } + return(p); +} + +static s7_pointer vector_reverse_in_place(s7_scheme *sc, s7_pointer p) +{ + if (is_immutable_vector(p)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->reverseb_symbol, p)); + { + s7_int len = vector_length(p); + s7_pointer *s1 = vector_elements(p), *s2; + if (len < 2) return(p); + s2 = (s7_pointer *)(s1 + len - 1); + if ((len & 0x3f) == 0) /* 63 for 2 32's */ + while (s1 < s2) + { + s7_pointer c; + LOOP_8(c = *s1; *s1++ = *s2; *s2-- = c); + LOOP_8(c = *s1; *s1++ = *s2; *s2-- = c); + LOOP_8(c = *s1; *s1++ = *s2; *s2-- = c); + LOOP_8(c = *s1; *s1++ = *s2; *s2-- = c); + } + else + if ((len & 0xf) == 0) + while (s1 < s2) + { + s7_pointer c; + LOOP_8(c = *s1; *s1++ = *s2; *s2-- = c); + } + else while (s1 < s2) {s7_pointer c; c = *s1; *s1++ = *s2; *s2-- = c;} + } + return(p); +} + +static s7_pointer g_reverse_in_place(s7_scheme *sc, s7_pointer args) +{ + #define H_reverse_in_place "(reverse! lst) reverses lst in place" + #define Q_reverse_in_place Q_reverse + + /* (reverse v) is only slighly faster than (reverse! (copy v)) */ + s7_pointer p = car(args); + switch (type(p)) + { + case T_NIL: + return(sc->nil); + + case T_PAIR: + if (is_immutable_pair(p)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->reverseb_symbol, p)); + { + s7_pointer np = any_list_reverse_in_place(sc, sc->nil, p); + if (is_null(np)) + { + if (!s7_is_proper_list(sc, p)) + wrong_type_error_nr(sc, sc->reverseb_symbol, 1, car(args), wrap_string(sc, "a proper list", 13)); + wrong_type_error_nr(sc, sc->reverseb_symbol, 1, car(args), wrap_string(sc, "a mutable proper list", 21)); + } + return(np); + } + /* (reverse! p) is supposed to change p directly and lisp programmers expect reverse! to be fast + * so in a sense this is different from the other cases: it assumes (set! p (reverse! p)) + * To make (reverse! p) direct: + * for (l = p, r = cdr(p); is_pair(r); l = r, r = cdr(r)) opt1(r) = l; + * if (!is_null(r)) sole_arg_wrong_type_error_nr(sc, sc->reverseb_symbol, p, a_proper_list_string); + * for (r = l, l = p; l != r; l = cdr(l)) {t = car(l); set_car(l, car(r)); set_car(r, t); if (cdr(l) != r) r = opt1(r);} + * immutable check is needed else (reverse! (catch #t 1 cons)) clobbers sc->wrong_type_arg_info + */ + + case T_BYTE_VECTOR: + case T_STRING: return(string_or_byte_vector_reverse_in_place(sc, p)); + case T_INT_VECTOR: return(int_vector_reverse_in_place(sc, p)); + case T_FLOAT_VECTOR: return(float_vector_reverse_in_place(sc, p)); + case T_VECTOR: return(vector_reverse_in_place(sc, p)); + + default: + if (is_immutable(p)) + { + if (is_simple_sequence(p)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->reverseb_symbol, p)); + sole_arg_wrong_type_error_nr(sc, sc->reverseb_symbol, p, a_sequence_string); + } + if ((is_simple_sequence(p)) && + (!has_active_methods(sc, p))) + sole_arg_wrong_type_error_nr(sc, sc->reverseb_symbol, p, wrap_string(sc, "a vector, string, or list", 25)); + return(method_or_bust_p(sc, p, sc->reverseb_symbol, a_sequence_string)); + } + return(p); +} + + +/* -------------------------------- fill! -------------------------------- */ +static s7_pointer pair_fill(s7_scheme *sc, s7_pointer args) +{ + /* ambiguous ("tree-fill"?) but if it's like vector-fill, we just stomp on the top level */ + s7_pointer obj = car(args), val; + s7_int i, start = 0, end, len; + +#if WITH_HISTORY + if ((is_immutable_pair(obj)) && (obj != sc->eval_history1) && (obj != sc->eval_history2)) +#else + if (is_immutable_pair(obj)) +#endif + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->fill_symbol, obj)); + if (obj == global_value(sc->features_symbol)) + error_nr(sc, sc->out_of_range_symbol, set_elist_1(sc, wrap_string(sc, "can't fill! *features*", 22))); + if (obj == global_value(sc->libraries_symbol)) + error_nr(sc, sc->out_of_range_symbol, set_elist_1(sc, wrap_string(sc, "can't fill! *libraries*", 23))); + + val = cadr(args); + len = s7_list_length(sc, obj); + end = len; + if (end < 0) end = -end; else {if (end == 0) end = 123123123;} + if (!is_null(cddr(args))) + { + s7_pointer p = start_and_end(sc, sc->fill_symbol, args, 3, cddr(args), &start, &end); + if (p != sc->unused) return(p); + if (start == end) return(val); + } + if (len > 0) + { + s7_pointer p; + if (end < len) len = end; + for (i = 0, p = obj; i < start; p = cdr(p), i++); + for (; i < len; p = cdr(p), i++) set_car(p, val); + return(val); + } + i = 0; + for (s7_pointer x = obj, y = obj; ; i++) + { + if ((end > 0) && (i >= end)) + return(val); + if (i >= start) set_car(x, val); + if (!is_pair(cdr(x))) + { + if (!is_null(cdr(x))) + set_cdr(x, val); + return(val); + } + x = cdr(x); + if ((i & 1) != 0) y = cdr(y); + if (x == y) + return(val); + } + return(val); +} + +s7_pointer s7_fill(s7_scheme *sc, s7_pointer args) +{ + #define H_fill "(fill! obj val (start 0) end) fills obj with val" + #define Q_fill s7_make_circular_signature(sc, 3, 4, sc->T, sc->is_sequence_symbol, sc->T, sc->is_integer_symbol) + + s7_pointer p = car(args); + switch (type(p)) + { + case T_STRING: return(g_string_fill_1(sc, sc->fill_symbol, args)); /* redundant type check here and below */ + case T_PAIR: return(pair_fill(sc, args)); + case T_HASH_TABLE: return(hash_table_fill(sc, args)); + case T_NIL: + if (!is_null(cddr(args))) /* (fill! () 1 21 #\a)? */ + syntax_error_nr(sc, "fill! () ... includes indices: ~S?", 34, cddr(args)); + return(cadr(args)); /* this parallels the empty vector case */ + case T_BYTE_VECTOR: case T_INT_VECTOR: case T_FLOAT_VECTOR: case T_VECTOR: + return(g_vector_fill_1(sc, sc->fill_symbol, args)); + case T_LET: + check_method(sc, p, sc->fill_symbol, args); + return(let_fill(sc, args)); + case T_C_OBJECT: + check_method(sc, p, sc->fill_symbol, args); + if (!c_object_fill(sc, p)) /* default is NULL (s7_make_c_type) */ + syntax_error_nr(sc, "attempt to fill ~S?", 19, p); + return((*(c_object_fill(sc, p)))(sc, args)); + default: + check_method(sc, p, sc->fill_symbol, args); + } + wrong_type_error_nr(sc, sc->fill_symbol, 1, p, a_sequence_string); /* (fill! 1 0) */ + return(NULL); +} + +#define g_fill s7_fill + + +/* -------------------------------- append -------------------------------- */ +static s7_int total_sequence_length(s7_scheme *sc, s7_pointer args, s7_pointer caller, uint8_t typ) +{ + s7_pointer p = args; + s7_int len = 0; + + for (s7_int i = 1; is_pair(p); p = cdr(p), i++) + { + s7_pointer seq = car(p); + s7_int n = sequence_length(sc, seq); + if ((n > 0) && + (typ != T_FREE) && + ((type(seq) == T_HASH_TABLE) || /* can't append hash-tables (no obvious meaning to the operation) */ + ((type(seq) == T_LET) && /* similarly for lets, unless this is a mock-string or something similar */ + ((!has_active_methods(sc, seq)) || (find_method(sc, seq, caller) == sc->undefined))))) + { + wrong_type_error_nr(sc, caller, i, seq, sc->type_names[typ]); + return(0); + } + if (n < 0) + { + wrong_type_error_nr(sc, caller, i, seq, (is_pair(seq)) ? a_proper_list_string : a_sequence_string); + return(0); + } + len += n; + } + return(len); +} + +static s7_pointer vector_append(s7_scheme *sc, s7_pointer args, uint8_t typ, s7_pointer caller) +{ + s7_pointer new_vec, p, pargs, vtyper = NULL; + s7_pointer *v_elements = NULL; + s7_double *fv_elements = NULL; + s7_int *iv_elements = NULL; + uint8_t *byte_elements = NULL; + s7_int i, len; + bool typed; + + s7_gc_protect_via_stack(sc, args); + len = total_sequence_length(sc, args, caller, (typ == T_VECTOR) ? T_FREE : ((typ == T_FLOAT_VECTOR) ? T_REAL : T_INTEGER)); + if (len > sc->max_vector_length) + { + unstack(sc); + error_nr(sc, sc->out_of_range_symbol, + set_elist_4(sc, wrap_string(sc, "~S new vector length, ~D, is larger than (*s7* 'max-vector-length): ~D", 70), + caller, + wrap_integer(sc, len), + wrap_integer(sc, sc->max_vector_length))); + } + new_vec = make_vector_1(sc, len, (typ == T_VECTOR) ? FILLED : NOT_FILLED, typ); /* might hit GC in loop below so we can't use NOT_FILLED here (??) */ + typed = (typ == T_VECTOR); + set_stack_protected2(sc, new_vec); + add_vector(sc, new_vec); + if (len == 0) + { + unstack(sc); + return(new_vec); + } + if (typ == T_VECTOR) + v_elements = vector_elements(new_vec); + else + if (typ == T_FLOAT_VECTOR) + fv_elements = float_vector_floats(new_vec); + else + if (typ == T_INT_VECTOR) + iv_elements = int_vector_ints(new_vec); + else byte_elements = byte_vector_bytes(new_vec); + + pargs = list_2(sc, sc->F, new_vec); /* car set below */ + /* push_stack_no_let(sc, OP_GC_PROTECT, new_vec, pargs); */ + set_stack_protected3(sc, pargs); + for (i = 0, p = args; is_pair(p); p = cdr(p)) /* in-place copy by goofing (temporarily) with new_vec's elements pointer */ + { + s7_pointer x = car(p); + s7_int n = sequence_length(sc, x); + if (n > 0) + { + if ((typed) && (is_normal_vector(x)) && (is_typed_vector(x))) + { + if (!vtyper) + vtyper = typed_vector_typer(x); + else + if (vtyper != typed_vector_typer(x)) + typed = false; + } + else typed = false; + vector_length(new_vec) = n; + set_car(pargs, x); + s7_copy_1(sc, caller, pargs); /* not set_plist_2 here! */ + vector_length(new_vec) = 0; /* so GC doesn't march off the end */ + i += n; + if (typ == T_VECTOR) + vector_elements(new_vec) = (s7_pointer *)(v_elements + i); + else + if (typ == T_FLOAT_VECTOR) + float_vector_floats(new_vec) = (s7_double *)(fv_elements + i); + else + if (typ == T_INT_VECTOR) + int_vector_ints(new_vec) = (s7_int *)(iv_elements + i); + else byte_vector_bytes(new_vec) = (uint8_t *)(byte_elements + i); + }} + /* unstack(sc); */ /* free_cell(sc, pargs); */ /* this is trouble if any arg is openlet with append method -- e.g. block */ + + if (typ == T_VECTOR) + vector_elements(new_vec) = v_elements; + else + if (typ == T_FLOAT_VECTOR) + float_vector_floats(new_vec) = fv_elements; + else + if (typ == T_INT_VECTOR) + int_vector_ints(new_vec) = iv_elements; + else byte_vector_bytes(new_vec) = byte_elements; + vector_length(new_vec) = len; + if ((typed) && (vtyper)) + { + set_typed_vector(new_vec); + typed_vector_set_typer(new_vec, vtyper); + } + unstack(sc); + return(new_vec); +} + +static s7_pointer hash_table_append(s7_scheme *sc, s7_pointer args) +{ + s7_pointer new_hash, key_typer = NULL, value_typer = NULL; + bool typed = true; + s7_gc_protect_via_stack(sc, args); + check_stack_size(sc); + new_hash = s7_make_hash_table(sc, sc->default_hash_table_length); + set_stack_protected2(sc, new_hash); + for (s7_pointer p = args; is_pair(p); p = cdr(p)) + { + s7_pointer seq = car(p); + if (!sequence_is_empty(sc, seq)) + { + s7_copy_1(sc, sc->append_symbol, set_plist_2(sc, seq, new_hash)); + if ((typed) && (is_hash_table(seq)) && (is_typed_hash_table(seq))) + { + if (!key_typer) + { /* the equality/mapping procedures are either partly implicit or in hash-table-procedures -- a bit of a mess currently */ + key_typer = hash_table_key_typer(seq); + value_typer = hash_table_value_typer(seq); + } + else + if ((hash_table_key_typer(seq) != key_typer) || + (hash_table_value_typer(seq) != value_typer)) + typed = false; + } + else typed = false; + }} + if ((typed) && (key_typer)) + { + hash_table_set_procedures(new_hash, make_hash_table_procedures(sc)); + set_is_typed_hash_table(new_hash); + hash_table_set_key_typer(new_hash, key_typer); + hash_table_set_value_typer(new_hash, value_typer); + } + if (is_weak_hash_table(car(args))) /* 16-May-23, args gc protected above, should we limit weak-hash result to pure weak-hash args? */ + { + set_weak_hash_table(new_hash); + weak_hash_iters(new_hash) = 0; + } + set_plist_2(sc, sc->nil, sc->nil); + unstack(sc); + return(new_hash); +} + +static s7_pointer let_append(s7_scheme *sc, s7_pointer args) +{ + s7_pointer new_let, e = car(args); + check_method(sc, e, sc->append_symbol, args); + s7_gc_protect_via_stack(sc, args); + new_let = make_let(sc, sc->nil); + set_stack_protected2(sc, new_let); + for (s7_pointer p = args; is_pair(p); p = cdr(p)) + if (!sequence_is_empty(sc, car(p))) + s7_copy_1(sc, sc->append_symbol, set_plist_2(sc, car(p), new_let)); + set_plist_2(sc, sc->nil, sc->nil); + unstack(sc); + return(new_let); +} + +static s7_pointer g_append(s7_scheme *sc, s7_pointer args) +{ + #define H_append "(append ...) returns its argument sequences appended into one sequence" + #define Q_append s7_make_circular_signature(sc, 0, 1, sc->T) + + if (is_null(args)) return(sc->nil); /* (append) -> () */ + if (is_null(cdr(args))) return(car(args)); /* (append ) -> */ + sc->value = args; + args = copy_proper_list(sc, args); /* copied since other args might invoke methods */ + sc->value = args; + switch (type(car(args))) + { + case T_NIL: return(g_list_append(sc, cdr(args))); + case T_PAIR: return(g_list_append(sc, args)); + case T_STRING: return(g_string_append_1(sc, args, sc->append_symbol)); + /* should this work in the generic append: (append "12" #\3) -- currently an error, (append (list 1 2) 3) -> '(1 2 . 3), but vector is error */ + case T_HASH_TABLE: return(hash_table_append(sc, args)); + case T_LET: return(let_append(sc, args)); + case T_VECTOR: case T_INT_VECTOR: case T_FLOAT_VECTOR: case T_BYTE_VECTOR: + return(vector_append(sc, args, type(car(args)), sc->append_symbol)); + default: check_method(sc, car(args), sc->append_symbol, args); + } + wrong_type_error_nr(sc, sc->append_symbol, 1, car(args), a_sequence_string); /* (append 1 0) */ + return(NULL); +} + +static s7_pointer append_p_ppp(s7_scheme *sc, s7_pointer p1, s7_pointer p2, s7_pointer p3) {return(g_append(sc, set_plist_3(sc, p1, p2, p3)));} + +s7_pointer s7_append(s7_scheme *sc, s7_pointer a, s7_pointer b) +{ + if (is_pair(a)) + { + s7_pointer q, p, np, op; + if ((!is_pair(b)) && (!is_null(b))) + return(g_list_append(sc, list_2(sc, a, b))); + q = list_1(sc, car(a)); + sc->temp8 = q; + for (op = a, p = cdr(a), np = q; (is_pair(p)) && (p != op); p = cdr(p), np = cdr(np), op = cdr(op)) + { + set_cdr(np, list_1_unchecked(sc, car(p))); p = cdr(p); np = cdr(np); + if (!is_pair(p)) break; + set_cdr(np, list_1(sc, car(p))); + } + if (!is_null(p)) + wrong_type_error_nr(sc, sc->append_symbol, 1, a, a_proper_list_string); + set_cdr(np, b); + sc->temp8 = sc->unused; + return(q); + } + if (is_null(a)) return(b); + return(g_append(sc, set_plist_2(sc, a, b))); +} + +static s7_pointer g_append_2(s7_scheme *sc, s7_pointer args) {return(s7_append(sc, car(args), cadr(args)));} + +static s7_pointer append_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer unused_expr, bool unused_ops) +{ + if (args == 2) return(sc->append_2); + return(f); +} + + +/* -------------------------------- object->let -------------------------------- */ +static s7_pointer byte_vector_to_list(s7_scheme *sc, const uint8_t *str, s7_int len) +{ + s7_pointer p; + if (len == 0) return(sc->nil); + check_free_heap_size(sc, len); + sc->w = sc->nil; + for (s7_int i = len - 1; i >= 0; i--) + sc->w = cons_unchecked(sc, small_int((uint32_t)(str[i])), sc->w); + p = sc->w; + sc->w = sc->unused; + return(p); +} + +static s7_pointer hash_table_to_list(s7_scheme *sc, s7_pointer obj) +{ + s7_pointer x, iterator; + if (hash_table_entries(obj) <= 0) return(sc->nil); + iterator = s7_make_iterator(sc, obj); + gc_protect_via_stack(sc, iterator); + sc->w = sc->nil; + while (true) + { + x = s7_iterate(sc, iterator); + if (iterator_is_at_end(iterator)) break; + sc->w = cons(sc, x, sc->w); + } + x = sc->w; + sc->w = sc->unused; + unstack(sc); + return(x); +} + +static s7_pointer iterator_to_list(s7_scheme *sc, s7_pointer obj) +{ + s7_pointer result = sc->nil, p = NULL; + s7_int results = 0; + while (true) + { + s7_pointer val = s7_iterate(sc, obj); + if ((val == ITERATOR_END) && + (iterator_is_at_end(obj))) + { + if (is_pair(result)) unstack(sc); + return(result); + } + if (sc->safety > NO_SAFETY) + { + results++; + if (results > 10000) + { + s7_warn(sc, 256, "iterator is creating a very long list!\n"); + results = S7_INT32_MIN; + }} + if (val != sc->no_value) + { + if (is_null(result)) + { + if (is_multiple_value(val)) + { + result = multiple_value(val); + clear_multiple_value(val); + for (p = result; is_pair(cdr(p)); p = cdr(p)); + } + else + { + result = list_1(sc, val); + p = result; + } + gc_protect_via_stack(sc, result); /* unstacked above */ + } + else + if (is_multiple_value(val)) + { + set_cdr(p, multiple_value(val)); + clear_multiple_value(val); + for (; is_pair(cdr(p)); p = cdr(p)); + } + else + { + set_cdr(p, list_1(sc, val)); + p = cdr(p); + }}} +} + +static s7_pointer c_obj_to_list(s7_scheme *sc, s7_pointer obj) /* "c_object_to_list" is the ->list method mentioned below */ +{ + int64_t len; + s7_pointer x, z, zc, result; + s7_int gc_z; + + if (c_object_to_list(sc, obj)) + return((*(c_object_to_list(sc, obj)))(sc, set_plist_1(sc, obj))); + + x = c_object_length(sc, obj); + if (!s7_is_integer(x)) return(sc->F); + len = s7_integer_clamped_if_gmp(sc, x); + if (len < 0) + return(sc->F); + if (len == 0) + return(sc->nil); + + result = make_list(sc, len, sc->nil); + sc->temp7 = result; + zc = make_mutable_integer(sc, 0); + z = list_2_unchecked(sc, obj, zc); + gc_z = gc_protect_1(sc, z); + x = result; + for (int64_t i = 0; i < len; i++, x = cdr(x)) /* used to save/restore sc->x|z here */ + { + integer(zc) = i; + set_car(x, (*(c_object_ref(sc, obj)))(sc, z)); + } + s7_gc_unprotect_at(sc, gc_z); + sc->temp7 = sc->unused; + return(result); +} + +static s7_pointer object_to_list(s7_scheme *sc, s7_pointer obj) /* used only in format_to_port_1 and (map values ...) */ +{ + switch (type(obj)) + { + case T_STRING: return(string_to_list(sc, string_value(obj), string_length(obj))); + case T_BYTE_VECTOR: return(byte_vector_to_list(sc, byte_vector_bytes(obj), byte_vector_length(obj))); + case T_HASH_TABLE: return(hash_table_to_list(sc, obj)); + case T_ITERATOR: return(iterator_to_list(sc, obj)); + case T_C_OBJECT: return(c_obj_to_list(sc, obj)); + case T_INT_VECTOR: case T_FLOAT_VECTOR: case T_VECTOR: + return(s7_vector_to_list(sc, obj)); + case T_LET: +#if (!WITH_PURE_S7) + check_method(sc, obj, sc->let_to_list_symbol, set_plist_1(sc, obj)); +#endif + return(s7_let_to_list(sc, obj)); + } + return(obj); +} + + +/* ---------------- object->let ---------------- */ +static s7_pointer symbol_to_let(s7_scheme *sc, s7_pointer obj) +{ + s7_pointer let = internal_inlet(sc, 4, sc->value_symbol, obj, + sc->type_symbol, (is_keyword(obj)) ? sc->is_keyword_symbol : + ((is_gensym(obj)) ? sc->is_gensym_symbol : sc->is_symbol_symbol)); + if (!is_keyword(obj)) + { + s7_int gc_loc = gc_protect_1(sc, let); + s7_pointer val = s7_symbol_value(sc, obj); + if (!sc->current_value_symbol) + sc->current_value_symbol = make_symbol(sc, "current-value", 13); + s7_varlet(sc, let, sc->current_value_symbol, val); + s7_varlet(sc, let, sc->setter_symbol, setter_p_pp(sc, obj, sc->curlet)); + s7_varlet(sc, let, sc->is_mutable_symbol, s7_make_boolean(sc, !is_immutable_symbol(obj))); + if (!is_undefined(val)) + { + const char *doc = s7_documentation(sc, obj); + if (doc) + s7_varlet(sc, let, sc->local_documentation_symbol, s7_make_string(sc, doc)); + } + s7_gc_unprotect_at(sc, gc_loc); + } + return(let); +} + +static s7_pointer random_state_to_let(s7_scheme *sc, s7_pointer obj) +{ +#if WITH_GMP + return(internal_inlet(sc, 4, sc->value_symbol, obj, sc->type_symbol, sc->is_random_state_symbol)); +#else + if (!sc->seed_symbol) + { + sc->seed_symbol = make_symbol(sc, "seed", 4); + sc->carry_symbol = make_symbol(sc, "carry", 5); + } + return(internal_inlet(sc, 8, sc->value_symbol, obj, + sc->type_symbol, sc->is_random_state_symbol, + sc->seed_symbol, make_integer(sc, random_seed(obj)), + sc->carry_symbol, make_integer(sc, random_carry(obj)))); +#endif +} + +static s7_pointer vector_to_let(s7_scheme *sc, s7_pointer obj) +{ + s7_pointer let; + s7_int gc_loc; + + if (!sc->dimensions_symbol) sc->dimensions_symbol = make_symbol(sc, "dimensions", 10); + if (!sc->original_vector_symbol) sc->original_vector_symbol = make_symbol(sc, "original-vector", 15); + let = internal_inlet(sc, 10, sc->value_symbol, obj, + sc->type_symbol, (is_subvector(obj)) ? cons(sc, sc->is_subvector_symbol, s7_type_of(sc, subvector_vector(obj))) : s7_type_of(sc, obj), + sc->size_symbol, s7_length(sc, obj), + sc->dimensions_symbol, g_vector_dimensions(sc, set_plist_1(sc, obj)), + sc->is_mutable_symbol, s7_make_boolean(sc, !is_immutable_vector(obj))); + gc_loc = gc_protect_1(sc, let); + if (is_subvector(obj)) + { + s7_int pos = 0; + switch (type(obj)) /* correct type matters here: gcc 10.2 with -O2 segfaults otherwise, cast to intptr_t has a similar role in earlier gcc's */ + { + case T_VECTOR: pos = (s7_int)((intptr_t)(vector_elements(obj) - vector_elements(subvector_vector(obj)))); break; + case T_INT_VECTOR: pos = (s7_int)((intptr_t)(int_vector_ints(obj) - int_vector_ints(subvector_vector(obj)))); break; + case T_FLOAT_VECTOR: pos = (s7_int)((intptr_t)(float_vector_floats(obj) - float_vector_floats(subvector_vector(obj)))); break; + case T_BYTE_VECTOR: pos = (s7_int)((intptr_t)(byte_vector_bytes(obj) - byte_vector_bytes(subvector_vector(obj)))); break; + } + s7_varlet(sc, let, sc->position_symbol, make_integer(sc, pos)); + s7_varlet(sc, let, sc->original_vector_symbol, subvector_vector(obj)); + } + if (is_typed_vector(obj)) + s7_varlet(sc, let, sc->signature_symbol, g_signature(sc, set_plist_1(sc, obj))); + s7_gc_unprotect_at(sc, gc_loc); + return(let); +} + +static void hash_table_checker_to_let(s7_scheme *sc, s7_pointer let, s7_pointer obj) +{ + if ((hash_table_checker(obj) == hash_eq) || + (hash_table_checker(obj) == hash_c_function) || + (hash_table_checker(obj) == hash_closure) || + (hash_table_checker(obj) == hash_equal_eq) || + (hash_table_checker(obj) == hash_equal_syntax) || + (hash_table_checker(obj) == hash_symbol)) + s7_varlet(sc, let, sc->function_symbol, sc->is_eq_symbol); + else + if (hash_table_checker(obj) == hash_eqv) + s7_varlet(sc, let, sc->function_symbol, sc->is_eqv_symbol); + else + if ((hash_table_checker(obj) == hash_equal) || + (hash_table_checker(obj) == hash_empty)) + s7_varlet(sc, let, sc->function_symbol, sc->is_equal_symbol); + else + if (hash_table_checker(obj) == hash_equivalent) + s7_varlet(sc, let, sc->function_symbol, sc->is_equivalent_symbol); + else + if ((hash_table_checker(obj) == hash_number_num_eq) || + (hash_table_checker(obj) == hash_int) || + (hash_table_checker(obj) == hash_float)) + s7_varlet(sc, let, sc->function_symbol, sc->num_eq_symbol); + else + if (hash_table_checker(obj) == hash_string) + s7_varlet(sc, let, sc->function_symbol, sc->string_eq_symbol); + else + if (hash_table_checker(obj) == hash_char) + s7_varlet(sc, let, sc->function_symbol, sc->char_eq_symbol); +#if (!WITH_PURE_S7) + else + if (hash_table_checker(obj) == hash_ci_char) + s7_varlet(sc, let, sc->function_symbol, sc->char_ci_eq_symbol); + else + if (hash_table_checker(obj) == hash_ci_string) + s7_varlet(sc, let, sc->function_symbol, sc->string_ci_eq_symbol); +#endif +} + +static s7_pointer hash_table_to_let(s7_scheme *sc, s7_pointer obj) +{ + s7_pointer let; + s7_int gc_loc; + if (!sc->entries_symbol) + { + sc->entries_symbol = make_symbol(sc, "entries", 7); + sc->weak_symbol = make_symbol(sc, "weak", 4); + } + let = internal_inlet(sc, 10, sc->value_symbol, obj, + sc->type_symbol, sc->is_hash_table_symbol, + sc->size_symbol, s7_length(sc, obj), + sc->entries_symbol, make_integer(sc, hash_table_entries(obj)), + sc->is_mutable_symbol, s7_make_boolean(sc, !is_immutable_hash_table(obj))); + gc_loc = gc_protect_1(sc, let); + if (is_weak_hash_table(obj)) + s7_varlet(sc, let, sc->weak_symbol, sc->T); + + if (is_typed_hash_table(obj)) + { + s7_pointer checker = hash_table_procedures_checker(obj); + if (checker == sc->T) /* perhaps typed because typers were set, but not checker/mapper */ + hash_table_checker_to_let(sc, let, obj); + else s7_varlet(sc, let, sc->function_symbol, list_2(sc, checker, hash_table_procedures_mapper(obj))); + s7_varlet(sc, let, sc->signature_symbol, + (is_typed_hash_table(obj)) ? + list_3(sc, + hash_table_typer_symbol(sc, hash_table_value_typer(obj)), + sc->is_hash_table_symbol, + hash_table_typer_symbol(sc, hash_table_key_typer(obj))) : + sc->hash_table_signature); + } + else hash_table_checker_to_let(sc, let, obj); + s7_gc_unprotect_at(sc, gc_loc); + return(let); +} + +static s7_pointer iterator_to_let(s7_scheme *sc, s7_pointer obj) +{ + s7_pointer let, seq = iterator_sequence(obj); + s7_int gc_loc; + if (!sc->at_end_symbol) + { + sc->at_end_symbol = make_symbol(sc, "at-end", 6); + sc->sequence_symbol = make_symbol(sc, "sequence", 8); + } + let = internal_inlet(sc, 8, sc->value_symbol, obj, + sc->type_symbol, sc->is_iterator_symbol, + sc->at_end_symbol, s7_make_boolean(sc, iterator_is_at_end(obj)), + sc->sequence_symbol, iterator_sequence(obj)); + gc_loc = gc_protect_1(sc, let); + if (is_pair(seq)) + s7_varlet(sc, let, sc->size_symbol, s7_length(sc, seq)); + else + if (is_hash_table(seq)) + s7_varlet(sc, let, sc->size_symbol, make_integer(sc, hash_table_entries(seq))); + else s7_varlet(sc, let, sc->size_symbol, s7_length(sc, obj)); + if ((is_string(seq)) || + (is_any_vector(seq)) || + (seq == sc->rootlet) || + (is_c_object(seq)) || + (is_hash_table(seq))) + s7_varlet(sc, let, sc->position_symbol, make_integer(sc, iterator_position(obj))); + else + if (is_pair(seq)) + s7_varlet(sc, let, sc->position_symbol, iterator_current(obj)); + s7_gc_unprotect_at(sc, gc_loc); + return(let); +} + +static s7_pointer let_to_let(s7_scheme *sc, s7_pointer obj) +{ + /* how to handle setters? + * (display (let ((e (let ((i 0)) (set! (setter 'i) integer?) (curlet)))) (object->let e))): + * "(inlet 'value (inlet 'i 0) 'type let? 'length 1 'open #f 'outlet () 'immutable? #f)" + */ + s7_pointer let; + s7_int gc_loc; + if (!sc->open_symbol) + { + sc->open_symbol = make_symbol(sc, "open", 4); + sc->alias_symbol = make_symbol(sc, "alias", 5); + } + let = internal_inlet(sc, 12, sc->value_symbol, obj, + sc->type_symbol, sc->is_let_symbol, + sc->size_symbol, s7_length(sc, obj), + sc->open_symbol, s7_make_boolean(sc, is_openlet(obj)), + sc->outlet_symbol, (obj == sc->rootlet) ? sc->nil : let_outlet(obj), + sc->is_mutable_symbol, s7_make_boolean(sc, !is_immutable_let(obj))); + gc_loc = gc_protect_1(sc, let); + if (obj == sc->rootlet) + s7_varlet(sc, let, sc->alias_symbol, sc->rootlet_symbol); + else + if (obj == sc->owlet) /* this can't happen, I think -- owlet is always copied first */ + s7_varlet(sc, let, sc->alias_symbol, sc->owlet_symbol); + else + if (is_funclet(obj)) + { + s7_varlet(sc, let, sc->function_symbol, funclet_function(obj)); + if ((has_let_file(obj)) && + (let_file(obj) <= (s7_int)sc->file_names_top) && + (let_line(obj) > 0) && + (let_line(obj) < 1000000)) + { + s7_varlet(sc, let, sc->file_symbol, sc->file_names[let_file(obj)]); + s7_varlet(sc, let, sc->line_symbol, make_integer(sc, let_line(obj))); + }} + else + if (obj == sc->s7_starlet) + { + s7_pointer iter = s7_make_iterator(sc, obj); + s7_int gc_loc1 = s7_gc_protect(sc, iter); + while (true) + { + s7_pointer x = s7_iterate(sc, iter); + if (iterator_is_at_end(iter)) break; + s7_varlet(sc, let, car(x), cdr(x)); + } + s7_gc_unprotect_at(sc, gc_loc1); + } + if (has_active_methods(sc, obj)) + { + s7_pointer func = find_method(sc, obj, sc->object_to_let_symbol); + if (func != sc->undefined) + s7_apply_function(sc, func, set_plist_2(sc, obj, let)); + } + s7_gc_unprotect_at(sc, gc_loc); + return(let); +} + +static s7_pointer c_object_to_let(s7_scheme *sc, s7_pointer obj) +{ + s7_pointer let, clet = c_object_let(obj); + if (!sc->class_symbol) + { + sc->class_symbol = make_symbol(sc, "class", 5); + sc->c_object_let_symbol = make_symbol(sc, "c-object-let", 12); + } + let = internal_inlet(sc, 10, sc->value_symbol, obj, + sc->type_symbol, sc->is_c_object_symbol, + sc->c_object_type_symbol, make_integer(sc, c_object_type(obj)), + sc->c_object_let_symbol, clet, + sc->class_symbol, c_object_type_to_let(sc, obj)); + if ((is_let(clet)) && + ((has_active_methods(sc, clet)) || (has_active_methods(sc, obj)))) + { + s7_int gc_loc = gc_protect_1(sc, let); + s7_pointer func = find_method(sc, clet, sc->object_to_let_symbol); + if (func != sc->undefined) + s7_apply_function(sc, func, set_plist_2(sc, obj, let)); + s7_gc_unprotect_at(sc, gc_loc); + } + return(let); +} + +static s7_pointer port_to_let(s7_scheme *sc, s7_pointer obj) /* note the underbars! */ +{ + s7_pointer let; + s7_int gc_loc; + if (!sc->data_symbol) + { + sc->data_symbol = make_symbol(sc, "data", 4); + sc->port_type_symbol = make_symbol(sc, "port-type", 9); + sc->closed_symbol = make_symbol(sc, "closed", 6); + sc->file_info_symbol = make_symbol(sc, "file-info", 9); + } + let = internal_inlet(sc, 10, sc->value_symbol, obj, + /* obj as 'value means it will say "(closed)" when subsequently the let is displayed */ + sc->type_symbol, (is_input_port(obj)) ? sc->is_input_port_symbol : sc->is_output_port_symbol, + sc->port_type_symbol, (is_string_port(obj)) ? sc->string_symbol : ((is_file_port(obj)) ? sc->file_symbol : sc->function_symbol), + sc->closed_symbol, s7_make_boolean(sc, port_is_closed(obj)), + sc->is_mutable_symbol, s7_make_boolean(sc, !is_immutable_port(obj))); + gc_loc = gc_protect_1(sc, let); + if (is_file_port(obj)) + { + s7_varlet(sc, let, sc->file_symbol, g_port_filename(sc, set_plist_1(sc, obj))); + if (is_input_port(obj)) + s7_varlet(sc, let, sc->line_symbol, g_port_line_number(sc, set_plist_1(sc, obj))); +#if (!MS_WINDOWS) + if ((!port_is_closed(obj)) && (obj != sc->standard_error) && (obj != sc->standard_input) && (obj != sc->standard_output)) + { + struct stat sb; + s7_varlet(sc, let, sc->file_symbol, make_integer(sc, fileno(port_file(obj)))); + if (fstat(fileno(port_file(obj)), &sb) != -1) + { + char c1[64], c2[64], str[512]; + int32_t bytes; + strftime(c1, 64, "%a %d-%b-%Y %H:%M", localtime(&sb.st_atime)); + strftime(c2, 64, "%a %d-%b-%Y %H:%M", localtime(&sb.st_mtime)); + bytes = snprintf(str, 512, "mode: #o%u, links: %ld, owner uid: %d gid: %d, size: %ld bytes, last file access: %s, last file modification: %s", + sb.st_mode, + (long)sb.st_nlink, + (int)sb.st_uid, (int)sb.st_gid, + (long)sb.st_size, + c1, c2); + s7_varlet(sc, let, sc->file_info_symbol, make_string_with_length(sc, (const char *)str, bytes)); + }} +#endif + } + if ((is_string_port(obj)) && /* file port might not have a data buffer */ + (port_data(obj)) && + (port_data_size(obj) > 0)) + { + s7_varlet(sc, let, sc->size_symbol, make_integer(sc, port_data_size(obj))); + s7_varlet(sc, let, sc->position_symbol, make_integer(sc, port_position(obj))); + /* I think port_data need not be null-terminated, but s7_make_string assumes it is: + * both valgrind and lib*san complain about the uninitialized data during strlen. + */ + s7_varlet(sc, let, sc->data_symbol, + make_string_with_length(sc, (const char *)port_data(obj), ((port_position(obj)) > 16) ? 16 : port_position(obj))); + } + if (is_function_port(obj)) + s7_varlet(sc, let, sc->function_symbol, port_string_or_function(obj)); + s7_gc_unprotect_at(sc, gc_loc); + return(let); +} + +static s7_pointer closure_to_let(s7_scheme *sc, s7_pointer obj) +{ + const char *doc = s7_documentation(sc, obj); + s7_pointer sig = s7_signature(sc, obj); + s7_pointer let = internal_inlet(sc, 8, sc->value_symbol, obj, + sc->type_symbol, (is_t_procedure(obj)) ? sc->is_procedure_symbol : sc->is_macro_symbol, + sc->arity_symbol, s7_arity(sc, obj), + sc->is_mutable_symbol, s7_make_boolean(sc, !is_immutable(obj))); + s7_int gc_loc = gc_protect_1(sc, let); + + if (is_pair(sig)) + s7_varlet(sc, let, sc->local_signature_symbol, sig); + if (doc) + s7_varlet(sc, let, sc->local_documentation_symbol, s7_make_string(sc, doc)); + + if (is_let(closure_let(obj))) + { + s7_pointer flet = closure_let(obj); + if ((has_let_file(flet)) && + (let_file(flet) <= (s7_int)sc->file_names_top) && + (let_line(flet) > 0)) + { + s7_varlet(sc, let, sc->file_symbol, sc->file_names[let_file(flet)]); + s7_varlet(sc, let, sc->line_symbol, make_integer(sc, let_line(flet))); + }} + + if (closure_setter(obj) != sc->F) + s7_varlet(sc, let, sc->local_setter_symbol, closure_setter(obj)); + + if (!sc->source_symbol) + sc->source_symbol = make_symbol(sc, "source", 6); + s7_varlet(sc, let, sc->source_symbol, + append_in_place(sc, list_2(sc, procedure_type_to_symbol(sc, type(obj)), closure_args(obj)), + closure_body(obj))); + s7_gc_unprotect_at(sc, gc_loc); + return(let); +} + +static s7_pointer c_pointer_to_let(s7_scheme *sc, s7_pointer obj) +{ + /* c_pointer_info can be a let and might have an object->let method (see c_object below) */ + if (!sc->c_type_symbol) + { + sc->c_type_symbol = make_symbol(sc, "c-type", 6); + sc->info_symbol = make_symbol(sc, "info", 4); + } + if (!sc->pointer_symbol) sc->pointer_symbol = make_symbol(sc, "pointer", 7); + return(internal_inlet(sc, 10, sc->value_symbol, obj, + sc->type_symbol, sc->is_c_pointer_symbol, + sc->pointer_symbol, make_integer(sc, (s7_int)((intptr_t)c_pointer(obj))), + sc->c_type_symbol, c_pointer_type(obj), + sc->info_symbol, c_pointer_info(obj))); +} + +static s7_pointer c_function_to_let(s7_scheme *sc, s7_pointer obj) +{ + const char *doc = s7_documentation(sc, obj); + s7_pointer sig = c_function_signature(obj); + s7_pointer let = internal_inlet(sc, 8, sc->value_symbol, obj, + sc->type_symbol, (is_t_procedure(obj)) ? sc->is_procedure_symbol : sc->is_macro_symbol, + sc->arity_symbol, s7_arity(sc, obj), + sc->is_mutable_symbol, s7_make_boolean(sc, !is_immutable(obj))); + s7_int gc_loc = gc_protect_1(sc, let); + + if (is_pair(sig)) + s7_varlet(sc, let, sc->local_signature_symbol, sig); + if (doc) + s7_varlet(sc, let, sc->local_documentation_symbol, s7_make_string(sc, doc)); + + if (c_function_setter(obj) != sc->F) /* c_macro_setter is the same underlying field */ + s7_varlet(sc, let, sc->local_setter_symbol, c_function_setter(obj)); + s7_gc_unprotect_at(sc, gc_loc); + return(let); +} + +static s7_pointer goto_to_let(s7_scheme *sc, s7_pointer obj) +{ + /* there's room in s7_cell to store the procedure, but we would have to mark it (goto escapes, context GC'd) */ + if (!sc->active_symbol) + sc->active_symbol = make_symbol(sc, "active", 6); + if (is_symbol(call_exit_name(obj))) + return(internal_inlet(sc, 8, sc->value_symbol, obj, sc->type_symbol, sc->is_goto_symbol, + sc->active_symbol, s7_make_boolean(sc, call_exit_active(obj)), + sc->name_symbol, call_exit_name(obj))); + return(internal_inlet(sc, 6, sc->value_symbol, obj, sc->type_symbol, sc->is_goto_symbol, + sc->active_symbol, s7_make_boolean(sc, call_exit_active(obj)))); +} + +static s7_pointer object_to_let_p_p(s7_scheme *sc, s7_pointer obj) +{ + switch (type(obj)) + { + case T_NIL: return(internal_inlet(sc, 4, sc->value_symbol, obj, sc->type_symbol, sc->is_null_symbol)); + case T_UNSPECIFIED: return(internal_inlet(sc, 4, sc->value_symbol, obj, sc->type_symbol, sc->is_unspecified_symbol)); + case T_UNDEFINED: return(internal_inlet(sc, 4, sc->value_symbol, obj, sc->type_symbol, sc->is_undefined_symbol)); + case T_EOF: return(internal_inlet(sc, 4, sc->value_symbol, obj, sc->type_symbol, sc->is_eof_object_symbol)); + case T_BOOLEAN: return(internal_inlet(sc, 4, sc->value_symbol, obj, sc->type_symbol, sc->is_boolean_symbol)); + case T_CHARACTER: return(internal_inlet(sc, 4, sc->value_symbol, obj, sc->type_symbol, sc->is_char_symbol)); + case T_SYMBOL: return(symbol_to_let(sc, obj)); + case T_RANDOM_STATE: return(random_state_to_let(sc, obj)); + case T_GOTO: return(goto_to_let(sc, obj)); + case T_C_POINTER: return(c_pointer_to_let(sc, obj)); + case T_ITERATOR: return(iterator_to_let(sc, obj)); + case T_HASH_TABLE: return(hash_table_to_let(sc, obj)); + case T_LET: return(let_to_let(sc, obj)); + case T_C_OBJECT: return(c_object_to_let(sc, obj)); + + case T_INTEGER: case T_BIG_INTEGER: return(internal_inlet(sc, 4, sc->value_symbol, obj, sc->type_symbol, sc->is_integer_symbol)); + case T_RATIO: case T_BIG_RATIO: return(internal_inlet(sc, 4, sc->value_symbol, obj, sc->type_symbol, sc->is_rational_symbol)); + case T_REAL: case T_BIG_REAL: return(internal_inlet(sc, 4, sc->value_symbol, obj, sc->type_symbol, sc->is_real_symbol)); + case T_COMPLEX: case T_BIG_COMPLEX: return(internal_inlet(sc, 4, sc->value_symbol, obj, sc->type_symbol, sc->is_complex_symbol)); + + case T_STRING: + return(internal_inlet(sc, 8, sc->value_symbol, obj, + sc->type_symbol, sc->is_string_symbol, + sc->size_symbol, str_length(sc, obj), + sc->is_mutable_symbol, s7_make_boolean(sc, !is_immutable_string(obj)))); + case T_PAIR: + return(internal_inlet(sc, 6, sc->value_symbol, obj, + sc->type_symbol, sc->is_pair_symbol, + sc->size_symbol, pair_length(sc, obj))); + case T_SYNTAX: + return(internal_inlet(sc, 6, sc->value_symbol, obj, + sc->type_symbol, sc->is_syntax_symbol, + sc->documentation_symbol, s7_make_string(sc, syntax_documentation(obj)))); + + case T_INT_VECTOR: case T_FLOAT_VECTOR: case T_BYTE_VECTOR: case T_VECTOR: + return(vector_to_let(sc, obj)); + + case T_CONTINUATION: /* perhaps include the continuation-key */ + if (is_symbol(continuation_name(obj))) + return(internal_inlet(sc, 6, sc->value_symbol, obj, sc->type_symbol, sc->is_continuation_symbol, sc->name_symbol, continuation_name(obj))); + return(internal_inlet(sc, 4, sc->value_symbol, obj, sc->type_symbol, sc->is_continuation_symbol)); + + case T_INPUT_PORT: case T_OUTPUT_PORT: + return(port_to_let(sc, obj)); + + case T_CLOSURE: case T_CLOSURE_STAR: case T_MACRO: case T_MACRO_STAR: case T_BACRO: case T_BACRO_STAR: + return(closure_to_let(sc, obj)); + + case T_C_MACRO: case T_C_FUNCTION_STAR: + case T_C_FUNCTION: case T_C_RST_NO_REQ_FUNCTION: + return(c_function_to_let(sc, obj)); + + default: + return(sc->F); + } + return(sc->F); +} + +static s7_pointer g_object_to_let(s7_scheme *sc, s7_pointer args) +{ + #define H_object_to_let "(object->let obj) returns a let (namespace) describing obj." + #define Q_object_to_let s7_make_signature(sc, 2, sc->is_let_symbol, sc->T) + return(object_to_let_p_p(sc, car(args))); +} + + +/* ---------------- stacktrace ---------------- */ +static s7_pointer stacktrace_find_caller(s7_scheme *sc, s7_pointer e) +{ + if ((is_let(e)) && (e != sc->rootlet)) + return(((is_funclet(e)) || (is_maclet(e))) ? funclet_function(e) : stacktrace_find_caller(sc, let_outlet(e))); + return(sc->F); +} + +static bool stacktrace_find_let(s7_scheme *sc, int64_t loc, s7_pointer e) +{ + return((loc > 0) && + ((stack_let(sc->stack, loc) == e) || + (stacktrace_find_let(sc, loc - 4, e)))); +} + +static int64_t stacktrace_find_error_hook_quit(s7_scheme *sc) +{ + for (int64_t i = stack_top(sc) - 1; i >= 3; i -= 4) + if (stack_op(sc->stack, i) == OP_ERROR_HOOK_QUIT) + return(i); + return(-1); +} + +static bool stacktrace_in_error_handler(s7_scheme *sc, int64_t loc) +{ + return((let_outlet(sc->owlet) == sc->curlet) || + (stacktrace_find_let(sc, loc * 4, let_outlet(sc->owlet))) || + (stacktrace_find_error_hook_quit(sc) > 0)); +} + +static bool stacktrace_error_hook_function(s7_scheme *sc, s7_pointer sym) +{ + if (is_symbol(sym)) + { + s7_pointer f = s7_symbol_value(sc, sym); + return((is_procedure(f)) && + (hook_has_functions(sc->error_hook)) && + (direct_memq(f, s7_hook_functions(sc, sc->error_hook)))); + } + return(false); +} + +static char *stacktrace_walker(s7_scheme *sc, s7_pointer code, s7_pointer e, char *notes, + s7_int code_cols, s7_int total_cols, s7_int notes_start_col, + bool as_comment, int32_t depth) +{ + if (is_symbol(code)) + { + if ((!symbol_is_in_list(sc, code)) && + (!is_slot(global_slot(code)))) + { + s7_pointer val; + add_symbol_to_list(sc, code); + val = s7_symbol_local_value(sc, code, e); + if ((val) && + (val != sc->undefined) && + (!is_any_macro(val))) + { + int32_t typ = type(val); + if (typ < T_CONTINUATION) + { + char *objstr, *str; + s7_pointer objp; + s7_int new_note_len, notes_max; + bool new_notes_line = false, old_short_print = sc->short_print; + s7_int old_len = sc->print_length, objlen; + + if (notes_start_col < 0) notes_start_col = 50; + if (notes_start_col > total_cols) notes_start_col = 0; + notes_max = total_cols - notes_start_col; + sc->short_print = true; + if (sc->print_length > 4) sc->print_length = 4; + objp = s7_object_to_string(sc, val, true); + objstr = string_value(objp); + objlen = string_length(objp); + if ((objlen > notes_max) && + (notes_max > 5)) + { + objstr[notes_max - 4] = '.'; + objstr[notes_max - 3] = '.'; + objstr[notes_max - 2] = '.'; + objstr[notes_max - 1] = '\0'; + objlen = notes_max; + } + sc->short_print = old_short_print; + sc->print_length = old_len; + + new_note_len = symbol_name_length(code) + 3 + objlen; + /* we want to append this much info to the notes, but does it need a new line? */ + if (notes_start_col < code_cols) + new_notes_line = true; + else + if (notes) + { + char *last_newline = strrchr(notes, (int)'\n'); /* returns ptr to end if none = nil if not found? */ + s7_int cur_line_len = (last_newline) ? (strlen(notes) - strlen(last_newline)) : strlen(notes); + new_notes_line = ((cur_line_len + new_note_len) > notes_max); + } + if (new_notes_line) + { + const char *spaces = " "; + s7_int spaces_len = 80; + new_note_len += (4 + notes_start_col + ((notes) ? strlen(notes) : 0)); + str = (char *)Malloc(new_note_len); /* str[0] = '\0'; */ + catstrs_direct(str, + (notes) ? notes : "", + "\n", + (as_comment) ? "; " : "", + (spaces_len >= notes_start_col) ? (const char *)(spaces + spaces_len - notes_start_col) : "", + (as_comment) ? "" : " ; ", + symbol_name(code), + ": ", + objstr, (const char *)NULL); + } + else + { + new_note_len += ((notes) ? strlen(notes) : 0) + 4; + str = (char *)Malloc(new_note_len); /* str[0] = '\0'; */ + catstrs_direct(str, + (notes) ? notes : "", + (notes) ? ", " : " ; ", + symbol_name(code), + ": ", + objstr, (const char *)NULL); + } + if (notes) free(notes); + return(str); + }}} + return(notes); + } + if ((is_pair(code)) && + (s7_list_length(sc, code) > 0) && + (depth < 32)) + { + notes = stacktrace_walker(sc, car(code), e, notes, code_cols, total_cols, notes_start_col, as_comment, depth + 1); + return(stacktrace_walker(sc, cdr(code), e, notes, code_cols, total_cols, notes_start_col, as_comment, depth + 2)); + } + return(notes); +} + +static block_t *stacktrace_add_func(s7_scheme *sc, s7_pointer f, s7_pointer code, const char *errstr, char *notes, s7_int code_max, bool as_comment) +{ + s7_int newlen, errlen = strlen(errstr); + char *newstr, *str; + block_t *newp, *b; + if ((is_symbol(f)) && + (f != car(code))) + { + newlen = symbol_name_length(f) + errlen + 10; + newp = mallocate(sc, newlen); + newstr = (char *)block_data(newp); /* newstr[0] = '\0'; */ + errlen = catstrs_direct(newstr, symbol_name(f), ": ", errstr, (const char *)NULL); + } + else + { + newlen = errlen + 8; + newp = mallocate(sc, newlen); + newstr = (char *)block_data(newp); /* newstr[0] = '\0'; */ + if ((errlen > 2) && (errstr[2] == '(')) + errlen = catstrs_direct(newstr, " ", errstr, (const char *)NULL); + else + { + memcpy((void *)newstr, (const void *)errstr, errlen); + newstr[errlen] = '\0'; + }} + newlen = code_max + 8 + ((notes) ? strlen(notes) : 0); + b = mallocate(sc, newlen); + str = (char *)block_data(b); /* str[0] = '\0'; */ + + if (errlen >= code_max) + { + newstr[code_max - 4] = '.'; + newstr[code_max - 3] = '.'; + newstr[code_max - 2] = '.'; + newstr[code_max - 1] = '\0'; + catstrs_direct(str, (as_comment) ? "; " : "", newstr, (notes) ? notes : "", "\n", (const char *)NULL); + } + else + { + /* send out newstr, pad with spaces to code_max, then notes */ + s7_int len = catstrs_direct(str, (as_comment) ? "; " : "", newstr, (const char *)NULL); + if (notes) + { + s7_int i; + for (i = len; i < code_max - 1; i++) str[i] = ' '; + str[i] = '\0'; + catstrs(str, newlen, notes, "\n", (char *)NULL); + } + else catstrs(str, newlen, "\n", (char *)NULL); + } + liberate(sc, newp); + return(b); +} + +static s7_pointer stacktrace_1(s7_scheme *sc, s7_int frames_max, s7_int code_cols, s7_int total_cols, s7_int notes_start_col, bool as_comment) +{ + char *str = NULL; + block_t *strp = NULL; + int64_t loc, frames = 0; + int64_t top = (sc->stack_end - sc->stack_start) / 4; /* (*s7* 'stack_top), not stack_top(sc)! */ + clear_symbol_list(sc); + + if (stacktrace_in_error_handler(sc, top)) + { + s7_pointer err_code = slot_value(sc->error_code); + if ((is_pair(err_code)) && + (!tree_is_cyclic(sc, err_code))) + { + char *notes = NULL; + s7_pointer current_let = let_outlet(sc->owlet); + s7_pointer errstr = s7_object_to_string(sc, err_code, false); + s7_pointer f = stacktrace_find_caller(sc, current_let); /* this is a symbol */ + if ((is_let(current_let)) && + (current_let != sc->rootlet)) + notes = stacktrace_walker(sc, err_code, current_let, NULL, code_cols, total_cols, notes_start_col, as_comment, 0); + strp = stacktrace_add_func(sc, f, err_code, string_value(errstr), notes, code_cols, as_comment); + str = (char *)block_data(strp); + } + loc = stacktrace_find_error_hook_quit(sc); /* if OP_ERROR_HOOK_QUIT is in the stack, jump past it! */ + if (loc > 0) top = (loc + 1) / 4; + } + for (loc = top - 1; loc > 0; loc--) + { + s7_int true_loc = (loc + 1) * 4 - 1; + s7_pointer code = stack_code(sc->stack, true_loc); + if ((is_pair(code)) && + (!tree_is_cyclic(sc, code))) + { + s7_pointer codep = s7_object_to_string(sc, code, false); + if (string_length(codep) > 0) + { + char *codestr = string_value(codep); + if ((!local_strcmp(codestr, "(result)")) && + (!local_strcmp(codestr, "(#f)")) && + (!strstr(codestr, "(stacktrace)")) && + (!strstr(codestr, "(stacktrace "))) + { + s7_pointer e = stack_let(sc->stack, true_loc); + s7_pointer f = stacktrace_find_caller(sc, e); + if (!stacktrace_error_hook_function(sc, f)) + { + char *notes = NULL, *newstr, *catstr; + block_t *newp, *catp; + s7_int newlen; + + frames++; + if (frames > frames_max) + return(block_to_string(sc, strp, safe_strlen((char *)block_data(strp)))); + + if ((is_let(e)) && (e != sc->rootlet)) + notes = stacktrace_walker(sc, code, e, NULL, code_cols, total_cols, notes_start_col, as_comment, 0); + newp = stacktrace_add_func(sc, f, code, codestr, notes, code_cols, as_comment); + newstr = (char *)block_data(newp); + + if ((notes) && (notes != newstr) && (is_let(e)) && (e != sc->rootlet)) + free(notes); + + newlen = strlen(newstr) + 1 + ((str) ? strlen(str) : 0); + catp = mallocate(sc, newlen); + catstr = (char *)block_data(catp); + catstrs_direct(catstr, (str) ? str : "", newstr, (const char *)NULL); + liberate(sc, newp); + if (strp) liberate(sc, strp); + strp = catp; + str = (char *)block_data(strp); + }}}}} + return((strp) ? block_to_string(sc, strp, safe_strlen((char *)block_data(strp))) : nil_string); +} + +s7_pointer s7_stacktrace(s7_scheme *sc) +{ + return(stacktrace_1(sc, + s7_integer_clamped_if_gmp(sc, car(sc->stacktrace_defaults)), + s7_integer_clamped_if_gmp(sc, cadr(sc->stacktrace_defaults)), + s7_integer_clamped_if_gmp(sc, caddr(sc->stacktrace_defaults)), + s7_integer_clamped_if_gmp(sc, cadddr(sc->stacktrace_defaults)), + s7_boolean(sc, s7_list_ref(sc, sc->stacktrace_defaults, 4)))); +} + +static s7_pointer g_stacktrace(s7_scheme *sc, s7_pointer args) +{ + #define H_stacktrace "(stacktrace (max-frames 30) (code-cols 50) (total-cols 80) (note-col 50) as-comment) returns \ +a stacktrace as a string. Each line has two portions, the code being evaluated and a note giving \ +the value of local variables in that code. The first argument sets how many lines are displayed. \ +The next three arguments set the length and layout of those lines. 'as-comment' if #t causes each \ +line to be preceded by a semicolon." + #define Q_stacktrace s7_make_signature(sc, 6, \ + sc->is_string_symbol, sc->is_integer_symbol, sc->is_integer_symbol, \ + sc->is_integer_symbol, sc->is_integer_symbol, sc->is_boolean_symbol) + + s7_int max_frames = 30, code_cols = 50, total_cols = 80, notes_start_col = 50; + bool as_comment = false; + + if (!is_null(args)) + { + if (!s7_is_integer(car(args))) + return(method_or_bust(sc, car(args), sc->stacktrace_symbol, args, sc->type_names[T_INTEGER], 1)); + max_frames = s7_integer_clamped_if_gmp(sc, car(args)); + if ((max_frames <= 0) || (max_frames > S7_INT32_MAX)) + max_frames = 30; + args = cdr(args); + if (!is_null(args)) + { + if (!s7_is_integer(car(args))) + wrong_type_error_nr(sc, sc->stacktrace_symbol, 2, car(args), sc->type_names[T_INTEGER]); + code_cols = s7_integer_clamped_if_gmp(sc, car(args)); + if ((code_cols <= 8) || (code_cols > 1024)) + code_cols = 50; + args = cdr(args); + if (!is_null(args)) + { + if (!s7_is_integer(car(args))) + wrong_type_error_nr(sc, sc->stacktrace_symbol, 3, car(args), sc->type_names[T_INTEGER]); + total_cols = s7_integer_clamped_if_gmp(sc, car(args)); + if ((total_cols <= code_cols) || (total_cols > S7_INT32_MAX)) + total_cols = 80; + args = cdr(args); + if (!is_null(args)) + { + if (!s7_is_integer(car(args))) + wrong_type_error_nr(sc, sc->stacktrace_symbol, 4, car(args), sc->type_names[T_INTEGER]); + notes_start_col = s7_integer_clamped_if_gmp(sc, car(args)); + if ((notes_start_col <= 0) || (notes_start_col > S7_INT32_MAX)) + notes_start_col = 50; + args = cdr(args); + if (!is_null(args)) + { + if (!is_boolean(car(args))) + wrong_type_error_nr(sc, sc->stacktrace_symbol, 5, car(args), sc->type_names[T_BOOLEAN]); + as_comment = s7_boolean(sc, car(args)); + }}}}} + return(stacktrace_1(sc, max_frames, code_cols, total_cols, notes_start_col, as_comment)); +} + + +/* -------- s7_history, s7_add_to_history, s7_history_enabled -------- */ + +s7_pointer s7_add_to_history(s7_scheme *sc, s7_pointer entry) +{ +#if WITH_HISTORY + set_current_code(sc, entry); +#endif + return(entry); +} + +s7_pointer s7_history(s7_scheme *sc) +{ +#if WITH_HISTORY + if (sc->cur_code == sc->history_sink) + return(sc->old_cur_code); +#endif + return(sc->cur_code); +} + +bool s7_history_enabled(s7_scheme *sc) +{ +#if WITH_HISTORY + return(sc->cur_code != sc->history_sink); +#else + return(false); +#endif +} + +bool s7_set_history_enabled(s7_scheme *sc, bool enabled) +{ +#if WITH_HISTORY + bool old_enabled = (sc->cur_code == sc->history_sink); + if (enabled) /* this needs to restore the old cur_code (saving its position in the history_buffer) */ + sc->cur_code = sc->old_cur_code; + else + if (sc->cur_code != sc->history_sink) + { + sc->old_cur_code = sc->cur_code; + sc->cur_code = sc->history_sink; + } + return(old_enabled); +#else + return(false); +#endif +} + +#if WITH_HISTORY +static s7_pointer history_cons(s7_scheme *sc, s7_pointer code, s7_pointer args) +{ + s7_pointer p = car(sc->history_pairs); + sc->history_pairs = cdr(sc->history_pairs); + set_car(p, code); + set_cdr(p, args); + return(p); +} +#else +#define history_cons(Sc, Code, Args) Code +#endif + + +/* -------------------------------- profile -------------------------------- */ +static void swap_stack(s7_scheme *sc, opcode_t new_op, s7_pointer new_code, s7_pointer new_args) +{ + s7_pointer code, args, e; + opcode_t op; + + sc->stack_end -= 4; + code = stack_end_code(sc); + e = stack_end_let(sc); + args = stack_end_args(sc); + op = (opcode_t)T_Op(stack_end_op(sc)); /* this should be begin1 */ + if ((S7_DEBUGGING) && (op != OP_BEGIN_NO_HOOK) && (op != OP_BEGIN_HOOK)) + fprintf(stderr, "swap %s in %s\n", op_names[op], display(s7_name_to_value(sc, "estr"))); + push_stack(sc, new_op, new_args, new_code); + stack_end_code(sc) = code; + stack_end_let(sc) = e; + stack_end_args(sc) = args; + stack_end_op(sc) = (s7_pointer)op; + sc->stack_end += 4; +} + +static s7_pointer find_funclet(s7_scheme *sc, s7_pointer e) +{ + if ((e == sc->rootlet) || (!is_let(e))) return(sc->F); + if (!((is_funclet(e)) || (is_maclet(e)))) e = let_outlet(e); + if ((e == sc->rootlet) || (!is_let(e))) return(sc->F); + return(((is_funclet(e)) || (is_maclet(e))) ? e : sc->F); +} + +#define PD_INITIAL_SIZE 16 +enum {PD_CALLS = 0, PD_RECUR, PD_START, PD_ITOTAL, PD_ETOTAL, PD_BLOCK_SIZE}; + +static s7_pointer g_profile_out(s7_scheme *sc, s7_pointer args) +{ + s7_int pos = integer(car(args)) * PD_BLOCK_SIZE; + profile_data_t *pd = sc->profile_data; + s7_int *v = (s7_int *)(pd->timing_data + pos); + v[PD_RECUR]--; + if (v[PD_RECUR] == 0) + { + s7_int cur_time = (my_clock() - v[PD_START]); + v[PD_ITOTAL] += cur_time; + v[PD_ETOTAL] += (cur_time - pd->excl[pd->excl_top]); + pd->excl_top--; + pd->excl[pd->excl_top] += cur_time; + } + return(sc->F); +} + +static s7_pointer g_profile_in(s7_scheme *sc, s7_pointer args) /* only external func -- added to each profiled func by add_profile above */ +{ + #define H_profile_in "(profile-in e) is the profiler's hook into closures" + #define Q_profile_in s7_make_signature(sc, 3, sc->T, sc->is_integer_symbol, sc->is_let_symbol) + + s7_pointer e; + s7_int pos; + if (sc->profile == 0) return(sc-> F); + + pos = integer(car(args)); + e = find_funclet(sc, cadr(args)); + + if ((is_let(e)) && + (is_symbol(funclet_function(e)))) + { + s7_pointer func_name = funclet_function(e); + s7_int *v; + profile_data_t *pd = sc->profile_data; + + if (pos >= pd->size) + { + s7_int new_size = 2 * pos; + pd->funcs = (s7_pointer *)Realloc(pd->funcs, new_size * sizeof(s7_pointer)); + memclr((void *)(pd->funcs + pd->size), (new_size - pd->size) * sizeof(s7_pointer)); + pd->timing_data = (s7_int *)Realloc(pd->timing_data, new_size * PD_BLOCK_SIZE * sizeof(s7_int)); + memclr((void *)(pd->timing_data + (pd->size * PD_BLOCK_SIZE)), (new_size - pd->size) * PD_BLOCK_SIZE * sizeof(s7_int)); + pd->let_names = (s7_pointer *)Realloc(pd->let_names, new_size * sizeof(s7_pointer)); + memclr((void *)(pd->let_names + pd->size), (new_size - pd->size) * sizeof(s7_pointer)); + pd->files = (s7_pointer *)Realloc(pd->files, new_size * sizeof(s7_pointer)); + memclr((void *)(pd->files + pd->size), (new_size - pd->size) * sizeof(s7_pointer)); + pd->lines = (s7_int *)Realloc(pd->lines, new_size * sizeof(s7_int)); + memclr((void *)(pd->lines + pd->size), (new_size - pd->size) * sizeof(s7_int)); + pd->size = new_size; + } + if (pd->funcs[pos] == NULL) + { + pd->funcs[pos] = func_name; + if (is_gensym(func_name)) sc->profiling_gensyms = true; + if (pos >= pd->top) pd->top = (pos + 1); + + /* perhaps add_profile needs to reuse ints if file/line exists? */ + if (is_symbol(sc->profile_prefix)) + { + s7_pointer let_name = s7_symbol_local_value(sc, sc->profile_prefix, e); + if (is_symbol(let_name)) pd->let_names[pos] = let_name; + } + if (has_let_file(e)) + { + pd->files[pos] = sc->file_names[let_file(e)]; + pd->lines[pos] = let_line(e); + }} + v = (s7_int *)(sc->profile_data->timing_data + (pos * PD_BLOCK_SIZE)); + v[PD_CALLS]++; + if (v[PD_RECUR] == 0) + { + v[PD_START] = my_clock(); + pd->excl_top++; + if (pd->excl_top == pd->excl_size) + { + pd->excl_size *= 2; + pd->excl = (s7_int *)Realloc(pd->excl, pd->excl_size * sizeof(s7_int)); + } + pd->excl[pd->excl_top] = 0; + } + v[PD_RECUR]++; + + /* this doesn't work in "continuation passing" code (e.g. cpstak.scm in the so-called standard benchmarks). + * swap_stack pushes dynamic_unwind, but we don't pop back to it, so the stack grows to the recursion depth. + */ + if (sc->stack_end >= sc->stack_resize_trigger) + { + #define PROFILE_MAX_STACK_SIZE 10000000 /* around 5G counting lets/arglists/slots, maybe an *s7* field for this? */ + if (sc->stack_size > PROFILE_MAX_STACK_SIZE) + error_nr(sc, make_symbol(sc, "stack-too-big", 13), + set_elist_2(sc, wrap_string(sc, "profiling stack size has grown past ~D", 38), wrap_integer(sc, PROFILE_MAX_STACK_SIZE))); + /* rather than raise an error, we could unwind the stack here, popping off all unwind entries, but this is + * a very rare problem, and the results will be confusing anyway. + */ + resize_stack(sc); + } + swap_stack(sc, OP_DYNAMIC_UNWIND_PROFILE, sc->profile_out, car(args)); + } + return(sc->F); +} + +static s7_pointer profile_info_out(s7_scheme *sc) +{ + s7_pointer p, pp, vs, vi, vn, vf, vl, matches; + s7_int i; + profile_data_t *pd = sc->profile_data; + if ((!pd) || (pd->top == 0)) return(sc->F); + p = make_list(sc, 7, sc->F); + sc->w = p; + set_car(p, vs = make_simple_vector(sc, pd->top)); + set_car(cdr(p), vi = make_simple_int_vector(sc, pd->top * PD_BLOCK_SIZE)); + set_car(cddr(p), make_integer(sc, ticks_per_second())); + pp = cdddr(p); + set_car(pp, vn = make_simple_vector(sc, pd->top)); + set_car(cdr(pp), vf = make_simple_vector(sc, pd->top)); + set_car(cddr(pp), vl = make_simple_int_vector(sc, pd->top)); + matches = cdddr(pp); + set_car(matches, sc->nil); + for (i = 0; i < pd->top; i++) + { + if (pd->funcs[i]) + { + vector_element(vs, i) = pd->funcs[i]; + if ((is_matched_symbol(pd->funcs[i])) && /* find ambiguous names */ + (!direct_memq(pd->funcs[i], car(matches)))) + set_car(matches, cons(sc, pd->funcs[i], car(matches))); + set_match_symbol(pd->funcs[i]); + } + else vector_element(vs, i) = sc->F; + vector_element(vn, i) = (!pd->let_names[i]) ? sc->F : pd->let_names[i]; + vector_element(vf, i) = (!pd->files[i]) ? sc->F : pd->files[i]; + } + for (i = 0; i < pd->top; i++) if (pd->funcs[i]) clear_match_symbol(pd->funcs[i]); + memcpy((void *)int_vector_ints(vl), (void *)pd->lines, pd->top * sizeof(s7_int)); + memcpy((void *)int_vector_ints(vi), (void *)pd->timing_data, pd->top * PD_BLOCK_SIZE * sizeof(s7_int)); + sc->w = sc->unused; + return(p); +} + +static s7_pointer clear_profile_info(s7_scheme *sc) +{ + if (sc->profile_data) + { + profile_data_t *pd = sc->profile_data; + memclr(pd->timing_data, pd->top * PD_BLOCK_SIZE * sizeof(s7_int)); + memclr(pd->funcs, pd->top * sizeof(s7_pointer)); + memclr(pd->let_names, pd->top * sizeof(s7_pointer)); + memclr(pd->files, pd->top * sizeof(s7_pointer)); + memclr(pd->lines, pd->top * sizeof(s7_int)); + pd->top = 0; + for (int32_t i = 0; i < pd->excl_top; i++) + pd->excl[i] = 0; + pd->excl_top = 0; + sc->profiling_gensyms = false; + } + return(sc->F); +} + +static s7_pointer make_profile_info(s7_scheme *sc) +{ + if (!sc->profile_data) + { + profile_data_t *pd = (profile_data_t *)Malloc(sizeof(profile_data_t)); + pd->size = PD_INITIAL_SIZE; + pd->excl_size = PD_INITIAL_SIZE; + pd->top = 0; + pd->excl_top = 0; + pd->funcs = (s7_pointer *)Calloc(pd->size, sizeof(s7_pointer)); + pd->let_names = (s7_pointer *)Calloc(pd->size, sizeof(s7_pointer)); + pd->files = (s7_pointer *)Calloc(pd->size, sizeof(s7_pointer)); + pd->lines = (s7_int *)Calloc(pd->size, sizeof(s7_int)); + pd->excl = (s7_int *)Calloc(pd->excl_size, sizeof(s7_int)); + pd->timing_data = (s7_int *)Calloc(pd->size * PD_BLOCK_SIZE, sizeof(s7_int)); + sc->profile_data = pd; + } + return(sc->F); +} + + +/* -------------------------------- dynamic-unwind -------------------------------- */ +static s7_pointer dynamic_unwind(s7_scheme *sc, s7_pointer func, s7_pointer e) +{ + if ((S7_DEBUGGING) && (is_multiple_value(sc->value))) + fprintf(stderr, "%s[%d]: unexpected multiple-value! %s %s %s\n", __func__, __LINE__, display(func), display(e), display(sc->value)); + return(s7_apply_function(sc, func, set_plist_2(sc, e, sc->value))); /* s7_apply_function returns sc->value */ +} + +static s7_pointer g_dynamic_unwind(s7_scheme *sc, s7_pointer args) +{ + #define H_dynamic_unwind "(dynamic-unwind func arg) pushes func and arg on the stack, then (func arg) is called when the stack unwinds." + #define Q_dynamic_unwind s7_make_signature(sc, 3, sc->is_procedure_symbol, sc->is_procedure_symbol, sc->T) + + s7_pointer func = car(args); + if (((is_closure(func)) && (closure_arity_to_int(sc, func) == 2)) || + ((is_c_function(func)) && (c_function_is_aritable(func, 2))) || + ((is_closure_star(func)) && (closure_star_arity_to_int(sc, func) == 2)) || + ((is_c_function_star(func)) && (c_function_max_args(func) == 2))) + swap_stack(sc, OP_DYNAMIC_UNWIND, func, cadr(args)); + else wrong_type_error_nr(sc, sc->dynamic_unwind_symbol, 1, func, wrap_string(sc, "a procedure of two arguments", 28)); + return(cadr(args)); +} + + +/* -------------------------------- catch -------------------------------- */ +static s7_pointer g_catch(s7_scheme *sc, s7_pointer args) +{ + #define H_catch "(catch tag thunk handler) evaluates thunk; if an error occurs that matches the tag (#t matches all), the handler is called" + #define Q_catch s7_make_signature(sc, 4, sc->values_symbol, \ + s7_make_signature(sc, 2, sc->is_symbol_symbol, sc->is_boolean_symbol), \ + sc->is_procedure_symbol, sc->is_procedure_symbol) + s7_pointer p, proc = cadr(args), err = caddr(args); + + /* Guile sets up the catch before looking for arg errors: (catch #t log (lambda args "hiho")) -> "hiho" + * which is consistent in that (catch #t (lambda () (log))...) should probably be the same as (catch #t log ...) + * but what if the error handler arg is messed up? Seems weird to handle args in reverse order with an intervening let etc. + */ + /* if (is_let(err)) check_method(sc, err, sc->catch_symbol, args); */ /* causes exit from s7! */ + + if (SHOW_EVAL_OPS) fprintf(stderr, "%s[%d]\n", __func__, __LINE__); + new_cell(sc, p, T_CATCH); + catch_tag(p) = car(args); + catch_goto_loc(p) = stack_top(sc); + catch_op_loc(p) = (int32_t)(sc->op_stack_now - sc->op_stack); + catch_set_handler(p, err); + catch_cstack(p) = sc->goto_start; + push_stack(sc, (intptr_t)((is_any_macro(err)) ? OP_CATCH_2 : OP_CATCH), args, p); + + /* not sure about these error checks -- they can be omitted */ + if (!is_thunk(sc, proc)) + wrong_type_error_nr(sc, sc->catch_symbol, 2, proc, a_thunk_string); + if (!is_applicable(err)) + wrong_type_error_nr(sc, sc->catch_symbol, 3, err, something_applicable_string); + + /* should we check here for (aritable? err 2)? (catch #t (lambda () 1) "hiho") -> 1 + * currently this is checked only if the error handler is called + */ + if (is_closure(proc)) /* not also lambda* here because we need to handle the arg defaults */ + { + /* is_thunk above checks is_aritable(proc, 0), but if it's (lambda args ...) we have to set up the let with args=() + * the case that caught this: (catch #t make-hook ...) + */ + sc->code = closure_body(proc); + if (is_symbol(closure_args(proc))) + sc->curlet = make_let_with_slot(sc, closure_let(proc), closure_args(proc), sc->nil); + else sc->curlet = inline_make_let(sc, closure_let(proc)); + push_stack_no_args_direct(sc, sc->begin_op); + } + else push_stack(sc, OP_APPLY, sc->nil, proc); + return(sc->F); +} + +s7_pointer s7_call_with_catch(s7_scheme *sc, s7_pointer tag, s7_pointer body, s7_pointer error_handler) +{ + s7_pointer p, result; + if (sc->stack_end == sc->stack_start) /* no stack! */ + push_stack_direct(sc, OP_EVAL_DONE); + + if (SHOW_EVAL_OPS) fprintf(stderr, "%s[%d]\n", __func__, __LINE__); + new_cell(sc, p, T_CATCH); + catch_tag(p) = tag; + catch_goto_loc(p) = stack_top(sc); + catch_op_loc(p) = (int32_t)(sc->op_stack_now - sc->op_stack); + catch_set_handler(p, error_handler); + catch_cstack(p) = sc->goto_start; + + { + declare_jump_info(); + TRACK(sc); + store_jump_info(sc); + set_jump_info(sc, S7_CALL_SET_JUMP); + + if (SHOW_EVAL_OPS) fprintf(stderr, "jump_loc: %d\n", jump_loc); + if (jump_loc == NO_JUMP) + { + catch_cstack(p) = &new_goto_start; + if (SHOW_EVAL_OPS) fprintf(stderr, " longjmp call %s\n", display(body)); + push_stack(sc, OP_CATCH, error_handler, p); + result = s7_call(sc, body, sc->nil); + if (stack_top_op(sc) == OP_CATCH) sc->stack_end -= 4; + } + else + { + if (SHOW_EVAL_OPS) fprintf(stderr, " jump back with %d (%d)\n", jump_loc, (sc->stack_end == sc->stack_start)); + if (jump_loc != ERROR_JUMP) + eval(sc, sc->cur_op); + if ((jump_loc == CATCH_JUMP) && /* we're returning from an error in catch */ + ((sc->stack_end == sc->stack_start) || + (((sc->stack_end - 4) == sc->stack_start) && (stack_top_op(sc) == OP_GC_PROTECT)))) /* s7_apply_function probably */ + push_stack_op(sc, OP_ERROR_QUIT); + result = sc->value; + } + restore_jump_info(sc); + } + + return(result); +} + +static void op_c_catch(s7_scheme *sc) +{ + /* (catch #t (lambda () (set! ("hi") #\a)) (lambda args args)) + * code is (catch #t (lambda () ....) (lambda args ....)) + */ + s7_pointer p, f = cadr(sc->code), args = cddr(sc->code), tag; + + /* defer making the error lambda */ + if (!is_pair(f)) /* (catch #t ...) or (catch sym ...) */ + tag = (is_symbol(f)) ? lookup_checked(sc, f) : f; + else tag = cadr(f); /* (catch 'sym ...) */ + + new_cell(sc, p, T_CATCH); /* the catch object sitting on the stack */ + catch_tag(p) = tag; + catch_goto_loc(p) = stack_top(sc); + catch_op_loc(p) = sc->op_stack_now - sc->op_stack; + catch_set_handler(p, cdadr(args)); /* not yet a closure... */ + catch_cstack(p) = sc->goto_start; + push_stack(sc, OP_CATCH_1, sc->code, p); /* code ignored here, except by GC */ + sc->curlet = inline_make_let(sc, sc->curlet); + sc->code = T_Pair(cddar(args)); +} + +static void op_c_catch_all(s7_scheme *sc) +{ + s7_pointer p; + new_cell(sc, p, T_CATCH); + catch_tag(p) = sc->T; + catch_goto_loc(p) = stack_top(sc); + catch_op_loc(p) = sc->op_stack_now - sc->op_stack; + catch_set_handler(p, sc->nil); + catch_cstack(p) = sc->goto_start; + push_stack(sc, OP_CATCH_ALL, opt2_con(sc->code), p); /* push_stack: op args code */ + sc->code = T_Pair(opt1_pair(cdr(sc->code))); /* the body of the first lambda (or car of it if catch_all_o) */ +} + +static void op_c_catch_all_a(s7_scheme *sc) +{ + op_c_catch_all(sc); + sc->value = fx_call(sc, sc->code); +} + + +/* -------------------------------- owlet -------------------------------- */ +/* error reporting info -- save filename and line number */ + +static s7_pointer init_owlet(s7_scheme *sc) +{ + s7_pointer p; /* watch out for order below */ + s7_pointer e = make_let(sc, sc->nil); + sc->temp3 = e; + sc->error_type = add_slot_checked_with_id(sc, e, make_symbol(sc, "error-type", 10), sc->F); /* the error type or tag ('division-by-zero) */ + sc->error_data = add_slot_unchecked_with_id(sc, e, make_symbol(sc, "error-data", 10), sc->F); /* the message or information passed by the error function */ + sc->error_code = add_slot_unchecked_with_id(sc, e, make_symbol(sc, "error-code", 10), sc->F); /* the code that s7 thinks triggered the error */ + sc->error_line = add_slot_unchecked_with_id(sc, e, make_symbol(sc, "error-line", 10), p = make_permanent_integer(0)); /* the line number of that code */ + add_saved_pointer(sc, p); + sc->error_file = add_slot_unchecked_with_id(sc, e, make_symbol(sc, "error-file", 10), sc->F); /* the file name of that code */ + sc->error_position = add_slot_unchecked_with_id(sc, e, make_symbol(sc, "error-position", 14), p = make_permanent_integer(0)); /* file-byte position of that code */ + add_saved_pointer(sc, p); +#if WITH_HISTORY + sc->error_history = add_slot_unchecked_with_id(sc, e, make_symbol(sc, "error-history", 13), sc->F); /* buffer of previous evaluations */ +#endif + sc->temp3 = sc->unused; + return(e); +} + +#if WITH_HISTORY +static s7_pointer cull_history(s7_scheme *sc, s7_pointer code) +{ + clear_symbol_list(sc); /* make a list of words banned from the history */ + add_symbol_to_list(sc, sc->s7_starlet_symbol); + add_symbol_to_list(sc, sc->eval_symbol); + add_symbol_to_list(sc, make_symbol(sc, "debug", 5)); + add_symbol_to_list(sc, make_symbol(sc, "trace-in", 8)); + add_symbol_to_list(sc, make_symbol(sc, "trace-out", 9)); + add_symbol_to_list(sc, sc->dynamic_unwind_symbol); + add_symbol_to_list(sc, make_symbol(sc, "history-enabled", 15)); + for (s7_pointer p = code; is_pair(p); p = cdr(p)) + { + if (tree_set_memq(sc, car(p))) + set_car(p, sc->nil); + if (cdr(p) == code) break; + } + return(code); +} +#endif + +static s7_pointer g_owlet(s7_scheme *sc, s7_pointer unused_args) +{ +#if WITH_HISTORY + #define H_owlet "(owlet) returns the environment at the point of the last error. \ +It has the additional local variables: error-type, error-data, error-code, error-line, error-file, and error-history." +#else + #define H_owlet "(owlet) returns the environment at the point of the last error. \ +It has the additional local variables: error-type, error-data, error-code, error-line, and error-file." +#endif + #define Q_owlet s7_make_signature(sc, 1, sc->is_let_symbol) + /* if owlet is not copied, (define e (owlet)), e changes as owlet does! */ + + s7_pointer e; + s7_int gc_loc; + bool old_gc = sc->gc_off; +#if WITH_HISTORY + slot_set_value(sc->error_history, cull_history(sc, slot_value(sc->error_history))); +#endif + e = let_copy(sc, sc->owlet); + gc_loc = gc_protect_1(sc, e); + + /* make sure the pairs/reals/strings/integers are copied: should be error-data, error-code, and error-history */ + sc->gc_off = true; + + for (s7_pointer x = let_slots(e); tis_slot(x); x = next_slot(x)) + if (is_pair(slot_value(x))) + { + s7_pointer new_list = copy_any_list(sc, slot_value(x)); + slot_set_value(x, new_list); + for (s7_pointer p = new_list, sp = p; is_pair(p); p = cdr(p), sp = cdr(sp)) + { + s7_pointer val = car(p); + if (is_t_real(val)) + set_car(p, make_real(sc, real(val))); + else + if (is_string(val)) + set_car(p, make_string_with_length(sc, string_value(val), string_length(val))); + else + if (is_t_integer(val)) + set_car(p, make_integer(sc, integer(val))); + p = cdr(p); + if ((!is_pair(p)) || (p == sp)) break; + val = car(p); + if (is_t_real(val)) + set_car(p, make_real(sc, real(val))); + else + if (is_string(val)) + set_car(p, make_string_with_length(sc, string_value(val), string_length(val))); + }} + sc->gc_off = old_gc; + s7_gc_unprotect_at(sc, gc_loc); + return(e); +} + + +/* -------- catch handlers -------- (don't free the catcher) */ +static void load_catch_cstack(s7_scheme *sc, s7_pointer c) +{ + if (catch_cstack(c)) + sc->goto_start = catch_cstack(c); +} + +static bool catch_all_function(s7_scheme *sc, s7_int catch_loc, s7_pointer type, s7_pointer info, bool *reset_hook) +{ + s7_pointer catcher = T_Cat(stack_code(sc->stack, catch_loc)); + if (SHOW_EVAL_OPS) fprintf(stderr, "catcher: %s\n", __func__); + sc->value = stack_args(sc->stack, catch_loc); /* error result, optimize_func_three_args -> op_c_catch_all etc */ + if (sc->value == sc->unused) sc->value = type; + sc->op_stack_now = (s7_pointer *)(sc->op_stack + catch_op_loc(catcher)); + sc->stack_end = (s7_pointer *)(sc->stack_start + catch_goto_loc(catcher)); + load_catch_cstack(sc, catcher); + pop_stack(sc); + return(true); +} + +static bool catch_2_function(s7_scheme *sc, s7_int catch_loc, s7_pointer type, s7_pointer info, bool *reset_hook) +{ + /* this is the macro-error-handler case from g_catch + * (let () (define-macro (m . args) (apply (car args) (cadr args))) (catch #t (lambda () (error abs -1)) m)) + */ + s7_pointer x = T_Cat(stack_code(sc->stack, catch_loc)); + if (SHOW_EVAL_OPS) fprintf(stderr, "catcher: %s\n", __func__); + if ((catch_tag(x) == sc->T) || (catch_tag(x) == type) || (type == sc->T)) + { + sc->op_stack_now = (s7_pointer *)(sc->op_stack + catch_op_loc(x)); + sc->stack_end = (s7_pointer *)(sc->stack_start + catch_goto_loc(x)); + sc->code = catch_handler(x); + load_catch_cstack(sc, x); + if (needs_copied_args(sc->code)) + sc->args = list_2(sc, type, info); + else sc->args = with_list_t2(type, info); /* very unlikely: need c_macro as error catcher: (catch #t (lambda () (error 'oops)) require) */ + sc->cur_op = OP_APPLY; + return(true); + } + return(false); +} + +static bool catch_1_function(s7_scheme *sc, s7_int catch_loc, s7_pointer type, s7_pointer info, bool *reset_hook) +{ + s7_pointer x = T_Cat(stack_code(sc->stack, catch_loc)); + if (SHOW_EVAL_OPS) fprintf(stderr, "catcher: %s\n", __func__); + if ((catch_tag(x) == sc->T) || /* the normal case */ + (catch_tag(x) == type) || + (type == sc->T)) + { + opcode_t op = stack_op(sc->stack, catch_loc); + s7_pointer catcher = x, error_body, error_args; + s7_pointer error_func = catch_handler(catcher); + uint64_t loc = catch_goto_loc(catcher); + + init_temp(sc->y, type); + sc->value = info; + + sc->temp4 = stack_let(sc->stack, catch_loc); /* GC protect this, since we're moving the stack top below */ + sc->op_stack_now = (s7_pointer *)(sc->op_stack + catch_op_loc(catcher)); + sc->stack_end = (s7_pointer *)(sc->stack_start + loc); + load_catch_cstack(sc, catcher); + + /* very often the error handler just returns either a constant ('error or #f), or + * the args passed to it, so there's no need to laboriously make a closure, + * and apply it -- just set sc->value to the closure body (or the args) and return. + * so first examine closure_body(error_func) + * if it is a constant, or quoted symbol, return that, + * if it is the args symbol, return (list type info) + */ + + /* if OP_CATCH_1, we deferred making the error handler until it is actually needed */ + if (op == OP_CATCH_1) + { + error_body = cdr(error_func); + error_args = car(error_func); + } + else + if (is_closure(error_func)) + { + error_body = closure_body(error_func); + error_args = closure_args(error_func); + } + else + { + error_body = NULL; + error_args = NULL; + } + if ((error_body) && (is_null(cdr(error_body)))) + { + s7_pointer y = NULL; + error_body = car(error_body); + if (is_pair(error_body)) + { + if (car(error_body) == sc->quote_symbol) + y = cadr(error_body); + else + if ((car(error_body) == sc->car_symbol) && + (is_pair(cdr(error_body))) && /* catch: (lambda (type info) (car)) */ + (cadr(error_body) == error_args)) + y = type; + } + else + if (!is_symbol(error_body)) + y = error_body; /* not pair or symbol */ + else + if (error_body == error_args) + y = list_2(sc, type, info); + else + if (is_keyword(error_body)) + y = error_body; + else + if ((is_pair(error_args)) && + (error_body == car(error_args))) + y = type; + if (y) + { + if ((SHOW_EVAL_OPS) && (loc > 4)) {fprintf(stderr, " about to pop_stack: \n"); s7_show_stack(sc);} + if (loc > 4) + pop_stack(sc); + /* we're at OP_CATCH, normally we want to pop that away, but (handwaving...) if we're coming + * from s7_eval (indirectly perhaps through s7_eval_c_string), we might push the OP_EVAL_DONE + * to end that call, but it's pushed at the precatch stack end (far beyond the catch loc). + * If we catch an error, catch unwinds to its starting point, and the pop_stack above + * puts us at the bottom of the stack (i.e. stack_end == stack_start), OP_EVAL_DONE. + * Now we return true, ending up back in eval, because the error handler jumped out of eval, + * back to wherever we were in eval when we hit the error. eval jumps back to the start + * of its loop, and pops the stack to see what to do next! So the (loc > 4) at least + * protects against stack underflow, but ideally we'd know we came from OP_CATCH+s7_eval. + * We can't do anything fancy here because we have to unwind the C stack as well as s7's stack. + * s7_eval doesn't know anything about the catches on the stack. We can't look back for + * OP_EVAL_DONE -- segfault in OP_BEGIN. Hmmmm. Perhaps catch should not unwind until the + * end? But we want the error handler to run as a part of the calling expression, and + * in any case the OP_EVAL_DONE is not useful (it marks the end of the no-error case). + */ + sc->value = y; + sc->y = sc->unused; + sc->temp4 = sc->unused; + sc->w = sc->unused; + if (loc == 4) + sc->code = cons(sc, sc->value, sc->nil); /* if we end up at op_begin, give it something it can handle */ + return(true); + }} + /* here type and info need to be GC protected (new_cell below), g_throw and error_nr, throw sc->w for type, but error_nr nothing currently */ + if (op == OP_CATCH_1) + { + s7_pointer p; + new_cell(sc, p, T_CLOSURE); + closure_set_args(p, car(error_func)); + closure_set_body(p, cdr(error_func)); + closure_set_setter(p, sc->F); + closure_set_arity(p, CLOSURE_ARITY_NOT_SET); + closure_set_let(p, sc->temp4); + sc->code = p; + if ((S7_DEBUGGING) && (!s7_is_aritable(sc, sc->code, 2))) fprintf(stderr, "%s[%d]: errfunc not aritable(2)!\n", __func__, __LINE__); + } + else + { + sc->code = error_func; + sc->y = sc->unused; + if (!s7_is_aritable(sc, sc->code, 2)) /* op_catch_1 from op_c_catch already checks this */ + wrong_number_of_arguments_error_nr(sc, "catch error handler should accept two arguments: ~S", 51, sc->code); + } + sc->temp4 = sc->unused; + /* if user (i.e. yers truly!) copies/pastes the preceding lambda () into the + * error handler portion of the catch, he gets the inexplicable message: + * ;(): too many arguments: (a1 ()) + * when this apply tries to call the handler. So, we need a special case error check here! + */ + sc->args = list_2(sc, type, info); /* almost never able to skip this -- costs more to check! */ + sc->w = sc->unused; + sc->y = sc->unused; + sc->cur_op = OP_APPLY; + /* explicit eval needed if s7_call called into scheme where a caught error occurred (ex6 in exs7.c) + * but putting it here (via eval(sc, OP_APPLY)) means the C stack is not cleared correctly in non-s7-call cases, + * so defer it until s7_call + */ + return(true); + } + return(false); +} + +static bool catch_dynamic_wind_function(s7_scheme *sc, s7_int catch_loc, s7_pointer type, s7_pointer info, bool *reset_hook) +{ + s7_pointer x = T_Dyn(stack_code(sc->stack, catch_loc)); + if (SHOW_EVAL_OPS) fprintf(stderr, "catcher: %s\n", __func__); + if (dynamic_wind_state(x) == DWIND_BODY) + { + dynamic_wind_state(x) = DWIND_FINISH; /* make sure an uncaught error in the exit thunk doesn't cause us to loop */ + if (dynamic_wind_out(x) != sc->F) + sc->value = s7_call(sc, dynamic_wind_out(x), sc->nil); + } + return(false); +} + +static bool catch_out_function(s7_scheme *sc, s7_int catch_loc, s7_pointer type, s7_pointer info, bool *reset_hook) +{ + s7_pointer x = T_Pro(stack_code(sc->stack, catch_loc)); /* "code" = port that we opened */ + if (SHOW_EVAL_OPS) fprintf(stderr, "catcher: %s\n", __func__); + s7_close_output_port(sc, x); + x = stack_args(sc->stack, catch_loc); /* "args" = port that we shadowed, if not # */ + if (x != sc->unused) + set_current_output_port(sc, x); + return(false); +} + +static bool catch_in_function(s7_scheme *sc, s7_int catch_loc, s7_pointer type, s7_pointer info, bool *reset_hook) +{ + if (SHOW_EVAL_OPS) fprintf(stderr, "catcher: %s\n", __func__); + s7_close_input_port(sc, T_Pri(stack_code(sc->stack, catch_loc))); /* "code" = port that we opened */ + if (stack_args(sc->stack, catch_loc) != sc->unused) + set_current_input_port(sc, stack_args(sc->stack, catch_loc)); /* "args" = port that we shadowed */ + return(false); +} + +static bool catch_read_function(s7_scheme *sc, s7_int catch_loc, s7_pointer type, s7_pointer info, bool *reset_hook) +{ + if (SHOW_EVAL_OPS) fprintf(stderr, "catcher: %s\n", __func__); + pop_input_port(sc); + return(false); +} + +static bool catch_eval_function(s7_scheme *sc, s7_int catch_loc, s7_pointer type, s7_pointer info, bool *reset_hook) +{ + if (SHOW_EVAL_OPS) fprintf(stderr, "catcher: %s\n", __func__); + s7_close_input_port(sc, current_input_port(sc)); + pop_input_port(sc); + return(false); +} + +static bool catch_barrier_function(s7_scheme *sc, s7_int catch_loc, s7_pointer type, s7_pointer info, bool *reset_hook) +{ /* can this happen? is it doing the right thing? read/eval/call_begin_hook push_stack op_barrier but only s7_read includes a port (this is not hit in s7test.scm) */ + if (SHOW_EVAL_OPS) fprintf(stderr, "catcher: %s\n", __func__); + if (is_input_port(stack_args(sc->stack, catch_loc))) + { + if (current_input_port(sc) == stack_args(sc->stack, catch_loc)) + pop_input_port(sc); + s7_close_input_port(sc, stack_args(sc->stack, catch_loc)); + } + return(false); +} + +static bool catch_error_hook_function(s7_scheme *sc, s7_int catch_loc, s7_pointer type, s7_pointer info, bool *reset_hook) +{ + if (SHOW_EVAL_OPS) fprintf(stderr, "catcher: %s\n", __func__); + let_set(sc, closure_let(sc->error_hook), sc->body_symbol, stack_code(sc->stack, catch_loc)); + /* apparently there was an error during *error-hook* evaluation, but Rick wants the hook re-established anyway */ + (*reset_hook) = true; + /* avoid infinite loop -- don't try to (re-)evaluate (buggy) *error-hook*! */ + return(false); +} + +static bool catch_goto_function(s7_scheme *sc, s7_int catch_loc, s7_pointer type, s7_pointer info, bool *reset_hook) +{ + if (SHOW_EVAL_OPS) fprintf(stderr, "catcher: %s\n", __func__); + call_exit_active(stack_args(sc->stack, catch_loc)) = false; + return(false); +} + +static bool catch_map_unwind_function(s7_scheme *sc, s7_int catch_loc, s7_pointer type, s7_pointer info, bool *reset_hook) +{ + if (SHOW_EVAL_OPS) fprintf(stderr, "catcher: %s\n", __func__); + sc->map_call_ctr--; + if ((S7_DEBUGGING) && (sc->map_call_ctr < 0)) {fprintf(stderr, "%s[%d]: map ctr: %" ld64 "\n", __func__, __LINE__, sc->map_call_ctr); sc->map_call_ctr = 0;} + return(false); +} + +static bool op_let_temp_done1(s7_scheme *sc); + +static bool catch_let_temporarily_function(s7_scheme *sc, s7_int catch_loc, s7_pointer type, s7_pointer info, bool *reset_hook) +{ + /* this is aimed at let-temp error-hook... error -- not yet tested much */ + if (SHOW_EVAL_OPS) fprintf(stderr, "catcher: %s\n", __func__); + if ((!*reset_hook) && + (hook_has_functions(sc->error_hook))) + { + s7_pointer error_hook_funcs = s7_hook_functions(sc, sc->error_hook); + + let_set(sc, closure_let(sc->error_hook), sc->body_symbol, sc->nil); + let_set(sc, closure_let(sc->let_temp_hook), sc->body_symbol, error_hook_funcs); + sc->code = sc->let_temp_hook; + sc->args = list_2(sc, type, info); + + push_stack_direct(sc, OP_EVAL_DONE); + sc->curlet = make_let(sc, closure_let(sc->code)); + eval(sc, OP_APPLY_LAMBDA); + + let_set(sc, closure_let(sc->error_hook), sc->body_symbol, error_hook_funcs); + let_set(sc, closure_let(sc->let_temp_hook), sc->body_symbol, sc->nil); + + sc->args = stack_args(sc->stack, catch_loc); + sc->code = stack_code(sc->stack, catch_loc); + set_curlet(sc, stack_let(sc->stack, catch_loc)); + + push_stack_direct(sc, OP_GC_PROTECT); + if (!op_let_temp_done1(sc)) + { + push_stack_direct(sc, OP_EVAL_DONE); + eval(sc, OP_SET_UNCHECKED); + }} + else let_temp_done(sc, stack_args(sc->stack, catch_loc), stack_let(sc->stack, catch_loc)); + return(false); +} + +static bool catch_let_temp_unwind_function(s7_scheme *sc, s7_int catch_loc, s7_pointer type, s7_pointer info, bool *reset_hook) +{ + if (SHOW_EVAL_OPS) fprintf(stderr, "catcher: %s\n", __func__); + slot_set_value(stack_code(sc->stack, catch_loc), stack_args(sc->stack, catch_loc)); + return(false); +} + +static bool catch_let_temp_s7_unwind_function(s7_scheme *sc, s7_int catch_loc, s7_pointer type, s7_pointer info, bool *reset_hook) +{ + if (SHOW_EVAL_OPS) fprintf(stderr, "catcher: %s\n", __func__); + s7_starlet_set_1(sc, T_Sym(stack_code(sc->stack, catch_loc)), stack_args(sc->stack, catch_loc)); + return(false); +} + +static bool catch_let_temp_s7_direct_unwind_function(s7_scheme *sc, s7_int catch_loc, s7_pointer type, s7_pointer info, bool *reset_hook) +{ + if (SHOW_EVAL_OPS) fprintf(stderr, "catcher: %s\n", __func__); + sc->has_openlets = (stack_args(sc->stack, catch_loc) != sc->F); + return(false); +} + +static bool catch_dynamic_unwind_function(s7_scheme *sc, s7_int catch_loc, s7_pointer type, s7_pointer info, bool *reset_hook) +{ + /* if func has an error, s7_error will call it as it unwinds the stack -- an infinite loop. So, cancel the unwind first */ + if (SHOW_EVAL_OPS) fprintf(stderr, "catcher: %s\n", __func__); + set_stack_op(sc->stack, catch_loc, OP_GC_PROTECT); + + /* we're in an error or throw, so there is no return value to report, but we need to decrement *debug-spaces* (if in debug) + * stack_let is the trace-in let at the point of the dynamic_unwind call + */ + if (sc->debug > 0) + { + s7_pointer spaces = lookup_slot_with_let(make_symbol(sc, "*debug-spaces*", 14), stack_let(sc->stack, catch_loc)); + if (is_slot(spaces)) + slot_set_value(spaces, make_integer(sc, max_i_ii(0LL, integer(slot_value(spaces)) - 2))); /* should involve only small_ints */ + } + return(false); +} + +typedef bool (*catch_function_t)(s7_scheme *sc, s7_int catch_loc, s7_pointer type, s7_pointer info, bool *reset_hook); +static catch_function_t catchers[NUM_OPS]; + +static void init_catchers(void) +{ + for (int32_t i = 0; i < NUM_OPS; i++) catchers[i] = NULL; + catchers[OP_CATCH_ALL] = catch_all_function; + catchers[OP_CATCH_2] = catch_2_function; + catchers[OP_CATCH_1] = catch_1_function; + catchers[OP_CATCH] = catch_1_function; + catchers[OP_DYNAMIC_WIND] = catch_dynamic_wind_function; + catchers[OP_DYNAMIC_UNWIND] = catch_dynamic_unwind_function; + catchers[OP_GET_OUTPUT_STRING] = catch_out_function; + catchers[OP_UNWIND_OUTPUT] = catch_out_function; + catchers[OP_UNWIND_INPUT] = catch_in_function; + catchers[OP_READ_DONE] = catch_read_function; /* perhaps an error during (read) */ + catchers[OP_EVAL_STRING] = catch_eval_function; + catchers[OP_BARRIER] = catch_barrier_function; + catchers[OP_DEACTIVATE_GOTO] = catch_goto_function; + catchers[OP_LET_TEMP_DONE] = catch_let_temporarily_function; + catchers[OP_LET_TEMP_UNWIND] = catch_let_temp_unwind_function; + catchers[OP_LET_TEMP_S7_UNWIND] = catch_let_temp_s7_unwind_function; + catchers[OP_LET_TEMP_S7_DIRECT_UNWIND] = catch_let_temp_s7_direct_unwind_function; + catchers[OP_ERROR_HOOK_QUIT] = catch_error_hook_function; + catchers[OP_MAP_UNWIND] = catch_map_unwind_function; +} + +/* -------------------------------- throw -------------------------------- */ +static s7_pointer g_throw(s7_scheme *sc, s7_pointer args) +{ + #define H_throw "(throw tag . info) is like (error ...) but it does not affect owlet. \ +It looks for an existing catch with a matching tag, and jumps to it if found. Otherwise it raises an error." + #define Q_throw s7_make_circular_signature(sc, 1, 2, sc->values_symbol, sc->T) + + bool ignored_flag = false; + s7_pointer type = car(args), info = cdr(args); + gc_protect_via_stack(sc, args); + /* type can be anything: (throw (list 1 2 3) (make-list 512)), sc->w and sc->value not good here for gc protection */ + + for (int64_t i = stack_top(sc) - 5; i >= 3; i -= 4) /* look for a catcher */ + { + catch_function_t catcher = catchers[stack_op(sc->stack, i)]; + if ((catcher) && + (catcher(sc, i, type, info, &ignored_flag))) + { + if (sc->longjmp_ok) LongJmp(*(sc->goto_start), THROW_JUMP); + return(sc->value); + }} + if (is_let(car(args))) + check_method(sc, car(args), sc->throw_symbol, args); + error_nr(sc, make_symbol(sc, "uncaught-throw", 14), + set_elist_3(sc, wrap_string(sc, "no catch found for (throw ~W~{~^ ~S~})", 38), type, info)); + return(sc->F); +} + + +/* -------------------------------- warn -------------------------------- */ +#if WITH_GCC +static __attribute__ ((format (printf, 3, 4))) void s7_warn(s7_scheme *sc, s7_int len, const char *ctrl, ...) +#else +static void s7_warn(s7_scheme *sc, s7_int len, const char *ctrl, ...) /* len = max size of output string (for vsnprintf) */ +#endif +{ + if ((current_error_port(sc) != sc->F) && (!sc->muffle_warnings)) + { + int32_t bytes; + va_list ap; + block_t *b = mallocate(sc, len); + char *str = (char *)block_data(b); + str[0] = '\0'; + va_start(ap, ctrl); + bytes = vsnprintf(str, len, ctrl, ap); + va_end(ap); + if (port_is_closed(current_error_port(sc))) + set_current_error_port(sc, sc->standard_error); + if ((bytes > 0) && (current_error_port(sc) != sc->F)) + port_write_string(current_error_port(sc))(sc, str, bytes, current_error_port(sc)); + liberate(sc, b); + } +} + + +/* -------------------------------- error -------------------------------- */ +static void fill_error_location(s7_scheme *sc) +{ + if (((is_input_port(current_input_port(sc))) && (is_loader_port(current_input_port(sc)))) || + (((sc->cur_op >= OP_READ_LIST) && (sc->cur_op <= OP_READ_DONE)))) + { + integer(slot_value(sc->error_line)) = port_line_number(current_input_port(sc)); + integer(slot_value(sc->error_position)) = port_position(current_input_port(sc)); + slot_set_value(sc->error_file, wrap_string(sc, port_filename(current_input_port(sc)), port_filename_length(current_input_port(sc)))); + } + else + { + integer(slot_value(sc->error_line)) = 0; + integer(slot_value(sc->error_position)) = 0; + slot_set_value(sc->error_file, sc->F); + } +} + +static void format_to_error_port(s7_scheme *sc, const char *str, s7_pointer args, s7_int len) +{ + if (current_error_port(sc) != sc->F) + format_to_port_1(sc, current_error_port(sc), str, args, NULL, false, true /* is_columnizing(str) */, len, NULL); + /* is_columnizing on every call is much slower than ignoring the issue */ +} + +static noreturn void error_nr(s7_scheme *sc, s7_pointer type, s7_pointer info) +{ + bool reset_error_hook = false; + s7_pointer cur_code = current_code(sc); + + sc->format_depth = -1; + sc->object_out_locked = false; /* possible error in obj->str method after object_out has set this flag */ + sc->has_openlets = true; /* same problem -- we need a cleaner way to handle this, op_?_unwind */ + sc->value = info; /* feeble GC protection (otherwise info is sometimes freed in this function), throw also protects type */ + + if (sc->current_safe_list > 0) + clear_list_in_use(sc->safe_lists[sc->current_safe_list]); + slot_set_value(sc->error_type, type); + slot_set_value(sc->error_data, info); + + if (unchecked_type(sc->curlet) != T_LET) + sc->curlet = sc->nil; /* in the reader, the sc->curlet stack entry is mostly ignored, so it can be (and usually is) garbage */ + let_set_outlet(sc->owlet, sc->curlet); + slot_set_value(sc->error_code, cur_code); + +#if WITH_HISTORY + slot_set_value(sc->error_history, sc->cur_code); + if (sc->cur_code != sc->history_sink) + { + sc->cur_code = (sc->using_history1) ? sc->eval_history2 : sc->eval_history1; + sc->using_history1 = (!sc->using_history1); + pair_fill(sc, set_plist_2(sc, sc->cur_code, sc->nil)); + } +#endif + if (is_pair(cur_code)) + { + s7_int line = -1, file, position; + if (has_location(cur_code)) + { + line = pair_line_number(cur_code); + file = pair_file_number(cur_code); + position = pair_position(cur_code); + } + else /* try to find a plausible line number! */ + for (s7_pointer p = cur_code, sp = cur_code; is_pair(p); p = cdr(p), sp = cdr(sp)) + { + if ((is_pair(car(p))) && /* what about p itself? */ + (has_location(car(p)))) + { + line = pair_line_number(car(p)); + file = pair_file_number(car(p)); + position = pair_position(car(p)); + break; + } + p = cdr(p); + if ((!is_pair(p)) || (p == sp)) break; + if ((is_pair(car(p))) && + (has_location(car(p)))) + { + line = pair_line_number(car(p)); + file = pair_file_number(car(p)); + position = pair_position(car(p)); + break; + }} + if ((line <= 0) || (file < 0)) + fill_error_location(sc); + else + { + integer(slot_value(sc->error_line)) = line; + integer(slot_value(sc->error_position)) = position; + slot_set_value(sc->error_file, sc->file_names[file]); + }} + else fill_error_location(sc); + + /* look for a catcher, call catch*function in the error context (before unwinding the stack), outlet(owlet) is curlet */ + /* top is 1 past actual top, top - 1 is op, if op = OP_CATCH, top - 4 is the cell containing the catch struct */ + for (int64_t i = stack_top(sc) - 1; i >= 3; i -= 4) + { + catch_function_t catcher = catchers[stack_op(sc->stack, i)]; + if ((SHOW_EVAL_OPS) && (catcher)) {fprintf(stderr, "before catch:\n"); s7_show_stack(sc);} + if ((catcher) && + (catcher(sc, i, type, info, &reset_error_hook))) + { + if (SHOW_EVAL_OPS) {fprintf(stderr, " after catch: \n"); s7_show_stack(sc);} + if ((S7_DEBUGGING) && (!sc->longjmp_ok)) fprintf(stderr, "s7_error jump not available?\n"); + LongJmp(*(sc->goto_start), CATCH_JUMP); + }} + /* error not caught (but catcher might have been called and returned false) */ + + if ((!reset_error_hook) && + (hook_has_functions(sc->error_hook))) + { + s7_pointer error_hook_funcs = s7_hook_functions(sc, sc->error_hook); + /* (set! (hook-functions *error-hook*) (list (lambda (h) (format *stderr* "got error ~A~%" (h 'args))))) */ + let_set(sc, closure_let(sc->error_hook), sc->body_symbol, sc->nil); + let_set(sc, closure_let(sc->let_temp_hook), sc->body_symbol, error_hook_funcs); + /* if the *error-hook* functions trigger an error, we had better not have hook_functions(*error-hook*) still set! */ + + /* here we have no catcher (anywhere!), we're headed back to the top-level(?), so error_hook_quit can call reset_stack? */ + push_stack(sc, OP_ERROR_HOOK_QUIT, sc->nil, error_hook_funcs); /* restore *error-hook* upon successful (or any!) evaluation */ + sc->code = sc->let_temp_hook; + sc->args = list_2(sc, type, info); + /* if we drop into the longjmp below, the hook functions are not called! + * OP_ERROR_HOOK_QUIT performs the longjmp, so it should be safe to go to eval. + */ + sc->curlet = make_let(sc, closure_let(sc->code)); + eval(sc, OP_APPLY_LAMBDA); + } + else + { + s7_int op = sc->print_length; + if (op < 32) sc->print_length = 32; + + if ((!is_output_port(current_error_port(sc))) || /* error-port can be #f */ + (port_is_closed(current_error_port(sc)))) + set_current_error_port(sc, sc->standard_error); + /* if info is not a list, send object->string to current error port, + * else assume car(info) is a format control string, and cdr(info) are its args + * if at all possible, get some indication of where we are! + */ + + if ((!is_pair(info)) || + (!is_string(car(info)))) + format_to_error_port(sc, "\n;~S ~S", set_plist_2(sc, type, info), 7); + else + { + /* it's possible that the error string is just a string -- not intended for format */ + if ((type != sc->format_error_symbol) && /* avoid an infinite loop of format errors */ + (strchr(string_value(car(info)), '~'))) + { + s7_int len = string_length(car(info)) + 8; + block_t *b = mallocate(sc, len); + char *errstr = (char *)block_data(b); + s7_int str_len = catstrs_direct(errstr, "\n;", string_value(car(info)), (const char *)NULL); + format_to_error_port(sc, errstr, cdr(info), str_len); + liberate(sc, b); + } + else format_to_error_port(sc, "\n;~S ~S", set_plist_2(sc, type, info), 7); /* 7 = ctrl str len */ + } + if (op < 32) sc->print_length = op; + + /* now display location at end */ + if (is_string(slot_value(sc->error_file))) + { + s7_newline(sc, current_error_port(sc)); + format_to_error_port(sc, "; ~A\n", set_plist_1(sc, object_to_truncated_string(sc, cur_code, 40)), 8); + format_to_error_port(sc, "; ~A, line ~D, position: ~D\n", + set_plist_3(sc, slot_value(sc->error_file), slot_value(sc->error_line), slot_value(sc->error_position)), 31); + } + else + { + if ((is_input_port(current_input_port(sc))) && + (port_file(current_input_port(sc)) != stdin) && + (!port_is_closed(current_input_port(sc)))) + { + const char *filename = port_filename(current_input_port(sc)); + int32_t line = port_line_number(current_input_port(sc)); + + if (filename) + format_to_error_port(sc, "\n; ~A[~D]", + set_plist_2(sc, wrap_string(sc, filename, port_filename_length(current_input_port(sc))), + wrap_integer(sc, line)), 10); + else + if ((line > 0) && + (integer(slot_value(sc->error_line)) > 0)) + format_to_error_port(sc, "\n; line ~D", set_plist_1(sc, wrap_integer(sc, line)), 11); + else + if (sc->input_port_stack_loc > 0) + { + s7_pointer p = sc->input_port_stack[sc->input_port_stack_loc - 1]; + if ((is_input_port(p)) && + (port_file(p) != stdin) && + (!port_is_closed(p))) + { + filename = port_filename(p); + line = port_line_number(p); + if (filename) + format_to_error_port(sc, "\n; ~A[~D]", + set_plist_2(sc, wrap_string(sc, filename, port_filename_length(current_input_port(sc))), + wrap_integer(sc, line)), 10); + }}} + else + { + const char *call_name = sc->s7_call_name; + if (call_name) + { + sc->s7_call_name = NULL; + if ((sc->s7_call_file) && + (sc->s7_call_line >= 0)) + format_to_error_port(sc, "\n; ~A ~A[~D]", + set_plist_3(sc, + s7_make_string_wrapper(sc, call_name), + s7_make_string_wrapper(sc, sc->s7_call_file), + wrap_integer(sc, sc->s7_call_line)), 13); + }} + s7_newline(sc, current_error_port(sc)); + } + /* look for __func__ in the error environment etc */ + if (current_error_port(sc) != sc->F) + { + s7_pointer errp = s7_stacktrace(sc); + if (string_length(errp) > 0) + { + port_write_string(current_error_port(sc))(sc, string_value(errp), string_length(errp), current_error_port(sc)); + port_write_character(current_error_port(sc))(sc, '\n', current_error_port(sc)); + }} + else + if (is_pair(slot_value(sc->error_code))) + { + format_to_error_port(sc, "; ~S", set_plist_1(sc, slot_value(sc->error_code)), 7); + s7_newline(sc, current_error_port(sc)); + } + /* if (is_continuation(type)) + * go into repl here with access to continuation? Or expect *error-handler* to deal with it? + */ + sc->value = type; + sc->cur_op = OP_ERROR_QUIT; + } + LongJmp(*(sc->goto_start), ERROR_JUMP); +} + +s7_pointer s7_error(s7_scheme *sc, s7_pointer type, s7_pointer info) /* s7.h backwards compatibility */ +{ + error_nr(sc, type, info); + return(type); +} + +static noreturn void read_error_1_nr(s7_scheme *sc, const char *errmsg, bool string_error) +{ + /* read errors happen before the evaluator gets involved, so forms such as: + * (catch #t (lambda () (car '( . ))) (lambda arg 'error)) + * do not catch the error if we simply signal an error when we encounter it. + */ + s7_pointer pt = current_input_port(sc); + + if (!string_error) + { + /* make an heroic effort to find where we slid off the tracks */ + if (is_string_port(current_input_port(sc))) + { + #define QUOTE_SIZE 40 + s7_int i, j, start = 0, end, slen, size; + char *recent_input = NULL; + + /* we can run off the end in cases like (eval-string "(. . ,.)") or (eval-string " (@ . ,.)") */ + if (port_position(pt) >= port_data_size(pt)) + port_position(pt) = port_data_size(pt) - 1; + + /* start at current position and look back a few chars */ + for (i = port_position(pt), j = 0; (i > 0) && (j < QUOTE_SIZE); i--, j++) + if ((port_data(pt)[i] == '\0') || (port_data(pt)[i] == '\n') || (port_data(pt)[i] == '\r')) + break; + start = i; + + /* start at current position and look ahead a few chars */ + size = port_data_size(pt); + for (i = port_position(pt), j = 0; (i < size) && (j < QUOTE_SIZE); i++, j++) + if ((port_data(pt)[i] == '\0') || (port_data(pt)[i] == '\n') || (port_data(pt)[i] == '\r')) + break; + end = i; + + slen = end - start; /* hopefully this is more or less the current line where the read error happened */ + if (slen > 0) + { + recent_input = (char *)Calloc(slen + 9, 1); + for (i = 0; i < (slen + 8); i++) recent_input[i] = '.'; + recent_input[3] = ' '; + recent_input[slen + 4] = ' '; + for (i = 0; i < slen; i++) recent_input[i + 4] = port_data(pt)[start + i]; + } + + if ((port_line_number(pt) > 0) && + (port_filename(pt))) + { + s7_int len = safe_strlen(recent_input) + safe_strlen(errmsg) + port_filename_length(pt) + safe_strlen(sc->current_file) + 64; + s7_pointer p = make_empty_string(sc, len, '\0'); + char *msg = string_value(p); + string_length(p) = snprintf(msg, len, "%s: %s %s[%u], last top-level form at: %s[%" ld64 "]", + errmsg, (recent_input) ? recent_input : "", port_filename(pt), port_line_number(pt), + sc->current_file, sc->current_line); + if (recent_input) free(recent_input); + error_nr(sc, sc->read_error_symbol, set_elist_1(sc, p)); + } + else + { + s7_int len = safe_strlen(recent_input) + safe_strlen(errmsg) + safe_strlen(sc->current_file) + 64; + s7_pointer p = make_empty_string(sc, len, '\0'); + char *msg = string_value(p); + if ((sc->current_file) && + (sc->current_line >= 0)) + string_length(p) = snprintf(msg, len, "%s: %s, last top-level form at %s[%" ld64 "]", + errmsg, (recent_input) ? recent_input : "", + sc->current_file, sc->current_line); + else string_length(p) = snprintf(msg, len, "%s: %s", errmsg, (recent_input) ? recent_input : ""); + if (recent_input) free(recent_input); + error_nr(sc, sc->read_error_symbol, set_elist_1(sc, p)); + }}} + + if ((port_line_number(pt) > 0) && + (port_filename(pt))) + { + s7_int nlen = 0; + s7_int len = safe_strlen(errmsg) + port_filename_length(pt) + safe_strlen(sc->current_file) + 128; + s7_pointer p = make_empty_string(sc, len, '\0'); + char *msg = string_value(p); + if (string_error) + nlen = snprintf(msg, len, "%s %s[%u],\n; possible culprit: \"%s...\"\n; last top-level form at %s[%" ld64 "]", + errmsg, port_filename(pt), port_line_number(pt), + sc->strbuf, sc->current_file, sc->current_line); + else nlen = snprintf(msg, len, "%s %s[%u], last top-level form at %s[%" ld64 "]", + errmsg, port_filename(pt), port_line_number(pt), + sc->current_file, sc->current_line); + string_length(p) = nlen; + error_nr(sc, sc->read_error_symbol, set_elist_1(sc, p)); + } + error_nr(sc, (string_error) ? sc->string_read_error_symbol : sc->read_error_symbol, + set_elist_1(sc, s7_make_string_wrapper(sc, errmsg))); +} + +static noreturn void read_error_nr(s7_scheme *sc, const char *errmsg) {read_error_1_nr(sc, errmsg, false);} +static noreturn void string_read_error_nr(s7_scheme *sc, const char *errmsg) {read_error_1_nr(sc, errmsg, true);} + +static s7_pointer g_error(s7_scheme *sc, s7_pointer args) +{ + #define H_error "(error type ...) signals an error. The 'type' can be used with catch to trap \ +particular errors. If the error is not caught, s7 treats the second argument as a format control string, \ +and applies it to the rest of the arguments." + #define Q_error s7_make_circular_signature(sc, 1, 2, sc->values_symbol, sc->T) + + if (is_string(car(args))) /* a CL-style error -- use tag='no-catch */ + error_nr(sc, make_symbol(sc, "no-catch", 8), args); + error_nr(sc, car(args), cdr(args)); + return(sc->unspecified); +} + +static char *truncate_string(char *form, s7_int len, use_write_t use_write) +{ + uint8_t *f = (uint8_t *)form; + s7_int i; + if (use_write != P_DISPLAY) + { + /* I guess we need to protect the outer double quotes in this case */ + for (i = len - 5; i >= (len / 2); i--) + if (is_white_space((int32_t)f[i])) + return(form); + i = len - 5; + if (i > 0) {form[i] = '.'; form[i + 1] = '.'; form[i + 2] = '.'; form[i + 3] = '"'; form[i + 4] = '\0';} + else + if (len >= 2) + { + form[len - 1] = '"'; + form[len] = '\0'; + }} + else + { + for (i = len - 4; i >= (len / 2); i--) + if (is_white_space((int32_t)f[i])) + { + form[i] = '.'; form[i + 1] = '.'; form[i + 2] = '.'; form[i + 3] = '\0'; + return(form); + } + i = len - 4; + if (i >= 0) {form[i] = '.'; form[i + 1] = '.'; form[i + 2] = '.'; form[i + 3] = '\0';} + else form[len] = '\0'; + } + return(form); +} + +static s7_pointer object_to_truncated_string(s7_scheme *sc, s7_pointer p, s7_int len) +{ + char *s; + s7_int s_len; + s7_pointer strp; + sc->objstr_max_len = len + 2; + strp = s7_object_to_string(sc, p, false); + s = string_value(strp); + sc->objstr_max_len = S7_INT64_MAX; + s_len = string_length(strp); + if (s_len > len) + truncate_string(s, len, P_DISPLAY); + return(strp); +} + +static s7_pointer tree_descend(s7_scheme *sc, s7_pointer p, uint32_t line) +{ + s7_pointer tp; + if (!is_pair(p)) return(NULL); + if (has_location(p)) + { + uint32_t x = (uint32_t)pair_line_number(p); + if (x > 0) + { + if (line == 0) /* first line number we encounter will be the current reader location (i.e. the end of the form) */ + line = x; + else + if (x < line) + return(p); + }} + tp = tree_descend(sc, car(p), line); + return((tp) ? tp : tree_descend(sc, cdr(p), line)); +} + +static noreturn void missing_close_paren_error_nr(s7_scheme *sc) +{ + char *syntax_msg = NULL; + s7_pointer pt = current_input_port(sc); + + if ((unchecked_type(sc->curlet) != T_LET) && (sc->curlet != sc->nil)) + sc->curlet = sc->nil; + + /* check *missing-close-paren-hook* */ + if (hook_has_functions(sc->missing_close_paren_hook)) + { + s7_pointer result; + if ((port_line_number(pt) > 0) && + (port_filename(pt))) + { + integer(slot_value(sc->error_line)) = port_line_number(pt); + integer(slot_value(sc->error_position)) = port_position(pt); + slot_set_value(sc->error_file, wrap_string(sc, port_filename(pt), port_filename_length(pt))); + } + result = s7_call(sc, sc->missing_close_paren_hook, sc->nil); + if (result != sc->unspecified) + g_throw(sc, list_1(sc, result)); + } + + if (is_pair(sc->args)) + { + s7_pointer p = tree_descend(sc, sc->args, 0); + if ((p) && (is_pair(p)) && + (has_location(p))) + { + s7_pointer strp = object_to_truncated_string(sc, p, 40); + char *form = string_value(strp); + s7_int form_len = string_length(strp); + s7_int msg_len = form_len + 128; + syntax_msg = (char *)Malloc(msg_len); + snprintf(syntax_msg, msg_len, "; current form awaiting a close paren starts around line %u: %s", (uint32_t)pair_line_number(p), form); + }} + + if ((port_line_number(pt) > 0) && + (port_filename(pt))) + { + s7_int nlen; + s7_int len = port_filename_length(pt) + safe_strlen(sc->current_file) + safe_strlen(syntax_msg) + 128; + s7_pointer p = make_empty_string(sc, len, '\0'); + char *msg = string_value(p); + if (syntax_msg) + { + nlen = snprintf(msg, len, "missing close paren, %s[%u], last top-level form at %s[%" ld64 "]\n%s", + port_filename(pt), port_line_number(pt), + sc->current_file, sc->current_line, syntax_msg); + free(syntax_msg); + } + else nlen = snprintf(msg, len, "missing close paren, %s[%u], last top-level form at %s[%" ld64 "]", + port_filename(pt), port_line_number(pt), + sc->current_file, sc->current_line); + string_length(p) = nlen; + error_nr(sc, sc->read_error_symbol, set_elist_1(sc, p)); + } + + if (syntax_msg) + { + s7_int len = safe_strlen(syntax_msg) + 128; + s7_pointer p = make_empty_string(sc, len, '\0'); + char *msg = string_value(p); + len = catstrs(msg, len, "missing close paren\n", syntax_msg, "\n", (char *)NULL); + free(syntax_msg); + string_length(p) = len; + error_nr(sc, sc->read_error_symbol, set_elist_1(sc, p)); + } + if ((is_input_port(pt)) && + (!port_is_closed(pt)) && + (port_data(pt)) && + (port_position(pt) > 0)) + { + s7_pointer p = make_empty_string(sc, 128, '\0'); + s7_int pos = port_position(pt); + s7_int start = pos - 40; + char *msg = string_value(p); + memcpy((void *)msg, (const void *)"missing close paren: ", 21); + if (start < 0) start = 0; + memcpy((void *)(msg + 21), (void *)(port_data(pt) + start), pos - start); + string_length(p) = 21 + pos - start; + error_nr(sc, sc->read_error_symbol, set_elist_1(sc, p)); + } + error_nr(sc, sc->read_error_symbol, set_elist_1(sc, wrap_string(sc, "missing close paren", 19))); +} + +static noreturn void improper_arglist_error_nr(s7_scheme *sc) +{ + /* sc->code is the last (dotted) arg, sc->args is the arglist reversed not including sc->code + * the original was `(func ,@(reverse args) . ,code) essentially where func is sc->value or pop_op_stack(sc) + */ + s7_pointer func = pop_op_stack(sc); + if (sc->args == sc->nil) /* (abs . 1) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "attempt to evaluate (~S . ~S)?", 30), func, sc->code)); + error_nr(sc, sc->syntax_error_symbol, + set_elist_4(sc, wrap_string(sc, "attempt to evaluate (~S ~S . ~S)?", 33), + func, sc->args = proper_list_reverse_in_place(sc, sc->args), sc->code)); +} + +static void op_error_hook_quit(s7_scheme *sc) +{ + let_set(sc, closure_let(sc->error_hook), sc->body_symbol, sc->code); /* restore old value */ + let_set(sc, closure_let(sc->let_temp_hook), sc->body_symbol, sc->nil); + /* now mimic the end of the normal error handler. Since this error hook evaluation can happen + * in an arbitrary s7_call nesting, we can't just return from the current evaluation -- + * we have to jump to the original (top-level) call. Otherwise '# or whatever + * is simply treated as the (non-error) return value, and the higher level evaluations + * get confused. + */ + stack_reset(sc); /* is this necessary? is it a good idea?? */ + push_stack_op(sc, OP_ERROR_QUIT); /* added 3-Dec-16: try to make sure we actually exit! */ + sc->cur_op = OP_ERROR_QUIT; + if (sc->longjmp_ok) + LongJmp(*(sc->goto_start), ERROR_QUIT_JUMP); +} + + +/* -------------------------------- begin_hook -------------------------------- */ +void (*s7_begin_hook(s7_scheme *sc))(s7_scheme *sc, bool *val) {return(sc->begin_hook);} + +void s7_set_begin_hook(s7_scheme *sc, void (*hook)(s7_scheme *sc, bool *val)) +{ + sc->begin_hook = hook; + sc->begin_op = (hook) ? OP_BEGIN_HOOK : OP_BEGIN_NO_HOOK; +} + +static bool call_begin_hook(s7_scheme *sc) +{ + bool result = false; + /* originally begin_hook was bool (*hook)(s7_scheme *sc): the value was returned directly, + * rather than going through a *bool arg (&result below). That works in gcc (Linux/OSX), + * but does not work in MS Visual C++. In the latter, the compiler apparently completely + * eliminates any local, returning (for example) a thread-relative stack-allocated value + * directly, but then by the time we get here, that variable has vanished, and we get + * garbage. We had to thwart the optimization by adding if ((flag) && (!flag)) fprintf(...); + * So, in the new form (26-Jun-13), the value is passed directly into an s7 variable + * that I hope can't be optimized out of existence. + * + * cm/src/Scheme.cpp, used in Snd (listener looking for C-g I think) + * originally this facility was aimed at interrupting infinite loops, and the expected usage was: + * set begin_hook, eval-string(...), unset begin_hook + */ + opcode_t op = sc->cur_op; + push_stack_direct(sc, OP_BARRIER); + sc->begin_hook(sc, &result); + if (result) + { + s7_pointer cur_code = current_code(sc); + /* set (owlet) in case we were interrupted and need to see why something was hung */ + slot_set_value(sc->error_type, sc->F); + slot_set_value(sc->error_data, sc->value); /* was sc->F but we now clobber this below */ + slot_set_value(sc->error_code, cur_code); + if (has_location(cur_code)) + { + integer(slot_value(sc->error_line)) = (s7_int)pair_line_number(cur_code); + slot_set_value(sc->error_file, sc->file_names[pair_file_number(cur_code)]); + integer(slot_value(sc->error_position)) = (s7_int)pair_position(cur_code); + } + else + { + integer(slot_value(sc->error_line)) = 0; + integer(slot_value(sc->error_position)) = 0; + slot_set_value(sc->error_file, sc->F); + } +#if WITH_HISTORY + slot_set_value(sc->error_history, sc->F); +#endif + let_set_outlet(sc->owlet, sc->curlet); + sc->value = make_symbol(sc, "begin-hook-interrupt", 20); + /* otherwise the evaluator returns whatever random thing is in sc->value (normally #) + * which makes debugging unnecessarily difficult. ?? why not return something useful? make return s7_pointer*, not bool* + */ + s7_quit(sc); /* don't call gc here -- eval_c_string is the context -- allows interrupt of infinite loop */ + return(true); + } + pop_stack_no_op(sc); + sc->cur_op = op; /* for better error handling. otherwise we get "barrier" as the offending function name in syntax_error */ + return(false); +} + + +/* -------------------------------- apply -------------------------------- */ +static s7_pointer apply_list_star(s7_scheme *sc, s7_pointer d) +{ + /* we check this ahead of time: if (is_null(cdr(d))) return(car(d)); */ + s7_pointer p; + gc_protect_via_stack(sc, d); + p = cons(sc, car(d), cdr(d)); + sc->w = p; + while (is_not_null(cddr(p))) + { + d = cdr(d); + set_cdr(p, cons(sc, car(d), cdr(d))); + if (is_not_null(cdr(d))) + p = cdr(p); + } + unstack(sc); + set_cdr(p, cadr(p)); + return(sc->w); +} + +static noreturn void apply_list_error_nr(s7_scheme *sc, s7_pointer lst) +{ + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_2(sc, wrap_string(sc, "apply's last argument should be a proper list: ~S", 49), lst)); +} + +static s7_pointer g_apply(s7_scheme *sc, s7_pointer args) +{ + #define H_apply "(apply func ...) applies func to the rest of the arguments" + #define Q_apply s7_make_circular_signature(sc, 2, 3, sc->values_symbol, s7_make_signature(sc, 2, sc->is_procedure_symbol, sc->is_sequence_symbol), sc->T) + + /* can apply always be replaced with apply values? (apply + '(1 2 3)) is the same as (+ (apply values '(1 2 3))) + * not if apply* in disguise, I think: (apply + 1 2 ()) -> 3, (apply + 1 2 (apply values ())) -> error + */ + s7_pointer func = car(args); + if (!is_applicable(func)) + apply_error_nr(sc, func, args); + + if (is_null(cdr(args))) + { + push_stack(sc, OP_APPLY, sc->nil, func); + return(sc->nil); + } + if (is_safe_procedure(func)) + { + s7_pointer p, q; + for (q = args, p = cdr(args); is_not_null(cdr(p)); q = p, p = cdr(p)); + /* the last arg is supposed to be a list, it will be spliced onto the end of the previous arg list (if any) below */ + + if (!s7_is_proper_list(sc, car(p))) /* (apply + #f) etc, the cycle protection here is checked in s7test */ + apply_list_error_nr(sc, args); + set_cdr(q, car(p)); /* args affected, so don't depend on cdr(args) from above */ + + if (is_c_function(func)) /* handle in-place to get better error messages */ + { + s7_int len; + uint8_t typ = type(func); + if (typ == T_C_RST_NO_REQ_FUNCTION) + return(c_function_call(func)(sc, cdr(args))); + len = proper_list_length(cdr(args)); + if (c_function_max_args(func) < len) + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_3(sc, too_many_arguments_string, func, set_ulist_1(sc, sc->apply_symbol, args))); + if ((typ == T_C_FUNCTION) && + (len < c_function_min_args(func))) + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_3(sc, not_enough_arguments_string, func, set_ulist_1(sc, sc->apply_symbol, args))); + return(c_function_call(func)(sc, cdr(args))); + } + push_stack(sc, OP_APPLY, cdr(args), func); + return(sc->nil); + } + sc->code = func; + sc->args = (is_null(cddr(args))) ? cadr(args) : apply_list_star(sc, cdr(args)); + if (!s7_is_proper_list(sc, sc->args)) + apply_list_error_nr(sc, sc->args); + + /* (define imp (immutable! (cons 0 (immutable! (cons 1 (immutable! (cons 2 ()))))))) + * (define (fop4 x y) (apply x y)) + * (display (object->string (apply (lambda (a . b) (cons a b)) imp) :readable)) -> (list 0 1 2) + * (display (object->string (fop4 (lambda (a . b) (cons a b)) imp) :readable)) -> (cons 0 (immutable! (cons 1 (immutable! (cons 2 ()) + * g_apply sees the first one and thinks the lambda arg is unsafe, apply_ss sees the second and thinks it is safe (hence the list is not copied), + * so calling sort on the first is fine, but on the second gets an immutable object error. + */ + sc->args = (needs_copied_args(sc->code)) ? copy_proper_list(sc, sc->args) : sc->args; + push_stack_direct(sc, OP_APPLY); + return(sc->nil); +} + +s7_pointer s7_apply_function(s7_scheme *sc, s7_pointer fnc, s7_pointer args) +{ + TRACK(sc); + if (is_c_function(fnc)) + return(c_function_call(fnc)(sc, args)); + /* if [if (!is_applicable(fnc)) apply_error_nr(sc, fnc, sc->args);] here, needs_copied_args can be T_App */ + push_stack_direct(sc, OP_EVAL_DONE); + sc->code = fnc; + sc->args = (needs_copied_args(sc->code)) ? copy_proper_list(sc, args) : args; + eval(sc, OP_APPLY); + /* we're limited in choices here -- the caller might be (say) car(sc->t1_1) = fn_proc(...) where the fn_proc + * happens to fallback on a method -- we can't just push OP_APPLY and drop back into the evaluator normally. + */ + return(sc->value); +} + +static s7_pointer apply_method_closure(s7_scheme *sc, s7_pointer func, s7_pointer args) +{ + push_stack_direct(sc, OP_EVAL_DONE); + sc->code = func; + sc->args = (needs_copied_args(sc->code)) ? copy_proper_list(sc, args) : args; + sc->curlet = make_let(sc, closure_let(sc->code)); + eval(sc, OP_APPLY_LAMBDA); + return(sc->value); +} + +static inline s7_pointer apply_c_function(s7_scheme *sc, s7_pointer func, s7_pointer args); + +static s7_pointer implicit_index_checked(s7_scheme *sc, s7_pointer obj, s7_pointer in_obj, s7_pointer indices) +{ + if (!is_applicable(in_obj)) + error_nr(sc, sc->syntax_error_symbol, + set_elist_4(sc, wrap_string(sc, "~$ becomes ~$, but ~S can't take arguments", 42), + set_ulist_1(sc, obj, indices), cons(sc, in_obj, cdr(indices)), in_obj)); + return(implicit_index(sc, in_obj, cdr(indices))); +} + +static s7_pointer implicit_index(s7_scheme *sc, s7_pointer obj, s7_pointer indices) +{ + /* (let ((lst '("12" "34"))) (lst 0 1)) -> #\2 + * (let ((lst (list #(1 2) #(3 4)))) (lst 0 1)) -> 2 + * this can get tricky: ((list (lambda (a) (+ a 1)) (lambda (b) (* b 2))) 1 2) -> 4 + * but what if func takes rest/optional args, etc: ((list (lambda args (car args))) 0 "hi" 0) + * should this return #\h or "hi"?? currently it is "hi" which is consistent with ((lambda args (car args)) "hi" 0) + * but ((lambda (arg) arg) "hi" 0) is currently an error (too many arguments) + * maybe it should be (((lambda (arg) arg) "hi") 0) -> #\h + */ + s7_pointer res, in_obj; + switch (type(obj)) + { + case T_VECTOR: /* (#(#(1 2) #(3 4)) 1 1) -> 4 */ + return(vector_ref_1(sc, obj, indices)); + + case T_FLOAT_VECTOR: + res = univect_ref(sc, set_ulist_1(sc, obj, indices), sc->float_vector_ref_symbol, T_FLOAT_VECTOR); + set_car(sc->u1_1, sc->F); + return(res); + + case T_INT_VECTOR: + res = univect_ref(sc, set_ulist_1(sc, obj, indices), sc->int_vector_ref_symbol, T_INT_VECTOR); + set_car(sc->u1_1, sc->F); + return(res); + + case T_BYTE_VECTOR: + res = univect_ref(sc, set_ulist_1(sc, obj, indices), sc->byte_vector_ref_symbol, T_BYTE_VECTOR); + set_car(sc->u1_1, sc->F); + return(res); + + case T_STRING: /* (#("12" "34") 0 1) -> #\2 */ + if (!is_null(cdr(indices))) + error_nr(sc, sc->wrong_number_of_args_symbol, set_elist_3(sc, too_many_arguments_string, obj, indices)); + if (!is_t_integer(car(indices))) + wrong_type_error_nr(sc, sc->string_ref_symbol, 2, car(indices), sc->type_names[T_INTEGER]); + return(string_ref_p_pi_unchecked(sc, obj, integer(car(indices)))); + + case T_PAIR: /* (#((1 2) (3 4)) 1 0) -> 3, (#((1 (2 3))) 0 1 0) -> 2 */ + in_obj = list_ref_1(sc, obj, car(indices)); + if (is_pair(cdr(indices))) + return(implicit_index_checked(sc, obj, in_obj, indices)); + return(in_obj); + + case T_HASH_TABLE: /* ((vector (hash-table '(a . 1) '(b . 2))) 0 'a) -> 1 */ + in_obj = s7_hash_table_ref(sc, obj, car(indices)); + if (is_pair(cdr(indices))) + return(implicit_index_checked(sc, obj, in_obj, indices)); + return(in_obj); + + case T_LET: + in_obj = let_ref(sc, obj, car(indices)); + if (is_pair(cdr(indices))) + return(implicit_index_checked(sc, obj, in_obj, indices)); + return(in_obj); + + case T_C_OBJECT: + res = (*(c_object_ref(sc, obj)))(sc, set_ulist_1(sc, obj, indices)); + set_car(sc->u1_1, sc->F); + return(res); + + case T_ITERATOR: /* indices is not nil, so this is an error */ + error_nr(sc, sc->wrong_number_of_args_symbol, set_elist_3(sc, too_many_arguments_string, obj, indices)); + + case T_CLOSURE: case T_CLOSURE_STAR: + if (!is_safe_closure(obj)) /* s7_call can't work in general with unsafe stuff */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "can't call a (possibly unsafe) function implicitly: ~S ~S", 57), obj, indices)); + check_stack_size(sc); + sc->temp10 = indices; /* (needs_copied_args(obj)) ? copy_proper_list(sc, indices) : indices; */ /* s7_call copies and this is safe? 2-Oct-22 (and below) */ + sc->value = s7_call(sc, obj, sc->temp10); + sc->temp10 = sc->unused; + if ((S7_DEBUGGING) && (is_multiple_value(sc->value))) fprintf(stderr, "mv: %s %s %s\n", display(obj), display(indices), display(sc->value)); + /* if mv: sc->value = splice_in_values(sc, multiple_value(sc->value)); */ + return(sc->value); + + case T_C_FUNCTION: + return(apply_c_function(sc, obj, indices)); + + case T_C_RST_NO_REQ_FUNCTION: + return(c_function_call(obj)(sc, indices)); + + default: + if (!is_applicable(obj)) /* (apply (list cons cons) (list 1 2)) needs the argnum check mentioned below */ + apply_error_nr(sc, obj, indices); + sc->temp10 = indices; /* (needs_copied_args(obj)) ? copy_proper_list(sc, indices) : indices; */ /* do not use sc->args here! */ + sc->value = s7_call(sc, obj, sc->temp10); + sc->temp10 = sc->unused; + if (is_multiple_value(sc->value)) + sc->value = splice_in_values(sc, multiple_value(sc->value)); + return(sc->value); + } +} + +static inline void fill_star_defaults(s7_scheme *sc, s7_pointer func, int32_t start_arg, int32_t n_args, s7_pointer par) +{ + s7_pointer *df = c_function_arg_defaults(func); + if (c_func_has_simple_defaults(func)) + for (int32_t i = start_arg; i < n_args; i++, par = cdr(par)) + set_car(par, df[i]); + else + for (int32_t i = start_arg; i < n_args; i++, par = cdr(par)) + { + s7_pointer defval = df[i]; + if (is_symbol(defval)) + set_car(par, lookup_checked(sc, defval)); + else set_car(par, (is_pair(defval)) ? s7_eval(sc, defval, sc->nil) : defval); + } +} + +static s7_pointer set_c_function_star_args(s7_scheme *sc) +{ + int32_t i, j; + s7_pointer arg, par, call_args, func = sc->code; + s7_pointer *df; + int32_t n_args = c_function_max_args(func); /* not counting keywords, I think */ + + if (is_safe_procedure(func)) + call_args = c_function_call_args(func); + else + { + call_args = make_list(sc, c_function_optional_args(func), sc->F); + gc_protect_via_stack(sc, call_args); + } + + /* assume at the start that there are no keywords */ + for (i = 0, arg = sc->args, par = call_args; (i < n_args) && (is_pair(arg)); i++, arg = cdr(arg), par = cdr(par)) + if (!is_symbol_and_keyword(car(arg))) + set_car(par, car(arg)); + else + { + s7_pointer kpar, karg; + int32_t ki; + /* oops -- there are keywords, change scanners (much duplicated code...) + * setting checked on the call_args here rather than parsing the parameters to use add_symbol_to_list + */ + for (kpar = call_args; kpar != par; kpar = cdr(kpar)) + set_checked(kpar); + for (; is_pair(kpar); kpar = cdr(kpar)) + clear_checked(kpar); + df = c_function_arg_names(func); + for (ki = i, karg = arg, kpar = par; (ki < n_args) && (is_pair(karg)); ki++, karg = cdr(karg)) + if (!is_symbol_and_keyword(car(karg))) + { + if (is_checked(kpar)) + { + if (!is_safe_procedure(func)) unstack(sc); + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_3(sc, parameter_set_twice_string, car(kpar), sc->args)); + } + set_checked(kpar); + set_car(kpar, car(karg)); + kpar = cdr(kpar); + } + else + { + s7_pointer p; + for (j = 0, p = call_args; j < n_args; j++, p = cdr(p)) + if (df[j] == car(karg)) + break; + if (j == n_args) + { + if (!c_function_allows_other_keys(func)) + { + if (!is_safe_procedure(func)) unstack(sc); + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_2(sc, wrap_string(sc, "~A: not a parameter name?", 25), car(karg))); + } + karg = cdr(karg); + if (is_null(karg)) /* (f :x) where f arglist includes :allow-other-keys */ + { + if (!is_safe_procedure(func)) unstack(sc); + error_nr(sc, sc->syntax_error_symbol, + set_elist_4(sc, wrap_string(sc, "~A: key ~S, but no value: ~S", 28), func, car(arg), sc->args)); + } + ki--; + } + else + { + if (is_checked(p)) + { + if (!is_safe_procedure(func)) unstack(sc); + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_3(sc, parameter_set_twice_string, car(p), sc->args)); + } + if (!is_pair(cdr(karg))) + { + if (!is_safe_procedure(func)) unstack(sc); + error_nr(sc, sc->syntax_error_symbol, + set_elist_4(sc, wrap_string(sc, "~A: key ~S, but no value: ~S", 28), func, car(karg), sc->args)); + } + set_checked(p); + karg = cdr(karg); + set_car(p, car(karg)); + kpar = cdr(kpar); + }} + if ((!is_null(karg)) && (!c_function_allows_other_keys(func))) + { + if (!is_safe_procedure(func)) unstack(sc); + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_4(sc, wrap_string(sc, "~A: too many arguments: (~A~{~^ ~S~})", 37), func, func, sc->args)); + } + if (ki < n_args) + { + df = c_function_arg_defaults(func); + if (c_func_has_simple_defaults(func)) + { + for (ki = i, kpar = par; ki < n_args; ki++, kpar = cdr(kpar)) + if (!is_checked(kpar)) + set_car(kpar, df[ki]); + } + else + for (ki = i, kpar = par; ki < n_args; ki++, kpar = cdr(kpar)) + if (!is_checked(kpar)) + { + s7_pointer defval = df[ki]; + if (is_symbol(defval)) + set_car(kpar, lookup_checked(sc, defval)); + else set_car(kpar, (is_pair(defval)) ? s7_eval(sc, defval, sc->nil) : defval); + }} + if (!is_safe_procedure(func)) unstack(sc); + return(call_args); + } + if (!is_null(arg)) + { + if (!is_safe_procedure(func)) unstack(sc); + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_4(sc, wrap_string(sc, "~A: too many arguments: (~A~{~^ ~S~})", 37), func, func, sc->args)); + } + if (i < n_args) + fill_star_defaults(sc, func, i, n_args, par); + if (!is_safe_procedure(func)) unstack(sc); + return(call_args); +} + +static s7_pointer set_c_function_star_defaults(s7_scheme *sc, int32_t num) +{ + s7_pointer call_args, func = sc->code, par; + int32_t n_args = c_function_max_args(func); + + if (is_safe_procedure(func)) + call_args = c_function_call_args(func); + else + { + call_args = make_list(sc, c_function_optional_args(func), sc->F); + gc_protect_via_stack(sc, call_args); + } + par = call_args; + if (num == 1) + { + set_car(par, car(sc->args)); + par = cdr(par); + } + fill_star_defaults(sc, func, num, n_args, par); + if (!is_safe_procedure(func)) unstack(sc); + return(call_args); +} + +#define apply_c_function_star(Sc) Sc->value = c_function_call(Sc->code)(Sc, set_c_function_star_args(Sc)) +#define apply_c_function_star_fill_defaults(Sc, Num) Sc->value = c_function_call(Sc->code)(Sc, set_c_function_star_defaults(Sc, Num)) + +s7_pointer s7_apply_function_star(s7_scheme *sc, s7_pointer fnc, s7_pointer args) +{ + TRACK(sc); + if (is_c_function_star(fnc)) + { + sc->w = sc->args; + sc->z = sc->code; + sc->args = T_Ext(args); + sc->code = fnc; + apply_c_function_star(sc); + sc->args = sc->w; + sc->code = sc->z; + return(sc->value); + } + push_stack_direct(sc, OP_EVAL_DONE); + sc->code = fnc; + sc->args = (needs_copied_args(sc->code)) ? copy_proper_list(sc, args) : args; + eval(sc, OP_APPLY); + return(sc->value); +} + +/* -------------------------------- eval -------------------------------- */ +s7_pointer s7_eval(s7_scheme *sc, s7_pointer code, s7_pointer e) +{ + declare_jump_info(); + TRACK(sc); + if (sc->safety > NO_SAFETY) + { + if (!s7_is_valid(sc, code)) + s7_warn(sc, 256, "the second argument to %s (the code to be evaluated): %p, is not an s7 object\n", __func__, code); + if (!s7_is_valid(sc, e)) + s7_warn(sc, 256, "the third argument to %s (the environment): %p, is not an s7 object\n", __func__, e); + } + store_jump_info(sc); + set_jump_info(sc, EVAL_SET_JUMP); + if (jump_loc != NO_JUMP) + { + if (jump_loc != ERROR_JUMP) + eval(sc, sc->cur_op); + } + else + { + push_stack_direct(sc, OP_EVAL_DONE); + sc->code = code; + if ((e != sc->rootlet) && + (is_let(e))) + set_curlet(sc, e); + else sc->curlet = sc->nil; + eval(sc, OP_EVAL); + } + restore_jump_info(sc); + + if (is_multiple_value(sc->value)) + sc->value = splice_in_values(sc, multiple_value(sc->value)); + return(sc->value); +} + +s7_pointer s7_eval_with_location(s7_scheme *sc, s7_pointer code, s7_pointer e, const char *caller, const char *file, s7_int line) +{ + s7_pointer result; + if (caller) + { + sc->s7_call_name = caller; + sc->s7_call_file = file; + sc->s7_call_line = line; + } + result = s7_eval(sc, code, e); + if (caller) + { + sc->s7_call_name = NULL; + sc->s7_call_file = NULL; + sc->s7_call_line = -1; + } + return(result); +} + +static s7_pointer g_eval(s7_scheme *sc, s7_pointer args) +{ + #define H_eval "(eval code (let (curlet))) evaluates code in the environment let. 'let' \ +defaults to the curlet; to evaluate something in the top-level environment instead, \ +pass (rootlet):\n\ +\n\ + (define x 32) \n\ + (let ((x 3))\n\ + (eval 'x (rootlet)))\n\ +\n\ + returns 32" + #define Q_eval s7_make_signature(sc, 3, sc->values_symbol, sc->T, sc->is_let_symbol) + + if (is_not_null(cdr(args))) + { + s7_pointer e = cadr(args); + if (!is_let(e)) + wrong_type_error_nr(sc, sc->eval_symbol, 2, e, a_let_string); + set_curlet(sc, (e == sc->rootlet) ? sc->nil : e); + } + sc->code = car(args); + + if ((sc->safety > NO_SAFETY) && + (is_pair(sc->code))) + { + check_free_heap_size(sc, 8192); + sc->code = copy_body(sc, sc->code); + } + else + if (is_optimized(sc->code)) + clear_all_optimizations(sc, sc->code); /* clears "unsafe" ops, not all ops */ + + set_current_code(sc, sc->code); + if (stack_top(sc) < 12) + push_stack_op(sc, OP_BARRIER); + push_stack_direct(sc, OP_EVAL); + return(sc->nil); +} + + +s7_pointer s7_call(s7_scheme *sc, s7_pointer func, s7_pointer args) +{ + if (is_c_function(func)) + return(c_function_call(func)(sc, args)); /* no check for wrong-number-of-args -- is that reasonable? maybe use apply_c_function(sc, func, args) */ + { + declare_jump_info(); + TRACK(sc); + set_current_code(sc, history_cons(sc, func, args)); + if (SHOW_EVAL_OPS) safe_print(fprintf(stderr, "%s: %s %s\n", __func__, display(func), display_80(args))); + + sc->temp4 = T_App(func); /* this is feeble GC protection */ + sc->temp2 = T_Lst(args); /* only use of temp2 */ + + store_jump_info(sc); + set_jump_info(sc, S7_CALL_SET_JUMP); + if (jump_loc != NO_JUMP) + { + if (jump_loc != ERROR_JUMP) + eval(sc, sc->cur_op); + + if ((jump_loc == CATCH_JUMP) && /* we're returning (back to eval) from an error in catch */ + (sc->stack_end == sc->stack_start)) + push_stack_op(sc, OP_ERROR_QUIT); + } + else + { + if (sc->safety > NO_SAFETY) + check_list_validity(sc, __func__, args); + push_stack_direct(sc, OP_EVAL_DONE); /* this saves the current evaluation and will eventually finish this (possibly) nested call */ + sc->code = func; + sc->args = (needs_copied_args(func)) ? copy_proper_list(sc, args) : args; + eval(sc, OP_APPLY); + } + restore_jump_info(sc); + /* don't clear temp4 or temp2 here -- lots of (Snd) code calls s7_call repeatedly and assumes the "func" arg is protected between calls */ + return(sc->value); + } +} + +s7_pointer s7_call_with_location(s7_scheme *sc, s7_pointer func, s7_pointer args, const char *caller, const char *file, s7_int line) +{ + s7_pointer result; + if (caller) + { + sc->s7_call_name = caller; + sc->s7_call_file = file; + sc->s7_call_line = line; + } + result = s7_call(sc, func, args); + if (caller) + { + sc->s7_call_name = NULL; + sc->s7_call_file = NULL; + sc->s7_call_line = -1; + } + return(result); +} + + +/* -------------------------------- type-of -------------------------------- */ +#if (!WITH_GCC) +static inline bool gen_type_match(s7_scheme *sc, s7_pointer val, uint8_t typ) /* opt3_byte = uint8_t */ +{ + return((type(val) == typ) || + ((has_active_methods(sc, val)) && + (apply_boolean_method(sc, val, sc->type_to_typers[typ]) != sc->F))); +} +#else +#define gen_type_match(Sc, Val, Typ) \ + ({s7_pointer _val_ = Val; \ + ((type(_val_) == Typ) || \ + ((has_active_methods(Sc, _val_)) && \ + (apply_boolean_method(Sc, _val_, Sc->type_to_typers[Typ]) != Sc->F)));}) +#endif + +static void init_typers(s7_scheme *sc) +{ + sc->type_to_typers[T_FREE] = sc->F; + sc->type_to_typers[T_PAIR] = sc->is_pair_symbol; + sc->type_to_typers[T_NIL] = sc->is_null_symbol; + sc->type_to_typers[T_UNUSED] = sc->F; + sc->type_to_typers[T_EOF] = sc->is_eof_object_symbol; + sc->type_to_typers[T_UNDEFINED] = sc->is_undefined_symbol; + sc->type_to_typers[T_UNSPECIFIED] = sc->is_unspecified_symbol; + sc->type_to_typers[T_BOOLEAN] = sc->is_boolean_symbol; + sc->type_to_typers[T_CHARACTER] = sc->is_char_symbol; + sc->type_to_typers[T_SYMBOL] = sc->is_symbol_symbol; /* and keyword? */ + sc->type_to_typers[T_SYNTAX] = sc->is_syntax_symbol; + sc->type_to_typers[T_INTEGER] = sc->is_integer_symbol; + sc->type_to_typers[T_RATIO] = sc->is_rational_symbol; + sc->type_to_typers[T_REAL] = sc->is_float_symbol; + sc->type_to_typers[T_COMPLEX] = sc->is_complex_symbol; + sc->type_to_typers[T_BIG_INTEGER] = sc->is_integer_symbol; + sc->type_to_typers[T_BIG_RATIO] = sc->is_rational_symbol; + sc->type_to_typers[T_BIG_REAL] = sc->is_float_symbol; + sc->type_to_typers[T_BIG_COMPLEX] = sc->is_complex_symbol; + sc->type_to_typers[T_STRING] = sc->is_string_symbol; + sc->type_to_typers[T_BYTE_VECTOR] = sc->is_byte_vector_symbol; + sc->type_to_typers[T_C_OBJECT] = sc->is_c_object_symbol; + sc->type_to_typers[T_VECTOR] = sc->is_vector_symbol; + sc->type_to_typers[T_INT_VECTOR] = sc->is_int_vector_symbol; + sc->type_to_typers[T_FLOAT_VECTOR] = sc->is_float_vector_symbol; + sc->type_to_typers[T_CATCH] = sc->F; + sc->type_to_typers[T_DYNAMIC_WIND] = sc->F; + sc->type_to_typers[T_HASH_TABLE] = sc->is_hash_table_symbol; + sc->type_to_typers[T_LET] = sc->is_let_symbol; + sc->type_to_typers[T_ITERATOR] = sc->is_iterator_symbol; + sc->type_to_typers[T_STACK] = sc->F; + sc->type_to_typers[T_COUNTER] = sc->F; + sc->type_to_typers[T_SLOT] = sc->F; + sc->type_to_typers[T_C_POINTER] = sc->is_c_pointer_symbol; + sc->type_to_typers[T_OUTPUT_PORT] = sc->is_output_port_symbol; + sc->type_to_typers[T_INPUT_PORT] = sc->is_input_port_symbol; + sc->type_to_typers[T_RANDOM_STATE] = sc->is_random_state_symbol; + sc->type_to_typers[T_GOTO] = sc->is_goto_symbol; + sc->type_to_typers[T_CONTINUATION] = sc->is_continuation_symbol; + sc->type_to_typers[T_CLOSURE] = sc->is_procedure_symbol; + sc->type_to_typers[T_CLOSURE_STAR] = sc->is_procedure_symbol; + sc->type_to_typers[T_C_MACRO] = sc->is_macro_symbol; + sc->type_to_typers[T_MACRO] = sc->is_macro_symbol; + sc->type_to_typers[T_MACRO_STAR] = sc->is_macro_symbol; + sc->type_to_typers[T_BACRO] = sc->is_macro_symbol; + sc->type_to_typers[T_BACRO_STAR] = sc->is_macro_symbol; + sc->type_to_typers[T_C_FUNCTION] = sc->is_procedure_symbol; + sc->type_to_typers[T_C_FUNCTION_STAR] = sc->is_procedure_symbol; + sc->type_to_typers[T_C_RST_NO_REQ_FUNCTION] = sc->is_procedure_symbol; +} + +s7_pointer s7_type_of(s7_scheme *sc, s7_pointer arg) {return(sc->type_to_typers[type(arg)]);} + +static s7_pointer g_type_of(s7_scheme *sc, s7_pointer args) +{ + #define H_type_of "(type-of obj) returns a symbol describing obj's type" + #define Q_type_of s7_make_signature(sc, 2, s7_make_signature(sc, 2, sc->is_symbol_symbol, sc->not_symbol), sc->T) + return(sc->type_to_typers[type(car(args))]); +} + + +/* -------------------------------- exit emergency-exit -------------------------------- */ +void s7_quit(s7_scheme *sc) +{ + sc->longjmp_ok = false; + pop_input_port(sc); + stack_reset(sc); + push_stack_op_let(sc, OP_EVAL_DONE); +} + +static s7_pointer g_emergency_exit(s7_scheme *sc, s7_pointer args) +{ + #define H_emergency_exit "(emergency-exit obj) exits s7 immediately" + #define Q_emergency_exit s7_make_signature(sc, 2, sc->T, sc->T) + + s7_pointer obj; +#ifndef EXIT_SUCCESS + #define EXIT_SUCCESS 0 + #define EXIT_FAILURE 1 +#endif + if (is_null(args)) + _exit(EXIT_SUCCESS); /* r7rs spec says use _exit here */ + obj = car(args); + if (obj == sc->F) + _exit(EXIT_FAILURE); + if ((obj == sc->T) || (!s7_is_integer(obj))) + _exit(EXIT_SUCCESS); + _exit((int)s7_integer_clamped_if_gmp(sc, obj)); + return(sc->F); +} + +static s7_pointer g_exit(s7_scheme *sc, s7_pointer args) +{ + #define H_exit "(exit obj) exits s7" + #define Q_exit s7_make_signature(sc, 2, sc->T, sc->T) + /* calling s7_eval_c_string in an atexit function seems to be problematic -- it works, but args can be changed? longjmp perhaps? */ + + s7_quit(sc); + if (show_gc_stats(sc)) + s7_warn(sc, 256, "gc calls %" ld64 " total time: %f\n", sc->gc_calls, (double)(sc->gc_total_time) / ticks_per_second()); + return(g_emergency_exit(sc, args)); +} + +#if WITH_GCC +static s7_pointer g_abort(s7_scheme *sc, s7_pointer args) {abort(); return(NULL);} +#endif + + +/* -------------------------------- optimizer stuff -------------------------------- */ +/* There is a problem with cache misses: a bigger cache reduces one test from 24 seconds to 17 (cachegrind agrees). + * But how to optimize s7 for cache hits? The culprits are eval and gc. Looking at the numbers, + * I think the least affected tests are able to use opt_info optimization which makes everything local? + */ + +#if S7_DEBUGGING +static void check_t_1(s7_scheme *sc, s7_pointer e, const char* func, s7_pointer expr, s7_pointer var) +{ + if (let_slots(e) != s7_slot(sc, var)) + { + fprintf(stderr, "%s%s %s is out of date (%s in %s -> %s)%s\n", bold_text, func, display(expr), display(var), display(sc->curlet), + (tis_slot(let_slots(e))) ? display(let_slots(e)) : "no slots", unbold_text); + if (sc->stop_at_error) abort(); + } +} + +static s7_pointer t_lookup_1(s7_scheme *sc, s7_pointer symbol, const char *func, s7_pointer expr) +{ + check_t_1(sc, sc->curlet, func, expr, symbol); + return(slot_value(let_slots(sc->curlet))); +} + +static s7_pointer T_lookup_1(s7_scheme *sc, s7_pointer symbol, const char *func, s7_pointer expr) +{ + check_t_1(sc, let_outlet(sc->curlet), func, expr, symbol); + return(slot_value(let_slots(let_outlet(sc->curlet)))); +} + +static void check_u_1(s7_scheme *sc, s7_pointer e, const char* func, s7_pointer expr, s7_pointer var) +{ + if (next_slot(let_slots(e)) != s7_slot(sc, var)) + { + fprintf(stderr, "%s%s %s is out of date (%s in %s -> %s)%s\n", bold_text, func, display(expr), display(var), display(e), + (tis_slot(next_slot(let_slots(e)))) ? display(next_slot(let_slots(e))) : "no next slot", unbold_text); + if (sc->stop_at_error) abort(); + } +} + +static s7_pointer u_lookup_1(s7_scheme *sc, s7_pointer symbol, const char *func, s7_pointer expr) +{ + check_u_1(sc, sc->curlet, func, expr, symbol); + return(slot_value(next_slot(let_slots(sc->curlet)))); +} + +static s7_pointer U_lookup_1(s7_scheme *sc, s7_pointer symbol, const char *func, s7_pointer expr) +{ + check_u_1(sc, let_outlet(sc->curlet), func, expr, symbol); + return(slot_value(next_slot(let_slots(let_outlet(sc->curlet))))); +} + +static void check_v_1(s7_scheme *sc, s7_pointer e, const char* func, s7_pointer expr, s7_pointer var) +{ + if (next_slot(next_slot(let_slots(e))) != s7_slot(sc, var)) + { + fprintf(stderr, "%s%s %s is out of date (%s in %s -> %s)%s\n", bold_text, func, display(expr), display(var), display(e), + (tis_slot(next_slot(next_slot(let_slots(e))))) ? display(next_slot(next_slot(let_slots(e)))) : "no next slot", unbold_text); + if (sc->stop_at_error) abort(); + } +} + +static s7_pointer v_lookup_1(s7_scheme *sc, s7_pointer symbol, const char *func, s7_pointer expr) +{ + check_v_1(sc, sc->curlet, func, expr, symbol); + return(slot_value(next_slot(next_slot(let_slots(sc->curlet))))); +} + +static s7_pointer V_lookup_1(s7_scheme *sc, s7_pointer symbol, const char *func, s7_pointer expr) +{ + check_v_1(sc, let_outlet(sc->curlet), func, expr, symbol); + return(slot_value(next_slot(next_slot(let_slots(let_outlet(sc->curlet)))))); +} + +static void check_o_1(s7_scheme *sc, s7_pointer e, const char* func, s7_pointer expr, s7_pointer var) +{ + s7_pointer slot = s7_slot(sc, var); + if (lookup_slot_with_let(var, e) != slot) + { + fprintf(stderr, "%s%s %s is out of date (%s in %s -> %s)%s\n", bold_text, func, display(expr), display(var), display(e), + (tis_slot(slot)) ? display(slot) : "undefined", unbold_text); + if (sc->stop_at_error) abort(); + } +} + +static s7_pointer o_lookup_1(s7_scheme *sc, s7_pointer symbol, const char *func, s7_pointer expr) +{ + check_o_1(sc, let_outlet(sc->curlet), func, expr, symbol); + return(inline_lookup_from(sc, symbol, let_outlet(sc->curlet))); +} + +#define t_lookup(Sc, Symbol, Expr) t_lookup_1(Sc, Symbol, __func__, Expr) +#define u_lookup(Sc, Symbol, Expr) u_lookup_1(Sc, Symbol, __func__, Expr) +#define v_lookup(Sc, Symbol, Expr) v_lookup_1(Sc, Symbol, __func__, Expr) +#define T_lookup(Sc, Symbol, Expr) T_lookup_1(Sc, Symbol, __func__, Expr) +#define U_lookup(Sc, Symbol, Expr) U_lookup_1(Sc, Symbol, __func__, Expr) +#define V_lookup(Sc, Symbol, Expr) V_lookup_1(Sc, Symbol, __func__, Expr) +#define o_lookup(Sc, Symbol, Expr) o_lookup_1(Sc, Symbol, __func__, Expr) +#else +#define t_lookup(Sc, Symbol, Expr) slot_value(let_slots(sc->curlet)) +#define u_lookup(Sc, Symbol, Expr) slot_value(next_slot(let_slots(sc->curlet))) +#define v_lookup(Sc, Symbol, Expr) slot_value(next_slot(next_slot(let_slots(sc->curlet)))) +#define T_lookup(Sc, Symbol, Expr) slot_value(let_slots(let_outlet(sc->curlet))) +#define U_lookup(Sc, Symbol, Expr) slot_value(next_slot(let_slots(let_outlet(sc->curlet)))) +#define V_lookup(Sc, Symbol, Expr) slot_value(next_slot(next_slot(let_slots(let_outlet(sc->curlet))))) +#define o_lookup(Sc, Symbol, Expr) inline_lookup_from(Sc, Symbol, let_outlet(Sc->curlet)) +#endif + +#define s_lookup(Sc, Sym, Expr) lookup(Sc, Sym) +#define g_lookup(Sc, Sym, Expr) lookup_global(Sc, Sym) + +/* arg here is the full expression */ +static s7_pointer fx_c(s7_scheme *sc, s7_pointer arg) {return(arg);} +static s7_pointer fx_q(s7_scheme *sc, s7_pointer arg) {return(cadr(arg));} +static s7_pointer fx_unsafe_s(s7_scheme *sc, s7_pointer arg) {return(lookup_checked(sc, T_Sym(arg)));} + +static s7_pointer fx_s(s7_scheme *sc, s7_pointer arg) {return(lookup(sc, T_Sym(arg)));} +static s7_pointer fx_g(s7_scheme *sc, s7_pointer arg) {return((is_global(arg)) ? global_value(arg) : lookup(sc, arg));} +static s7_pointer fx_o(s7_scheme *sc, s7_pointer arg) {return(o_lookup(sc, T_Sym(arg), arg));} +static s7_pointer fx_t(s7_scheme *sc, s7_pointer arg) {return(t_lookup(sc, T_Sym(arg), arg));} +static s7_pointer fx_u(s7_scheme *sc, s7_pointer arg) {return(u_lookup(sc, T_Sym(arg), arg));} +static s7_pointer fx_v(s7_scheme *sc, s7_pointer arg) {return(v_lookup(sc, T_Sym(arg), arg));} +static s7_pointer fx_T(s7_scheme *sc, s7_pointer arg) {return(T_lookup(sc, T_Sym(arg), arg));} +static s7_pointer fx_U(s7_scheme *sc, s7_pointer arg) {return(U_lookup(sc, T_Sym(arg), arg));} +static s7_pointer fx_V(s7_scheme *sc, s7_pointer arg) {return(V_lookup(sc, T_Sym(arg), arg));} +static s7_pointer fx_c_nc(s7_scheme *sc, s7_pointer arg) {return(fc_call(sc, arg));} +static s7_pointer fx_c_0c(s7_scheme *sc, s7_pointer arg) {return(fn_proc(arg)(sc, sc->nil));} +static s7_pointer fx_cons_cc(s7_scheme *sc, s7_pointer arg) {return(cons(sc, cadr(arg), caddr(arg)));} +static s7_pointer fx_curlet(s7_scheme *sc, s7_pointer arg) {return(sc->curlet);} + +#define fx_c_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + return(fn_proc(arg)(sc, with_list_t1(Lookup(sc, cadr(arg), arg)))); \ + } + +fx_c_any(fx_c_s, s_lookup) +fx_c_any(fx_c_g, g_lookup) +fx_c_any(fx_c_t, t_lookup) +fx_c_any(fx_c_u, u_lookup) +fx_c_any(fx_c_v, v_lookup) +fx_c_any(fx_c_o, o_lookup) +fx_c_any(fx_c_T, T_lookup) +fx_c_any(fx_c_V, V_lookup) + +static s7_pointer fx_c_g_direct(s7_scheme *sc, s7_pointer arg) {return(((s7_p_p_t)opt2_direct(cdr(arg)))(sc, lookup_global(sc, cadr(arg))));} +static s7_pointer fx_c_s_direct(s7_scheme *sc, s7_pointer arg) {return(((s7_p_p_t)opt2_direct(cdr(arg)))(sc, lookup(sc, cadr(arg))));} +static s7_pointer fx_c_o_direct(s7_scheme *sc, s7_pointer arg) {return(((s7_p_p_t)opt2_direct(cdr(arg)))(sc, o_lookup(sc, cadr(arg), arg)));} +static s7_pointer fx_c_t_direct(s7_scheme *sc, s7_pointer arg) {return(((s7_p_p_t)opt2_direct(cdr(arg)))(sc, t_lookup(sc, cadr(arg), arg)));} +static s7_pointer fx_c_u_direct(s7_scheme *sc, s7_pointer arg) {return(((s7_p_p_t)opt2_direct(cdr(arg)))(sc, u_lookup(sc, cadr(arg), arg)));} +static s7_pointer fx_c_v_direct(s7_scheme *sc, s7_pointer arg) {return(((s7_p_p_t)opt2_direct(cdr(arg)))(sc, v_lookup(sc, cadr(arg), arg)));} + + +#define fx_car_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer val = Lookup(sc, cadr(arg), arg); \ + return((is_pair(val)) ? car(val) : g_car(sc, set_plist_1(sc, val))); \ + } + /* using car_p_p(val) here is exactly the same in speed according to callgrind */ + +fx_car_any(fx_car_s, s_lookup) +fx_car_any(fx_car_t, t_lookup) +fx_car_any(fx_car_u, u_lookup) +fx_car_any(fx_car_o, o_lookup) +fx_car_any(fx_car_T, T_lookup) +fx_car_any(fx_car_U, U_lookup) + + +#define fx_cdr_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer val = Lookup(sc, cadr(arg), arg); \ + return((is_pair(val)) ? cdr(val) : g_cdr(sc, set_plist_1(sc, val))); \ + } + +fx_cdr_any(fx_cdr_s, s_lookup) +fx_cdr_any(fx_cdr_t, t_lookup) +fx_cdr_any(fx_cdr_u, u_lookup) +fx_cdr_any(fx_cdr_v, v_lookup) +fx_cdr_any(fx_cdr_o, o_lookup) +fx_cdr_any(fx_cdr_T, T_lookup) +fx_cdr_any(fx_cdr_U, U_lookup) + + +#define fx_cadr_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg)\ + { \ + s7_pointer val = Lookup(sc, cadr(arg), arg); \ + return(((is_pair(val)) && (is_pair(cdr(val)))) ? cadr(val) : g_cadr(sc, set_plist_1(sc, val))); \ + } + +fx_cadr_any(fx_cadr_s, s_lookup) +fx_cadr_any(fx_cadr_t, t_lookup) +fx_cadr_any(fx_cadr_u, u_lookup) +fx_cadr_any(fx_cadr_o, o_lookup) + + +#define fx_cddr_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg)\ + { \ + s7_pointer val = Lookup(sc, cadr(arg), arg); \ + return(((is_pair(val)) && (is_pair(cdr(val)))) ? cddr(val) : g_cddr(sc, set_plist_1(sc, val))); \ + } + +fx_cddr_any(fx_cddr_s, s_lookup) +fx_cddr_any(fx_cddr_t, t_lookup) +fx_cddr_any(fx_cddr_u, u_lookup) +fx_cddr_any(fx_cddr_o, o_lookup) + + +#define fx_add_s1_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer x = Lookup(sc, cadr(arg), arg); \ + if ((!WITH_GMP) && (is_t_integer(x))) return(make_integer(sc, integer(x) + 1)); \ + return(g_add_x1_1(sc, x, 1)); /* arg=(+ x 1) */ \ + } + +fx_add_s1_any(fx_add_s1, s_lookup) +fx_add_s1_any(fx_add_t1, t_lookup) +fx_add_s1_any(fx_add_u1, u_lookup) +fx_add_s1_any(fx_add_v1, v_lookup) +fx_add_s1_any(fx_add_T1, T_lookup) +fx_add_s1_any(fx_add_U1, U_lookup) +fx_add_s1_any(fx_add_V1, V_lookup) + + +static s7_pointer fx_num_eq_xi_1(s7_scheme *sc, s7_pointer args, s7_pointer val, s7_int y) +{ + if ((S7_DEBUGGING) && (is_t_integer(val))) fprintf(stderr, "%s[%d]: %s is an integer\n", __func__, __LINE__, display(val)); + switch (type(val)) + { + case T_REAL: return(make_boolean(sc, real(val) == y)); + case T_RATIO: + case T_COMPLEX: return(sc->F); +#if WITH_GMP + case T_BIG_INTEGER: return(make_boolean(sc, mpz_cmp_si(big_integer(val), y) == 0)); + case T_BIG_REAL: return(make_boolean(sc, mpfr_cmp_si(big_real(val), y) == 0)); + case T_BIG_RATIO: + case T_BIG_COMPLEX: return(sc->F); +#endif + default: return(method_or_bust_pp(sc, val, sc->num_eq_symbol, val, cadr(args), a_number_string, 1)); + } + return(sc->T); +} + +static s7_pointer fx_num_eq_s0f(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer val = lookup(sc, cadr(arg)); + if (is_t_real(val)) return(make_boolean(sc, real(val) == 0.0)); + return(make_boolean(sc, num_eq_b_7pp(sc, val, real_zero))); +} + +#define fx_num_eq_si_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer args = cdr(arg); \ + s7_pointer val = Lookup(sc, car(args), arg); \ + s7_int y = integer(cadr(args)); \ + return((is_t_integer(val)) ? make_boolean(sc, integer(val) == y) : \ + ((is_t_real(val)) ? make_boolean(sc, real(val) == y) : fx_num_eq_xi_1(sc, args, val, y))); \ +} + +fx_num_eq_si_any(fx_num_eq_si, s_lookup) +fx_num_eq_si_any(fx_num_eq_ti, t_lookup) +fx_num_eq_si_any(fx_num_eq_ui, u_lookup) +fx_num_eq_si_any(fx_num_eq_vi, v_lookup) +fx_num_eq_si_any(fx_num_eq_Ti, T_lookup) +fx_num_eq_si_any(fx_num_eq_oi, o_lookup) + +#define fx_num_eq_s0_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer val = Lookup(sc, cadr(arg), arg); \ + return((is_t_integer(val)) ? make_boolean(sc, integer(val) == 0) : fx_num_eq_xi_1(sc, cdr(arg), val, 0)); \ + } + +fx_num_eq_s0_any(fx_num_eq_s0, s_lookup) +fx_num_eq_s0_any(fx_num_eq_t0, t_lookup) +fx_num_eq_s0_any(fx_num_eq_u0, u_lookup) +fx_num_eq_s0_any(fx_num_eq_v0, v_lookup) + +static s7_pointer fx_num_eq_0s(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer val = lookup(sc, opt3_sym(arg)); /* opt3_sym: caddr(arg) -- this actually makes a measurable difference in callgrind! */ + return((is_t_integer(val)) ? make_boolean(sc, integer(val) == 0) : g_num_eq(sc, set_plist_2(sc, val, int_zero))); +} + + +static s7_pointer fx_random_i(s7_scheme *sc, s7_pointer arg) +{ +#if WITH_GMP + return(g_random_i(sc, cdr(arg))); +#else + return(make_integer(sc, (s7_int)(integer(cadr(arg)) * next_random(sc->default_random_state)))); +#endif +} + +#if (!WITH_GMP) +static s7_pointer fx_add_i_random(s7_scheme *sc, s7_pointer arg) +{ + s7_int x = integer(cadr(arg)); + s7_int y = opt3_int(cdr(arg)); /* cadadr */ + return(make_integer(sc, x + (s7_int)(y * next_random(sc->default_random_state)))); /* (+ -1 (random 1)) -- placement of the (s7_int) cast matters! */ +} +#endif + +static s7_pointer fx_add_sf(s7_scheme *sc, s7_pointer arg) {return(g_add_xf(sc, lookup(sc, cadr(arg)), real(opt2_con(cdr(arg))), 1));} +static s7_pointer fx_add_fs(s7_scheme *sc, s7_pointer arg) {return(g_add_xf(sc, lookup(sc, opt2_sym(cdr(arg))), real(cadr(arg)), 2));} +static s7_pointer fx_add_tf(s7_scheme *sc, s7_pointer arg) {return(g_add_xf(sc, t_lookup(sc, cadr(arg), arg), real(opt2_con(cdr(arg))), 1));} +static s7_pointer fx_add_ft(s7_scheme *sc, s7_pointer arg) {return(g_add_xf(sc, t_lookup(sc, opt2_sym(cdr(arg)), arg), real(cadr(arg)), 2));} + +#define fx_add_si_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer x = Lookup(sc, cadr(arg), arg); \ + if ((!WITH_GMP) && (is_t_integer(x))) \ + { \ + if (HAVE_OVERFLOW_CHECKS) \ + { \ + s7_int val; \ + if (!add_overflow(integer(x), integer(opt2_con(cdr(arg))), &val)) \ + return(make_integer(sc, val)); \ + } \ + else return(make_integer(sc, integer(x) + integer(opt2_con(cdr(arg))))); \ + } \ + return(add_p_pp(sc, x, opt2_con(cdr(arg)))); /* caddr(arg) */ \ + } + +fx_add_si_any(fx_add_si, s_lookup) +fx_add_si_any(fx_add_ti, t_lookup) + +static s7_pointer fx_add_ss(s7_scheme *sc, s7_pointer arg) {return(add_p_pp(sc, s_lookup(sc, cadr(arg), arg), s_lookup(sc, opt2_sym(cdr(arg)), arg)));} +static s7_pointer fx_add_ts(s7_scheme *sc, s7_pointer arg) {return(add_p_pp(sc, t_lookup(sc, cadr(arg), arg), s_lookup(sc, opt2_sym(cdr(arg)), arg)));} +static s7_pointer fx_add_tu(s7_scheme *sc, s7_pointer arg) {return(add_p_pp(sc, t_lookup(sc, cadr(arg), arg), u_lookup(sc, opt2_sym(cdr(arg)), arg)));} +static s7_pointer fx_add_ut(s7_scheme *sc, s7_pointer arg) {return(add_p_pp(sc, u_lookup(sc, cadr(arg), arg), t_lookup(sc, opt2_sym(cdr(arg)), arg)));} +static s7_pointer fx_add_uv(s7_scheme *sc, s7_pointer arg) {return(add_p_pp(sc, u_lookup(sc, cadr(arg), arg), v_lookup(sc, opt2_sym(cdr(arg)), arg)));} +static s7_pointer fx_add_us(s7_scheme *sc, s7_pointer arg) {return(add_p_pp(sc, u_lookup(sc, cadr(arg), arg), s_lookup(sc, opt2_sym(cdr(arg)), arg)));} +static s7_pointer fx_add_vu(s7_scheme *sc, s7_pointer arg) {return(add_p_pp(sc, v_lookup(sc, cadr(arg), arg), u_lookup(sc, opt2_sym(cdr(arg)), arg)));} + +#define fx_subtract_s1_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer x = Lookup(sc, cadr(arg), arg); \ + if ((!WITH_GMP) && (is_t_integer(x))) return(make_integer(sc, integer(x) - 1)); \ + return(minus_c1(sc, x)); \ + } + +fx_subtract_s1_any(fx_subtract_s1, s_lookup) +fx_subtract_s1_any(fx_subtract_t1, t_lookup) +fx_subtract_s1_any(fx_subtract_u1, u_lookup) +fx_subtract_s1_any(fx_subtract_v1, v_lookup) +fx_subtract_s1_any(fx_subtract_T1, T_lookup) +fx_subtract_s1_any(fx_subtract_U1, U_lookup) + + +#define fx_subtract_si_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer x = Lookup(sc, cadr(arg), arg); \ + if ((!WITH_GMP) && (is_t_integer(x))) \ + { \ + if (HAVE_OVERFLOW_CHECKS) \ + { \ + s7_int val; \ + if (!subtract_overflow(integer(x), integer(opt2_con(cdr(arg))), &val)) \ + return(make_integer(sc, val)); \ + } \ + else return(make_integer(sc, integer(x) - integer(opt2_con(cdr(arg))))); \ + } \ + return(subtract_p_pp(sc, x, opt2_con(cdr(arg)))); /* caddr(arg) */ \ + } + +fx_subtract_si_any(fx_subtract_si, s_lookup) +fx_subtract_si_any(fx_subtract_ti, t_lookup) +fx_subtract_si_any(fx_subtract_ui, u_lookup) + + +#define fx_subtract_sf_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer x = Lookup(sc, cadr(arg), arg); \ + if (is_t_real(x)) \ + return(make_real(sc, real(x) - real(opt2_con(cdr(arg))))); /* caddr(arg) */ \ + return(g_subtract_2f(sc, set_plist_2(sc, x, opt2_con(cdr(arg))))); /* caddr(arg) */ \ + } + +fx_subtract_sf_any(fx_subtract_sf, s_lookup) +fx_subtract_sf_any(fx_subtract_tf, t_lookup) + + +#define fx_subtract_ss_any(Name, Lookup1, Lookup2) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) {return(subtract_p_pp(sc, Lookup1(sc, cadr(arg), arg), Lookup2(sc, opt2_sym(cdr(arg)), arg)));} + +fx_subtract_ss_any(fx_subtract_ss, s_lookup, s_lookup) +fx_subtract_ss_any(fx_subtract_ts, t_lookup, s_lookup) +fx_subtract_ss_any(fx_subtract_tu, t_lookup, u_lookup) +fx_subtract_ss_any(fx_subtract_ut, u_lookup, t_lookup) +fx_subtract_ss_any(fx_subtract_us, u_lookup, s_lookup) + + +static s7_pointer fx_subtract_fs(s7_scheme *sc, s7_pointer arg) +{ + s7_double n = real(cadr(arg)); + s7_pointer x = lookup(sc, opt2_sym(cdr(arg))); /* caddr(arg) */ + switch (type(x)) + { + case T_INTEGER: return(make_real(sc, n - integer(x))); + case T_RATIO: return(make_real(sc, n - fraction(x))); + case T_REAL: return(make_real(sc, n - real(x))); + case T_COMPLEX: return(make_complex_not_0i(sc, n - real_part(x), -imag_part(x))); +#if WITH_GMP + case T_BIG_INTEGER: case T_BIG_RATIO: case T_BIG_REAL: case T_BIG_COMPLEX: + return(subtract_p_pp(sc, cadr(arg), x)); +#endif + default: + return(method_or_bust_pp(sc, x, sc->subtract_symbol, cadr(arg), x, a_number_string, 2)); + } + return(x); +} + +#define fx_is_eq_sc_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + return(make_boolean(sc, Lookup(sc, cadr(arg), arg) == opt2_con(cdr(arg)))); /* fx_choose checks that the second arg is not unspecified */ \ + } + +fx_is_eq_sc_any(fx_is_eq_sc, s_lookup) +fx_is_eq_sc_any(fx_is_eq_tc, t_lookup) +fx_is_eq_sc_any(fx_is_eq_uc, u_lookup) + + +#define fx_is_eq_car_sq_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer a = cdr(arg); \ + s7_pointer lst = Lookup(sc, opt3_sym(a), arg); \ + return(make_boolean(sc, (is_pair(lst)) ? (car(lst) == opt2_con(a)) : s7_is_eq(g_car(sc, set_plist_1(sc, lst)), opt2_con(a)))); \ + } + +fx_is_eq_car_sq_any(fx_is_eq_car_sq, s_lookup) +fx_is_eq_car_sq_any(fx_is_eq_car_tq, t_lookup) + + +static s7_pointer fx_is_eq_caar_sq(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer a = cdr(arg); + s7_pointer lst = lookup(sc, opt3_sym(a)); + if ((is_pair(lst)) && (is_pair(car(lst)))) + return(make_boolean(sc, caar(lst) == opt2_con(a))); + return(make_boolean(sc, s7_is_eq(g_caar(sc, set_plist_1(sc, lst)), opt2_con(a)))); +} + +static s7_pointer fx_not_is_eq_car_sq(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer lst = lookup(sc, opt1_sym(cdr(arg))); + if (is_pair(lst)) + return(make_boolean(sc, car(lst) != opt3_con(cdr(arg)))); + return(make_boolean(sc, !s7_is_eq(g_car(sc, set_plist_1(sc, lst)), opt3_con(cdr(arg))))); +} + +#define fx_is_pair_car_s_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer p = Lookup(sc, opt3_sym(arg), arg); \ + return((is_pair(p)) ? make_boolean(sc, is_pair(car(p))) : g_is_pair(sc, set_plist_1(sc, g_car(sc, set_plist_1(sc, p))))); \ + } + +fx_is_pair_car_s_any(fx_is_pair_car_s, s_lookup) +fx_is_pair_car_s_any(fx_is_pair_car_t, t_lookup) + + +#define fx_is_pair_cdr_s_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer p = Lookup(sc, opt3_sym(arg), arg); \ + return((is_pair(p)) ? make_boolean(sc, is_pair(cdr(p))) : g_is_pair(sc, set_plist_1(sc, g_cdr(sc, set_plist_1(sc, p))))); \ + } + +fx_is_pair_cdr_s_any(fx_is_pair_cdr_s, s_lookup) +fx_is_pair_cdr_s_any(fx_is_pair_cdr_t, t_lookup) +fx_is_pair_cdr_s_any(fx_is_pair_cdr_u, u_lookup) + + +#define fx_is_pair_cadr_s_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer p = Lookup(sc, opt3_sym(arg), arg); \ + return(((is_pair(p)) && (is_pair(cdr(p)))) ? make_boolean(sc, is_pair(cadr(p))) : g_is_pair(sc, set_plist_1(sc, g_cadr(sc, set_plist_1(sc, p))))); \ + } + +fx_is_pair_cadr_s_any(fx_is_pair_cadr_s, s_lookup) +fx_is_pair_cadr_s_any(fx_is_pair_cadr_t, t_lookup) + + +#define fx_is_pair_cddr_s_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer p = Lookup(sc, opt3_sym(arg), arg); \ + return(((is_pair(p)) && (is_pair(cdr(p)))) ? make_boolean(sc, is_pair(cddr(p))) : g_is_pair(sc, set_plist_1(sc, g_cddr(sc, set_plist_1(sc, p))))); \ + } + +fx_is_pair_cddr_s_any(fx_is_pair_cddr_s, s_lookup) +fx_is_pair_cddr_s_any(fx_is_pair_cddr_t, t_lookup) + + +#define fx_is_null_cdr_s_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer p = Lookup(sc, opt2_sym(cdr(arg)), arg); \ + return((is_pair(p)) ? make_boolean(sc, is_null(cdr(p))) : g_is_null(sc, set_plist_1(sc, g_cdr(sc, set_plist_1(sc, p))))); \ + } + +fx_is_null_cdr_s_any(fx_is_null_cdr_s, s_lookup) +fx_is_null_cdr_s_any(fx_is_null_cdr_t, t_lookup) + + +#define fx_is_null_cadr_s_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer p = Lookup(sc, opt2_sym(cdr(arg)), arg); \ + return(((is_pair(p)) && (is_pair(cdr(p)))) ? make_boolean(sc, is_null(cadr(p))) : g_is_null(sc, set_plist_1(sc, g_cadr(sc, set_plist_1(sc, p))))); \ + } + +fx_is_null_cadr_s_any(fx_is_null_cadr_s, s_lookup) +fx_is_null_cadr_s_any(fx_is_null_cadr_t, t_lookup) + + +#define fx_is_null_cddr_s_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer p = Lookup(sc, opt2_sym(cdr(arg)), arg); \ + return(((is_pair(p)) && (is_pair(cdr(p)))) ? make_boolean(sc, is_null(cddr(p))) : g_is_null(sc, set_plist_1(sc, g_cddr(sc, set_plist_1(sc, p))))); \ + } + +fx_is_null_cddr_s_any(fx_is_null_cddr_s, s_lookup) +fx_is_null_cddr_s_any(fx_is_null_cddr_t, t_lookup) + + +#define fx_is_symbol_cadr_s_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer p = Lookup(sc, opt2_sym(cdr(arg)), arg); \ + return(((is_pair(p)) && (is_pair(cdr(p)))) ? make_boolean(sc, is_symbol(cadr(p))) : g_is_symbol(sc, set_plist_1(sc, g_cadr(sc, set_plist_1(sc, p))))); \ + } + +fx_is_symbol_cadr_s_any(fx_is_symbol_cadr_s, s_lookup) +fx_is_symbol_cadr_s_any(fx_is_symbol_cadr_t, t_lookup) + +static s7_pointer fx_is_symbol_car_t(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer val = t_lookup(sc, opt2_sym(cdr(arg)), arg); + return(make_boolean(sc, (is_pair(val)) ? is_symbol(car(val)) : is_symbol(g_car(sc, set_plist_1(sc, val))))); +} + +static s7_pointer fx_floor_sqrt_s(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer p = lookup(sc, opt2_sym(cdr(arg))); +#if WITH_GMP + if ((is_t_big_integer(p)) && + (mpz_cmp_ui(big_integer(p), 0) >= 0)) /* p >= 0 */ + { + mpz_sqrt(sc->mpz_1, big_integer(p)); + return(mpz_to_integer(sc, sc->mpz_1)); + } +#else + if (!is_negative_b_7p(sc, p)) + return(make_integer(sc, (s7_int)floor(sqrt(s7_number_to_real_with_location(sc, p, sc->sqrt_symbol))))); +#endif + return(floor_p_p(sc, sqrt_p_p(sc, p))); +} + + +static s7_pointer fx_is_positive_u(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer p1 = u_lookup(sc, cadr(arg), arg); + if (is_t_integer(p1)) return(make_boolean(sc, integer(p1) > 0)); + return(make_boolean(sc, is_positive_b_7p(sc, p1))); +} + +static s7_pointer fx_is_zero_u(s7_scheme *sc, s7_pointer arg) {return(make_boolean(sc, is_zero_b_7p(sc, u_lookup(sc, cadr(arg), arg))));} + +#define fx_real_part_s_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer z = Lookup(sc, cadr(arg), arg); \ + return((is_t_complex(z)) ? make_real(sc, real_part(z)) : real_part_p_p(sc, z)); \ + } + +fx_real_part_s_any(fx_real_part_s, s_lookup) +fx_real_part_s_any(fx_real_part_t, t_lookup) + +#define fx_imag_part_s_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer z = Lookup(sc, cadr(arg), arg); \ + return((is_t_complex(z)) ? make_real(sc, imag_part(z)) : imag_part_p_p(sc, z)); \ + } + +fx_imag_part_s_any(fx_imag_part_s, s_lookup) +fx_imag_part_s_any(fx_imag_part_t, t_lookup) /* not used in current timing tests */ + +#define fx_iterate_s_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer iter = Lookup(sc, cadr(arg), arg); \ + if (is_iterator(iter)) \ + return((iterator_next(iter))(sc, iter)); \ + return(method_or_bust_p(sc, iter, sc->iterate_symbol, sc->type_names[T_ITERATOR])); \ + } + +fx_iterate_s_any(fx_iterate_s, s_lookup) +fx_iterate_s_any(fx_iterate_o, o_lookup) +fx_iterate_s_any(fx_iterate_T, T_lookup) + +static s7_pointer fx_read_char_0(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer port = input_port_if_not_loading(sc); + if (!port) return(eof_object); + if (!is_input_port(port)) + return(method_or_bust_p(sc, port, sc->read_char_symbol, an_input_port_string)); + return(chars[port_read_character(port)(sc, port)]); +} + +static s7_pointer fx_length_s(s7_scheme *sc, s7_pointer arg) {return(s7_length(sc, lookup(sc, cadr(arg))));} +static s7_pointer fx_length_t(s7_scheme *sc, s7_pointer arg) {return(s7_length(sc, t_lookup(sc, cadr(arg), arg)));} + +static s7_pointer fx_num_eq_length_i(s7_scheme *sc, s7_pointer arg) +{ + /* avoid make_integer (and telescope opts), we get here with car=length expr, cadr=int */ + s7_int ilen = integer(opt2_con(cdr(arg))); /* is_t_integer checked in fx_choose */ + s7_pointer val = lookup(sc, opt3_sym(cdr(arg))); + + switch (type(val)) + { + case T_PAIR: return(make_boolean(sc, s7_list_length(sc, val) == ilen)); + case T_NIL: return(make_boolean(sc, ilen == 0)); + case T_STRING: return(make_boolean(sc, string_length(val) == ilen)); + case T_HASH_TABLE: return(make_boolean(sc, (hash_table_mask(val) + 1) == ilen)); + case T_C_OBJECT: return(make_boolean(sc, c_object_length_to_int(sc, val) == ilen)); + case T_LET: return(make_boolean(sc, let_length(sc, val) == ilen)); + + case T_BYTE_VECTOR: case T_INT_VECTOR: case T_FLOAT_VECTOR: case T_VECTOR: + return(make_boolean(sc, vector_length(val) == ilen)); + + case T_ITERATOR: + { + s7_pointer len = s7_length(sc, iterator_sequence(val)); + return(make_boolean(sc, (is_t_integer(len)) && (integer(len) == ilen))); + } + + case T_CLOSURE: + case T_CLOSURE_STAR: + if (has_active_methods(sc, val)) + return(make_boolean(sc, closure_length(sc, val) == ilen)); + /* fall through */ + + default: + sole_arg_wrong_type_error_nr(sc, sc->length_symbol, val, a_sequence_string); + /* here we already lost because we checked for the length above */ + } + return(sc->F); +} + +static s7_pointer fx_less_length_i(s7_scheme *sc, s7_pointer arg) +{ + s7_int ilen = integer(opt2_con(cdr(arg))); /* caddr(arg) */ + s7_pointer val = lookup(sc, opt3_sym(cdr(arg))); /* cadadr(arg) */ + + switch (type(val)) + { + case T_PAIR: return(make_boolean(sc, s7_list_length(sc, val) < ilen)); + case T_NIL: return(make_boolean(sc, ilen > 0)); + case T_STRING: return(make_boolean(sc, string_length(val) < ilen)); + case T_HASH_TABLE: return(make_boolean(sc, (hash_table_mask(val) + 1) < ilen)); /* was <=? -- changed 15-Dec-15, then again 6-Jan-17: mask is len-1 */ + case T_C_OBJECT: return(make_boolean(sc, c_object_length_to_int(sc, val) < ilen)); + case T_LET: return(make_boolean(sc, let_length(sc, val) < ilen)); /* this works because let_length handles the length method itself! */ + + case T_BYTE_VECTOR: case T_INT_VECTOR: case T_FLOAT_VECTOR: case T_VECTOR: + return(make_boolean(sc, vector_length(val) < ilen)); + + case T_ITERATOR: + { + s7_pointer len = s7_length(sc, iterator_sequence(val)); + return(make_boolean(sc, (is_t_integer(len)) && (integer(len) < ilen))); + } + + case T_CLOSURE: + case T_CLOSURE_STAR: + if (has_active_methods(sc, val)) + return(make_boolean(sc, closure_length(sc, val) < ilen)); + /* fall through */ + + default: + sole_arg_wrong_type_error_nr(sc, sc->length_symbol, val, a_sequence_string); /* no check method here because we checked above */ + } + return(sc->F); +} + +static s7_pointer fx_is_null_s(s7_scheme *sc, s7_pointer arg) {return((is_null(lookup(sc, cadr(arg)))) ? sc->T : sc->F);} +static s7_pointer fx_is_null_o(s7_scheme *sc, s7_pointer arg) {return((is_null(o_lookup(sc, cadr(arg), arg))) ? sc->T : sc->F);} /* very few hits */ +static s7_pointer fx_is_null_t(s7_scheme *sc, s7_pointer arg) {return((is_null(t_lookup(sc, cadr(arg), arg))) ? sc->T : sc->F);} +static s7_pointer fx_is_null_u(s7_scheme *sc, s7_pointer arg) {return((is_null(u_lookup(sc, cadr(arg), arg))) ? sc->T : sc->F);} +static s7_pointer fx_is_null_v(s7_scheme *sc, s7_pointer arg) {return((is_null(v_lookup(sc, cadr(arg), arg))) ? sc->T : sc->F);} +static s7_pointer fx_is_null_T(s7_scheme *sc, s7_pointer arg) {return((is_null(T_lookup(sc, cadr(arg), arg))) ? sc->T : sc->F);} +static s7_pointer fx_is_symbol_s(s7_scheme *sc, s7_pointer arg) {return((is_symbol(lookup(sc, cadr(arg)))) ? sc->T : sc->F);} +static s7_pointer fx_is_symbol_t(s7_scheme *sc, s7_pointer arg) {return((is_symbol(t_lookup(sc, cadr(arg), arg))) ? sc->T : sc->F);} +static s7_pointer fx_is_symbol_u(s7_scheme *sc, s7_pointer arg) {return((is_symbol(u_lookup(sc, cadr(arg), arg))) ? sc->T : sc->F);} +static s7_pointer fx_is_eof_s(s7_scheme *sc, s7_pointer arg) {return((lookup(sc, cadr(arg)) == eof_object) ? sc->T : sc->F);} +static s7_pointer fx_is_eof_t(s7_scheme *sc, s7_pointer arg) {return((t_lookup(sc, cadr(arg), arg) == eof_object) ? sc->T : sc->F);} +static s7_pointer fx_is_eof_u(s7_scheme *sc, s7_pointer arg) {return((u_lookup(sc, cadr(arg), arg) == eof_object) ? sc->T : sc->F);} +static s7_pointer fx_is_type_s(s7_scheme *sc, s7_pointer arg) {return(make_boolean(sc, (uint8_t)(opt3_byte(cdr(arg))) == type(lookup(sc, cadr(arg)))));} +static s7_pointer fx_is_type_t(s7_scheme *sc, s7_pointer arg) {return(make_boolean(sc, (uint8_t)(opt3_byte(cdr(arg))) == type(t_lookup(sc, cadr(arg), arg))));} +static s7_pointer fx_is_type_u(s7_scheme *sc, s7_pointer arg) {return(make_boolean(sc, (uint8_t)(opt3_byte(cdr(arg))) == type(u_lookup(sc, cadr(arg), arg))));} +#if WITH_GMP +static s7_pointer fx_is_integer_s(s7_scheme *sc, s7_pointer arg) {return((s7_is_integer(lookup(sc, cadr(arg)))) ? sc->T : sc->F);} +static s7_pointer fx_is_integer_t(s7_scheme *sc, s7_pointer arg) {return((s7_is_integer(t_lookup(sc, cadr(arg), arg))) ? sc->T : sc->F);} +#else +static s7_pointer fx_is_integer_s(s7_scheme *sc, s7_pointer arg) {return((is_t_integer(lookup(sc, cadr(arg)))) ? sc->T : sc->F);} +static s7_pointer fx_is_integer_t(s7_scheme *sc, s7_pointer arg) {return((is_t_integer(t_lookup(sc, cadr(arg), arg))) ? sc->T : sc->F);} +#endif +static s7_pointer fx_is_string_s(s7_scheme *sc, s7_pointer arg) {return((is_string(lookup(sc, cadr(arg)))) ? sc->T : sc->F);} +static s7_pointer fx_is_string_t(s7_scheme *sc, s7_pointer arg) {return((is_string(t_lookup(sc, cadr(arg), arg))) ? sc->T : sc->F);} +static s7_pointer fx_is_procedure_s(s7_scheme *sc, s7_pointer arg) {return((is_procedure(lookup(sc, cadr(arg)))) ? sc->T : sc->F);} +static s7_pointer fx_is_procedure_t(s7_scheme *sc, s7_pointer arg) {return((is_procedure(t_lookup(sc, cadr(arg), arg))) ? sc->T : sc->F);} +static s7_pointer fx_is_pair_s(s7_scheme *sc, s7_pointer arg) {return((is_pair(lookup(sc, cadr(arg)))) ? sc->T : sc->F);} +static s7_pointer fx_is_pair_t(s7_scheme *sc, s7_pointer arg) {return((is_pair(t_lookup(sc, cadr(arg), arg))) ? sc->T : sc->F);} +static s7_pointer fx_is_pair_u(s7_scheme *sc, s7_pointer arg) {return((is_pair(u_lookup(sc, cadr(arg), arg))) ? sc->T : sc->F);} +static s7_pointer fx_is_pair_v(s7_scheme *sc, s7_pointer arg) {return((is_pair(v_lookup(sc, cadr(arg), arg))) ? sc->T : sc->F);} +static s7_pointer fx_is_keyword_s(s7_scheme *sc, s7_pointer arg) {return((is_symbol_and_keyword(lookup(sc, cadr(arg)))) ? sc->T : sc->F);} +static s7_pointer fx_is_vector_s(s7_scheme *sc, s7_pointer arg) {return((is_any_vector(lookup(sc, cadr(arg)))) ? sc->T : sc->F);} +static s7_pointer fx_is_vector_t(s7_scheme *sc, s7_pointer arg) {return((is_any_vector(t_lookup(sc, cadr(arg), arg))) ? sc->T : sc->F);} +static s7_pointer fx_is_proper_list_s(s7_scheme *sc, s7_pointer arg) {return((s7_is_proper_list(sc, lookup(sc, cadr(arg)))) ? sc->T : sc->F);} +static s7_pointer fx_not_s(s7_scheme *sc, s7_pointer arg) {return(make_boolean(sc, is_false(sc, lookup(sc, cadr(arg)))));} +static s7_pointer fx_not_t(s7_scheme *sc, s7_pointer arg) {return(make_boolean(sc, is_false(sc, t_lookup(sc, cadr(arg), arg))));} +static s7_pointer fx_not_o(s7_scheme *sc, s7_pointer arg) {return(make_boolean(sc, is_false(sc, o_lookup(sc, cadr(arg), arg))));} +static s7_pointer fx_not_is_pair_s(s7_scheme *sc, s7_pointer arg) {return((is_pair(lookup(sc, opt3_sym(arg)))) ? sc->F : sc->T);} +static s7_pointer fx_not_is_pair_t(s7_scheme *sc, s7_pointer arg) {return((is_pair(t_lookup(sc, opt3_sym(arg), arg))) ? sc->F : sc->T);} +static s7_pointer fx_not_is_pair_u(s7_scheme *sc, s7_pointer arg) {return((is_pair(u_lookup(sc, opt3_sym(arg), arg))) ? sc->F : sc->T);} +static s7_pointer fx_not_is_pair_v(s7_scheme *sc, s7_pointer arg) {return((is_pair(v_lookup(sc, opt3_sym(arg), arg))) ? sc->F : sc->T);} +static s7_pointer fx_not_is_null_s(s7_scheme *sc, s7_pointer arg) {return((is_null(lookup(sc, opt3_sym(arg)))) ? sc->F : sc->T);} +static s7_pointer fx_not_is_null_t(s7_scheme *sc, s7_pointer arg) {return((is_null(t_lookup(sc, opt3_sym(arg), arg))) ? sc->F : sc->T);} +static s7_pointer fx_not_is_null_u(s7_scheme *sc, s7_pointer arg) {return((is_null(u_lookup(sc, opt3_sym(arg), arg))) ? sc->F : sc->T);} +static s7_pointer fx_not_is_symbol_s(s7_scheme *sc, s7_pointer arg) {return((is_symbol(lookup(sc, opt3_sym(arg)))) ? sc->F : sc->T);} +static s7_pointer fx_not_is_symbol_t(s7_scheme *sc, s7_pointer arg) {return((is_symbol(t_lookup(sc, opt3_sym(arg), arg))) ? sc->F : sc->T);} + +#define fx_c_sc_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + set_car(sc->t2_1, Lookup(sc, cadr(arg), arg)); \ + set_car(sc->t2_2, opt2_con(cdr(arg))); \ + return(fn_proc(arg)(sc, sc->t2_1)); \ + } + +fx_c_sc_any(fx_c_sc, s_lookup) +fx_c_sc_any(fx_c_tc, t_lookup) +fx_c_sc_any(fx_c_uc, u_lookup) /* few hits */ +fx_c_sc_any(fx_c_vc, v_lookup) +fx_c_sc_any(fx_c_oc, o_lookup) + + +static s7_pointer fx_c_sc_direct(s7_scheme *sc, s7_pointer arg) {return(((s7_p_pp_t)opt3_direct(cdr(arg)))(sc, lookup(sc, cadr(arg)), opt2_con(cdr(arg))));} +static s7_pointer fx_c_si_direct(s7_scheme *sc, s7_pointer arg) {return(((s7_p_pi_t)opt3_direct(cdr(arg)))(sc, lookup(sc, cadr(arg)), integer(opt2_con(cdr(arg)))));} +static s7_pointer fx_c_ti_direct(s7_scheme *sc, s7_pointer arg) {return(((s7_p_pi_t)opt3_direct(cdr(arg)))(sc, t_lookup(sc, cadr(arg), arg), integer(opt2_con(cdr(arg)))));} +static s7_pointer fx_c_ti_remainder(s7_scheme *sc, s7_pointer arg) {return(remainder_p_pi(sc, t_lookup(sc, cadr(arg), arg), integer(opt2_con(cdr(arg)))));} +static s7_pointer fx_c_tc_direct(s7_scheme *sc, s7_pointer arg) {return(((s7_p_pp_t)opt3_direct(cdr(arg)))(sc, t_lookup(sc, cadr(arg), arg), opt2_con(cdr(arg))));} +static s7_pointer fx_vector_ref_tc(s7_scheme *sc, s7_pointer arg) {return(vector_ref_p_pi(sc, t_lookup(sc, cadr(arg), arg), integer(opt2_con(cdr(arg)))));} + /* tc happens a lot, but others almost never */ + +static s7_pointer fx_memq_sc(s7_scheme *sc, s7_pointer arg) {return(memq_p_pp(sc, lookup(sc, cadr(arg)), opt2_con(cdr(arg))));} +static s7_pointer fx_memq_sc_3(s7_scheme *sc, s7_pointer arg) {return(memq_3_p_pp(sc, lookup(sc, cadr(arg)), opt2_con(cdr(arg))));} +static s7_pointer fx_memq_tc(s7_scheme *sc, s7_pointer arg) {return(memq_p_pp(sc, t_lookup(sc, cadr(arg), arg), opt2_con(cdr(arg))));} +static s7_pointer fx_leq_sc(s7_scheme *sc, s7_pointer arg) {return(leq_p_pp(sc, lookup(sc, cadr(arg)), opt2_con(cdr(arg))));} +static s7_pointer fx_lt_sc(s7_scheme *sc, s7_pointer arg) {return(lt_p_pp(sc, lookup(sc, cadr(arg)), opt2_con(cdr(arg))));} +static s7_pointer fx_gt_sc(s7_scheme *sc, s7_pointer arg) {return(gt_p_pp(sc, lookup(sc, cadr(arg)), opt2_con(cdr(arg))));} +static s7_pointer fx_geq_sc(s7_scheme *sc, s7_pointer arg) {return(geq_p_pp(sc, lookup(sc, cadr(arg)), opt2_con(cdr(arg))));} + +#define fx_char_eq_sc_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer c = Lookup(sc, cadr(arg), arg); \ + if (c == opt2_con(cdr(arg))) return(sc->T); \ + if (is_character(c)) return(sc->F); \ + return(method_or_bust(sc, cadr(arg), sc->char_eq_symbol, cdr(arg), sc->type_names[T_CHARACTER], 1)); \ + } + +fx_char_eq_sc_any(fx_char_eq_sc, s_lookup) +fx_char_eq_sc_any(fx_char_eq_tc, t_lookup) + + +#define fx_c_cs_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + set_car(sc->t2_1, opt1_con(cdr(arg))); /* cadr(arg) or cadadr */ \ + set_car(sc->t2_2, Lookup(sc, opt2_sym(cdr(arg)), arg)); /* caddr(arg) */ \ + return(fn_proc(arg)(sc, sc->t2_1)); \ + } + +fx_c_cs_any(fx_c_cs, s_lookup) +fx_c_cs_any(fx_c_ct, t_lookup) +fx_c_cs_any(fx_c_cu, u_lookup) + + +static s7_pointer fx_c_ct_direct(s7_scheme *sc, s7_pointer arg) +{ + return(((s7_p_pp_t)opt3_direct(cdr(arg)))(sc, opt1_con(cdr(arg)), t_lookup(sc, opt2_sym(cdr(arg)), arg))); +} + +static s7_pointer fx_cons_cs(s7_scheme *sc, s7_pointer arg) {return(cons(sc, opt1_con(cdr(arg)), lookup(sc, opt2_sym(cdr(arg)))));} +static s7_pointer fx_cons_ct(s7_scheme *sc, s7_pointer arg) {return(cons(sc, opt1_con(cdr(arg)), t_lookup(sc, opt2_sym(cdr(arg)), arg)));} + + +#define fx_c_ss_any(Name, Lookup1, Lookup2) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + set_car(sc->t2_1, Lookup1(sc, cadr(arg), arg)); \ + set_car(sc->t2_2, Lookup2(sc, opt2_sym(cdr(arg)), arg)); \ + return(fn_proc(arg)(sc, sc->t2_1)); \ +} + +fx_c_ss_any(fx_c_ss, s_lookup, s_lookup) +fx_c_ss_any(fx_c_st, s_lookup, t_lookup) +fx_c_ss_any(fx_c_ts, t_lookup, s_lookup) +fx_c_ss_any(fx_c_tu, t_lookup, u_lookup) +fx_c_ss_any(fx_c_uv, u_lookup, v_lookup) +fx_c_ss_any(fx_c_tU, t_lookup, U_lookup) + +static s7_pointer fx_memq_ss(s7_scheme *sc, s7_pointer arg) {return(memq_p_pp(sc, lookup(sc, cadr(arg)), lookup(sc, opt2_sym(cdr(arg)))));} +static s7_pointer fx_memq_tu(s7_scheme *sc, s7_pointer arg) {return(memq_p_pp(sc, t_lookup(sc, cadr(arg), arg), u_lookup(sc, opt2_sym(cdr(arg)), arg)));} +static s7_pointer fx_assq_ss(s7_scheme *sc, s7_pointer arg) {return(assq_p_pp(sc, lookup(sc, cadr(arg)), lookup(sc, opt2_sym(cdr(arg)))));} +static s7_pointer fx_vref_ss(s7_scheme *sc, s7_pointer arg) {return(vector_ref_p_pp(sc, lookup(sc, cadr(arg)), lookup(sc, opt2_sym(cdr(arg)))));} +static s7_pointer fx_vref_st(s7_scheme *sc, s7_pointer arg) {return(vector_ref_p_pp(sc, lookup(sc, cadr(arg)), t_lookup(sc, opt2_sym(cdr(arg)), arg)));} +static s7_pointer fx_vref_ts(s7_scheme *sc, s7_pointer arg) {return(vector_ref_p_pp(sc, t_lookup(sc, cadr(arg), arg), s_lookup(sc, opt2_sym(cdr(arg)), arg)));} +static s7_pointer fx_vref_tu(s7_scheme *sc, s7_pointer arg) {return(vector_ref_p_pp(sc, t_lookup(sc, cadr(arg), arg), u_lookup(sc, opt2_sym(cdr(arg)), arg)));} +static s7_pointer fx_vref_ot(s7_scheme *sc, s7_pointer arg) {return(vector_ref_p_pp(sc, o_lookup(sc, cadr(arg), arg), t_lookup(sc, opt2_sym(cdr(arg)), arg)));} +static s7_pointer fx_vref_gt(s7_scheme *sc, s7_pointer arg) {return(vector_ref_p_pp(sc, lookup_global(sc, cadr(arg)), t_lookup(sc, opt2_sym(cdr(arg)), arg)));} +static s7_pointer fx_sref_ss(s7_scheme *sc, s7_pointer arg) {return(string_ref_p_pp(sc, lookup(sc, cadr(arg)), lookup(sc, opt2_sym(cdr(arg)))));} +static s7_pointer fx_sref_su(s7_scheme *sc, s7_pointer arg) {return(string_ref_p_pp(sc, lookup(sc, cadr(arg)), u_lookup(sc, opt2_sym(cdr(arg)), arg)));} +static s7_pointer fx_cons_ss(s7_scheme *sc, s7_pointer arg) {return(cons(sc, lookup(sc, cadr(arg)), lookup(sc, opt2_sym(cdr(arg)))));} +static s7_pointer fx_cons_st(s7_scheme *sc, s7_pointer arg) {return(cons(sc, s_lookup(sc, cadr(arg), arg), t_lookup(sc, opt2_sym(cdr(arg)), arg)));} +static s7_pointer fx_cons_ts(s7_scheme *sc, s7_pointer arg) {return(cons(sc, t_lookup(sc, cadr(arg), arg), lookup(sc, opt2_sym(cdr(arg)))));} +static s7_pointer fx_cons_tu(s7_scheme *sc, s7_pointer arg) {return(cons(sc, t_lookup(sc, cadr(arg), arg), u_lookup(sc, caddr(arg), arg)));} +static s7_pointer fx_cons_tU(s7_scheme *sc, s7_pointer arg) {return(cons(sc, t_lookup(sc, cadr(arg), arg), U_lookup(sc, caddr(arg), arg)));} +/* static s7_pointer fx_cons_Ts(s7_scheme *sc, s7_pointer arg) {return(cons(sc, T_lookup(sc, cadr(arg), arg), s_lookup(sc, caddr(arg), arg)));} */ + +#define fx_c_ss_direct_any(Name, Lookup1, Lookup2) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + return(((s7_p_pp_t)opt3_direct(cdr(arg)))(sc, Lookup1(sc, cadr(arg), arg), Lookup2(sc, opt2_sym(cdr(arg)), arg))); \ + } + +fx_c_ss_direct_any(fx_c_ss_direct, s_lookup, s_lookup) +fx_c_ss_direct_any(fx_c_ts_direct, t_lookup, s_lookup) +fx_c_ss_direct_any(fx_c_tu_direct, t_lookup, u_lookup) +fx_c_ss_direct_any(fx_c_st_direct, s_lookup, t_lookup) +fx_c_ss_direct_any(fx_c_gt_direct, g_lookup, t_lookup) +fx_c_ss_direct_any(fx_c_tU_direct, t_lookup, U_lookup) + +static s7_pointer fx_multiply_ss(s7_scheme *sc, s7_pointer arg) {return(multiply_p_pp(sc, lookup(sc, cadr(arg)), lookup(sc, opt2_sym(cdr(arg)))));} +static s7_pointer fx_multiply_ts(s7_scheme *sc, s7_pointer arg) {return(multiply_p_pp(sc, t_lookup(sc, cadr(arg), arg), lookup(sc, opt2_sym(cdr(arg)))));} +/* static s7_pointer fx_multiply_Ts(s7_scheme *sc, s7_pointer arg) {return(multiply_p_pp(sc, T_lookup(sc, cadr(arg), arg), lookup(sc, opt2_sym(cdr(arg)))));} */ +static s7_pointer fx_multiply_fs(s7_scheme *sc, s7_pointer arg) {return(g_mul_xf(sc, lookup(sc, opt2_sym(cdr(arg))), real(cadr(arg)), 2));} +static s7_pointer fx_multiply_sf(s7_scheme *sc, s7_pointer arg) {return(g_mul_xf(sc, lookup(sc, cadr(arg)), real(opt2_con(cdr(arg))), 1));} +static s7_pointer fx_multiply_tf(s7_scheme *sc, s7_pointer arg) {return(g_mul_xf(sc, t_lookup(sc, cadr(arg), arg), real(opt2_con(cdr(arg))), 1));} +static s7_pointer fx_multiply_si(s7_scheme *sc, s7_pointer arg) {return(g_mul_xi(sc, lookup(sc, cadr(arg)), integer(opt2_con(cdr(arg))), 1));} +static s7_pointer fx_multiply_ti(s7_scheme *sc, s7_pointer arg) {return(g_mul_xi(sc, t_lookup(sc, cadr(arg), arg), integer(opt2_con(cdr(arg))), 1));} +static s7_pointer fx_multiply_ui(s7_scheme *sc, s7_pointer arg) {return(g_mul_xi(sc, u_lookup(sc, cadr(arg), arg), integer(opt2_con(cdr(arg))), 1));} +static s7_pointer fx_multiply_is(s7_scheme *sc, s7_pointer arg) {return(g_mul_xi(sc, lookup(sc, opt2_sym(cdr(arg))), integer(cadr(arg)), 2));} +static s7_pointer fx_multiply_it(s7_scheme *sc, s7_pointer arg) {return(g_mul_xi(sc, t_lookup(sc, opt2_sym(cdr(arg)), arg), integer(cadr(arg)), 2));} +static s7_pointer fx_multiply_tu(s7_scheme *sc, s7_pointer arg) {return(multiply_p_pp(sc, t_lookup(sc, cadr(arg), arg), u_lookup(sc, caddr(arg), arg)));} + +static inline s7_pointer fx_sqr_1(s7_scheme *sc, s7_pointer x) +{ + if (is_t_real(x)) return(make_real(sc, real(x) * real(x))); + +#if WITH_GMP + return(multiply_p_pp(sc, x, x)); +#else + switch (type(x)) + { +#if HAVE_OVERFLOW_CHECKS + case T_INTEGER: + { + s7_int val; + if (multiply_overflow(integer(x), integer(x), &val)) + { + if (WITH_WARNINGS) s7_warn(sc, 128, "integer sqr overflow: (* %" ld64 " %" ld64 ")\n", integer(x), integer(x)); + return(make_real(sc, (long_double)integer(x) * (long_double)integer(x))); + } + return(make_integer(sc, val)); + } + case T_RATIO: + { + s7_int num, den; + if ((multiply_overflow(numerator(x), numerator(x), &num)) || + (multiply_overflow(denominator(x), denominator(x), &den))) + return(make_real(sc, fraction(x) * fraction(x))); + return(make_ratio_with_div_check(sc, sc->multiply_symbol, num, den)); + } +#else + case T_INTEGER: return(make_integer(sc, integer(x) * integer(x))); + case T_RATIO: return(make_ratio(sc, numerator(x) * numerator(x), denominator(x) * denominator(x))); +#endif + case T_REAL: return(make_real(sc, real(x) * real(x))); + case T_COMPLEX: return(s7_make_complex(sc, real_part(x) * real_part(x) - imag_part(x) * imag_part(x), 2.0 * real_part(x) * imag_part(x))); + default: return(method_or_bust_pp(sc, x, sc->multiply_symbol, x, x, a_number_string, 1)); + } + return(x); +#endif +} + +static s7_pointer fx_sqr_s(s7_scheme *sc, s7_pointer arg) {return(fx_sqr_1(sc, lookup(sc, cadr(arg))));} +static s7_pointer fx_sqr_t(s7_scheme *sc, s7_pointer arg) {return(fx_sqr_1(sc, t_lookup(sc, cadr(arg), arg)));} + +static s7_pointer fx_add_sqr_sqr(s7_scheme *sc, s7_pointer arg) /* tbig -- need t case here */ +{ + sc->temp5 = fx_sqr_1(sc, lookup(sc, car(opt1_pair(cdr(arg))))); /* cadadr(arg) */ + return(add_p_pp(sc, sc->temp5, fx_sqr_1(sc, lookup(sc, car(opt3_pair(arg)))))); /* cadaddr(arg) */ +} + +static s7_pointer fx_c_s_sqr(s7_scheme *sc, s7_pointer arg) /* call */ +{ + set_car(sc->t2_1, lookup(sc, cadr(arg))); + set_car(sc->t2_2, fx_sqr_1(sc, lookup(sc, opt2_sym(cdr(arg))))); /* cadaddr(arg) */ + return(fn_proc(arg)(sc, sc->t2_1)); +} + +static s7_pointer fx_c_c_sqr(s7_scheme *sc, s7_pointer arg) /* fb */ +{ + set_car(sc->t2_2, fx_sqr_1(sc, lookup(sc, opt1_sym(cdr(arg))))); /* cadaddr(arg) */ + set_car(sc->t2_1, cadr(arg)); + return(fn_proc(arg)(sc, sc->t2_1)); +} + +static s7_pointer fx_geq_ss(s7_scheme *sc, s7_pointer arg) {return(geq_p_pp(sc, lookup(sc, cadr(arg)), lookup(sc, opt2_sym(cdr(arg)))));} +static s7_pointer fx_geq_ts(s7_scheme *sc, s7_pointer arg) {return(geq_p_pp(sc, t_lookup(sc, cadr(arg), arg), lookup(sc, opt2_sym(cdr(arg)))));} +static s7_pointer fx_geq_st(s7_scheme *sc, s7_pointer arg) {return(geq_p_pp(sc, lookup(sc, cadr(arg)), t_lookup(sc, opt2_sym(cdr(arg)), arg)));} +static s7_pointer fx_geq_us(s7_scheme *sc, s7_pointer arg) {return(geq_p_pp(sc, u_lookup(sc, cadr(arg), arg), lookup(sc, opt2_sym(cdr(arg)))));} +static s7_pointer fx_geq_vs(s7_scheme *sc, s7_pointer arg) {return(geq_p_pp(sc, v_lookup(sc, cadr(arg), arg), lookup(sc, opt2_sym(cdr(arg)))));} +static s7_pointer fx_geq_tT(s7_scheme *sc, s7_pointer arg) {return(geq_p_pp(sc, t_lookup(sc, cadr(arg), arg), T_lookup(sc, caddr(arg), arg)));} +static s7_pointer fx_geq_tu(s7_scheme *sc, s7_pointer arg) {return(geq_p_pp(sc, t_lookup(sc, cadr(arg), arg), u_lookup(sc, caddr(arg), arg)));} +static s7_pointer fx_geq_TU(s7_scheme *sc, s7_pointer arg) {return(geq_p_pp(sc, T_lookup(sc, cadr(arg), arg), U_lookup(sc, caddr(arg), arg)));} +static s7_pointer fx_geq_to(s7_scheme *sc, s7_pointer arg) {return(geq_p_pp(sc, t_lookup(sc, cadr(arg), arg), o_lookup(sc, opt2_sym(cdr(arg)), arg)));} +static s7_pointer fx_geq_vo(s7_scheme *sc, s7_pointer arg) {return(geq_p_pp(sc, v_lookup(sc, cadr(arg), arg), o_lookup(sc, opt2_sym(cdr(arg)), arg)));} +static s7_pointer fx_geq_ot(s7_scheme *sc, s7_pointer arg) {return(geq_p_pp(sc, o_lookup(sc, cadr(arg), arg), t_lookup(sc, opt2_sym(cdr(arg)), arg)));} + +static s7_pointer fx_gt_ss(s7_scheme *sc, s7_pointer arg) {return(gt_p_pp(sc, lookup(sc, cadr(arg)), lookup(sc, opt2_sym(cdr(arg)))));} +static s7_pointer fx_gt_ts(s7_scheme *sc, s7_pointer arg) {return(gt_p_pp(sc, t_lookup(sc, cadr(arg), arg), lookup(sc, opt2_sym(cdr(arg)))));} +static s7_pointer fx_gt_to(s7_scheme *sc, s7_pointer arg) {return(gt_p_pp(sc, t_lookup(sc, cadr(arg), arg), o_lookup(sc, opt2_sym(cdr(arg)), arg)));} +static s7_pointer fx_gt_tu(s7_scheme *sc, s7_pointer arg) {return(gt_p_pp(sc, t_lookup(sc, cadr(arg), arg), u_lookup(sc, caddr(arg), arg)));} +static s7_pointer fx_gt_ut(s7_scheme *sc, s7_pointer arg) {return(gt_p_pp(sc, u_lookup(sc, cadr(arg), arg), t_lookup(sc, caddr(arg), arg)));} +static s7_pointer fx_gt_tg(s7_scheme *sc, s7_pointer arg) {return(gt_p_pp(sc, t_lookup(sc, cadr(arg), arg), global_value(opt2_sym(cdr(arg)))));} + +static s7_pointer fx_gt_tT(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer p1 = t_lookup(sc, cadr(arg), arg); + s7_pointer p2 = T_lookup(sc, caddr(arg), arg); + return(((is_t_integer(p1)) && (is_t_integer(p2))) ? make_boolean(sc, integer(p1) > integer(p2)) : gt_p_pp(sc, p1, p2)); +} + +#define fx_gt_si_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer x = Lookup(sc, cadr(arg), arg); \ + if (is_t_integer(x)) return(make_boolean(sc, integer(x) > integer(opt2_con(cdr(arg))))); \ + if (is_t_real(x)) return(make_boolean(sc, real(x) > integer(opt2_con(cdr(arg))))); \ + return(g_greater_xi(sc, set_plist_2(sc, x, opt2_con(cdr(arg))))); /* caddr(arg) */ \ + } + +fx_gt_si_any(fx_gt_si, s_lookup) +fx_gt_si_any(fx_gt_ti, t_lookup) +fx_gt_si_any(fx_gt_ui, u_lookup) + +static s7_pointer fx_leq_ss(s7_scheme *sc, s7_pointer arg) {return(leq_p_pp(sc, lookup(sc, cadr(arg)), lookup(sc, opt2_sym(cdr(arg)))));} +static s7_pointer fx_leq_ts(s7_scheme *sc, s7_pointer arg) {return(leq_p_pp(sc, t_lookup(sc, cadr(arg), arg), lookup(sc, opt2_sym(cdr(arg)))));} +static s7_pointer fx_leq_tu(s7_scheme *sc, s7_pointer arg) {return(leq_p_pp(sc, t_lookup(sc, cadr(arg), arg), u_lookup(sc, caddr(arg), arg)));} + +#define fx_leq_si_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer x = Lookup(sc, cadr(arg), arg); \ + if (is_t_integer(x)) return(make_boolean(sc, integer(x) <= integer(opt2_con(cdr(arg))))); \ + return(g_leq_xi(sc, set_plist_2(sc, x, opt2_con(cdr(arg))))); /* caddr(arg) */ \ + } + +fx_leq_si_any(fx_leq_si, s_lookup) +fx_leq_si_any(fx_leq_ti, t_lookup) +fx_leq_si_any(fx_leq_ui, u_lookup) +fx_leq_si_any(fx_leq_vi, v_lookup) + +static s7_pointer fx_lt_ss(s7_scheme *sc, s7_pointer arg) {return(lt_p_pp(sc, lookup(sc, cadr(arg)), lookup(sc, opt2_sym(cdr(arg)))));} +static s7_pointer fx_lt_sg(s7_scheme *sc, s7_pointer arg) {return(lt_p_pp(sc, lookup(sc, cadr(arg)), lookup_global(sc, opt2_sym(cdr(arg)))));} +static s7_pointer fx_lt_tg(s7_scheme *sc, s7_pointer arg) {return(lt_p_pp(sc, t_lookup(sc, cadr(arg), arg), lookup_global(sc, opt2_sym(cdr(arg)))));} + +static s7_pointer fx_lt_gsg(s7_scheme *sc, s7_pointer arg) /* gsg is much faster than sss */ +{ + s7_pointer v1 = lookup_global(sc, cadr(arg)); + s7_pointer v2 = lookup(sc, opt1_sym(cdr(arg))); /* caddr(arg) */ + s7_pointer v3 = lookup_global(sc, opt2_sym(cdr(arg))); /* cadddr(arg) */ + if ((is_t_integer(v1)) && (is_t_integer(v2)) && (is_t_integer(v3))) + return(make_boolean(sc, ((integer(v1) < integer(v2)) && (integer(v2) < integer(v3))))); + if (!is_real(v3)) + wrong_type_error_nr(sc, sc->lt_symbol, 3, v3, sc->type_names[T_REAL]); /* else (< 2 1 1+i) returns #f */ + return(make_boolean(sc, (lt_b_7pp(sc, v1, v2)) && (lt_b_7pp(sc, v2, v3)))); +} + +static s7_pointer fx_lt_ts(s7_scheme *sc, s7_pointer arg) {return(lt_p_pp(sc, t_lookup(sc, cadr(arg), arg), lookup(sc, opt2_sym(cdr(arg)))));} +static s7_pointer fx_lt_tT(s7_scheme *sc, s7_pointer arg) {return(lt_p_pp(sc, t_lookup(sc, cadr(arg), arg), T_lookup(sc, opt2_sym(cdr(arg)), cadr(arg))));} +static s7_pointer fx_lt_tu(s7_scheme *sc, s7_pointer arg) {return(lt_p_pp(sc, t_lookup(sc, cadr(arg), arg), u_lookup(sc, caddr(arg), arg)));} +static s7_pointer fx_lt_tU(s7_scheme *sc, s7_pointer arg) {return(lt_p_pp(sc, t_lookup(sc, cadr(arg), arg), U_lookup(sc, caddr(arg), arg)));} +static s7_pointer fx_lt_ut(s7_scheme *sc, s7_pointer arg) {return(lt_p_pp(sc, u_lookup(sc, cadr(arg), arg), t_lookup(sc, caddr(arg), arg)));} + +static s7_pointer fx_lt_tf(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer x = t_lookup(sc, cadr(arg), arg); + if (is_t_real(x)) return(make_boolean(sc, real(x) < real(opt2_con(cdr(arg))))); + return(g_less_xf(sc, set_plist_2(sc, x, opt2_con(cdr(arg))))); /* caddr(arg) */ +} + +#define fx_lt_si_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer x = Lookup(sc, cadr(arg), arg); \ + if (is_t_integer(x)) return(make_boolean(sc, integer(x) < integer(opt2_con(cdr(arg))))); \ + return(g_less_xi(sc, set_plist_2(sc, x, opt2_con(cdr(arg))))); /* caddr(arg) */ \ + } + +fx_lt_si_any(fx_lt_si, s_lookup) +fx_lt_si_any(fx_lt_ti, t_lookup) + +static s7_pointer fx_lt_t0(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer x = t_lookup(sc, cadr(arg), arg); + if (is_t_integer(x)) return(make_boolean(sc, integer(x) < 0)); + return(g_less_xi(sc, set_plist_2(sc, x, int_zero))); +} + +static s7_pointer fx_lt_t1(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer x = t_lookup(sc, cadr(arg), arg); + if (is_t_integer(x)) return(make_boolean(sc, integer(x) < 1)); + return(g_less_xi(sc, set_plist_2(sc, x, int_one))); +} + +static s7_pointer fx_lt_t2(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer x = t_lookup(sc, cadr(arg), arg); + if (is_t_integer(x)) return(make_boolean(sc, integer(x) < 2)); + return(g_less_xi(sc, set_plist_2(sc, x, int_two))); +} + +static s7_pointer fx_geq_tf(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer x = t_lookup(sc, cadr(arg), arg); + if (is_t_real(x)) return(make_boolean(sc, real(x) >= real(opt2_con(cdr(arg))))); + return(g_geq_xf(sc, set_plist_2(sc, x, opt2_con(cdr(arg))))); /* caddr(arg) */ +} + +#define fx_geq_si_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer x = Lookup(sc, cadr(arg), arg); \ + if (is_t_integer(x)) return(make_boolean(sc, integer(x) >= integer(opt2_con(cdr(arg))))); \ + return(g_geq_xi(sc, set_plist_2(sc, x, opt2_con(cdr(arg))))); /* caddr(arg) */ \ + } + +fx_geq_si_any(fx_geq_si, s_lookup) +fx_geq_si_any(fx_geq_ti, t_lookup) + +static s7_pointer fx_geq_t0(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer x = t_lookup(sc, cadr(arg), arg); + if (is_t_integer(x)) return(make_boolean(sc, integer(x) >= 0)); + return(g_geq_xi(sc, set_plist_2(sc, x, int_zero))); +} + +#define fx_num_eq_ss_any(Name, Lookup1, Lookup2) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer x = Lookup1(sc, cadr(arg), arg); \ + s7_pointer y = Lookup2(sc, opt2_sym(cdr(arg)), arg); \ + return(make_boolean(sc, ((is_t_integer(x)) && (is_t_integer(y))) ? (integer(x) == integer(y)) : num_eq_b_7pp(sc, x, y))); \ + } + +fx_num_eq_ss_any(fx_num_eq_ss, s_lookup, s_lookup) +fx_num_eq_ss_any(fx_num_eq_ts, t_lookup, s_lookup) +fx_num_eq_ss_any(fx_num_eq_to, t_lookup, o_lookup) +fx_num_eq_ss_any(fx_num_eq_tg, t_lookup, g_lookup) +fx_num_eq_ss_any(fx_num_eq_tT, t_lookup, T_lookup) +fx_num_eq_ss_any(fx_num_eq_tu, t_lookup, u_lookup) +fx_num_eq_ss_any(fx_num_eq_tv, t_lookup, v_lookup) +fx_num_eq_ss_any(fx_num_eq_ut, u_lookup, t_lookup) +fx_num_eq_ss_any(fx_num_eq_us, u_lookup, s_lookup) +fx_num_eq_ss_any(fx_num_eq_vs, v_lookup, s_lookup) +fx_num_eq_ss_any(fx_num_eq_uU, u_lookup, U_lookup) +fx_num_eq_ss_any(fx_num_eq_vU, v_lookup, U_lookup) + + +#define fx_is_eq_ss_any(Name, Lookup1, Lookup2) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer x = Lookup1(sc, cadr(arg), arg); \ + s7_pointer y = Lookup2(sc, opt2_sym(cdr(arg)), arg); \ + return(make_boolean(sc, (x == y) || ((is_unspecified(x)) && (is_unspecified(y))))); \ + } + +fx_is_eq_ss_any(fx_is_eq_ss, s_lookup, s_lookup) +fx_is_eq_ss_any(fx_is_eq_ts, t_lookup, s_lookup) +fx_is_eq_ss_any(fx_is_eq_tu, t_lookup, u_lookup) +fx_is_eq_ss_any(fx_is_eq_to, t_lookup, o_lookup) + + +static s7_pointer fx_not_is_eq_ss(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer x = lookup(sc, opt3_sym(arg)); + s7_pointer y = lookup(sc, opt1_sym(cdr(arg))); + return(make_boolean(sc, (x != y) && ((!is_unspecified(x)) || (!is_unspecified(y))))); +} + +static s7_pointer fx_not_is_eq_sq(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer x = lookup(sc, opt2_sym(cdr(arg))); + s7_pointer y = opt3_con(cdr(arg)); + return(make_boolean(sc, (x != y) && ((!is_unspecified(x)) || (!is_unspecified(y))))); +} + +static s7_pointer x_hash_table_ref_ss(s7_scheme *sc, s7_pointer table, s7_pointer key) +{ + return((is_hash_table(table)) ? hash_entry_value((*hash_table_checker(table))(sc, table, key)) : g_hash_table_ref(sc, set_plist_2(sc, table, key))); +} + +static s7_pointer fx_hash_table_ref_ss(s7_scheme *sc, s7_pointer arg) {return(x_hash_table_ref_ss(sc, lookup(sc, cadr(arg)), lookup(sc, opt2_sym(cdr(arg)))));} +static s7_pointer fx_hash_table_ref_st(s7_scheme *sc, s7_pointer arg) {return(x_hash_table_ref_ss(sc, lookup(sc, cadr(arg)), t_lookup(sc, opt2_sym(cdr(arg)), arg)));} +static s7_pointer fx_hash_table_ref_TV(s7_scheme *sc, s7_pointer arg) {return(x_hash_table_ref_ss(sc, T_lookup(sc, cadr(arg), arg), V_lookup(sc, opt2_sym(cdr(arg)), arg)));} + +static s7_pointer fx_hash_table_ref_car(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer table = lookup(sc, cadr(arg)); + s7_pointer lst = lookup(sc, opt2_sym(cdr(arg))); + if (!is_pair(lst)) + sole_arg_wrong_type_error_nr(sc, sc->car_symbol, lst, sc->type_names[T_PAIR]); + return((is_hash_table(table)) ? hash_entry_value((*hash_table_checker(table))(sc, table, car(lst))) : g_hash_table_ref(sc, set_plist_2(sc, table, car(lst)))); +} + + +static inline s7_pointer fx_hash_table_increment_1(s7_scheme *sc, s7_pointer table, s7_pointer key, s7_pointer arg) +{ + hash_entry_t *val; + if (!is_hash_table(table)) + return(mutable_method_or_bust_ppp(sc, table, sc->hash_table_set_symbol, table, key, fx_call(sc, cdddr(arg)), sc->type_names[T_HASH_TABLE], 1)); + val = (*hash_table_checker(table))(sc, table, key); + if (val != sc->unentry) + { + if (!is_t_integer(hash_entry_value(val))) + sole_arg_wrong_type_error_nr(sc, sc->add_symbol, cadddr(arg), sc->type_names[T_INTEGER]); + hash_entry_set_value(val, make_integer(sc, integer(hash_entry_value(val)) + 1)); + return(hash_entry_value(val)); + } + s7_hash_table_set(sc, table, key, int_one); + return(int_one); +} + +static s7_pointer fx_hash_table_increment(s7_scheme *sc, s7_pointer arg) +{ + return(fx_hash_table_increment_1(sc, lookup(sc, cadr(arg)), lookup(sc, caddr(arg)), arg)); +} + + +static s7_pointer fx_lint_let_ref_s(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer sym; + s7_pointer lt = s_lookup(sc, opt2_sym(arg), arg); /* (var-ref local-var) -> local-var, opt_sym2(arg) == cadr(arg) */ + if (!is_pair(lt)) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_3(sc, wrap_string(sc, "~S should be (cons name let), but it is ~S", 42), opt2_sym(arg), lt)); + lt = cdr(lt); + if (!is_let(lt)) wrong_type_error_nr(sc, sc->let_ref_symbol, 1, lt, a_let_string); + sym = opt2_sym(cdr(arg)); /* (let-ref (cdr v) 'ref) -> ref == opt3_sym(cdar(closure_body(opt1_lambda(arg)))); */ + for (s7_pointer y = let_slots(lt); tis_slot(y); y = next_slot(y)) + if (slot_symbol(y) == sym) + return(slot_value(y)); + return(lint_let_ref_p_pp(sc, let_outlet(lt), sym)); +} + +static s7_pointer fx_memq_sq_2(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer p = opt2_con(cdr(arg)); + s7_pointer obj = lookup(sc, cadr(arg)); + if (obj == car(p)) return(p); + return((obj == cadr(p)) ? cdr(p) : sc->F); +} + +static s7_pointer fx_c_cq(s7_scheme *sc, s7_pointer arg) {return(fn_proc(arg)(sc, with_list_t2(cadr(arg), opt2_con(cdr(arg)))));} + +#define fx_c_sss_any(Name, Lookup1, Lookup2, Lookup3) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + set_car(sc->t3_1, Lookup1(sc, cadr(arg), arg)); \ + set_car(sc->t3_2, Lookup2(sc, opt1_sym(cdr(arg)), arg)); /* caddr(arg) */ \ + set_car(sc->t3_3, Lookup3(sc, opt2_sym(cdr(arg)), arg)); /* cadddr(arg) */ \ + return(fn_proc(arg)(sc, sc->t3_1)); \ + } + +fx_c_sss_any(fx_c_sss, s_lookup, s_lookup, s_lookup) +fx_c_sss_any(fx_c_sts, s_lookup, t_lookup, s_lookup) +fx_c_sss_any(fx_c_tus, t_lookup, u_lookup, s_lookup) +fx_c_sss_any(fx_c_tuv, t_lookup, u_lookup, v_lookup) + + +static s7_pointer fx_c_sss_direct(s7_scheme *sc, s7_pointer arg) +{ + return(((s7_p_ppp_t)opt3_direct(cdr(arg)))(sc, lookup(sc, cadr(arg)), lookup(sc, opt1_sym(cdr(arg))), lookup(sc, opt2_sym(cdr(arg))))); +} + +static s7_pointer fx_c_tuv_direct(s7_scheme *sc, s7_pointer arg) +{ + return(((s7_p_ppp_t)opt3_direct(cdr(arg)))(sc, t_lookup(sc, cadr(arg), arg), u_lookup(sc, opt1_sym(cdr(arg)), arg), v_lookup(sc, opt2_sym(cdr(arg)), arg))); +} + +static s7_pointer fx_vset_sts(s7_scheme *sc, s7_pointer arg) +{ + return(vector_set_p_ppp(sc, lookup(sc, cadr(arg)), t_lookup(sc, opt1_sym(cdr(arg)), arg), lookup(sc, opt2_sym(cdr(arg))))); +} + +static s7_pointer fx_vset_oto(s7_scheme *sc, s7_pointer arg) +{ + return(vector_set_p_ppp(sc, o_lookup(sc, cadr(arg), arg), t_lookup(sc, opt1_sym(cdr(arg)), arg), o_lookup(sc, opt2_sym(cdr(arg)), arg))); +} + +#define fx_c_scs_any(Name, Lookup1, Lookup2) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + set_car(sc->t3_1, Lookup1(sc, cadr(arg), arg)); \ + set_car(sc->t3_3, Lookup2(sc, opt2_sym(cdr(arg)), arg)); /* cadddr(arg) */ \ + set_car(sc->t3_2, opt1_con(cdr(arg))); /* caddr(arg) */ \ + return(fn_proc(arg)(sc, sc->t3_1)); \ + } + +fx_c_scs_any(fx_c_scs, s_lookup, s_lookup) +fx_c_scs_any(fx_c_tcs, t_lookup, s_lookup) + + +#define fx_c_scs_direct_any(Name, Lookup1, Lookup2) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + return(((s7_p_ppp_t)opt3_direct(cdr(arg)))(sc, Lookup1(sc, cadr(arg), arg), opt1_con(cdr(arg)), Lookup2(sc, opt2_sym(cdr(arg)), arg))); \ + } + +fx_c_scs_direct_any(fx_c_scs_direct, s_lookup, s_lookup) +fx_c_scs_direct_any(fx_c_tcu_direct, t_lookup, u_lookup) +fx_c_scs_direct_any(fx_c_tcs_direct, t_lookup, s_lookup) +fx_c_scs_direct_any(fx_c_TcU_direct, T_lookup, U_lookup) + + +static s7_pointer fx_c_scc(s7_scheme *sc, s7_pointer arg) +{ + set_car(sc->t3_1, lookup(sc, cadr(arg))); + set_car(sc->t3_2, opt1_con(cdr(arg))); /* caddr(arg) */ + set_car(sc->t3_3, opt2_con(cdr(arg))); /* cadddr(arg) */ + return(fn_proc(arg)(sc, sc->t3_1)); +} + +#define fx_c_css_any(Name, Lookup1, Lookup2) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + set_car(sc->t3_2, Lookup1(sc, opt1_sym(cdr(arg)), arg)); /* caddr(arg) */ \ + set_car(sc->t3_3, Lookup2(sc, opt2_sym(cdr(arg)), arg)); /* cadddr(arg) */ \ + set_car(sc->t3_1, cadr(arg)); \ + return(fn_proc(arg)(sc, sc->t3_1)); \ + } + +fx_c_css_any(fx_c_css, s_lookup, s_lookup) +fx_c_css_any(fx_c_ctv, t_lookup, v_lookup) + +static s7_pointer fx_c_csc(s7_scheme *sc, s7_pointer arg) +{ + set_car(sc->t3_2, lookup(sc, opt1_sym(cdr(arg)))); /* caddr(arg) */ + set_car(sc->t3_1, opt3_con(cdr(arg))); /* cadr(arg) or maybe cadadr if quoted? */ + set_car(sc->t3_3, opt2_con(cdr(arg))); /* cadddr(arg) */ + return(fn_proc(arg)(sc, sc->t3_1)); +} + +static s7_pointer fx_c_ccs(s7_scheme *sc, s7_pointer arg) +{ + set_car(sc->t3_3, lookup(sc, opt1_sym(cdr(arg)))); /* cadddr(arg) */ + set_car(sc->t3_1, cadr(arg)); /* maybe opt3_con? */ + set_car(sc->t3_2, opt2_con(cdr(arg))); /* caddr(arg) */ + return(fn_proc(arg)(sc, sc->t3_1)); +} + +#define fx_c_ssc_any(Name, Lookup1, Lookup2) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + set_car(sc->t3_1, Lookup1(sc, cadr(arg), arg)); \ + set_car(sc->t3_2, Lookup2(sc, opt1_sym(cdr(arg)), arg)); /* caddr(arg) */ \ + set_car(sc->t3_3, opt2_con(cdr(arg))); /* cadddr(arg) */ \ + return(fn_proc(arg)(sc, sc->t3_1)); \ + } + +fx_c_ssc_any(fx_c_ssc, s_lookup, s_lookup) +fx_c_ssc_any(fx_c_tuc, t_lookup, u_lookup) + +static s7_pointer fx_c_opncq(s7_scheme *sc, s7_pointer arg) +{ + return(fn_proc(arg)(sc, with_list_t1(fc_call(sc, cadr(arg))))); +} + +#define fx_c_opsq_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer largs = cadr(arg); \ + set_car(sc->t1_1, fn_proc(largs)(sc, with_list_t1(Lookup(sc, cadr(largs), largs)))); \ + return(fn_proc(arg)(sc, sc->t1_1)); \ + } + +fx_c_opsq_any(fx_c_opsq, s_lookup) +fx_c_opsq_any(fx_c_optq, t_lookup) + +static s7_pointer fx_c_optq_direct(s7_scheme *sc, s7_pointer arg) +{ + return(((s7_p_p_t)opt2_direct(cdr(arg)))(sc, ((s7_p_p_t)opt3_direct(cdr(arg)))(sc, t_lookup(sc, opt1_sym(cdr(arg)), arg)))); +} + +#define fx_c_car_s_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer val = Lookup(sc, opt2_sym(cdr(arg)), arg); \ + set_car(sc->t1_1, (is_pair(val)) ? car(val) : g_car(sc, set_plist_1(sc, val))); \ + return(fn_proc(arg)(sc, sc->t1_1)); \ + } + +fx_c_car_s_any(fx_c_car_s, s_lookup) +fx_c_car_s_any(fx_c_car_t, t_lookup) +fx_c_car_s_any(fx_c_car_u, u_lookup) + +#define fx_c_cdr_s_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer val = Lookup(sc, opt2_sym(cdr(arg)), arg); \ + set_car(sc->t1_1, (is_pair(val)) ? cdr(val) : g_cdr(sc, set_plist_1(sc, val))); \ + return(fn_proc(arg)(sc, sc->t1_1)); \ + } + +fx_c_cdr_s_any(fx_c_cdr_s, s_lookup) +fx_c_cdr_s_any(fx_c_cdr_t, t_lookup) + +#define fx_is_type_opsq_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + set_car(sc->t1_1, Lookup(sc, opt2_sym(cdr(arg)), arg)); \ + return(make_boolean(sc, (uint8_t)(opt3_byte(cdr(arg))) == type(fn_proc(cadr(arg))(sc, sc->t1_1)))); \ + } + +fx_is_type_opsq_any(fx_is_type_opsq, s_lookup) +fx_is_type_opsq_any(fx_is_type_optq, t_lookup) + +static s7_pointer fx_is_type_car_s(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer val = lookup(sc, opt2_sym(cdr(arg))); + return(make_boolean(sc, (is_pair(val)) ? + ((uint8_t)(opt3_byte(cdr(arg))) == type(car(val))) : + ((uint8_t)(opt3_byte(cdr(arg))) == type(g_car(sc, set_plist_1(sc, val)))))); +} + +static s7_pointer fx_is_type_car_t(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer func, val = t_lookup(sc, opt2_sym(cdr(arg)), arg); + if (is_pair(val)) + return(make_boolean(sc, (uint8_t)(opt3_byte(cdr(arg))) == type(car(val)))); + if (!has_active_methods(sc, val)) /* this verbosity saves 1/3 total compute time (overhead!) */ + wrong_type_error_nr(sc, sc->car_symbol, 1, val, sc->type_names[T_PAIR]); + func = find_method_with_let(sc, val, sc->car_symbol); + if (func == sc->undefined) + wrong_type_error_nr(sc, sc->car_symbol, 1, val, sc->type_names[T_PAIR]); + return(make_boolean(sc, type(s7_apply_function(sc, func, set_plist_1(sc, val))) == (uint8_t)opt3_byte(cdr(arg)))); +} + +static s7_pointer fx_eq_weak1_type_s(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer func, val = lookup(sc, opt2_sym(cdr(arg))); + if (is_c_pointer(val)) /* (let? (c-pointer-weak1 val)) etc */ + return(make_boolean(sc, (uint8_t)(opt3_byte(cdr(arg))) == type(c_pointer_weak1(val)))); + if (!has_active_methods(sc, val)) /* calling g_c_pointer_weak1 here instead is much slower, error by itself is much faster! splitting out does not help */ + wrong_type_error_nr(sc, sc->c_pointer_weak1_symbol, 1, val, sc->type_names[T_C_POINTER]); + func = find_method_with_let(sc, val, sc->c_pointer_weak1_symbol); + if (func == sc->undefined) + wrong_type_error_nr(sc, sc->c_pointer_weak1_symbol, 1, val, sc->type_names[T_C_POINTER]); + return(make_boolean(sc, type(s7_apply_function(sc, func, set_plist_1(sc, val))) == (uint8_t)opt3_byte(cdr(arg)))); +} + +#define fx_not_opsq_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer largs = cadr(arg); \ + set_car(sc->t1_1, Lookup(sc, cadr(largs), arg)); \ + return((fn_proc(largs)(sc, sc->t1_1) == sc->F) ? sc->T : sc->F); \ + } + +fx_not_opsq_any(fx_not_opsq, s_lookup) +fx_not_opsq_any(fx_not_optq, t_lookup) + +static s7_pointer fx_not_car_t(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer p = t_lookup(sc, opt1_sym(cdr(arg)), arg); /* cadadr */ + s7_pointer res = (is_pair(p)) ? car(p) : g_car(sc, set_plist_1(sc, p)); + return((res == sc->F) ? sc->T : sc->F); +} + + +#define fx_c_opssq_any(Name, Lookup1, Lookup2) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + set_car(sc->t2_1, Lookup1(sc, opt3_sym(arg), arg)); \ + set_car(sc->t2_2, Lookup2(sc, opt1_sym(cdr(arg)), arg)); /* or opt2_sym */ \ + return(fn_proc(arg)(sc, with_list_t1(fn_proc(cadr(arg))(sc, sc->t2_1)))); \ + } + +fx_c_opssq_any(fx_c_opssq, s_lookup, s_lookup) +fx_c_opssq_any(fx_c_optuq, t_lookup, u_lookup) +fx_c_opssq_any(fx_c_opstq, s_lookup, t_lookup) + + +#define fx_c_opssq_direct_any(Name, Lookup1, Lookup2) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + return(((s7_p_p_t)opt2_direct(cdr(arg)))(sc, \ + ((s7_p_pp_t)opt3_direct(cdr(arg)))(sc, Lookup1(sc, opt3_sym(arg), arg), Lookup2(sc, opt1_sym(cdr(arg)), arg)))); \ + } + +fx_c_opssq_direct_any(fx_c_opssq_direct, s_lookup, s_lookup) +fx_c_opssq_direct_any(fx_c_opstq_direct, s_lookup, t_lookup) +fx_c_opssq_direct_any(fx_c_optuq_direct, t_lookup, u_lookup) + + +#define fx_not_opssq_any(Name, Lookup1, Lookup2) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer larg = cadr(arg); \ + set_car(sc->t2_1, Lookup1(sc, cadr(larg), larg)); \ + set_car(sc->t2_2, Lookup2(sc, opt2_sym(cdr(larg)), larg)); \ + return((fn_proc(larg)(sc, sc->t2_1) == sc->F) ? sc->T : sc->F); \ + } + +fx_not_opssq_any(fx_not_opssq, s_lookup, s_lookup) +fx_not_opssq_any(fx_not_oputq, u_lookup, t_lookup) + + +static s7_pointer fx_not_lt_ut(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer y = u_lookup(sc, opt3_sym(arg), arg); + s7_pointer x = t_lookup(sc, opt1_sym(cdr(arg)), arg); + return(make_boolean(sc, ((is_t_integer(x)) && (is_t_integer(y))) ? (integer(y) >= integer(x)) : geq_b_7pp(sc, y, x))); +} + +static s7_pointer fx_is_zero_remainder_car(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer t = t_lookup(sc, opt1_sym(cdr(arg)), arg); + s7_pointer u = u_lookup(sc, opt3_sym(arg), arg); + u = (is_pair(u)) ? car(u) : g_car(sc, set_plist_1(sc, u)); /* g_car much less overhead than car_p_p or simple_error(?) */ + if ((is_t_integer(u)) && (is_t_integer(t))) + return(make_boolean(sc, remainder_i_7ii(sc, integer(u), integer(t)) == 0)); + return(make_boolean(sc, is_zero_b_7p(sc, remainder_p_pp(sc, u, t)))); +} + +static s7_pointer fx_is_zero_remainder_o(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer s = o_lookup(sc, opt3_sym(arg), arg); + s7_pointer t = t_lookup(sc, opt1_sym(cdr(arg)), arg); + if ((is_t_integer(s)) && (is_t_integer(t))) + return(make_boolean(sc, remainder_i_7ii(sc, integer(s), integer(t)) == 0)); + return(make_boolean(sc, is_zero_b_7p(sc, remainder_p_pp(sc, s, t)))); +} + +#define fx_c_opscq_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer largs = cadr(arg); \ + set_car(sc->t2_1, Lookup(sc, cadr(largs), largs)); \ + set_car(sc->t2_2, opt2_con(cdr(largs))); \ + return(fn_proc(arg)(sc, with_list_t1(fn_proc(largs)(sc, sc->t2_1)))); \ + } + +fx_c_opscq_any(fx_c_opscq, s_lookup) +fx_c_opscq_any(fx_c_optcq, t_lookup) + +static s7_pointer fx_is_zero_remainder_ti(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer larg = cdadr(arg); + s7_pointer t = t_lookup(sc, car(larg), arg); + s7_int u = integer(cadr(larg)); + if (is_t_integer(t)) return(make_boolean(sc, (integer(t) % u) == 0)); + return(make_boolean(sc, is_zero_b_7p(sc, remainder_p_pi(sc, t, u)))); +} + +static s7_pointer fx_not_opscq(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer largs = cadr(arg); + set_car(sc->t2_1, lookup(sc, cadr(largs))); + set_car(sc->t2_2, opt2_con(cdr(largs))); + return((fn_proc(largs)(sc, sc->t2_1) == sc->F) ? sc->T : sc->F); +} + +static s7_pointer fx_c_opcsq(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer largs = cadr(arg); + set_car(sc->t2_2, lookup(sc, caddr(largs))); + set_car(sc->t2_1, opt1_con(cdr(largs))); /* cadr(largs) or cadadr */ + return(fn_proc(arg)(sc, with_list_t1(fn_proc(largs)(sc, sc->t2_1)))); +} + +static s7_pointer fx_c_opcsq_c(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer largs = cadr(arg); + set_car(sc->t2_2, lookup(sc, caddr(largs))); + set_car(sc->t2_1, opt1_con(cdr(largs))); /* cadr(largs) or cadadr */ + set_car(sc->t2_1, fn_proc(largs)(sc, sc->t2_1)); + set_car(sc->t2_2, caddr(arg)); + return(fn_proc(arg)(sc, sc->t2_1)); +} + +static s7_pointer fx_c_opcsq_s(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer largs = cadr(arg); + set_car(sc->t2_2, lookup(sc, caddr(largs))); + set_car(sc->t2_1, opt1_con(cdr(largs))); /* cadr(largs) or cadadr */ + set_car(sc->t2_1, fn_proc(largs)(sc, sc->t2_1)); + set_car(sc->t2_2, lookup(sc, caddr(arg))); + return(fn_proc(arg)(sc, sc->t2_1)); +} + +static s7_pointer fx_c_opssq_s(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer largs = cadr(arg); + set_car(sc->t2_1, lookup(sc, cadr(largs))); + set_car(sc->t2_2, lookup(sc, opt2_sym(cdr(largs)))); + set_car(sc->t2_1, fn_proc(largs)(sc, sc->t2_1)); + set_car(sc->t2_2, lookup(sc, caddr(arg))); + return(fn_proc(arg)(sc, sc->t2_1)); +} + +static s7_pointer fx_c_opssq_s_direct(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer largs = opt3_pair(arg); /* cdadr(arg) */ + return(((s7_p_pp_t)opt2_direct(cdr(arg)))(sc, + ((s7_p_pp_t)opt3_direct(cdr(arg)))(sc, lookup(sc, car(largs)), lookup(sc, opt2_sym(largs))), + lookup(sc, caddr(arg)))); +} + +static s7_pointer fx_add_mul_opssq_s(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer largs = opt3_pair(arg); /* cdadr(arg) */ + s7_pointer a = lookup(sc, car(largs)); + s7_pointer b = lookup(sc, opt2_sym(largs)); + s7_pointer c = lookup(sc, caddr(arg)); + if ((is_t_integer(a)) && (is_t_integer(b)) && (is_t_integer(c))) +#if HAVE_OVERFLOW_CHECKS + { + s7_int val; + if ((multiply_overflow(integer(a), integer(b), &val)) || + (add_overflow(val, integer(c), &val))) + { + if (WITH_WARNINGS) s7_warn(sc, 128, "integer multiply/add overflow: (+ (* %" ld64 " %" ld64 ") %" ld64 ")\n", integer(a), integer(b), integer(c)); + return(make_real(sc, ((long_double)integer(a) * (long_double)integer(b)) + (long_double)integer(c))); + } + return(make_integer(sc, val)); + } +#else + return(make_integer(sc, (integer(a) * integer(b)) + integer(c))); +#endif + return(add_p_pp(sc, multiply_p_pp(sc, a, b), c)); +} + +static s7_pointer fx_add_vref_s(s7_scheme *sc, s7_pointer arg) +{ + return(add_p_pp(sc, vector_ref_p_pp(sc, lookup(sc, car(opt3_pair(arg))), lookup(sc, opt2_sym(opt3_pair(arg)))), lookup(sc, caddr(arg)))); +} + +static s7_pointer fx_add_s_vref(s7_scheme *sc, s7_pointer arg) +{ + return(add_p_pp(sc, lookup(sc, cadr(arg)), vector_ref_p_pp(sc, lookup(sc, car(opt3_pair(arg))), lookup(sc, opt2_sym(opt3_pair(arg)))))); +} + +static s7_pointer fx_subtract_vref_s(s7_scheme *sc, s7_pointer arg) +{ + return(subtract_p_pp(sc, vector_ref_p_pp(sc, lookup(sc, car(opt3_pair(arg))), lookup(sc, opt2_sym(opt3_pair(arg)))), lookup(sc, caddr(arg)))); +} + +static s7_pointer fx_subtract_s_vref(s7_scheme *sc, s7_pointer arg) +{ + return(subtract_p_pp(sc, lookup(sc, cadr(arg)), vector_ref_p_pp(sc, lookup(sc, car(opt3_pair(arg))), lookup(sc, opt2_sym(opt3_pair(arg)))))); +} + +static s7_pointer fx_multiply_s_vref(s7_scheme *sc, s7_pointer arg) +{ + return(multiply_p_pp(sc, lookup(sc, cadr(arg)), vector_ref_p_pp(sc, lookup(sc, car(opt3_pair(arg))), lookup(sc, opt2_sym(opt3_pair(arg)))))); +} + +static s7_pointer fx_cons_cons_s(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer largs = opt3_pair(arg); /* cdadr(arg) */ + return(cons_unchecked(sc, cons(sc, lookup(sc, car(largs)), lookup(sc, opt2_sym(largs))), lookup(sc, caddr(arg)))); +} + +#define fx_add_sqr_s_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer p1 = Lookup(sc, car(opt3_pair(arg)), arg); \ + s7_pointer p3 = lookup(sc, caddr(arg)); \ + if ((is_t_complex(p1)) && (is_t_complex(p3))) \ + { \ + s7_double r = real_part(p1), i = imag_part(p1); \ + return(make_complex(sc, real_part(p3) + r * r - i * i, imag_part(p3) + 2.0 * r * i)); \ + } \ + return(add_p_pp(sc, fx_sqr_1(sc, p1), p3)); \ + } + +fx_add_sqr_s_any(fx_add_sqr_s, s_lookup) +fx_add_sqr_s_any(fx_add_sqr_T, T_lookup) + +static s7_pointer fx_add_sub_s(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer largs = opt3_pair(arg); /* cdadr(arg) */ + s7_pointer p1 = lookup(sc, car(largs)); + s7_pointer p2 = lookup(sc, opt2_sym(largs)); + s7_pointer p3 = lookup(sc, caddr(arg)); + if ((is_t_real(p1)) && (is_t_real(p2)) && (is_t_real(p3))) return(make_real(sc, real(p3) + real(p1) - real(p2))); + return(add_p_pp(sc, subtract_p_pp(sc, p1, p2), p3)); +} + +static s7_pointer fx_add_sub_tu_s(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer p1 = t_lookup(sc, car(cdadr(arg)), arg); + s7_pointer p2 = u_lookup(sc, cadr(cdadr(arg)), arg); + s7_pointer p3 = lookup(sc, caddr(arg)); + if ((is_t_real(p1)) && (is_t_real(p2)) && (is_t_real(p3))) return(make_real(sc, real(p3) + real(p1) - real(p2))); + return(add_p_pp(sc, subtract_p_pp(sc, p1, p2), p3)); +} + +static s7_pointer fx_gt_add_s(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer largs = opt3_pair(arg); /* cdadr(arg) */ + s7_pointer x1 = lookup(sc, car(largs)); + s7_pointer x2 = lookup(sc, opt2_sym(largs)); + s7_pointer x3 = lookup(sc, caddr(arg)); + if ((is_t_real(x1)) && (is_t_real(x2)) && (is_t_real(x3))) return(make_boolean(sc, (real(x1) + real(x2)) > real(x3))); + return(gt_p_pp(sc, add_p_pp(sc, x1, x2), x3)); +} + +static s7_pointer fx_gt_add_tu_s(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer x1 = t_lookup(sc, car(cdadr(arg)), arg); + s7_pointer x2 = u_lookup(sc, cadr(cdadr(arg)), arg); + s7_pointer x3 = lookup(sc, caddr(arg)); + if ((is_t_real(x1)) && (is_t_real(x2)) && (is_t_real(x3))) return(make_boolean(sc, (real(x1) + real(x2)) > real(x3))); + return(gt_p_pp(sc, add_p_pp(sc, x1, x2), x3)); +} + +static s7_pointer fx_gt_vref_s(s7_scheme *sc, s7_pointer arg) +{ + return(gt_p_pp(sc, vector_ref_p_pp(sc, lookup(sc, car(opt3_pair(arg))), lookup(sc, opt2_sym(opt3_pair(arg)))), lookup(sc, caddr(arg)))); +} + +static s7_pointer fx_geq_s_vref(s7_scheme *sc, s7_pointer arg) +{ + return(geq_p_pp(sc, lookup(sc, cadr(arg)), vector_ref_p_pp(sc, lookup(sc, car(opt3_pair(arg))), lookup(sc, opt2_sym(opt3_pair(arg)))))); +} + +static s7_pointer fx_is_eq_s_vref(s7_scheme *sc, s7_pointer arg) +{ + return(make_boolean(sc, lookup(sc, cadr(arg)) == vector_ref_p_pp(sc, lookup(sc, car(opt3_pair(arg))), lookup(sc, opt2_sym(opt3_pair(arg)))))); +} + +static s7_pointer fx_href_s_vref(s7_scheme *sc, s7_pointer arg) +{ + return(hash_table_ref_p_pp(sc, lookup(sc, cadr(arg)), vector_ref_p_pp(sc, lookup(sc, car(opt3_pair(arg))), lookup(sc, opt2_sym(opt3_pair(arg)))))); +} + +static s7_pointer fx_lref_s_vref(s7_scheme *sc, s7_pointer arg) +{ + return(let_ref(sc, lookup(sc, cadr(arg)), vector_ref_p_pp(sc, lookup(sc, car(opt3_pair(arg))), lookup(sc, opt2_sym(opt3_pair(arg)))))); +} + +static s7_pointer fx_vref_s_add(s7_scheme *sc, s7_pointer arg) +{ + return(vector_ref_p_pp(sc, lookup(sc, cadr(arg)), add_p_pp(sc, lookup(sc, car(opt3_pair(arg))), lookup(sc, opt2_sym(opt3_pair(arg)))))); +} + +static inline s7_pointer fx_vref_vref_3(s7_scheme *sc, s7_pointer v1, s7_pointer p1, s7_pointer p2) +{ + if ((is_t_integer(p1)) && (is_t_integer(p2)) && ((is_normal_vector(v1)) && (vector_rank(v1) == 1))) + { + s7_int i1 = integer(p1), i2 = integer(p2); + if ((i1 >= 0) && (i2 >= 0) && (i1 < vector_length(v1))) + { + s7_pointer v2 = vector_element(v1, i1); + if ((is_normal_vector(v2)) && (vector_rank(v2) == 1) && (i2 < vector_length(v2))) + return(vector_element(v2, i2)); + }} + return(vector_ref_p_pp(sc, vector_ref_p_pp(sc, v1, p1), p2)); +} + +#define fx_vref_vref_ss_s_any(Name, Lookup1, Lookup2, Lookup3) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + return(fx_vref_vref_3(sc, Lookup1(sc, car(opt3_pair(arg)), arg), Lookup2(sc, opt2_sym(opt3_pair(arg)), arg), Lookup3(sc, caddr(arg), arg))); \ + } + +fx_vref_vref_ss_s_any(fx_vref_vref_ss_s, s_lookup, s_lookup, s_lookup) +fx_vref_vref_ss_s_any(fx_vref_vref_gs_t, g_lookup, s_lookup, t_lookup) +fx_vref_vref_ss_s_any(fx_vref_vref_go_t, g_lookup, o_lookup, t_lookup) +fx_vref_vref_ss_s_any(fx_vref_vref_tu_v, t_lookup, u_lookup, v_lookup) + +static s7_pointer fx_vref_vref_3_no_let(s7_scheme *sc, s7_pointer code) /* out one level from vref_vref_tu_v */ +{ + return(fx_vref_vref_3(sc, lookup(sc, cadr(code)), lookup(sc, opt2_sym(code)), lookup(sc, opt3_sym(code)))); +} + +static s7_pointer fx_c_opscq_c(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer largs = cadr(arg); + set_car(sc->t2_1, lookup(sc, cadr(largs))); + set_car(sc->t2_2, opt2_con(cdr(largs))); + set_car(sc->t2_1, fn_proc(largs)(sc, sc->t2_1)); + set_car(sc->t2_2, caddr(arg)); + return(fn_proc(arg)(sc, sc->t2_1)); +} + +#define fx_c_opssq_c_any(Name, Lookup1, Lookup2) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer largs = cadr(arg); \ + set_car(sc->t2_1, Lookup1(sc, cadr(largs), largs)); \ + set_car(sc->t2_2, Lookup2(sc, opt2_sym(cdr(largs)), largs)); \ + set_car(sc->t2_1, fn_proc(largs)(sc, sc->t2_1)); \ + set_car(sc->t2_2, opt3_con(cdr(arg))); /* caddr */ \ + return(fn_proc(arg)(sc, sc->t2_1)); \ + } + +fx_c_opssq_c_any(fx_c_opssq_c, s_lookup, s_lookup) +fx_c_opssq_c_any(fx_c_opstq_c, s_lookup, t_lookup) + + +static s7_pointer fx_c_opstq_c_direct(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer largs = cadr(arg); + return(((s7_p_pp_t)opt3_direct(arg))(sc, fn_proc(largs)(sc, set_plist_2(sc, lookup(sc, cadr(largs)), t_lookup(sc, caddr(largs), arg))), opt3_con(cdr(arg)))); +} + +static s7_pointer fx_is_eq_vref_opotq_c(s7_scheme *sc, s7_pointer arg) /* experiment, (eqv? <> char) is <>==char without error checks? */ +{ + s7_pointer largs = cdadr(arg); + return(make_boolean(sc, vector_ref_p_pp(sc, o_lookup(sc, car(largs), largs), t_lookup(sc, cadr(largs), arg)) == opt3_con(cdr(arg)))); +} + +#define fx_c_opsq_s_any(Name, Lookup1, Lookup2) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer largs = cadr(arg); \ + set_car(sc->t2_1, fn_proc(largs)(sc, with_list_t1(Lookup1(sc, cadr(largs), arg)))); /* also opt1_sym(cdr(arg)) */ \ + set_car(sc->t2_2, Lookup2(sc, opt3_sym(arg), arg)); /* caddr(arg) */ \ + return(fn_proc(arg)(sc, sc->t2_1)); \ + } + +fx_c_opsq_s_any(fx_c_opsq_s, s_lookup, s_lookup) +fx_c_opsq_s_any(fx_c_optq_s, t_lookup, s_lookup) +fx_c_opsq_s_any(fx_c_opuq_t, u_lookup, t_lookup) + + +#define fx_c_opsq_s_direct_any(Name, Lookup1, Lookup2) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + return(((s7_p_pp_t)opt2_direct(cdr(arg)))(sc, \ + ((s7_p_p_t)opt3_direct(cdr(arg)))(sc, Lookup1(sc, opt1_sym(cdr(arg)), arg)), \ + Lookup2(sc, opt3_sym(arg), arg))); \ + } + +fx_c_opsq_s_direct_any(fx_c_opsq_s_direct, s_lookup, s_lookup) +fx_c_opsq_s_direct_any(fx_c_optq_s_direct, t_lookup, s_lookup) +fx_c_opsq_s_direct_any(fx_c_opuq_t_direct, u_lookup, t_lookup) + +#define fx_cons_car_s_s_any(Name, Lookup1, Lookup2) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer p = Lookup1(sc, opt1_sym(cdr(arg)), arg); \ + if (is_pair(p)) return(cons(sc, car(p), Lookup2(sc, opt3_sym(arg), arg))); \ + return(cons(sc, car_p_p(sc, p), Lookup2(sc, opt3_sym(arg), arg))); \ + } + +fx_cons_car_s_s_any(fx_cons_car_s_s, s_lookup, s_lookup) +fx_cons_car_s_s_any(fx_cons_car_t_s, t_lookup, s_lookup) +fx_cons_car_s_s_any(fx_cons_car_t_v, t_lookup, v_lookup) +fx_cons_car_s_s_any(fx_cons_car_u_t, u_lookup, t_lookup) + + +static s7_pointer fx_cons_opuq_t(s7_scheme *sc, s7_pointer arg) +{ + return(cons(sc, ((s7_p_p_t)opt3_direct(cdr(arg)))(sc, u_lookup(sc, opt1_sym(cdr(arg)), arg)), t_lookup(sc, opt3_sym(arg), arg))); +} + +#define fx_c_opsq_cs_any(Name, Lookup1, Lookup2) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + set_car(sc->t3_1, fn_proc(cadr(arg))(sc, with_list_t1(Lookup1(sc, opt3_sym(cdr(arg)), arg)))); /* cadadr(arg); */ \ + set_car(sc->t3_2, opt1_con(cdr(arg))); /* caddr(arg) or cadaddr(arg) */ \ + set_car(sc->t3_3, Lookup2(sc, opt2_sym(cdr(arg)), arg)); /* cadddr(arg); */ \ + return(fn_proc(arg)(sc, sc->t3_1)); \ + } + +fx_c_opsq_cs_any(fx_c_opsq_cs, s_lookup, s_lookup) +fx_c_opsq_cs_any(fx_c_optq_cu, t_lookup, u_lookup) + + +#define fx_c_opsq_c_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + set_car(sc->t2_1, fn_proc(cadr(arg))(sc, with_list_t1(Lookup(sc, opt1_sym(cdr(arg)), arg)))); /* cadadr */ \ + set_car(sc->t2_2, opt2_con(cdr(arg))); \ + return(fn_proc(arg)(sc, sc->t2_1)); \ + } + +fx_c_opsq_c_any(fx_c_opsq_c, s_lookup) +fx_c_opsq_c_any(fx_c_optq_c, t_lookup) + + +static s7_pointer fx_c_optq_c_direct(s7_scheme *sc, s7_pointer arg) +{ + return(((s7_p_pp_t)opt3_direct(arg))(sc, ((s7_p_p_t)opt3_direct(cdr(arg)))(sc, t_lookup(sc, opt1_sym(cdr(arg)), arg)), opt2_con(cdr(arg)))); +} + +static s7_pointer fx_c_optq_i_direct(s7_scheme *sc, s7_pointer arg) +{ + return(((s7_p_ii_t)opt3_direct(arg))(sc, ((s7_i_7p_t)opt3_direct(cdr(arg)))(sc, t_lookup(sc, opt1_sym(cdr(arg)), arg)), integer(opt2_con(cdr(arg))))); +} + +static s7_pointer fx_memq_car_s(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer x = opt2_con(cdr(arg)); + s7_pointer obj = lookup(sc, opt1_sym(cdr(arg))); + obj = (is_pair(obj)) ? car(obj) : g_car(sc, set_plist_1(sc, obj)); + while (true) LOOP_4(if (obj == car(x)) return(x); x = cdr(x); if (!is_pair(x)) return(sc->F)); + return(sc->F); +} + +static s7_pointer fx_memq_car_s_2(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer x = opt2_con(cdr(arg)); + s7_pointer obj = lookup(sc, opt1_sym(cdr(arg))); + obj = (is_pair(obj)) ? car(obj) : g_car(sc, set_plist_1(sc, obj)); + if (obj == car(x)) return(x); + return((obj == cadr(x)) ? cdr(x) : sc->F); +} + +static s7_pointer fx_c_s_opssq(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer largs = caddr(arg); + set_car(sc->t2_2, fn_proc(largs)(sc, with_list_t2(lookup(sc, cadr(largs)), lookup(sc, opt2_sym(cdr(largs)))))); + set_car(sc->t2_1, lookup(sc, cadr(arg))); + return(fn_proc(arg)(sc, sc->t2_1)); +} + +#define fx_c_s_opssq_direct_any(Name, Lookup1, Lookup2) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer largs = opt3_pair(arg); /* cdaddr(arg) */ \ + arg = cdr(arg); \ + return(((s7_p_pp_t)opt2_direct(arg))(sc, Lookup1(sc, car(arg), arg), \ + ((s7_p_pp_t)opt3_direct(arg))(sc, lookup(sc, car(largs)), Lookup2(sc, opt2_sym(largs), largs)))); \ + } + +fx_c_s_opssq_direct_any(fx_c_s_opssq_direct, s_lookup, s_lookup) +fx_c_s_opssq_direct_any(fx_c_s_opstq_direct, s_lookup, t_lookup) +fx_c_s_opssq_direct_any(fx_c_t_opsuq_direct, t_lookup, u_lookup) + +static s7_pointer fx_vref_g_vref_gs(s7_scheme *sc, s7_pointer arg) +{ + return(vector_ref_p_pp(sc, lookup_global(sc, cadr(arg)), + vector_ref_p_pp(sc, lookup_global(sc, car(opt3_pair(arg))), lookup(sc, opt2_sym(opt3_pair(arg)))))); +} + +static s7_pointer fx_vref_g_vref_gt(s7_scheme *sc, s7_pointer arg) +{ + return(vector_ref_p_pp(sc, lookup_global(sc, cadr(arg)), + vector_ref_p_pp(sc, lookup_global(sc, car(opt3_pair(arg))), t_lookup(sc, opt2_sym(opt3_pair(arg)), arg)))); +} + +static s7_pointer fx_c_c_opssq(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer largs = caddr(arg); + set_car(sc->t2_2, fn_proc(largs)(sc, with_list_t2(lookup(sc, cadr(largs)), lookup(sc, opt2_sym(cdr(largs)))))); + set_car(sc->t2_1, cadr(arg)); /* currently ( 'a ) goes to safe_c_ca so this works by inadvertence */ + return(fn_proc(arg)(sc, sc->t2_1)); +} + +static s7_pointer fx_c_c_opssq_direct(s7_scheme *sc, s7_pointer arg) +{ + return(((s7_p_pp_t)opt2_direct(cdr(arg)))(sc, cadr(arg), /* see above */ + ((s7_p_pp_t)opt3_direct(cdr(arg)))(sc, lookup(sc, opt3_sym(arg)), lookup(sc, opt1_sym(cdr(arg)))))); +} + +static s7_pointer fx_c_nc_opssq_direct(s7_scheme *sc, s7_pointer arg) /* clm2xen (* 1.0 (oscil g2 x2)) */ +{ + s7_double x2 = ((s7_d_pd_t)opt3_direct(cdr(arg)))(lookup(sc, opt3_sym(arg)), real_to_double(sc, lookup(sc, opt1_sym(cdr(arg))), __func__)); + return(((s7_p_dd_t)opt2_direct(cdr(arg)))(sc, real_to_double(sc, cadr(arg), __func__), x2)); +} + +static s7_pointer fx_multiply_c_opssq(s7_scheme *sc, s7_pointer arg) /* (* c=float (* x1 x2))! */ +{ + s7_pointer x1 = lookup(sc, opt3_sym(arg)); + s7_pointer x2 = lookup(sc, opt1_sym(cdr(arg))); + if ((is_t_real(x1)) && (is_t_real(x2))) return(make_real(sc, real(cadr(arg)) * real(x1) * real(x2))); + return(multiply_p_pp(sc, cadr(arg), multiply_p_pp(sc, x1, x2))); +} + +#define fx_c_s_opscq_any(Name, Lookup1, Lookup2) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer largs = caddr(arg); \ + set_car(sc->t2_1, Lookup2(sc, cadr(largs), arg)); \ + set_car(sc->t2_2, opt2_con(cdr(largs))); \ + set_car(sc->t2_2, fn_proc(largs)(sc, sc->t2_1)); \ + set_car(sc->t2_1, Lookup1(sc, cadr(arg), arg)); \ + return(fn_proc(arg)(sc, sc->t2_1)); \ + } + +fx_c_s_opscq_any(fx_c_s_opscq, s_lookup, s_lookup) +fx_c_s_opscq_any(fx_c_u_optcq, u_lookup, t_lookup) +/* also fx_c_T_optcq */ + +static s7_pointer fx_c_s_opscq_direct(s7_scheme *sc, s7_pointer arg) +{ + return(((s7_p_pp_t)opt2_direct(cdr(arg)))(sc, lookup(sc, cadr(arg)), ((s7_p_pp_t)opt3_direct(cdr(arg)))(sc, lookup(sc, opt3_sym(arg)), opt1_con(cdr(arg))))); +} + +static s7_pointer fx_c_s_opsiq_direct(s7_scheme *sc, s7_pointer arg) +{ + return(((s7_p_pp_t)opt2_direct(cdr(arg)))(sc, lookup(sc, cadr(arg)), + ((s7_p_pi_t)opt3_direct(cdr(arg)))(sc, lookup(sc, opt3_sym(arg)), integer(opt1_con(cdr(arg)))))); +} + +static s7_pointer fx_c_u_optiq_direct(s7_scheme *sc, s7_pointer arg) +{ + return(((s7_p_pp_t)opt2_direct(cdr(arg)))(sc, u_lookup(sc, cadr(arg), arg), + ((s7_p_pi_t)opt3_direct(cdr(arg)))(sc, t_lookup(sc, opt3_sym(arg), arg), integer(opt1_con(cdr(arg)))))); +} + +static s7_pointer fx_c_t_opoiq_direct(s7_scheme *sc, s7_pointer arg) +{ + return(((s7_p_pp_t)opt2_direct(cdr(arg)))(sc, t_lookup(sc, cadr(arg), arg), + ((s7_p_pi_t)opt3_direct(cdr(arg)))(sc, o_lookup(sc, opt3_sym(arg), arg), integer(opt1_con(cdr(arg)))))); +} + +static s7_pointer fx_vref_p1(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer i = lookup(sc, opt3_sym(arg)); + s7_pointer v = lookup(sc, cadr(arg)); + if ((is_t_integer(i)) && (is_normal_vector(v)) && (vector_rank(v) == 1)) + { + s7_int index = integer(i) + 1; + if ((index >= 0) && (vector_length(v) > index)) + return(vector_element(v, index)); + } + return(vector_ref_p_pp(sc, v, g_add_xi(sc, i, 1, 2))); +} + +static s7_pointer fx_num_eq_add_s_si(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer i1 = lookup(sc, cadr(arg)); + s7_pointer i2 = lookup(sc, opt3_sym(arg)); + if ((is_t_integer(i1)) && (is_t_integer(i2))) + return(make_boolean(sc, integer(i1) == (integer(i2) + integer(opt1_con(cdr(arg)))))); + return(make_boolean(sc, num_eq_b_7pp(sc, i1, g_add_xi(sc, i2, integer(opt1_con(cdr(arg))), 2)))); +} + +static s7_pointer fx_num_eq_subtract_s_si(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer i1 = lookup(sc, cadr(arg)); + s7_pointer i2 = lookup(sc, opt3_sym(arg)); + if ((is_t_integer(i1)) && (is_t_integer(i2))) + return(make_boolean(sc, integer(i1) == (integer(i2) - integer(opt1_con(cdr(arg)))))); + return(make_boolean(sc, num_eq_b_7pp(sc, i1, g_sub_xi(sc, i2, integer(opt1_con(cdr(arg))))))); +} + +#define fx_c_t_opscq_direct_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + return(((s7_p_pp_t)opt2_direct(cdr(arg)))(sc, t_lookup(sc, cadr(arg), arg), \ + ((s7_p_pp_t)opt3_direct(cdr(arg)))(sc, Lookup(sc, opt3_sym(arg), arg), opt1_con(cdr(arg))))); \ + } + +fx_c_t_opscq_direct_any(fx_c_t_opscq_direct, s_lookup) +fx_c_t_opscq_direct_any(fx_c_t_opucq_direct, u_lookup) + + +static s7_pointer fx_c_s_opsq(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer largs = caddr(arg); + set_car(sc->t2_2, fn_proc(largs)(sc, with_list_t1(lookup(sc, cadr(largs))))); + set_car(sc->t2_1, lookup(sc, cadr(arg))); + return(fn_proc(arg)(sc, sc->t2_1)); +} + +#define fx_c_s_opsq_direct_any(Name, Lookup1, Lookup2) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + arg = cdr(arg); \ + return(((s7_p_pp_t)opt2_direct(arg))(sc, Lookup1(sc, car(arg), arg), ((s7_p_p_t)opt3_direct(arg))(sc, Lookup2(sc, opt1_sym(arg), arg)))); /* cadadr */ \ + } + +fx_c_s_opsq_direct_any(fx_c_s_opsq_direct, s_lookup, s_lookup) +fx_c_s_opsq_direct_any(fx_c_t_opsq_direct, t_lookup, s_lookup) +fx_c_s_opsq_direct_any(fx_c_t_opuq_direct, t_lookup, u_lookup) +fx_c_s_opsq_direct_any(fx_c_u_opvq_direct, u_lookup, v_lookup) + +#define fx_c_s_car_s_any(Name, Lookup1, Lookup2) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer val = Lookup2(sc, opt2_sym(cdr(arg)), arg); \ + set_car(sc->t2_2, (is_pair(val)) ? car(val) : g_car(sc, set_plist_1(sc, val))); \ + set_car(sc->t2_1, Lookup1(sc, cadr(arg), arg)); \ + return(fn_proc(arg)(sc, sc->t2_1)); \ + } + +fx_c_s_car_s_any(fx_c_s_car_s, s_lookup, s_lookup) +fx_c_s_car_s_any(fx_c_s_car_t, s_lookup, t_lookup) +fx_c_s_car_s_any(fx_c_t_car_u, t_lookup, u_lookup) +fx_c_s_car_s_any(fx_c_t_car_v, t_lookup, v_lookup) + + +#define fx_add_s_car_s_any(Name, Lookup1, Lookup2) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer val1 = Lookup1(sc, cadr(arg), arg); \ + s7_pointer val2 = Lookup2(sc, opt2_sym(cdr(arg)), arg); \ + val2 = (is_pair(val2)) ? car(val2) : g_car(sc, set_plist_1(sc, val2)); \ + return(((is_t_integer(val1)) && (is_t_integer(val2))) ? make_integer(sc, integer(val1) + integer(val2)) : add_p_pp(sc, val1, val2)); \ + } + +fx_add_s_car_s_any(fx_add_s_car_s, s_lookup, s_lookup) +fx_add_s_car_s_any(fx_add_u_car_t, u_lookup, t_lookup) +fx_add_s_car_s_any(fx_add_t_car_v, t_lookup, v_lookup) + + +static s7_pointer fx_cons_s_cdr_s(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer val = lookup(sc, opt2_sym(cdr(arg))); + val = (is_pair(val)) ? cdr(val) : g_cdr(sc, set_plist_1(sc, val)); + return(cons(sc, lookup(sc, cadr(arg)), val)); +} + +static s7_pointer fx_c_op_s_opsqq(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer outer = cadr(arg); + s7_pointer args = caddr(outer); + set_car(sc->t2_2, fn_proc(args)(sc, with_list_t1(lookup(sc, cadr(args))))); + set_car(sc->t2_1, lookup(sc, cadr(outer))); + return(fn_proc(arg)(sc, with_list_t1(fn_proc(outer)(sc, sc->t2_1)))); +} + +static s7_pointer fx_not_op_s_opsqq(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer outer = cadr(arg); + s7_pointer args = caddr(outer); + set_car(sc->t2_2, fn_proc(args)(sc, with_list_t1(lookup(sc, cadr(args))))); + set_car(sc->t2_1, lookup(sc, cadr(outer))); + return(((fn_proc(outer)(sc, sc->t2_1)) == sc->F) ? sc->T : sc->F); +} + +static s7_pointer fx_c_op_opsq_sq(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer outer = cadr(arg); + s7_pointer args = cadr(outer); + set_car(sc->t2_1, fn_proc(args)(sc, with_list_t1(lookup(sc, cadr(args))))); + set_car(sc->t2_2, lookup(sc, caddr(outer))); + return(fn_proc(arg)(sc, with_list_t1(fn_proc(outer)(sc, sc->t2_1)))); +} + +static s7_pointer fx_not_op_optq_sq(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer outer = cadr(arg); + s7_pointer args = cadr(outer); + set_car(sc->t2_1, fn_proc(args)(sc, with_list_t1(t_lookup(sc, cadr(args), arg)))); + set_car(sc->t2_2, lookup(sc, caddr(outer))); + return((fn_proc(outer)(sc, sc->t2_1) == sc->F) ? sc->T : sc->F); +} + +static s7_pointer fx_c_c_opsq(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer largs = opt3_pair(arg); /* caddr(arg); */ + set_car(sc->t2_2, fn_proc(largs)(sc, with_list_t1(lookup(sc, cadr(largs))))); + set_car(sc->t2_1, cadr(arg)); + return(fn_proc(arg)(sc, sc->t2_1)); +} + +static s7_pointer fx_c_c_opsq_direct(s7_scheme *sc, s7_pointer arg) +{ + return(((s7_p_pp_t)opt2_direct(cdr(arg)))(sc, cadr(arg), ((s7_p_p_t)opt3_direct(cdr(arg)))(sc, lookup(sc, opt1_sym(cdr(arg)))))); +} + +/* perhaps fx_c_c_opt|T|Vq_direct tlet/tmisc */ + +static s7_pointer fx_c_opsq_opsq(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer largs = cdr(arg); + gc_protect_via_stack(sc, fn_proc(car(largs))(sc, with_list_t1(lookup(sc, cadar(largs))))); + largs = cadr(largs); + set_car(sc->t2_2, fn_proc(largs)(sc, with_list_t1(lookup(sc, cadr(largs))))); + set_car(sc->t2_1, stack_protected1(sc)); + unstack(sc); + return(fn_proc(arg)(sc, sc->t2_1)); +} + +static s7_pointer fx_c_opsq_opsq_direct(s7_scheme *sc, s7_pointer arg) +{ + return(((s7_p_pp_t)opt3_direct(arg))(sc, + ((s7_p_p_t)opt2_direct(cdr(arg)))(sc, lookup(sc, cadadr(arg))), /* no free field in arg or cdr(arg) */ + ((s7_p_p_t)opt3_direct(cdr(arg)))(sc, lookup(sc, opt1_sym(cdr(arg)))))); /* cadaddr(arg) */ +} + +static s7_pointer fx_c_optq_optq_direct(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer x = t_lookup(sc, opt1_sym(cdr(arg)), arg); /* cadadr and cadaddr */ + return(((s7_p_pp_t)opt3_direct(arg))(sc, ((s7_p_p_t)opt2_direct(cdr(arg)))(sc, x), ((s7_p_p_t)opt3_direct(cdr(arg)))(sc, x))); +} + +#define fx_car_s_car_s_any(Name, Lookup1, Lookup2) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + s7_pointer p1 = Lookup1(sc, opt1_sym(cdr(arg)), arg); \ + s7_pointer p2 = Lookup2(sc, opt2_sym(cdr(arg)), arg); /* cadaddr(arg) */ \ + return(((s7_p_pp_t)opt3_direct(arg))(sc, (is_pair(p1)) ? car(p1) : g_car(sc, set_plist_1(sc, p1)), \ + (is_pair(p2)) ? car(p2) : g_car(sc, set_plist_1(sc, p2)))); \ + } + +fx_car_s_car_s_any(fx_car_s_car_s, s_lookup, s_lookup) +fx_car_s_car_s_any(fx_car_t_car_u, t_lookup, u_lookup) + + +static s7_pointer fx_cdr_s_cdr_s(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer p1 = lookup(sc, opt1_sym(cdr(arg))); + s7_pointer p2 = lookup(sc, opt2_sym(cdr(arg))); /* cadaddr(arg) */ + return(((s7_p_pp_t)opt3_direct(arg))(sc, (is_pair(p1)) ? cdr(p1) : g_cdr(sc, set_plist_1(sc, p1)), + (is_pair(p2)) ? cdr(p2) : g_cdr(sc, set_plist_1(sc, p2)))); +} + +static s7_pointer fx_is_eq_car_car_tu(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer p1 = t_lookup(sc, opt1_sym(cdr(arg)), arg); + s7_pointer p2 = u_lookup(sc, opt2_sym(cdr(arg)), arg); + p1 = (is_pair(p1)) ? car(p1) : g_car(sc, set_plist_1(sc, p1)); + p2 = (is_pair(p2)) ? car(p2) : g_car(sc, set_plist_1(sc, p2)); + return(make_boolean(sc, (p1 == p2) || ((is_unspecified(p1)) && (is_unspecified(p2))))); +} + +static s7_pointer fx_c_opsq_opssq(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer largs = cdr(arg); + gc_protect_via_stack(sc, fn_proc(car(largs))(sc, with_list_t1(lookup(sc, cadar(largs))))); + largs = cadr(largs); + set_car(sc->t2_1, lookup(sc, cadr(largs))); + set_car(sc->t2_2, lookup(sc, opt2_sym(cdr(largs)))); /* caddr(largs) */ + set_car(sc->t2_2, fn_proc(largs)(sc, sc->t2_1)); + set_car(sc->t2_1, stack_protected1(sc)); + unstack(sc); + return(fn_proc(arg)(sc, sc->t2_1)); +} + +static s7_pointer fx_c_opsq_optuq_direct(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer largs = cdr(arg); + return(((s7_p_pp_t)opt3_direct(arg))(sc, + ((s7_p_p_t)opt2_direct(largs))(sc, lookup(sc, cadar(largs))), + ((s7_p_pp_t)opt3_direct(largs))(sc, t_lookup(sc, opt2_sym(cdr(largs)), arg), u_lookup(sc, opt1_sym(largs), arg)))); +} + +static s7_pointer fx_num_eq_car_v_add_tu(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer p1 = car_p_p(sc, v_lookup(sc, cadadr(arg), arg)); + s7_pointer p2 = t_lookup(sc, opt2_sym(cddr(arg)), arg); + s7_pointer p3 = u_lookup(sc, opt1_sym(cdr(arg)), arg); + if ((is_t_integer(p1)) && (is_t_integer(p2)) && (is_t_integer(p3))) + return(make_boolean(sc, integer(p1) == (integer(p2) + integer(p3)))); + return(make_boolean(sc, num_eq_b_7pp(sc, p1, add_p_pp(sc, p2, p3)))); +} + +static s7_pointer fx_num_eq_car_v_subtract_tu(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer p1 = car_p_p(sc, v_lookup(sc, cadadr(arg), arg)); + s7_pointer p2 = t_lookup(sc, opt2_sym(cddr(arg)), arg); + s7_pointer p3 = u_lookup(sc, opt1_sym(cdr(arg)), arg); + if ((is_t_integer(p1)) && (is_t_integer(p2)) && (is_t_integer(p3))) + return(make_boolean(sc, integer(p1) == (integer(p2) - integer(p3)))); + return(make_boolean(sc, num_eq_b_7pp(sc, p1, subtract_p_pp(sc, p2, p3)))); +} + +static s7_pointer fx_c_opssq_opsq(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer largs = cdr(arg); + set_car(sc->t2_1, lookup(sc, cadar(largs))); + set_car(sc->t2_2, lookup(sc, opt2_sym(cdar(largs)))); + gc_protect_via_stack(sc, fn_proc(car(largs))(sc, sc->t2_1)); + largs = cadr(largs); + set_car(sc->t2_2, fn_proc(largs)(sc, with_list_t1(lookup(sc, cadr(largs))))); + set_car(sc->t2_1, stack_protected1(sc)); + unstack(sc); + return(fn_proc(arg)(sc, sc->t2_1)); +} + +static s7_pointer fx_c_opssq_opssq(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer largs = cdr(arg); + set_car(sc->t2_1, lookup(sc, cadar(largs))); + set_car(sc->t2_2, lookup(sc, opt2_sym(cdar(largs)))); + gc_protect_via_stack(sc, fn_proc(car(largs))(sc, sc->t2_1)); + largs = cadr(largs); + set_car(sc->t2_1, lookup(sc, cadr(largs))); + set_car(sc->t2_2, lookup(sc, opt2_sym(cdr(largs)))); + set_car(sc->t2_2, fn_proc(largs)(sc, sc->t2_1)); + set_car(sc->t2_1, stack_protected1(sc)); + unstack(sc); + return(fn_proc(arg)(sc, sc->t2_1)); +} + +static s7_pointer fx_sub_mul_mul(s7_scheme *sc, s7_pointer arg) /* (- (* s1 s2) (* s3 s4)) */ +{ + s7_pointer a1 = opt3_pair(arg); /* cdaddr(arg); */ + s7_pointer s1 = lookup(sc, car(a1)); + s7_pointer s2 = lookup(sc, cadr(a1)); + s7_pointer a2 = opt1_pair(cdr(arg)); /* cdadr(arg) */ /* here and elsewhere this should be GC safe -- opssq->* (no methods?) etc */ + s7_pointer s3 = lookup(sc, car(a2)); + s7_pointer s4 = lookup(sc, cadr(a2)); + if ((is_t_real(s1)) && (is_t_real(s2)) && (is_t_real(s3)) && (is_t_real(s4))) + return(make_real(sc, (real(s3) * real(s4)) - (real(s1) * real(s2)))); + sc->temp5 = multiply_p_pp(sc, s1, s2); + return(subtract_p_pp(sc, multiply_p_pp(sc, s3, s4), sc->temp5)); +} + +static s7_pointer fx_add_mul_mul(s7_scheme *sc, s7_pointer arg) /* (+ (* s1 s2) (* s3 s4)) */ +{ + s7_pointer a1 = opt3_pair(arg); /* cdaddr(arg); */ + s7_pointer s1 = lookup(sc, car(a1)); + s7_pointer s2 = lookup(sc, cadr(a1)); + s7_pointer a2 = opt1_pair(cdr(arg)); /* cdadr(arg) */ + s7_pointer s3 = lookup(sc, car(a2)); + s7_pointer s4 = lookup(sc, cadr(a2)); + if ((is_t_real(s1)) && (is_t_real(s2)) && (is_t_real(s3)) && (is_t_real(s4))) + return(make_real(sc, (real(s3) * real(s4)) + (real(s1) * real(s2)))); + sc->temp5 = multiply_p_pp(sc, s1, s2); + return(add_p_pp(sc, multiply_p_pp(sc, s3, s4), sc->temp5)); +} + +static s7_pointer fx_mul_sub_sub(s7_scheme *sc, s7_pointer arg) /* (* (- s1 s2) (- s3 s4)) */ +{ + s7_pointer a1 = opt3_pair(arg); /* cdaddr(arg); */ + s7_pointer s1 = lookup(sc, car(a1)); + s7_pointer s2 = lookup(sc, cadr(a1)); + s7_pointer a2 = opt1_pair(cdr(arg)); /* cdadr(arg) */ + s7_pointer s3 = lookup(sc, car(a2)); + s7_pointer s4 = lookup(sc, cadr(a2)); + if ((is_t_real(s1)) && (is_t_real(s2)) && (is_t_real(s3)) && (is_t_real(s4))) + return(make_real(sc, (real(s3) - real(s4)) * (real(s1) - real(s2)))); + sc->temp5 = subtract_p_pp(sc, s1, s2); + return(multiply_p_pp(sc, subtract_p_pp(sc, s3, s4), sc->temp5)); +} + +static s7_pointer fx_lt_sub2(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer a1 = opt3_pair(arg); /* cdaddr(arg); */ + sc->temp5 = subtract_p_pp(sc, lookup(sc, car(a1)), lookup(sc, cadr(a1))); + a1 = opt1_pair(cdr(arg)); /* cdadr(arg) */ + return(lt_p_pp(sc, subtract_p_pp(sc, lookup(sc, car(a1)), lookup(sc, cadr(a1))), sc->temp5)); +} + +static s7_pointer fx_sub_vref2(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer a1 = cdadr(arg); + s7_pointer v1 = lookup(sc, car(a1)); + s7_pointer p1 = lookup(sc, cadr(a1)); + s7_pointer p2 = lookup(sc, opt3_sym(arg)); /* caddaddr(arg)); */ + if ((is_t_integer(p1)) && (is_t_integer(p2)) && ((is_normal_vector(v1)) && (vector_rank(v1) == 1))) + { + s7_int i1 = integer(p1), i2 = integer(p2); + if ((i1 >= 0) && (i1 <= vector_length(v1)) && (i2 >= 0) && (i2 < vector_length(v1))) + return(subtract_p_pp(sc, vector_ref_p_pi(sc, v1, i1), vector_ref_p_pi(sc, v1, i2))); + } + return(subtract_p_pp(sc, vector_ref_p_pp(sc, v1, p1), vector_ref_p_pp(sc, v1, p2))); +} + +static s7_pointer fx_c_op_opsqq(s7_scheme *sc, s7_pointer code) +{ + set_car(sc->t1_1, fn_proc(opt3_pair(code))(sc, with_list_t1(lookup(sc, opt3_sym(cdr(code)))))); + set_car(sc->t1_1, fn_proc(cadr(code))(sc, sc->t1_1)); + return(fn_proc(code)(sc, sc->t1_1)); +} + +static s7_pointer fx_not_op_opsqq(s7_scheme *sc, s7_pointer code) +{ + set_car(sc->t1_1, fn_proc(opt3_pair(code))(sc, with_list_t1(lookup(sc, opt3_sym(cdr(code)))))); + return((fn_proc(cadr(code))(sc, sc->t1_1) == sc->F) ? sc->T : sc->F); +} + +static s7_pointer fx_not_is_pair_opsq(s7_scheme *sc, s7_pointer code) +{ + return(make_boolean(sc, !is_pair(fn_proc(opt3_pair(code))(sc, set_plist_1(sc, lookup(sc, opt3_sym(cdr(code)))))))); +} + +static s7_pointer fx_sref_t_last(s7_scheme *sc, s7_pointer arg) {return(string_ref_p_plast(sc, t_lookup(sc, cadr(arg), arg), int_zero));} /* both syms are t_lookup */ + +static s7_pointer fx_c_a(s7_scheme *sc, s7_pointer arg) +{ + return(fn_proc(arg)(sc, with_list_t1(fx_call(sc, cdr(arg))))); +} + +static s7_pointer fx_c_a_direct(s7_scheme *sc, s7_pointer arg) {return(((s7_p_p_t)opt3_direct(arg))(sc, fx_call(sc, cdr(arg))));} +static s7_pointer fx_not_a(s7_scheme *sc, s7_pointer arg) {return((fx_call(sc, cdr(arg)) == sc->F) ? sc->T : sc->F);} + +static s7_pointer fx_c_saa(s7_scheme *sc, s7_pointer arg) +{ + gc_protect_via_stack(sc, fx_call(sc, opt3_pair(arg))); /* opt3_pair=cddr */ + set_car(sc->t3_3, fx_call(sc, cdr(opt3_pair(arg)))); + set_car(sc->t3_1, lookup(sc, cadr(arg))); + set_car(sc->t3_2, stack_protected1(sc)); + unstack(sc); + return(fn_proc(arg)(sc, sc->t3_1)); +} + +#define fx_c_ssa_any(Name, Lookup1, Lookup2) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + set_car(sc->t3_3, fx_call(sc, cdr(opt3_pair(arg)))); \ + set_car(sc->t3_1, Lookup1(sc, cadr(arg), arg));\ + set_car(sc->t3_2, Lookup2(sc, car(opt3_pair(arg)), arg)); \ + return(fn_proc(arg)(sc, sc->t3_1));\ + } + +fx_c_ssa_any(fx_c_ssa, s_lookup, s_lookup) +fx_c_ssa_any(fx_c_tsa, t_lookup, s_lookup) +fx_c_ssa_any(fx_c_sta, s_lookup, t_lookup) + +static s7_pointer fx_c_ssa_direct(s7_scheme *sc, s7_pointer arg) +{ + return(((s7_p_ppp_t)opt2_direct(cdr(arg)))(sc, lookup(sc, cadr(arg)), lookup(sc, car(opt3_pair(arg))), fx_call(sc, cdr(opt3_pair(arg))))); +} + +static s7_pointer fx_c_ass(s7_scheme *sc, s7_pointer arg) +{ + set_car(sc->t3_1, fx_call(sc, cdr(arg))); + set_car(sc->t3_2, lookup(sc, car(opt3_pair(arg)))); + set_car(sc->t3_3, lookup(sc, cadr(opt3_pair(arg)))); + return(fn_proc(arg)(sc, sc->t3_1)); +} + +static s7_pointer fx_c_agg(s7_scheme *sc, s7_pointer arg) +{ + set_car(sc->t3_1, fx_call(sc, cdr(arg))); + set_car(sc->t3_2, fx_call(sc, opt3_pair(arg))); + set_car(sc->t3_3, fx_call(sc, cdr(opt3_pair(arg)))); + return(fn_proc(arg)(sc, sc->t3_1)); +} + +static s7_pointer fx_c_sas(s7_scheme *sc, s7_pointer arg) +{ + set_car(sc->t3_2, fx_call(sc, opt3_pair(arg))); + set_car(sc->t3_1, lookup(sc, cadr(arg))); + set_car(sc->t3_3, lookup(sc, cadr(opt3_pair(arg)))); + return(fn_proc(arg)(sc, sc->t3_1)); +} + +static s7_pointer fx_c_sca(s7_scheme *sc, s7_pointer arg) +{ + set_car(sc->t3_3, fx_call(sc, cdr(opt3_pair(arg)))); + set_car(sc->t3_1, lookup(sc, cadr(arg))); + set_car(sc->t3_2, car(opt3_pair(arg))); + return(fn_proc(arg)(sc, sc->t3_1)); +} + +static s7_pointer fx_c_Tca(s7_scheme *sc, s7_pointer arg) +{ + set_car(sc->t3_3, fx_call(sc, cdr(opt3_pair(arg)))); + set_car(sc->t3_1, T_lookup(sc, cadr(arg), arg)); + set_car(sc->t3_2, car(opt3_pair(arg))); + return(fn_proc(arg)(sc, sc->t3_1)); +} + +static s7_pointer fx_c_csa(s7_scheme *sc, s7_pointer arg) +{ + set_car(sc->t3_3, fx_call(sc, cdr(opt3_pair(arg)))); + set_car(sc->t3_1, cadr(arg)); + set_car(sc->t3_2, lookup(sc, car(opt3_pair(arg)))); + return(fn_proc(arg)(sc, sc->t3_1)); +} + +static s7_pointer fx_c_cac(s7_scheme *sc, s7_pointer arg) +{ + set_car(sc->t3_2, fx_call(sc, opt3_pair(arg))); + set_car(sc->t3_1, cadr(arg)); + set_car(sc->t3_3, cadr(opt3_pair(arg))); + return(fn_proc(arg)(sc, sc->t3_1)); +} + +static s7_pointer fx_c_aa(s7_scheme *sc, s7_pointer arg) +{ + /* check_stack_size(sc); */ + gc_protect_via_stack(sc, fx_call(sc, cdr(arg))); + set_car(sc->t2_2, fx_call(sc, opt3_pair(arg))); /* cddr(arg) */ + set_car(sc->t2_1, T_Ext(stack_protected1(sc))); + unstack(sc); + return(fn_proc(arg)(sc, sc->t2_1)); +} + +static s7_pointer fx_c_ca(s7_scheme *sc, s7_pointer arg) +{ + set_car(sc->t2_2, fx_call(sc, cddr(arg))); + set_car(sc->t2_1, opt3_con(arg)); + return(fn_proc(arg)(sc, sc->t2_1)); +} + +static s7_pointer fx_c_ac(s7_scheme *sc, s7_pointer arg) +{ + set_car(sc->t2_1, fx_call(sc, cdr(arg))); + set_car(sc->t2_2, opt3_con(arg)); + return(fn_proc(arg)(sc, sc->t2_1)); +} + +static s7_pointer fx_c_ac_direct(s7_scheme *sc, s7_pointer arg) {return(((s7_p_pp_t)opt3_direct(cdr(arg)))(sc, fx_call(sc, cdr(arg)), opt3_con(arg)));} + +static s7_pointer fx_is_eq_ac(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer y = opt3_con(arg); + s7_pointer x = fx_call(sc, cdr(arg)); + return(make_boolean(sc, (x == y) || ((is_unspecified(x)) && (is_unspecified(y))))); +} + +#define fx_c_sa_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + set_car(sc->t2_2, fx_call(sc, cddr(arg))); \ + set_car(sc->t2_1, Lookup(sc, opt3_sym(arg), arg)); \ + return(fn_proc(arg)(sc, sc->t2_1)); \ + } + +fx_c_sa_any(fx_c_sa, s_lookup) +fx_c_sa_any(fx_c_ta, t_lookup) +fx_c_sa_any(fx_c_ua, u_lookup) + +static s7_pointer fx_c_za(s7_scheme *sc, s7_pointer arg) /* "z"=unsafe_s */ +{ + s7_pointer val = lookup_checked(sc, cadr(arg)); /* this can call an autoload function that steps on sc->t2_1 */ + set_car(sc->t2_2, fx_call(sc, opt3_pair(arg))); + set_car(sc->t2_1, val); + return(fn_proc(arg)(sc, sc->t2_1)); +} + +#define fx_c_sa_direct_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + return(((s7_p_pp_t)opt3_direct(cdr(arg)))(sc, Lookup(sc, opt3_sym(arg), arg), fx_call(sc, cddr(arg)))); \ + } + +fx_c_sa_direct_any(fx_c_sa_direct, s_lookup) +fx_c_sa_direct_any(fx_c_ua_direct, u_lookup) + +static s7_pointer fx_cons_ca(s7_scheme *sc, s7_pointer arg) {return(cons(sc, opt3_con(arg), fx_call(sc, cddr(arg))));} +static s7_pointer fx_cons_ac(s7_scheme *sc, s7_pointer arg) {return(cons(sc, sc->temp3 = fx_call(sc, cdr(arg)), opt3_con(arg)));} +static s7_pointer fx_cons_sa(s7_scheme *sc, s7_pointer arg) {return(cons(sc, lookup(sc, opt3_sym(arg)), fx_call(sc, cddr(arg))));} +static s7_pointer fx_cons_as(s7_scheme *sc, s7_pointer arg) {return(cons(sc, sc->temp3 = fx_call(sc, cdr(arg)), lookup(sc, opt3_sym(arg))));} +static s7_pointer fx_cons_aa(s7_scheme *sc, s7_pointer arg) {return(cons(sc, sc->temp3 = fx_call(sc, cdr(arg)), fx_call(sc, opt3_pair(arg))));} + +#define fx_c_as_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + set_car(sc->t2_1, fx_call(sc, cdr(arg))); \ + set_car(sc->t2_2, Lookup(sc, opt3_sym(arg), arg)); \ + return(fn_proc(arg)(sc, sc->t2_1)); \ + } + +fx_c_as_any(fx_c_as, s_lookup) +fx_c_as_any(fx_c_at, t_lookup) + +static s7_pointer fx_c_as_direct(s7_scheme *sc, s7_pointer arg) +{ + return(((s7_p_pp_t)opt3_direct(cdr(arg)))(sc, fx_call(sc, cdr(arg)), lookup(sc, opt3_sym(arg)))); +} + +static s7_pointer fx_add_as(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer x1 = fx_call(sc, cdr(arg)); + s7_pointer x2 = lookup(sc, opt3_sym(arg)); + if ((is_t_real(x1)) && (is_t_real(x2))) return(make_real(sc, real(x1) + real(x2))); + return(add_p_pp(sc, x1, x2)); +} + +static s7_pointer fx_multiply_sa(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer x1 = lookup(sc, cadr(arg)); + s7_pointer x2 = fx_call(sc, cddr(arg)); + if ((is_t_real(x1)) && (is_t_real(x2))) return(make_real(sc, real(x1) * real(x2))); + return(multiply_p_pp(sc, x1, x2)); +} + +static s7_pointer fx_subtract_aa(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer x2; + s7_pointer x1 = fx_call(sc, cdr(arg)); + sc->value = x1; + x2 = fx_call(sc, opt3_pair(arg)); + if ((is_t_real(x1)) && (is_t_real(x2))) return(make_real(sc, real(x1) - real(x2))); + return(subtract_p_pp(sc, x1, x2)); +} + +static s7_pointer fx_add_aa(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer x2; + s7_pointer x1 = fx_call(sc, cdr(arg)); + sc->value = x1; + x2 = fx_call(sc, opt3_pair(arg)); + if (is_t_real(x1)) {if (is_t_real(x2)) return(make_real(sc, real(x1) + real(x2)));} + else if ((is_t_integer(x1)) && (is_t_integer(x2))) return(make_integer(sc, integer(x1) + integer(x2))); + /* maybe use add_if_overflow_to_real_or_big_integer, but that seems unnecessary currently */ + return(add_p_pp(sc, x1, x2)); +} + +static s7_pointer fx_multiply_aa(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer x2; + s7_pointer x1 = fx_call(sc, cdr(arg)); + sc->value = x1; + x2 = fx_call(sc, opt3_pair(arg)); + if ((is_t_real(x1)) && (is_t_real(x2))) return(make_real(sc, real(x1) * real(x2))); + return(multiply_p_pp(sc, x1, x2)); +} + +static s7_pointer fx_add_sa(s7_scheme *sc, s7_pointer arg) {return(add_p_pp(sc, lookup(sc, opt3_sym(arg)), fx_call(sc, cddr(arg))));} +static s7_pointer fx_number_to_string_aa(s7_scheme *sc, s7_pointer arg) {return(number_to_string_p_pp(sc, fx_call(sc, cdr(arg)), fx_call(sc, opt3_pair(arg))));} + +static s7_pointer fx_c_3g(s7_scheme *sc, s7_pointer arg) +{ + set_car(sc->t3_1, fx_call(sc, cdr(arg))); + set_car(sc->t3_2, fx_call(sc, opt3_pair(arg))); + set_car(sc->t3_3, fx_call(sc, cdr(opt3_pair(arg)))); + return(fn_proc(arg)(sc, sc->t3_1)); +} + +static s7_pointer fx_c_aaa(s7_scheme *sc, s7_pointer arg) +{ + /* check_stack_size(sc); */ + gc_protect_2_via_stack(sc, fx_call(sc, cdr(arg)), fx_call(sc, opt3_pair(arg))); + set_car(sc->t3_3, fx_call(sc, cdr(opt3_pair(arg)))); + set_car(sc->t3_2, stack_protected2(sc)); + set_car(sc->t3_1, stack_protected1(sc)); + unstack(sc); + return(fn_proc(arg)(sc, sc->t3_1)); +} + +static s7_pointer fx_c_gac(s7_scheme *sc, s7_pointer arg) +{ + set_car(sc->t3_2, fx_call(sc, opt3_pair(arg))); + set_car(sc->t3_3, cadr(opt3_pair(arg))); + set_car(sc->t3_1, lookup_global(sc, cadr(arg))); + return(fn_proc(arg)(sc, sc->t3_1)); +} + +static s7_pointer fx_c_opaq_s(s7_scheme *sc, s7_pointer arg) +{ + set_car(sc->t2_1, fn_proc(cadr(arg))(sc, with_list_t1(fx_call(sc, cdadr(arg))))); + set_car(sc->t2_2, lookup_checked(sc, caddr(arg))); + return(fn_proc(arg)(sc, sc->t2_1)); +} + +static s7_pointer fx_c_s_opaq(s7_scheme *sc, s7_pointer arg) +{ + set_car(sc->t2_2, fn_proc(caddr(arg))(sc, with_list_t1(fx_call(sc, opt3_pair(arg))))); /* cdaddr(arg); */ + set_car(sc->t2_1, lookup_checked(sc, cadr(arg))); + return(fn_proc(arg)(sc, sc->t2_1)); +} + +static s7_pointer fx_c_opaq(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer p = cadr(arg); + set_car(sc->t1_1, fx_call(sc, cdr(p))); + return(fn_proc(arg)(sc, with_list_t1(fn_proc(p)(sc, sc->t1_1)))); +} + +static s7_pointer fx_c_opaaq(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer p = cadr(arg); + /* check_stack_size(sc); */ + gc_protect_via_stack(sc, fx_call(sc, cdr(p))); + set_car(sc->t2_2, fx_call(sc, cddr(p))); + set_car(sc->t2_1, stack_protected1(sc)); + unstack(sc); + return(fn_proc(arg)(sc, with_list_t1(fn_proc(p)(sc, sc->t2_1)))); +} + +static s7_pointer fx_c_opsaq(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer p = cadr(arg); + set_car(sc->t2_2, fx_call(sc, cddr(p))); + set_car(sc->t2_1, lookup(sc, cadr(p))); + return(fn_proc(arg)(sc, with_list_t1(fn_proc(p)(sc, sc->t2_1)))); +} + +static s7_pointer fx_c_opaaaq(s7_scheme *sc, s7_pointer code) +{ + s7_pointer arg = cadr(code); + gc_protect_2_via_stack(sc, fx_call(sc, cdr(arg)), fx_call(sc, opt3_pair(arg))); /* cddr(arg) */ + set_car(sc->t3_3, fx_call(sc, cdr(opt3_pair(arg)))); + set_car(sc->t3_1, stack_protected1(sc)); + set_car(sc->t3_2, stack_protected2(sc)); + unstack(sc); + return(fn_proc(code)(sc, with_list_t1(fn_proc(arg)(sc, sc->t3_1)))); +} + +static s7_pointer fx_c_s_opaaq(s7_scheme *sc, s7_pointer code) +{ + s7_pointer arg = caddr(code); + gc_protect_via_stack(sc, fx_call(sc, cdr(arg))); + set_car(sc->t2_2, fx_call(sc, cddr(arg))); + set_car(sc->t2_1, stack_protected1(sc)); + set_car(sc->t2_2, fn_proc(arg)(sc, sc->t2_1)); + set_car(sc->t2_1, lookup(sc, cadr(code))); + unstack(sc); + return(fn_proc(code)(sc, sc->t2_1)); +} + +static s7_pointer fx_c_4a(s7_scheme *sc, s7_pointer code) +{ + s7_pointer res = cdr(code); + check_stack_size(sc); /* t718 pp cycles #f */ + gc_protect_2_via_stack(sc, fx_call(sc, res), fx_call(sc, cdr(res))); + res = cddr(res); + set_stack_protected3(sc, fx_call(sc, res)); + set_car(sc->t3_3, fx_call(sc, cdr(res))); + set_car(sc->t3_2, stack_protected3(sc)); + set_car(sc->t3_1, stack_protected2(sc)); + set_car(sc->t4_1, stack_protected1(sc)); + unstack(sc); + res = fn_proc(code)(sc, sc->t4_1); + set_car(sc->t4_1, sc->F); + return(res); +} + +static s7_pointer fx_c_4g(s7_scheme *sc, s7_pointer code) +{ /* all opts in use for code, opt1 free cdr(code), code opt3 is line_number, cdr(code) opt3 is arglen?? */ + s7_pointer res = cdr(code); + set_car(sc->t4_1, fx_call(sc, res)); + set_car(sc->t3_1, fx_call(sc, cdr(res))); + set_car(sc->t3_2, fx_call(sc, opt3_pair(code))); /* cddr(res) */ + set_car(sc->t3_3, fx_call(sc, cdr(opt3_pair(code)))); /* cdddr(res) */ + res = fn_proc(code)(sc, sc->t4_1); + set_car(sc->t4_1, sc->F); + return(res); +} + +static s7_pointer fx_c_c_opscq(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer largs = caddr(arg); + set_car(sc->t2_1, lookup(sc, cadr(largs))); + set_car(sc->t2_2, opt2_con(cdr(largs))); + set_car(sc->t2_2, fn_proc(largs)(sc, sc->t2_1)); + set_car(sc->t2_1, cadr(arg)); + return(fn_proc(arg)(sc, sc->t2_1)); +} + +static s7_pointer fx_c_s_opcsq(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer largs = caddr(arg); + set_car(sc->t2_2, lookup(sc, caddr(largs))); + set_car(sc->t2_1, opt1_con(cdr(largs))); /* cadr(largs) or cadadr */ + set_car(sc->t2_2, fn_proc(largs)(sc, sc->t2_1)); + set_car(sc->t2_1, lookup(sc, cadr(arg))); + return(fn_proc(arg)(sc, sc->t2_1)); +} + +static s7_pointer fx_c_op_opssqq_s(s7_scheme *sc, s7_pointer code) +{ + s7_pointer arg = opt1_pair(cdr(code)); + set_car(sc->t2_1, lookup(sc, cadr(arg))); + set_car(sc->t2_2, lookup(sc, opt2_sym(cdr(arg)))); + set_car(sc->t2_1, fn_proc(cadr(code))(sc, with_list_t1(fn_proc(arg)(sc, sc->t2_1)))); + set_car(sc->t2_2, lookup(sc, caddr(code))); + return(fn_proc(code)(sc, sc->t2_1)); +} + +static s7_pointer fx_c_op_opssqq_s_direct(s7_scheme *sc, s7_pointer code) +{ + s7_pointer arg = opt1_pair(cdr(code)); + return(((s7_p_pp_t)opt3_direct(code))(sc, + ((s7_p_p_t)opt2_direct(cdr(code)))(sc, + ((s7_p_pp_t)opt3_direct(cdr(code)))(sc, lookup(sc, cadr(arg)), lookup(sc, caddr(arg)))), + lookup(sc, caddr(code)))); +} + +static s7_pointer fx_c_ns(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer p1, lst = safe_list_if_possible(sc, opt3_arglen(cdr(arg))); + if (in_heap(lst)) gc_protect_via_stack(sc, lst); + for (s7_pointer args = cdr(arg), p = lst; is_pair(args); args = cdr(args), p = cdr(p)) + set_car(p, lookup(sc, car(args))); + p1 = fn_proc(arg)(sc, lst); + if (in_heap(lst)) unstack(sc); + else clear_list_in_use(lst); + return(p1); +} + +static s7_pointer fx_list_ns(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer lst = make_list(sc, opt3_arglen(cdr(arg)), sc->unused); + for (s7_pointer args = cdr(arg), p = lst; is_pair(args); args = cdr(args), p = cdr(p)) + set_car(p, lookup(sc, car(args))); + return(lst); +} + +static s7_pointer fx_c_all_ca(s7_scheme *sc, s7_pointer code) +{ + s7_pointer p1, lst = safe_list_if_possible(sc, opt3_arglen(cdr(code))); + if (in_heap(lst)) gc_protect_via_stack(sc, lst); + for (s7_pointer args = cdr(code), p = lst; is_pair(args); args = cdr(args), p = cddr(p)) + { + set_car(p, opt2_con(args)); + args = cdr(args); + set_car(cdr(p), fx_call(sc, args)); + } + p1 = fn_proc(code)(sc, lst); + if (in_heap(lst)) unstack(sc); + else clear_list_in_use(lst); + return(p1); +} + +static s7_pointer fx_inlet_ca(s7_scheme *sc, s7_pointer code) +{ + s7_pointer new_e, sp = NULL; + int64_t id; + + new_cell(sc, new_e, T_LET | T_SAFE_PROCEDURE); + let_set_slots(new_e, slot_end); /* needed by add_slot_unchecked */ + let_set_outlet(new_e, sc->nil); + gc_protect_via_stack(sc, new_e); + + /* as in let, we need to call the var inits before making the new let, but a simpler equivalent is to make the new let + * but don't set its id yet, and don't set local_slot until end either because fx_call might refer to same-name symbol in outer let. + * That is, symbol_id=outer_let_id so lookup->local_slot, so we better not set local_slot ahead of time here. + * As far as I can tell, this is the only place we do fx_call at the time of new_slot with new let id unset. + */ + for (s7_pointer x = cdr(code); is_pair(x); x = cddr(x)) + { + s7_pointer symbol = car(x), value; + symbol = (is_symbol_and_keyword(symbol)) ? keyword_symbol(symbol) : cadr(symbol); /* (inlet ':allow-other-keys 3) */ + if (is_constant_symbol(sc, symbol)) /* (inlet 'pi 1) */ + { + unstack(sc); + wrong_type_error_nr(sc, sc->inlet_symbol, 1, symbol, a_non_constant_symbol_string); + } + value = fx_call(sc, cdr(x)); /* it's necessary to do this first, before add_slot_unchecked */ + if (!sp) + { + add_slot_unchecked_no_local(sc, new_e, symbol, value); + sp = let_slots(new_e); + } + else sp = add_slot_at_end_no_local(sc, sp, symbol, value); + } + id = ++sc->let_number; + let_set_id(new_e, id); + for (s7_pointer x = let_slots(new_e); tis_slot(x); x = next_slot(x)) + symbol_set_local_slot_unincremented(slot_symbol(x), id, x); /* was symbol_set_id(slot_symbol(x), id) */ + unstack(sc); + return(new_e); +} + +static s7_pointer fx_c_na(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer args, p, val = safe_list_if_possible(sc, opt3_arglen(cdr(arg))); + if (in_heap(val)) gc_protect_via_stack(sc, val); + for (args = cdr(arg), p = val; is_pair(args); args = cdr(args), p = cdr(p)) + set_car(p, fx_call(sc, args)); + p = fn_proc(arg)(sc, val); + if (in_heap(val)) unstack(sc); + else clear_list_in_use(val); + return(p); +} + +static s7_pointer fx_vector_ns(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer args = cdr(arg); + s7_pointer vec = make_simple_vector(sc, opt3_arglen(cdr(arg))); + s7_pointer *els = (s7_pointer *)vector_elements(vec); + for (s7_int i = 0; is_pair(args); args = cdr(args), i++) + els[i] = lookup(sc, car(args)); + return(vec); +} + +static s7_pointer fx_vector_na(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer args = cdr(arg); + s7_pointer v = make_simple_vector(sc, opt3_arglen(cdr(arg))); /* was s7_make_vector */ + s7_pointer *els = vector_elements(v); + gc_protect_via_stack(sc, v); + normal_vector_fill(v, sc->nil); /* fx_calls below can trigger GC, so all elements of v must be legit */ + for (s7_int i = 0; is_pair(args); args = cdr(args), i++) + els[i] = fx_call(sc, args); + sc->value = v; /* full-s7test 12262 list_p_p case */ + unstack(sc); + return(v); +} + +static s7_pointer fx_if_a_a(s7_scheme *sc, s7_pointer arg) +{ + return((is_true(sc, fx_call(sc, cdr(arg)))) ? fx_call(sc, opt1_pair(arg)) : sc->unspecified); +} + +static s7_pointer fx_if_not_a_a(s7_scheme *sc, s7_pointer arg) +{ + return((is_false(sc, fx_call(sc, opt1_pair(arg)))) ? fx_call(sc, opt2_pair(arg)) : sc->unspecified); +} + +static s7_pointer fx_if_a_a_a(s7_scheme *sc, s7_pointer arg) +{ + return((is_true(sc, fx_call(sc, cdr(arg)))) ? fx_call(sc, opt1_pair(arg)) : fx_call(sc, opt2_pair(arg))); +} + +#define fx_if_s_a_a_any(Name, Lookup) \ + static s7_pointer Name(s7_scheme *sc, s7_pointer arg) \ + { \ + return((Lookup(sc, cadr(arg), arg) != sc->F) ? fx_call(sc, opt1_pair(arg)) : fx_call(sc, opt2_pair(arg))); \ + } + +fx_if_s_a_a_any(fx_if_s_a_a, s_lookup) +fx_if_s_a_a_any(fx_if_o_a_a, o_lookup) /* diff s->o of ca 3 */ + + +static s7_pointer fx_if_and2_s_a(s7_scheme *sc, s7_pointer arg) +{ + return(((fx_call(sc, opt1_pair(arg)) == sc->F) || (fx_call(sc, opt2_pair(arg)) == sc->F)) ? fx_call(sc, cdddr(arg)) : lookup(sc, opt3_sym(arg))); +} + +static s7_pointer fx_if_not_a_a_a(s7_scheme *sc, s7_pointer arg) +{ + return((is_false(sc, fx_call(sc, opt1_pair(arg)))) ? fx_call(sc, opt2_pair(arg)) : fx_call(sc, opt3_pair(arg))); +} + +static s7_pointer fx_if_a_c_c(s7_scheme *sc, s7_pointer arg) {return((is_true(sc, fx_call(sc, cdr(arg)))) ? opt1_con(arg) : opt2_con(arg));} + +static s7_pointer fx_if_is_type_s_a_a(s7_scheme *sc, s7_pointer arg) +{ + if (gen_type_match(sc, lookup(sc, opt2_sym(cdr(arg))), opt3_byte(cdr(arg)))) + return(fx_call(sc, cddr(arg))); + return(fx_call(sc, opt2_pair(arg))); /* cdddr(arg) */ +} + +static inline s7_pointer fx_and_2a(s7_scheme *sc, s7_pointer arg) /* arg is the full expr: (and ...) */ +{ + return((fx_call(sc, cdr(arg)) == sc->F) ? sc->F : fx_call(sc, cddr(arg))); +} + +static inline s7_pointer fx_and_s_2(s7_scheme *sc, s7_pointer arg) +{ + set_car(sc->t1_1, lookup(sc, opt3_sym(cdr(arg)))); /* cadadr(arg) */ + return((fn_proc(cadr(arg))(sc, sc->t1_1) == sc->F) ? sc->F : fn_proc(caddr(arg))(sc, sc->t1_1)); +} + +static s7_pointer fx_and_or_2a_vref(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer or1 = cadr(arg); + s7_pointer arg11 = cdadr(or1); + s7_pointer v = lookup(sc, cadar(arg11)); + if ((is_normal_vector(v)) && (vector_rank(v) == 1)) + { + s7_pointer ip = lookup(sc, opt3_sym(or1)); + s7_pointer jp = lookup(sc, opt1_sym(or1)); + if ((is_t_integer(ip)) && (is_t_integer(jp))) + { + s7_int i = integer(ip), j = integer(jp); + if ((i >= 0) && (j >= 0) && + (i < vector_length(v)) && (j < vector_length(v)) && + (is_t_real(vector_element(v, i))) && (is_t_real(vector_element(v, j)))) + { + s7_pointer xp = lookup(sc, cadr(arg11)); + if (is_t_real(xp)) + { + s7_double vi = real(vector_element(v, i)), vj = real(vector_element(v, j)), xf = real(xp); + return(make_boolean(sc, ((vi > xf) || (xf >= vj)) && ((vj > xf) || (xf >= vi)))); + }}}} + return(fx_and_2a(sc, arg)); +} + +static s7_pointer fx_len2_t(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer val = t_lookup(sc, opt1_sym(cdr(arg)), arg); /* isn't this unprotected from mock pair? */ /* opt1_sym == cadadr(arg) */ + return(make_boolean(sc, is_pair(val) && (is_pair(cdr(val))) && (is_null(cddr(val))))); +} + +static s7_pointer fx_len3_t(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer val = t_lookup(sc, opt1_sym(cdr(arg)), arg); + return(make_boolean(sc, is_pair(val) && (is_pair(cdr(val))) && (is_pair(cddr(val))))); +} + +static s7_pointer fx_and_3a(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer p = cdr(arg); + if (fx_call(sc, p) == sc->F) return(sc->F); + p = cdr(p); + return((fx_call(sc, p) == sc->F) ? sc->F : fx_call(sc, cdr(p))); +} + +static s7_pointer fx_and_n(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer x = sc->T; + for (s7_pointer p = cdr(arg); (is_pair(p)) && (x != sc->F); p = cdr(p)) /* in lg, 5/6 args appears to predominate */ + x = fx_call(sc, p); + return(x); +} + +static s7_pointer fx_or_2a(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer p = cdr(arg); + s7_pointer val = fx_call(sc, p); + return((val != sc->F) ? val : fx_call(sc, cdr(p))); +} + +static s7_pointer fx_or_s_2(s7_scheme *sc, s7_pointer arg) +{ + /* the "s" is looked up once here -- not obvious how to use fx_call anyway */ + s7_pointer x = fn_proc(cadr(arg))(sc, with_list_t1(lookup(sc, opt3_sym(cdr(arg))))); /* cadadr(arg); */ + return((x != sc->F) ? x : fn_proc(caddr(arg))(sc, sc->t1_1)); +} + +static s7_pointer fx_or_s_type_2(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer x = lookup(sc, opt3_sym(cdr(arg))); /* cadadr(arg)); */ + return(make_boolean(sc, (type(x) == opt3_int(arg)) || (type(x) == opt2_int(cdr(arg))))); +} + +static s7_pointer fx_not_symbol_or_keyword(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer val = lookup(sc, opt3_sym(arg)); + return(make_boolean(sc, (!is_symbol(val)) || (is_keyword(val)))); +} + +static s7_pointer fx_or_and_2a(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer p = cdr(arg); + s7_pointer val = fx_call(sc, p); + if (val != sc->F) return(val); + p = opt3_pair(arg); /* cdadr(p); */ + val = fx_call(sc, p); + return((val == sc->F) ? val : fx_call(sc, cdr(p))); +} + +static s7_pointer fx_or_and_3a(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer p = cdr(arg); + s7_pointer val = fx_call(sc, p); + if (val != sc->F) return(val); + p = opt3_pair(arg); /* cdadr(p); */ + val = fx_call(sc, p); + if (val == sc->F) return(val); + p = cdr(p); + val = fx_call(sc, p); + return((val == sc->F) ? val : fx_call(sc, cdr(p))); +} + +static s7_pointer fx_or_3a(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer p = cdr(arg); + s7_pointer val = fx_call(sc, p); + if (val != sc->F) return(val); + p = cdr(p); + val = fx_call(sc, p); + return((val != sc->F) ? val : fx_call(sc, cdr(p))); +} + +static s7_pointer fx_or_n(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer x = sc->F; + for (s7_pointer p = cdr(arg); (is_pair(p)) && (x == sc->F); p = cdr(p)) + x = fx_call(sc, p); + return(x); +} + +static s7_pointer fx_begin_aa(s7_scheme *sc, s7_pointer arg) +{ + fx_call(sc, cdr(arg)); + return(fx_call(sc, cddr(arg))); +} + +static s7_pointer fx_begin_na(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer p; + for (p = cdr(arg); is_pair(cdr(p)); p = cdr(p)) + fx_call(sc, p); + return(fx_call(sc, p)); +} + +static s7_pointer fx_safe_thunk_a(s7_scheme *sc, s7_pointer code) +{ + s7_pointer f = opt1_lambda(code), result; + gc_protect_via_stack(sc, sc->curlet); /* we do need to GC protect curlet here and below (not just remember it) */ + set_curlet(sc, closure_let(f)); + result = fx_call(sc, closure_body(f)); + set_curlet(sc, stack_protected1(sc)); + unstack(sc); + return(result); +} + +static s7_pointer fx_safe_closure_s_a(s7_scheme *sc, s7_pointer code) /* also called from h_safe_closure_s_a in eval */ +{ + s7_pointer result; + gc_protect_via_stack(sc, sc->curlet); + sc->curlet = update_let_with_slot(sc, closure_let(opt1_lambda(code)), lookup(sc, opt2_sym(code))); + result = fx_call(sc, closure_body(opt1_lambda(code))); + set_curlet(sc, stack_protected1(sc)); + unstack(sc); + return(result); +} + +static s7_pointer op_safe_closure_s_a(s7_scheme *sc, s7_pointer code) /* also called from h_safe_closure_s_a in eval */ +{ + sc->curlet = update_let_with_slot(sc, closure_let(opt1_lambda(code)), lookup(sc, opt2_sym(code))); + return(fx_call(sc, closure_body(opt1_lambda(code)))); +} + +static s7_pointer fx_safe_closure_t_a(s7_scheme *sc, s7_pointer code) +{ + s7_pointer result; + gc_protect_via_stack(sc, sc->curlet); + sc->curlet = update_let_with_slot(sc, closure_let(opt1_lambda(code)), t_lookup(sc, opt2_sym(code), code)); + result = fx_call(sc, closure_body(opt1_lambda(code))); + set_curlet(sc, stack_protected1(sc)); + unstack(sc); + return(result); +} + +static s7_pointer fx_safe_closure_s_to_s(s7_scheme *sc, s7_pointer arg) +{ + return(fn_proc(car(closure_body(opt1_lambda(arg))))(sc, with_list_t1(lookup(sc, opt2_sym(arg))))); +} + +static s7_pointer fx_safe_closure_s_to_sc(s7_scheme *sc, s7_pointer arg) +{ + set_car(sc->t2_2, opt3_con(cdr(arg))); + set_car(sc->t2_1, lookup(sc, opt2_sym(arg))); + return(fn_proc(car(closure_body(opt1_lambda(arg))))(sc, sc->t2_1)); +} + +static s7_pointer fx_safe_closure_s_to_vref(s7_scheme *sc, s7_pointer arg) {return(vector_ref_p_pp(sc, lookup(sc, opt2_sym(arg)), opt3_con(cdr(arg))));} + +static s7_pointer fx_safe_closure_s_to_sub1(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer p = lookup(sc, opt2_sym(arg)); + if ((!WITH_GMP) && (is_t_integer(p))) + return(make_integer(sc, integer(p) - 1)); + return(minus_c1(sc, p)); +} + +static s7_pointer fx_safe_closure_s_to_add1(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer p = lookup(sc, opt2_sym(arg)); + if ((!WITH_GMP) && (is_t_integer(p))) + return(make_integer(sc, integer(p) + 1)); + return(g_add_x1_1(sc, p, 1)); +} + +static s7_pointer fx_c_ff(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer p = cdr(arg); + s7_pointer x = fx_proc(cdar(p))(sc, car(p)); + sc->value = x; + set_car(sc->t2_2, fx_proc(cdadr(p))(sc, cadr(p))); + set_car(sc->t2_1, x); + return(fn_proc(arg)(sc, sc->t2_1)); +} + +static s7_pointer fx_safe_closure_a_to_sc(s7_scheme *sc, s7_pointer arg) +{ + set_car(sc->t2_1, fx_call(sc, cdr(arg))); + set_car(sc->t2_2, opt3_con(cdr(arg))); + return(fn_proc(car(closure_body(opt1_lambda(arg))))(sc, sc->t2_1)); +} + +static s7_pointer fx_safe_closure_a_to_vref(s7_scheme *sc, s7_pointer arg) {return(vector_ref_p_pp(sc, fx_call(sc, cdr(arg)), opt3_con(cdr(arg))));} + +static s7_pointer fx_safe_closure_s_and_2a(s7_scheme *sc, s7_pointer code) /* safe_closure_s_a where "a" is fx_and_2a */ +{ + s7_pointer result; + gc_protect_via_stack(sc, sc->curlet); + sc->curlet = update_let_with_slot(sc, closure_let(opt1_lambda(code)), lookup(sc, opt2_sym(code))); + code = cdar(closure_body(opt1_lambda(code))); + result = fx_call(sc, code); /* have to unwind the stack so this can't return */ + if (result != sc->F) + result = fx_call(sc, cdr(code)); + set_curlet(sc, stack_protected1(sc)); + unstack(sc); + return(result); +} + +static s7_pointer fx_safe_closure_s_and_pair(s7_scheme *sc, s7_pointer code) /* safe_closure_s_a where "a" is fx_and_2a with is_pair as first clause */ +{ + s7_pointer result; + gc_protect_via_stack(sc, sc->curlet); + sc->curlet = update_let_with_slot(sc, closure_let(opt1_lambda(code)), lookup(sc, opt2_sym(code))); + code = cdar(closure_body(opt1_lambda(code))); + result = (is_pair(t_lookup(sc, cadar(code), code))) ? fx_call(sc, cdr(code)) : sc->F; /* pair? arg = func par, pair? is global, symbol_id=0 */ + set_curlet(sc, stack_protected1(sc)); + unstack(sc); + return(result); +} + +static s7_pointer fx_safe_closure_a_a(s7_scheme *sc, s7_pointer code) +{ + s7_pointer result; + gc_protect_via_stack(sc, sc->curlet); + sc->curlet = update_let_with_slot(sc, closure_let(opt1_lambda(code)), fx_call(sc, cdr(code))); + result = fx_call(sc, closure_body(opt1_lambda(code))); + set_curlet(sc, stack_protected1(sc)); + unstack(sc); + return(result); +} + +static s7_pointer op_safe_closure_a_a(s7_scheme *sc, s7_pointer code) +{ + sc->curlet = update_let_with_slot(sc, closure_let(opt1_lambda(code)), fx_call(sc, cdr(code))); + return(fx_call(sc, closure_body(opt1_lambda(code)))); +} + +static s7_pointer fx_safe_closure_a_sqr(s7_scheme *sc, s7_pointer code) {return(fx_sqr_1(sc, fx_call(sc, cdr(code))));} +static s7_pointer fx_safe_closure_s_sqr(s7_scheme *sc, s7_pointer code) {return(fx_sqr_1(sc, lookup(sc, opt2_sym(code))));} + +static s7_pointer fx_safe_closure_a_and_2a(s7_scheme *sc, s7_pointer code) +{ + s7_pointer and_arg = cdar(closure_body(opt1_lambda(code))); + s7_pointer result; + gc_protect_via_stack(sc, sc->curlet); + sc->curlet = update_let_with_slot(sc, closure_let(opt1_lambda(code)), fx_call(sc, cdr(code))); + result = fx_call(sc, and_arg); + if (result != sc->F) result = fx_call(sc, cdr(and_arg)); + set_curlet(sc, stack_protected1(sc)); + unstack(sc); + return(result); +} + +static s7_pointer fx_safe_closure_ss_a(s7_scheme *sc, s7_pointer code) +{ + s7_pointer result; + gc_protect_via_stack(sc, sc->curlet); + sc->curlet = update_let_with_two_slots(sc, closure_let(opt1_lambda(code)), lookup(sc, cadr(code)), lookup(sc, opt2_sym(code))); + result = fx_call(sc, closure_body(opt1_lambda(code))); + set_curlet(sc, stack_protected1(sc)); + unstack(sc); + return(result); +} + +static s7_pointer op_safe_closure_ss_a(s7_scheme *sc, s7_pointer code) +{ + sc->curlet = update_let_with_two_slots(sc, closure_let(opt1_lambda(code)), lookup(sc, cadr(code)), lookup(sc, opt2_sym(code))); + return(fx_call(sc, closure_body(opt1_lambda(code)))); +} + +static s7_pointer fx_safe_closure_3s_a(s7_scheme *sc, s7_pointer code) +{ + s7_pointer result; + gc_protect_via_stack(sc, sc->curlet); + sc->curlet = update_let_with_three_slots(sc, closure_let(opt1_lambda(code)), lookup(sc, cadr(code)), lookup(sc, opt2_sym(code)), lookup(sc, opt3_sym(code))); + result = fx_call(sc, closure_body(opt1_lambda(code))); + set_curlet(sc, stack_protected1(sc)); + unstack(sc); + return(result); +} + +static s7_pointer op_safe_closure_3s_a(s7_scheme *sc, s7_pointer code) +{ + sc->curlet = update_let_with_three_slots(sc, closure_let(opt1_lambda(code)), lookup(sc, cadr(code)), lookup(sc, opt2_sym(code)), lookup(sc, opt3_sym(code))); + return(fx_call(sc, closure_body(opt1_lambda(code)))); +} + +static s7_pointer fx_safe_closure_aa_a(s7_scheme *sc, s7_pointer code) +{ + s7_pointer p = cdr(code); + s7_pointer f = opt1_lambda(code); + gc_protect_2_via_stack(sc, sc->curlet, fx_call(sc, cdr(p))); /* this is needed even if one of the args is a symbol */ + sc->curlet = update_let_with_two_slots(sc, closure_let(f), fx_call(sc, p), stack_protected2(sc)); + p = fx_call(sc, closure_body(f)); + set_curlet(sc, stack_protected1(sc)); + unstack(sc); + return(p); +} + +static inline s7_pointer fx_cond_na_na(s7_scheme *sc, s7_pointer code) /* all tests are fxable, results are all fx, no =>, no missing results */ +{ + for (s7_pointer p = cdr(code); is_pair(p); p = cdr(p)) + if (is_true(sc, fx_call(sc, car(p)))) + { + for (p = cdar(p); is_pair(cdr(p)); p = cdr(p)) + fx_call(sc, p); + return(fx_call(sc, p)); + } + return(sc->unspecified); +} + +static s7_pointer s7_starlet(s7_scheme *sc, s7_int choice); + +static s7_pointer fx_implicit_s7_starlet_ref_s(s7_scheme *sc, s7_pointer arg) {return(s7_starlet(sc, opt3_int(arg)));} +static s7_pointer fx_implicit_s7_starlet_print_length(s7_scheme *sc, s7_pointer arg) {return(make_integer(sc, sc->print_length));} +static s7_pointer fx_implicit_s7_starlet_safety(s7_scheme *sc, s7_pointer arg) {return(make_integer(sc, sc->safety));} + +static s7_function *fx_function = NULL; + +static bool is_fxable(s7_scheme *sc, s7_pointer p) +{ + if (!is_pair(p)) return(true); + if ((is_optimized(p)) && /* this is needed. In check_tc, for example, is_fxable can be confused by early optimize_op */ + (fx_function[optimize_op(p)])) + return(true); + return(is_proper_quote(sc, p)); +} + +static bool is_gxable(s7_pointer p) +{ + opcode_t op; + if (!is_optimized(p)) return(false); + op = optimize_op(p); + return((is_symbol(car(p))) && (symbol_ctr(car(p)) == 1) && + (op < FIRST_UNHOPPABLE_OP) && + (op > OP_GC_PROTECT) && + (fx_function[op | 1])); +} + +static int32_t fx_count(s7_scheme *sc, s7_pointer x) +{ + int32_t count = 0; + for (s7_pointer p = cdr(x); is_pair(p); p = cdr(p)) + if (is_fxable(sc, car(p))) + count++; + return(count); +} + +static bool is_code_constant(s7_scheme *sc, s7_pointer p) {return((is_pair(p)) ? (car(p) == sc->quote_symbol) : is_constant(sc, p));} + +static inline s7_pointer check_quote(s7_scheme *sc, s7_pointer code); + +static s7_p_dd_t s7_p_dd_function(s7_pointer f); +static s7_p_pi_t s7_p_pi_function(s7_pointer f); +static s7_p_ii_t s7_p_ii_function(s7_pointer f); + +#define is_unchanged_global(P) \ + ((is_symbol(P)) && (is_global(P)) && (symbol_id(P) == 0) && \ + (is_slot(initial_slot(P))) && \ + (initial_value(P) == global_value(P))) + +#define is_global_and_has_func(P, Func) ((is_unchanged_global(P)) && (Func(global_value(P)))) /* Func = s7_p_pp_function and friends */ + +static bool fx_matches(s7_pointer symbol, const s7_pointer target_symbol) {return((symbol == target_symbol) && (is_unchanged_global(symbol)));} + +typedef bool (safe_sym_t)(s7_scheme *sc, s7_pointer sym, s7_pointer e); + +/* #define fx_choose(Sc, Holder, E, Checker) fx_choose_1(Sc, Holder, E, Checker, __func__, __LINE__) */ +static s7_function fx_choose(s7_scheme *sc, s7_pointer holder, s7_pointer cur_env, safe_sym_t *checker) /* , const char *func, int32_t line) */ +{ + s7_pointer arg = car(holder); + if (!is_pair(arg)) + { + if (is_symbol(arg)) + { + if ((is_keyword(arg)) || ((arg == sc->else_symbol) && (is_global(arg)))) return(fx_c); + return((is_global(arg)) ? fx_g : ((checker(sc, arg, cur_env)) ? fx_s : fx_unsafe_s)); + } + return(fx_c); + } + if (is_optimized(arg)) + { + switch (optimize_op(arg)) + { + case HOP_SAFE_C_NC: /* includes 0-arg cases, newline/current-input|output-port, [make-]hash-table?, read-line, [float-]vector/list, gensym */ + if (cdr(arg) == sc->nil) return((fn_proc(arg) == g_read_char) ? fx_read_char_0 : fx_c_0c); +#if (!WITH_GMP) + if (fn_proc(arg) == g_add_i_random) return(fx_add_i_random); +#endif + return((fn_proc(arg) == g_random_i) ? fx_random_i : ((fn_proc(arg) == g_cons) ? fx_cons_cc : fx_c_nc)); + + case OP_OR_2A: + if (fx_proc(cddr(arg)) == fx_and_2a) {set_opt3_pair(arg, cdaddr(arg)); return(fx_or_and_2a);} + if (fx_proc(cddr(arg)) == fx_and_3a) {set_opt3_pair(arg, cdaddr(arg)); return(fx_or_and_3a);} + if ((fx_proc(cdr(arg)) == fx_not_is_symbol_s) && (fx_proc(cddr(arg)) == fx_is_keyword_s) && (cadr(cadadr(arg)) == cadaddr(arg))) + { + /* (or (not (symbol? body)) (keyword? body)) */ + set_opt3_sym(arg, cadaddr(arg)); + return(fx_not_symbol_or_keyword); + } + return(fx_or_2a); + + case OP_AND_2A: + if ((fx_proc(cdr(arg)) == fx_or_2a) && (fx_proc(cddr(arg)) == fx_or_2a)) + { + s7_pointer o1 = cadr(arg), o2 = caddr(arg); + if ((fx_proc(cdr(o1)) == fx_gt_vref_s) && + (fx_proc(cddr(o1)) == fx_geq_s_vref) && + (fx_proc(cdr(o2)) == fx_gt_vref_s) && + (fx_proc(cddr(o2)) == fx_geq_s_vref)) + { + s7_pointer v = cadr(cadadr(o1)); + if ((v == cadr(cadadr(o2))) && (v == (cadr(caddaddr(o1)))) && (v == (cadr(caddaddr(o2))))) + { + s7_pointer x = caddadr(o1); + if ((x == caddadr(o2)) && (x == cadaddr(o1)) && (x == cadaddr(o2))) + { + s7_pointer i = caddr(cadadr(o1)), j = caddaddr(caddr(o1)); + if ((j == caddr(cadadr(o2))) && (i == caddaddr(caddr(o2)))) + { + set_opt1_sym(o1, j); + set_opt3_sym(o1, i); + return(fx_and_or_2a_vref); + }}}}} + return(fx_and_2a); + + case HOP_SAFE_C_S: + if (is_unchanged_global(car(arg))) /* mus-copy would work here but in tgen (for example) it's loading generators.scm with local mus-copy methods */ + { + uint8_t typ; + if (car(arg) == sc->cdr_symbol) return(fx_cdr_s); + if (car(arg) == sc->car_symbol) return(fx_car_s); + if (car(arg) == sc->cadr_symbol) return(fx_cadr_s); + if (car(arg) == sc->cddr_symbol) return(fx_cddr_s); + if (car(arg) == sc->is_null_symbol) return(fx_is_null_s); + if (car(arg) == sc->is_pair_symbol) return(fx_is_pair_s); + if (car(arg) == sc->is_symbol_symbol) return(fx_is_symbol_s); + if (car(arg) == sc->is_eof_object_symbol) return(fx_is_eof_s); + if (car(arg) == sc->is_integer_symbol) return(fx_is_integer_s); + if (car(arg) == sc->is_string_symbol) return(fx_is_string_s); + if (car(arg) == sc->not_symbol) return(fx_not_s); + if (car(arg) == sc->is_proper_list_symbol) return(fx_is_proper_list_s); + if (car(arg) == sc->is_vector_symbol) return(fx_is_vector_s); + if (car(arg) == sc->is_keyword_symbol) return(fx_is_keyword_s); + if (car(arg) == sc->is_procedure_symbol) return(fx_is_procedure_s); + if (car(arg) == sc->length_symbol) return(fx_length_s); + /* not read_char here... */ + typ = symbol_type(car(arg)); + if (typ > 0) + { + set_opt3_byte(cdr(arg), typ); + return(fx_is_type_s); + } + /* car_p_p (et al) does not look for a method so in: + * (define kar car) (load "mockery.scm") (let ((p (mock-pair '(1 2 3)))) (call-with-exit (lambda (x) (x (kar p))))) + * "kar" fails but not "car" because symbol_id(kar) == 0! symbol_id(car) > 0 because mockery provides a method for it. + */ + if (symbol_id(c_function_name_to_symbol(sc, global_value(car(arg)))) == 0) + { + s7_p_p_t f = s7_p_p_function(global_value(car(arg))); + if (f) + { + set_opt2_direct(cdr(arg), (s7_pointer)f); + if (f == real_part_p_p) return(fx_real_part_s); + if (f == imag_part_p_p) return(fx_imag_part_s); + if (f == iterate_p_p) return(fx_iterate_s); + if (f == car_p_p) return(fx_car_s); /* can happen if (define var-name car) etc */ + return((is_global(cadr(arg))) ? fx_c_g_direct : fx_c_s_direct); + }}} + return((is_global(cadr(arg))) ? fx_c_g : fx_c_s); + + case HOP_SAFE_C_SS: + if (fn_proc(arg) == g_cons) return(fx_cons_ss); + if (fx_matches(car(arg), sc->num_eq_symbol)) return(fx_num_eq_ss); + if (fn_proc(arg) == g_geq_2) return(fx_geq_ss); + if (fn_proc(arg) == g_greater_2) return(fx_gt_ss); + if (fn_proc(arg) == g_leq_2) return(fx_leq_ss); + if (fn_proc(arg) == g_less_2) return((is_global(caddr(arg))) ? fx_lt_sg : fx_lt_ss); + if ((fx_matches(car(arg), sc->multiply_symbol)) && (cadr(arg) == caddr(arg))) return(fx_sqr_s); + if (fn_proc(arg) == g_multiply_2) return(fx_multiply_ss); + if (fn_proc(arg) == g_is_eq) return(fx_is_eq_ss); + if (fn_proc(arg) == g_add_2) return(fx_add_ss); + if (fn_proc(arg) == g_subtract_2) return(fx_subtract_ss); + if (fn_proc(arg) == g_hash_table_ref_2) return(fx_hash_table_ref_ss); + + if (is_global_and_has_func(car(arg), s7_p_pp_function)) + { + if (car(arg) == sc->assq_symbol) return(fx_assq_ss); + if (car(arg) == sc->memq_symbol) return(fx_memq_ss); + if (car(arg) == sc->vector_ref_symbol) return(fx_vref_ss); + if (car(arg) == sc->string_ref_symbol) return(fx_sref_ss); + set_opt3_direct(cdr(arg), (s7_pointer)(s7_p_pp_function(global_value(car(arg))))); + return(fx_c_ss_direct); + } + /* fx_c_ss_direct via b_7pp is slower than fx_c_ss + g_<> */ + return(fx_c_ss); + + case HOP_SAFE_C_NS: + if (fn_proc(arg) == g_list) return(fx_list_ns); /* it is no faster here to divide out the big list cases!? */ + return((fn_proc(arg) == g_vector) ? fx_vector_ns : fx_c_ns); + + case HOP_SAFE_C_opSq_S: + if ((is_global_and_has_func(car(arg), s7_p_pp_function)) && + (is_global_and_has_func(caadr(arg), s7_p_p_function))) + { + set_opt2_direct(cdr(arg), (s7_pointer)(s7_p_pp_function(global_value(car(arg))))); + set_opt3_direct(cdr(arg), (s7_pointer)(s7_p_p_function(global_value(caadr(arg))))); + return(((car(arg) == sc->cons_symbol) && (caadr(arg) == sc->car_symbol)) ? fx_cons_car_s_s : fx_c_opsq_s_direct); + } + return(fx_c_opsq_s); + + case HOP_SAFE_C_SSS: + if ((fn_proc(arg) == g_less) && (is_global(cadr(arg))) && (is_global(cadddr(arg)))) return(fx_lt_gsg); + if (is_global_and_has_func(car(arg), s7_p_ppp_function)) + { + set_opt3_direct(cdr(arg), (s7_pointer)(s7_p_ppp_function(global_value(car(arg))))); + return(fx_c_sss_direct); + } + return(fx_c_sss); + + case HOP_SAFE_C_SSA: + if (is_global_and_has_func(car(arg), s7_p_ppp_function)) + { + set_opt2_direct(cdr(arg), (s7_pointer)(s7_p_ppp_function(global_value(car(arg))))); + return(fx_c_ssa_direct); + } + return(fx_c_ssa); + + case HOP_SAFE_C_SCS: + if (is_global_and_has_func(car(arg), s7_p_ppp_function)) + { + set_opt3_direct(cdr(arg), (s7_pointer)(s7_p_ppp_function(global_value(car(arg))))); + return(fx_c_scs_direct); + } + return(fx_c_scs); + + case HOP_SAFE_C_AAA: + if ((fx_proc(cdr(arg)) == fx_g) && (fx_proc(cdddr(arg)) == fx_c)) return(fx_c_gac); + if ((is_unquoted_pair(cadr(arg))) || (is_unquoted_pair(caddr(arg))) || (is_unquoted_pair(cadddr(arg)))) return(fx_c_aaa); + return(fx_c_3g); + + case HOP_SAFE_C_4A: + set_opt3_pair(arg, cdddr(arg)); + for (s7_pointer p = cdr(arg); is_pair(p); p = cdr(p)) + if (is_unquoted_pair(car(p))) + return(fx_c_4a); + return(fx_c_4g); /* fx_c_ssaa doesn't save much */ + + case HOP_SAFE_C_S_opSSq: + { + s7_pointer s2 = caddr(arg); + if ((fx_matches(car(s2), sc->multiply_symbol)) && (cadr(s2) == caddr(s2))) return(fx_c_s_sqr); + + if ((is_global_and_has_func(car(arg), s7_p_pp_function)) && + (is_global_and_has_func(car(s2), s7_p_pp_function))) + { + set_opt2_direct(cdr(arg), (s7_pointer)(s7_p_pp_function(global_value(car(arg))))); + set_opt3_direct(cdr(arg), (s7_pointer)(s7_p_pp_function(global_value(car(s2))))); + set_opt3_pair(arg, cdr(s2)); + if (car(s2) == sc->vector_ref_symbol) + { + if (car(arg) == sc->add_symbol) return(fx_add_s_vref); + if (car(arg) == sc->subtract_symbol) return(fx_subtract_s_vref); + if (car(arg) == sc->multiply_symbol) return(fx_multiply_s_vref); + if (car(arg) == sc->geq_symbol) return(fx_geq_s_vref); + if (car(arg) == sc->is_eq_symbol) return(fx_is_eq_s_vref); + if (car(arg) == sc->hash_table_ref_symbol) return(fx_href_s_vref); + if (car(arg) == sc->let_ref_symbol) return(fx_lref_s_vref); + if ((is_global(cadr(arg))) && (is_global(cadr(s2))) && (car(arg) == sc->vector_ref_symbol)) return(fx_vref_g_vref_gs); + } + if ((car(arg) == sc->vector_ref_symbol) && (car(s2) == sc->add_symbol)) return(fx_vref_s_add); + return(fx_c_s_opssq_direct); + } + return(fx_c_s_opssq); + } + + case HOP_SAFE_C_opSSq_S: + if ((is_global_and_has_func(car(arg), s7_p_pp_function)) && + (is_global_and_has_func(caadr(arg), s7_p_pp_function))) + { + /* op_c_opgsq_t */ + set_opt2_direct(cdr(arg), (s7_pointer)(s7_p_pp_function(global_value(car(arg))))); + set_opt3_direct(cdr(arg), (s7_pointer)(s7_p_pp_function(global_value(caadr(arg))))); + set_opt3_pair(arg, cdadr(arg)); + if (caadr(arg) == sc->vector_ref_symbol) + { + if (car(arg) == sc->subtract_symbol) return(fx_subtract_vref_s); + if (car(arg) == sc->gt_symbol) return(fx_gt_vref_s); + if (car(arg) == sc->vector_ref_symbol) return(fx_vref_vref_ss_s); + if (car(arg) == sc->add_symbol) return(fx_add_vref_s); + } + if (car(arg) == sc->add_symbol) + { + if ((caadr(arg) == sc->multiply_symbol) && (cadadr(arg) == caddadr(arg))) return(fx_add_sqr_s); + if (caadr(arg) == sc->subtract_symbol) return(fx_add_sub_s); + } + if ((car(arg) == sc->cons_symbol) && (caadr(arg) == sc->cons_symbol)) return(fx_cons_cons_s); + /* also div(sub)[2] mul(div) */ + return(((car(arg) == sc->gt_symbol) && (caadr(arg) == sc->add_symbol)) ? fx_gt_add_s : + (((car(arg) == sc->add_symbol) && (caadr(arg) == sc->multiply_symbol)) ? fx_add_mul_opssq_s : fx_c_opssq_s_direct)); + } + return(fx_c_opssq_s); + + case HOP_SAFE_C_opSSq_opSSq: + { + s7_pointer s1 = cadr(arg), s2 = caddr(arg); + set_opt3_pair(arg, cdr(s2)); + if ((fx_matches(car(s1), sc->multiply_symbol)) && (car(s2) == sc->multiply_symbol)) + { + set_opt1_pair(cdr(arg), cdr(s1)); + if (car(arg) == sc->subtract_symbol) return(fx_sub_mul_mul); + if (car(arg) == sc->add_symbol) + return(((cadr(s1) == caddr(s1)) && (cadr(s2) == caddr(s2))) ? fx_add_sqr_sqr : fx_add_mul_mul); + } + if ((fx_matches(car(s1), sc->subtract_symbol)) && (car(s2) == sc->subtract_symbol)) + { + set_opt1_pair(cdr(arg), cdr(s1)); + if (car(arg) == sc->multiply_symbol) return(fx_mul_sub_sub); + if (car(arg) == sc->lt_symbol) return(fx_lt_sub2); + } + if ((fx_matches(car(arg), sc->subtract_symbol)) && + (fx_matches(car(s1), sc->vector_ref_symbol)) && + (car(s2) == sc->vector_ref_symbol) && + (cadr(s1) == cadr(s2))) + { + set_opt3_sym(arg, cadr(cdaddr(arg))); + return(fx_sub_vref2); + } + return(fx_c_opssq_opssq); + } + + case HOP_SAFE_C_opSq: + if (is_unchanged_global(caadr(arg))) + { + if (fx_matches(car(arg), sc->is_pair_symbol)) + { + if (caadr(arg) == sc->car_symbol) {set_opt3_sym(arg, cadadr(arg)); return(fx_is_pair_car_s);} /* (pair? ...) is ok, so loc can be sym? 7 in lg */ + if (caadr(arg) == sc->cdr_symbol) {set_opt3_sym(arg, cadadr(arg)); return(fx_is_pair_cdr_s);} + if (caadr(arg) == sc->cadr_symbol) {set_opt3_sym(arg, cadadr(arg)); return(fx_is_pair_cadr_s);} + if (caadr(arg) == sc->cddr_symbol) {set_opt3_sym(arg, cadadr(arg)); return(fx_is_pair_cddr_s);} + } + if (fx_matches(car(arg), sc->is_null_symbol)) + { + if (caadr(arg) == sc->cdr_symbol) {set_opt2_sym(cdr(arg), cadadr(arg)); return(fx_is_null_cdr_s);} + if (caadr(arg) == sc->cadr_symbol) {set_opt2_sym(cdr(arg), cadadr(arg)); return(fx_is_null_cadr_s);} + if (caadr(arg) == sc->cddr_symbol) {set_opt2_sym(cdr(arg), cadadr(arg)); return(fx_is_null_cddr_s);} + } + if ((fx_matches(car(arg), sc->is_symbol_symbol)) && + (caadr(arg) == sc->cadr_symbol)) + {set_opt2_sym(cdr(arg), cadadr(arg)); return(fx_is_symbol_cadr_s);} + + if (fx_matches(car(arg), sc->not_symbol)) + { + if (caadr(arg) == sc->is_pair_symbol) {set_opt3_sym(arg, cadadr(arg)); return(fx_not_is_pair_s);} + if (caadr(arg) == sc->is_null_symbol) {set_opt3_sym(arg, cadadr(arg)); return(fx_not_is_null_s);} + if (caadr(arg) == sc->is_symbol_symbol) {set_opt3_sym(arg, cadadr(arg)); return(fx_not_is_symbol_s);} + return(fx_not_opsq); + } + if ((fx_matches(car(arg), sc->floor_symbol)) && (caadr(arg) == sc->sqrt_symbol)) + {set_opt2_sym(cdr(arg), cadadr(arg)); return(fx_floor_sqrt_s);} + } + if (is_unchanged_global(car(arg))) /* (? (op arg)) where (op arg) might return a let with a ? method etc */ + { /* other possibility: fx_c_a */ + uint8_t typ = symbol_type(car(arg)); + if (typ > 0) /* h_safe_c here so the type checker isn't shadowed */ + { + set_opt2_sym(cdr(arg), cadadr(arg)); + set_opt3_byte(cdr(arg), typ); + if (fn_proc(cadr(arg)) == (s7_function)g_c_pointer_weak1) + return(fx_eq_weak1_type_s); + return(fx_matches(caadr(arg), sc->car_symbol) ? fx_is_type_car_s : fx_is_type_opsq); + }} + /* this should follow the is_type* check above */ + if (fx_matches(caadr(arg), sc->car_symbol)) + { + set_opt2_sym(cdr(arg), cadadr(arg)); + return(fx_c_car_s); + } + if (fx_matches(caadr(arg), sc->cdr_symbol)) + { + set_opt2_sym(cdr(arg), cadadr(arg)); + return(fx_c_cdr_s); + } + return(fx_c_opsq); + + case HOP_SAFE_C_SC: + if (is_unchanged_global(car(arg))) + { + if (car(arg) == sc->add_symbol) + { + if (is_t_real(caddr(arg))) return(fx_add_sf); + if (is_t_integer(caddr(arg))) return((integer(caddr(arg)) == 1) ? fx_add_s1 : fx_add_si); + } + if (car(arg) == sc->subtract_symbol) + { + if (is_t_real(caddr(arg))) return(fx_subtract_sf); + if (is_t_integer(caddr(arg))) return((integer(caddr(arg)) == 1) ? fx_subtract_s1 : fx_subtract_si); + } + if (car(arg) == sc->multiply_symbol) + { + if (is_t_real(caddr(arg))) return(fx_multiply_sf); + if (is_t_integer(caddr(arg))) return(fx_multiply_si); + } + if ((fn_proc(arg) == g_memq_2) && (is_pair(caddr(arg)))) return(fx_memq_sq_2); + if ((fn_proc(arg) == g_is_eq) && (!is_unspecified(caddr(arg)))) return(fx_is_eq_sc); + + if ((is_t_integer(caddr(arg))) && (s7_p_pi_function(global_value(car(arg))))) + { + if (car(arg) == sc->num_eq_symbol) return((integer(caddr(arg)) == 0) ? fx_num_eq_s0 : fx_num_eq_si); + if (car(arg) == sc->lt_symbol) return(fx_lt_si); + if (car(arg) == sc->leq_symbol) return(fx_leq_si); + if (car(arg) == sc->gt_symbol) return(fx_gt_si); + if (car(arg) == sc->geq_symbol) return(fx_geq_si); + set_opt3_direct(cdr(arg), (s7_pointer)(s7_p_pi_function(global_value(car(arg))))); + return(fx_c_si_direct); + } + if ((is_t_real(caddr(arg))) && (real(caddr(arg)) == 0.0) && (car(arg) == sc->num_eq_symbol)) return(fx_num_eq_s0f); + if ((s7_p_pp_function(global_value(car(arg)))) && (fn_proc(arg) != g_divide_by_2)) + { + if (car(arg) == sc->memq_symbol) + { + if ((is_pair(caddr(arg))) && (is_proper_list_3(sc, cadaddr(arg)))) return(fx_memq_sc_3); + return(fx_memq_sc); + } + if ((car(arg) == sc->char_eq_symbol) && (is_character(caddr(arg)))) return(fx_char_eq_sc); /* maybe fx_char_eq_newline */ + if (car(arg) == sc->lt_symbol) return(fx_lt_sc); /* integer case handled above */ + if (car(arg) == sc->leq_symbol) return(fx_leq_sc); + if (car(arg) == sc->gt_symbol) return(fx_gt_sc); + if (car(arg) == sc->geq_symbol) return(fx_geq_sc); + set_opt3_direct(cdr(arg), (s7_pointer)(s7_p_pp_function(global_value(car(arg))))); + return(fx_c_sc_direct); + }} + return(fx_c_sc); + + case HOP_SAFE_C_CS: + if (is_unchanged_global(car(arg))) + { + if (car(arg) == sc->cons_symbol) return(fx_cons_cs); + if ((car(arg) == sc->add_symbol) && (is_t_real(cadr(arg)))) return(fx_add_fs); + if ((car(arg) == sc->subtract_symbol) && (is_t_real(cadr(arg)))) return(fx_subtract_fs); + if ((car(arg) == sc->num_eq_symbol) && (cadr(arg) == int_zero)) + { + set_opt3_sym(arg, caddr(arg)); /* opt3_location is in use, but the num_eq is ok, so only symbol might care about that info? (or use cdr(arg)) */ + return(fx_num_eq_0s); + } + if (car(arg) == sc->multiply_symbol) + { + if (is_t_real(cadr(arg))) return(fx_multiply_fs); + if (is_t_integer(cadr(arg))) return(fx_multiply_is); + }} + return(fx_c_cs); + + case HOP_SAFE_C_S_opSq: + if (fx_matches(car(caddr(arg)), sc->car_symbol)) + { + set_opt2_sym(cdr(arg), cadaddr(arg)); + if (fx_matches(car(arg), sc->hash_table_ref_symbol)) return(fx_hash_table_ref_car); + return(fx_matches(car(arg), sc->add_symbol) ? fx_add_s_car_s : fx_c_s_car_s); + } + if ((is_global_and_has_func(car(arg), s7_p_pp_function)) && + (is_global_and_has_func(caaddr(arg), s7_p_p_function))) + { + if ((car(arg) == sc->cons_symbol) && (caaddr(arg) == sc->cdr_symbol)) {set_opt2_sym(cdr(arg), cadaddr(arg)); return(fx_cons_s_cdr_s);} + set_opt1_sym(cdr(arg), cadaddr(arg)); + set_opt2_direct(cdr(arg), (s7_pointer)(s7_p_pp_function(global_value(car(arg))))); + set_opt3_direct(cdr(arg), (s7_pointer)(s7_p_p_function(global_value(caaddr(arg))))); /* arg opt3 only location, but no change in callgrind */ + return(fx_c_s_opsq_direct); + } + return(fx_c_s_opsq); + + case HOP_SAFE_C_C_opSq: + if (is_global_and_has_func(car(arg), s7_p_pp_function)) + { + s7_pointer arg2 = caddr(arg); + if (is_global_and_has_func(car(arg2), s7_p_p_function)) + { + set_opt2_direct(cdr(arg), (s7_pointer)(s7_p_pp_function(global_value(car(arg))))); + set_opt3_direct(cdr(arg), (s7_pointer)(s7_p_p_function(global_value(car(arg2))))); + set_opt1_sym(cdr(arg), cadr(arg2)); + return(fx_c_c_opsq_direct); + }} + return(fx_c_c_opsq); + + case HOP_SAFE_C_opSq_C: + if (is_unchanged_global(car(arg))) + { + if ((car(arg) == sc->memq_symbol) && + (fx_matches(caadr(arg), sc->car_symbol)) && + (is_proper_quote(sc, caddr(arg))) && + (is_pair(cadaddr(arg)))) + return((s7_list_length(sc, opt2_con(cdr(arg))) == 2) ? fx_memq_car_s_2 : fx_memq_car_s); + + if (car(arg) == sc->is_eq_symbol) + { + if (((fx_matches(caadr(arg), sc->car_symbol)) || (fx_matches(caadr(arg), sc->caar_symbol))) && + (is_proper_quote(sc, caddr(arg)))) + { + set_opt3_sym(cdr(arg), cadadr(arg)); + set_opt2_con(cdr(arg), cadaddr(arg)); + return((caadr(arg) == sc->car_symbol) ? fx_is_eq_car_sq : fx_is_eq_caar_sq); + }} + if (((car(arg) == sc->lt_symbol) || (car(arg) == sc->num_eq_symbol)) && + (is_t_integer(caddr(arg))) && + (fx_matches(caadr(arg), sc->length_symbol))) + { + set_opt3_sym(cdr(arg), cadadr(arg)); + set_opt2_con(cdr(arg), caddr(arg)); + return((car(arg) == sc->lt_symbol) ? fx_less_length_i : fx_num_eq_length_i); + }} + set_opt1_sym(cdr(arg), cadadr(arg)); + return(fx_c_opsq_c); + + case HOP_SAFE_C_op_opSqq: + return((fx_matches(car(arg), sc->not_symbol)) ? ((fn_proc(cadr(arg)) == g_is_pair) ? fx_not_is_pair_opsq : fx_not_op_opsqq) : fx_c_op_opsqq); + + case HOP_SAFE_C_opSCq: + if (fx_matches(car(arg), sc->not_symbol)) + { + if (fn_proc(cadr(arg)) == g_is_eq) + { + set_opt2_sym(cdr(arg), cadadr(arg)); + set_opt3_con(cdr(arg), (is_pair(caddadr(arg))) ? cadaddr(cadr(arg)) : caddadr(arg)); + return(fx_not_is_eq_sq); + } + return(fx_not_opscq); + } + return(fx_c_opscq); + + case HOP_SAFE_C_S_opSCq: + if (is_global_and_has_func(car(arg), s7_p_pp_function)) + { + s7_pointer arg2 = caddr(arg); + if ((is_global_and_has_func(car(arg2), s7_p_pi_function)) && + (is_t_integer(caddr(arg2)))) + { + set_opt2_direct(cdr(arg), (s7_pointer)(s7_p_pp_function(global_value(car(arg))))); + set_opt3_direct(cdr(arg), (s7_pointer)(s7_p_pi_function(global_value(car(arg2))))); + set_opt3_sym(arg, cadr(arg2)); + set_opt1_con(cdr(arg), caddr(arg2)); + if (car(arg) == sc->num_eq_symbol) + { + if (car(arg2) == sc->add_symbol) return(fx_num_eq_add_s_si); + if (car(arg2) == sc->subtract_symbol) return(fx_num_eq_subtract_s_si); + } + if ((car(arg) == sc->vector_ref_symbol) && (car(arg2) == sc->add_symbol) && (integer(caddr(arg2)) == 1)) + return(fx_vref_p1); + return(fx_c_s_opsiq_direct); + } + if (is_global_and_has_func(car(arg2), s7_p_pp_function)) + { + set_opt2_direct(cdr(arg), (s7_pointer)(s7_p_pp_function(global_value(car(arg))))); + set_opt3_direct(cdr(arg), (s7_pointer)(s7_p_pp_function(global_value(car(arg2))))); + set_opt3_sym(arg, cadr(arg2)); + set_opt1_con(cdr(arg), (is_pair(caddr(arg2))) ? cadaddr(arg2) : caddr(arg2)); + return(fx_c_s_opscq_direct); + }} + return(fx_c_s_opscq); + + case HOP_SAFE_C_opSSq: + if (fx_matches(car(arg), sc->not_symbol)) + { + if (fn_proc(cadr(arg)) == g_is_eq) return(fx_not_is_eq_ss); + return(fx_not_opssq); + } + if ((is_global_and_has_func(car(arg), s7_p_p_function)) && + (is_global_and_has_func(caadr(arg), s7_p_pp_function))) + { + set_opt2_direct(cdr(arg), (s7_pointer)(s7_p_p_function(global_value(car(arg))))); + set_opt3_direct(cdr(arg), (s7_pointer)(s7_p_pp_function(global_value(caadr(arg))))); + return(fx_c_opssq_direct); + } + return(fx_c_opssq); + + case HOP_SAFE_C_C_opSSq: + { + s7_pointer s2 = caddr(arg); + if ((fx_matches(car(s2), sc->multiply_symbol)) && (cadr(s2) == caddr(s2))) + return(fx_c_c_sqr); + } + if ((is_small_real(cadr(arg))) && + (is_global_and_has_func(car(arg), s7_p_dd_function)) && + (is_global_and_has_func(caaddr(arg), s7_d_pd_function))) /* not * currently (this is for clm) */ + { + set_opt3_direct(cdr(arg), s7_d_pd_function(global_value(caaddr(arg)))); + set_opt2_direct(cdr(arg), s7_p_dd_function(global_value(car(arg)))); + set_opt3_sym(arg, cadaddr(arg)); + set_opt1_sym(cdr(arg), caddaddr(arg)); + return(fx_c_nc_opssq_direct); + } + if ((is_global_and_has_func(car(arg), s7_p_pp_function)) && + (is_global_and_has_func(caaddr(arg), s7_p_pp_function))) + { + set_opt2_direct(cdr(arg), (s7_pointer)(s7_p_pp_function(global_value(car(arg))))); + set_opt3_direct(cdr(arg), (s7_pointer)(s7_p_pp_function(global_value(caaddr(arg))))); + set_opt3_sym(arg, cadaddr(arg)); + set_opt1_sym(cdr(arg), caddaddr(arg)); + if ((is_t_real(cadr(arg))) && (car(arg) == caaddr(arg)) && (car(arg) == sc->multiply_symbol)) return(fx_multiply_c_opssq); + return(fx_c_c_opssq_direct); + } + return(fx_c_c_opssq); + + case HOP_SAFE_C_opSq_opSq: + if ((is_global_and_has_func(car(arg), s7_p_pp_function)) && + (is_global_and_has_func(caadr(arg), s7_p_p_function)) && + (is_global_and_has_func(caaddr(arg), s7_p_p_function))) + { + set_opt3_direct(arg, (s7_pointer)(s7_p_pp_function(global_value(car(arg))))); + set_opt2_direct(cdr(arg), (s7_pointer)(s7_p_p_function(global_value(caadr(arg))))); + set_opt3_direct(cdr(arg), (s7_pointer)(s7_p_p_function(global_value(caaddr(arg))))); + if ((caadr(arg) == caaddr(arg)) && ((caadr(arg) == sc->cdr_symbol) || (caadr(arg) == sc->car_symbol))) + { + set_opt1_sym(cdr(arg), cadadr(arg)); + set_opt2_sym(cdr(arg), cadaddr(arg)); /* usable because we know func is cdr|car */ + return((caadr(arg) == sc->cdr_symbol) ? fx_cdr_s_cdr_s : fx_car_s_car_s); + } + set_opt1_sym(cdr(arg), cadaddr(arg)); /* opt2 is taken by second func */ + return(fx_c_opsq_opsq_direct); + } + return(fx_c_opsq_opsq); + + case HOP_SAFE_C_op_S_opSqq: + return((fx_matches(car(arg), sc->not_symbol)) ? fx_not_op_s_opsqq : fx_c_op_s_opsqq); + + case HOP_SAFE_C_op_opSSqq_S: + if ((is_global_and_has_func(car(arg), s7_p_pp_function)) && + (is_global_and_has_func(caadr(arg), s7_p_p_function)) && + (is_global_and_has_func(car(cadadr(arg)), s7_p_pp_function))) + { + set_opt3_direct(arg, (s7_pointer)(s7_p_pp_function(global_value(car(arg))))); + set_opt2_direct(cdr(arg), (s7_pointer)(s7_p_p_function(global_value(caadr(arg))))); + set_opt3_direct(cdr(arg), (s7_pointer)(s7_p_pp_function(global_value(caadr(cadr(arg)))))); + return(fx_c_op_opssqq_s_direct); + } + return(fx_c_op_opssqq_s); + + case HOP_SAFE_C_A: + if (fx_matches(car(arg), sc->not_symbol)) + { + if (fx_proc(cdr(arg)) == fx_is_eq_car_sq) + { + set_opt1_sym(cdr(arg), cadadr(cadr(arg))); + set_opt3_con(cdr(arg), cadaddr(cadr(arg))); + return(fx_not_is_eq_car_sq); + } + return(fx_not_a); + } + if (is_global_and_has_func(car(arg), s7_p_p_function)) + { + set_opt3_direct(arg, (s7_pointer)(s7_p_p_function(global_value(car(arg))))); + return(fx_c_a_direct); + } + return(fx_c_a); + + case HOP_SAFE_C_AC: + if (fn_proc(arg) == g_cons) return(fx_cons_ac); + if (fx_matches(car(arg), sc->is_eq_symbol)) return(fx_is_eq_ac); + if (is_global_and_has_func(car(arg), s7_p_pp_function)) + { + set_opt3_direct(cdr(arg), (s7_pointer)(s7_p_pp_function(global_value(car(arg))))); + if ((opt3_direct(cdr(arg)) == (s7_pointer)string_ref_p_pp) && (is_t_integer(caddr(arg))) && (integer(caddr(arg)) == 0)) + set_opt3_direct(cdr(arg), string_ref_p_p0); + if (opt3_direct(cdr(arg)) == (s7_pointer)memq_p_pp) + { + if (fn_proc(arg) == g_memq_2) + set_opt3_direct(cdr(arg), (s7_pointer)memq_2_p_pp); + else + if ((is_pair(caddr(arg))) && (is_proper_list_3(sc, cadaddr(arg)))) + set_opt3_direct(cdr(arg), memq_3_p_pp); + else + if (fn_proc(arg) == g_memq_4) + set_opt3_direct(cdr(arg), memq_4_p_pp); /* this does not parallel 2 and 3 above (sigh) */ + } + return(fx_c_ac_direct); + } + return(fx_c_ac); + + case HOP_SAFE_C_CA: + return((fn_proc(arg) == g_cons) ? fx_cons_ca : fx_c_ca); + + case HOP_SAFE_C_SA: + if (fn_proc(arg) == g_multiply_2) return(fx_multiply_sa); + if (fn_proc(arg) == g_add_2) return(fx_add_sa); + if (is_global_and_has_func(car(arg), s7_p_pp_function)) + { + set_opt3_direct(cdr(arg), (s7_pointer)(s7_p_pp_function(global_value(car(arg))))); + return((fn_proc(arg) == g_cons) ? fx_cons_sa : fx_c_sa_direct); + } + return(fx_c_sa); + + case HOP_SAFE_C_AS: + if (fn_proc(arg) == g_add_2) return(fx_add_as); + if (is_global_and_has_func(car(arg), s7_p_pp_function)) + { + set_opt3_direct(cdr(arg), (s7_pointer)(s7_p_pp_function(global_value(car(arg))))); + return((fn_proc(arg) == g_cons) ? fx_cons_as : fx_c_as_direct); + } + return(fx_c_as); + + case HOP_SAFE_C_AA: /* (* wr (float-vector-ref rl 0 j)) (* wr (block-ref (vector-ref rl j) 0)) (- (float-vector-ref rl 0 i) tempr) */ + if (fn_proc(arg) == g_add_2) return(fx_add_aa); + if (fn_proc(arg) == g_subtract_2) return(fx_subtract_aa); + if (fn_proc(arg) == g_multiply_2) return(fx_multiply_aa); + if (fn_proc(arg) == g_number_to_string) return(fx_number_to_string_aa); + if (fn_proc(arg) == g_cons) return(fx_cons_aa); + /* we can get here from gx_annotate which does not call fx_tree, where A=fx_unsafe_s */ + if (fx_proc(cdr(arg)) == fx_unsafe_s) return(fx_c_za); + return(fx_c_aa); + + case HOP_SAFE_C_opAAq: + return((fx_proc(cdadr(arg)) == fx_s) ? fx_c_opsaq : fx_c_opaaq); + + case HOP_SAFE_C_NA: + return((fn_proc(arg) == g_vector) ? fx_vector_na : fx_c_na); + + case HOP_SAFE_C_ALL_CA: + return((fn_proc(arg) == g_simple_inlet) ? fx_inlet_ca : fx_c_all_ca); + + case HOP_SAFE_CLOSURE_S_A: + { + s7_pointer body = car(closure_body(opt1_lambda(arg))); + if (is_pair(body)) + { + if (optimize_op(body) == OP_AND_2A) + { + if ((fx_matches(caadr(body), sc->is_pair_symbol)) && + (cadadr(body) == car(closure_args(opt1_lambda(arg))))) + return(fx_safe_closure_s_and_pair); /* lint arg: (len>1? init), args: (x) body: (and (pair? x) (pair? (cdr x))) */ + return(fx_safe_closure_s_and_2a); + } + if (optimize_op(body) == HOP_SAFE_C_opSq_C) + { + if ((fn_proc(body) == g_lint_let_ref) && + (cadadr(body) == car(closure_args(opt1_lambda(arg))))) + { + set_opt2_sym(cdr(arg), cadaddr(body)); + return(fx_lint_let_ref_s); /* (var-ref local-var) -> (let-ref (cdr v=local_var) 'ref) */ + }}} + return((fx_proc(closure_body(opt1_lambda(arg))) == fx_sqr_t) ? fx_safe_closure_s_sqr : fx_safe_closure_s_a); + } + + case HOP_SAFE_CLOSURE_S_TO_SC: + { + s7_pointer body = car(closure_body(opt1_lambda(arg))); + if (fn_proc(body) == g_vector_ref_2) return(fx_safe_closure_s_to_vref); + if ((is_t_integer(caddr(body))) && (integer(caddr(body)) == 1)) + { + if (car(body) == sc->subtract_symbol) return(fx_safe_closure_s_to_sub1); + if (car(body) == sc->add_symbol) return(fx_safe_closure_s_to_add1); + } + return(fx_safe_closure_s_to_sc); + } + + case HOP_SAFE_CLOSURE_A_TO_SC: + return((fn_proc(car(closure_body(opt1_lambda(arg)))) == g_vector_ref_2) ? fx_safe_closure_a_to_vref : fx_safe_closure_a_to_sc); + + case HOP_SAFE_CLOSURE_A_A: + if (fx_proc(closure_body(opt1_lambda(arg))) == fx_and_2a) + return(fx_safe_closure_a_and_2a); + return((fx_proc(closure_body(opt1_lambda(arg))) == fx_sqr_t) ? fx_safe_closure_a_sqr : fx_safe_closure_a_a); + + case HOP_SAFE_CLOSURE_3S_A: + if (fx_proc(closure_body(opt1_lambda(arg))) == fx_vref_vref_tu_v) return(fx_vref_vref_3_no_let); + return(fx_function[optimize_op(arg)]); + + case OP_IMPLICIT_S7_STARLET_REF_S: + if (opt3_int(arg) == SL_PRINT_LENGTH) return(fx_implicit_s7_starlet_print_length); + if (opt3_int(arg) == SL_SAFETY) return(fx_implicit_s7_starlet_safety); + return(fx_implicit_s7_starlet_ref_s); + + case HOP_C: + if ((is_unchanged_global(car(arg))) && (car(arg) == sc->curlet_symbol)) return(fx_curlet); + /* fall through */ + + default: + /* if ((!fx_function[optimize_op(arg)]) && (is_h_optimized(arg))) fprintf(stderr, "fx_choose %s %s\n", op_names[optimize_op(arg)], display(arg)); */ + return(fx_function[optimize_op(arg)]); + }} /* is_optimized */ + + if ((car(arg) == sc->quote_symbol) && + (is_global(sc->quote_symbol))) + { + check_quote(sc, arg); + return(fx_q); + } + return(NULL); +} + +#if S7_DEBUGGING +#define with_fx(P, F) with_fx_1(sc, P, F) +static bool with_fx_1(s7_scheme *sc, s7_pointer p, s7_function f) /* sc needed for set_opt2 under debugger = set_opt2_1(sc,...) */ +#else +static bool with_fx(s7_pointer p, s7_function f) +#endif +{ + set_fx_direct(p, f); + return(true); +} + +static bool o_var_ok(const s7_pointer p, const s7_pointer var1, const s7_pointer var2, const s7_pointer var3) {return((p != var1) && (p != var2) && (p != var3));} + +static bool fx_tree_out(s7_scheme *sc, s7_pointer tree, const s7_pointer var1, const s7_pointer var2, const s7_pointer var3, bool unused_more_vars) +{ + s7_pointer p = car(tree); +#if 0 + if ((s7_tree_memq(sc, var1, p)) || ((var2) && (s7_tree_memq(sc, var2, p))) || ((var3) && (s7_tree_memq(sc, var3, p)))) + fprintf(stderr, "%s[%d] %s %s %s %d %d: %s\n", __func__, __LINE__, display(var1), (var2) ? display(var2) : "", (var3) ? display(var3) : "", + has_fx(tree), unused_more_vars, display(p)); +#endif + if (is_symbol(p)) + { + if ((fx_proc(tree) == fx_s) || (fx_proc(tree) == fx_o)) + { + if (p == var1) return(with_fx(tree, fx_T)); + if (p == var2) return(with_fx(tree, fx_U)); + if (p == var3) return(with_fx(tree, fx_V)); + } + return(false); + } + if ((is_pair(p)) && (is_pair(cdr(p)))) + { + if (cadr(p) == var1) + { + if ((fx_proc(tree) == fx_c_s) || (fx_proc(tree) == fx_c_o)) return(with_fx(tree, fx_c_T)); /* fx_c_T_direct got no hits */ + if ((fx_proc(tree) == fx_car_s) || (fx_proc(tree) == fx_car_o)) return(with_fx(tree, fx_car_T)); + if ((fx_proc(tree) == fx_cdr_s) || (fx_proc(tree) == fx_cdr_o)) return(with_fx(tree, fx_cdr_T)); + if (fx_proc(tree) == fx_is_null_s) return(with_fx(tree, fx_is_null_T)); + if (fx_proc(tree) == fx_iterate_o) return(with_fx(tree, fx_iterate_T)); + if (fx_proc(tree) == fx_subtract_s1) return(with_fx(tree, fx_subtract_T1)); + if (fx_proc(tree) == fx_add_s1) return(with_fx(tree, fx_add_T1)); + if (fx_proc(tree) == fx_c_sca) return(with_fx(tree, fx_c_Tca)); + if ((fx_proc(tree) == fx_num_eq_si) || (fx_proc(tree) == fx_num_eq_s0) || (fx_proc(tree) == fx_num_eq_oi)) return(with_fx(tree, fx_num_eq_Ti)); + /* if (fx_proc(tree) == fx_cons_ss) return(with_fx(tree, fx_cons_Ts)); */ /* can be fooled -- there is no fx_cons_us etc -- need fx_cons_os */ + /* if (fx_proc(tree) == fx_multiply_ss) return(with_fx(tree, fx_multiply_Ts)); */ /* this also can be fooled? */ + if ((fx_proc(tree) == fx_c_scs_direct) && (cadddr(p) == var2)) return(with_fx(tree, fx_c_TcU_direct)); + if ((fx_proc(tree) == fx_hash_table_ref_ss) && (var3 == caddr(p))) return(with_fx(tree, fx_hash_table_ref_TV)); + if ((fx_proc(tree) == fx_geq_ss) && (var2 == caddr(p))) return(with_fx(tree, fx_geq_TU)); + } + else + if (cadr(p) == var2) + { + if (fx_proc(tree) == fx_subtract_s1) return(with_fx(tree, fx_subtract_U1)); + if (fx_proc(tree) == fx_add_s1) return(with_fx(tree, fx_add_U1)); + if ((fx_proc(tree) == fx_car_s) || (fx_proc(tree) == fx_car_o)) return(with_fx(tree, fx_car_U)); + if ((fx_proc(tree) == fx_cdr_s) || (fx_proc(tree) == fx_cdr_o)) return(with_fx(tree, fx_cdr_U)); + } + else + if (cadr(p) == var3) + { + if ((fx_proc(tree) == fx_c_s) || (fx_proc(tree) == fx_c_o)) return(with_fx(tree, fx_c_V)); + if (fx_proc(tree) == fx_add_s1) return(with_fx(tree, fx_add_V1)); + } + else + if (is_pair(cddr(p))) + { + if (caddr(p) == var1) + { + if ((fx_proc(tree) == fx_num_eq_ts) || (fx_proc(tree) == fx_num_eq_to)) return(with_fx(tree, fx_num_eq_tT)); + if ((fx_proc(tree) == fx_gt_ts) || (fx_proc(tree) == fx_gt_to)) return(with_fx(tree, fx_gt_tT)); + if (fx_proc(tree) == fx_lt_ts) return(with_fx(tree, fx_lt_tT)); + if ((fx_proc(tree) == fx_geq_ts) || (fx_proc(tree) == fx_geq_to)) return(with_fx(tree, fx_geq_tT)); + } + else + if (caddr(p) == var2) + { + if (fx_proc(tree) == fx_c_ts) return(with_fx(tree, fx_c_tU)); + if (fx_proc(tree) == fx_cons_ts) return(with_fx(tree, fx_cons_tU)); + if (fx_proc(tree) == fx_c_ts_direct) return(with_fx(tree, fx_c_tU_direct)); + if (fx_proc(tree) == fx_lt_ts) return(with_fx(tree, fx_lt_tU)); + if (fx_proc(tree) == fx_num_eq_us) return(with_fx(tree, fx_num_eq_uU)); + if (fx_proc(tree) == fx_num_eq_vs) return(with_fx(tree, fx_num_eq_vU)); + } + else + if ((fx_proc(tree) == fx_add_sqr_s) && (cadadr(p) == var1)) return(with_fx(tree, fx_add_sqr_T)); + }} +#if 0 + if ((s7_tree_memq(sc, var1, p)) || ((var2) && (s7_tree_memq(sc, var2, p))) || ((var3) && (s7_tree_memq(sc, var3, p)))) + fprintf(stderr, "%s[%d] %s %s %s %d %d: %s %s\n", __func__, __LINE__, display(var1), (var2) ? display(var2) : "", (var3) ? display(var3) : "", + has_fx(tree), unused_more_vars, display(p), op_names[optimize_op(p)]); +#endif + return(false); +} + +static void fx_tree_outer(s7_scheme *sc, s7_pointer tree, s7_pointer var1, s7_pointer var2, s7_pointer var3, bool more_vars) +{ + if ((!is_pair(tree)) || + ((is_symbol(car(tree))) && (is_definer_or_binder(car(tree)))) || + (is_syntax(car(tree)))) + return; + if ((!has_fx(tree)) || + (!fx_tree_out(sc, tree, var1, var2, var3, more_vars))) + fx_tree_outer(sc, car(tree), var1, var2, var3, more_vars); + fx_tree_outer(sc, cdr(tree), var1, var2, var3, more_vars); +} + +static bool fx_tree_in(s7_scheme *sc, s7_pointer tree, s7_pointer var1, s7_pointer var2, s7_pointer var3, bool more_vars) +{ + s7_pointer p = car(tree); +#if 0 + /* if ((s7_tree_memq(sc, var1, p)) || ((var2) && (s7_tree_memq(sc, var2, p))) || ((var3) && (s7_tree_memq(sc, var3, p)))) */ + fprintf(stderr, "fx_tree_in %s %s %s %s: %s, treed: %d\n", op_names[optimize_op(p)], + display(var1), (var2) ? display(var2) : "", (var3) ? display(var3) : "", display_80(p), is_fx_treed(tree)); +#endif + if (is_symbol(p)) + { + if (fx_proc(tree) == fx_s) + { + if (p == var1) return(with_fx(tree, fx_t)); + if (p == var2) return(with_fx(tree, fx_u)); + if (p == var3) return(with_fx(tree, fx_v)); + if (is_global(p)) return(with_fx(tree, fx_g)); + if (!more_vars) return(with_fx(tree, fx_o)); + } + return(false); + } + if ((!is_pair(p)) || (is_fx_treed(tree)) || (!has_fx(tree))) return(false); + set_fx_treed(tree); + switch (optimize_op(p)) + { + case HOP_SAFE_C_S: + if (cadr(p) == var1) + { + if (fx_proc(tree) == fx_c_s) return(with_fx(tree, fx_c_t)); + if (fx_proc(tree) == fx_c_s_direct) return(with_fx(tree, (opt2_direct(cdr(p)) == (s7_pointer)cddr_p_p) ? fx_cddr_t : fx_c_t_direct)); + if (fx_proc(tree) == fx_car_s) return(with_fx(tree, fx_car_t)); + if (fx_proc(tree) == fx_cdr_s) return(with_fx(tree, fx_cdr_t)); + if (fx_proc(tree) == fx_cddr_s) return(with_fx(tree, fx_cddr_t)); + if (fx_proc(tree) == fx_cadr_s) return(with_fx(tree, fx_cadr_t)); + if (fx_proc(tree) == fx_not_s) return(with_fx(tree, fx_not_t)); + if (fx_proc(tree) == fx_is_null_s) return(with_fx(tree, fx_is_null_t)); + if (fx_proc(tree) == fx_is_pair_s) return(with_fx(tree, fx_is_pair_t)); + if (fx_proc(tree) == fx_is_symbol_s) return(with_fx(tree, fx_is_symbol_t)); + if (fx_proc(tree) == fx_is_eof_s) return(with_fx(tree, fx_is_eof_t)); + if (fx_proc(tree) == fx_is_string_s) return(with_fx(tree, fx_is_string_t)); + if (fx_proc(tree) == fx_is_vector_s) return(with_fx(tree, fx_is_vector_t)); + if (fx_proc(tree) == fx_is_integer_s) return(with_fx(tree, fx_is_integer_t)); + if (fx_proc(tree) == fx_is_procedure_s) return(with_fx(tree, fx_is_procedure_t)); + if (fx_proc(tree) == fx_is_type_s) return(with_fx(tree, fx_is_type_t)); + if (fx_proc(tree) == fx_length_s) return(with_fx(tree, fx_length_t)); + if (fx_proc(tree) == fx_real_part_s) return(with_fx(tree, fx_real_part_t)); + if (fx_proc(tree) == fx_imag_part_s) return(with_fx(tree, fx_imag_part_t)); + return(false); + } + if (cadr(p) == var2) + { + if (fx_proc(tree) == fx_c_s) + { + if (is_global_and_has_func(car(p), s7_p_p_function)) + { + set_opt2_direct(cdr(p), (s7_pointer)(s7_p_p_function(global_value(car(p))))); + return(with_fx(tree, (car(p) == sc->cddr_symbol) ? fx_cddr_u : + ((car(p) == sc->is_positive_symbol) ? fx_is_positive_u : + ((car(p) == sc->is_zero_symbol) ? fx_is_zero_u : fx_c_u_direct)))); + } + return(with_fx(tree, fx_c_u)); + } + if (fx_proc(tree) == fx_c_s_direct) + return(with_fx(tree, (car(p) == sc->cddr_symbol) ? fx_cddr_u : + ((car(p) == sc->is_positive_symbol) ? fx_is_positive_u : + ((car(p) == sc->is_zero_symbol) ? fx_is_zero_u : fx_c_u_direct)))); + + if (fx_proc(tree) == fx_cdr_s) return(with_fx(tree, fx_cdr_u)); + if (fx_proc(tree) == fx_cadr_s) return(with_fx(tree, fx_cadr_u)); + if (fx_proc(tree) == fx_cddr_s) return(with_fx(tree, fx_cddr_u)); + if (fx_proc(tree) == fx_car_s) return(with_fx(tree, fx_car_u)); + if (fx_proc(tree) == fx_is_null_s) return(with_fx(tree, fx_is_null_u)); + if (fx_proc(tree) == fx_is_type_s) return(with_fx(tree, fx_is_type_u)); + if (fx_proc(tree) == fx_is_pair_s) return(with_fx(tree, fx_is_pair_u)); + if (fx_proc(tree) == fx_is_symbol_s) return(with_fx(tree, fx_is_symbol_u)); + if (fx_proc(tree) == fx_is_eof_s) return(with_fx(tree, fx_is_eof_u)); + return(false); + } + if (cadr(p) == var3) + { + if (fx_proc(tree) == fx_cdr_s) return(with_fx(tree, fx_cdr_v)); + if (fx_proc(tree) == fx_is_null_s) return(with_fx(tree, fx_is_null_v)); + if (fx_proc(tree) == fx_is_pair_s) return(with_fx(tree, fx_is_pair_v)); + if (fx_proc(tree) == fx_c_s) return(with_fx(tree, fx_c_v)); + if (fx_proc(tree) == fx_c_s_direct) return(with_fx(tree, fx_c_v_direct)); + return(false); + } + if (!more_vars) + { + if (fx_proc(tree) == fx_is_null_s) return(with_fx(tree, fx_is_null_o)); + if (fx_proc(tree) == fx_car_s) return(with_fx(tree, fx_car_o)); + if (fx_proc(tree) == fx_cdr_s) return(with_fx(tree, fx_cdr_o)); + if (fx_proc(tree) == fx_cadr_s) return(with_fx(tree, fx_cadr_o)); + if (fx_proc(tree) == fx_cddr_s) return(with_fx(tree, fx_cddr_o)); + if (fx_proc(tree) == fx_iterate_s) return(with_fx(tree, fx_iterate_o)); + if (fx_proc(tree) == fx_not_s) return(with_fx(tree, fx_not_o)); + if (fx_proc(tree) == fx_c_s_direct) return(with_fx(tree, fx_c_o_direct)); + if (fx_proc(tree) == fx_c_s) return(with_fx(tree, fx_c_o)); + } + break; + + case HOP_SAFE_C_SC: + if (cadr(p) == var1) + { + if ((fx_proc(tree) == fx_char_eq_sc) || (fn_proc(p) == g_char_equal_2)) return(with_fx(tree, fx_char_eq_tc)); + if (fx_proc(tree) == fx_c_sc) return(with_fx(tree, fx_c_tc)); + if (fx_proc(tree) == fx_add_sf) return(with_fx(tree, fx_add_tf)); + if (fn_proc(p) == g_less_xf) return(with_fx(tree, fx_lt_tf)); + if (fn_proc(p) == g_less_x0) return(with_fx(tree, fx_lt_t0)); + if (fn_proc(p) == g_less_xi) + return(with_fx(tree, (integer(caddr(p)) == 2) ? fx_lt_t2 : ((integer(caddr(p)) == 1) ? fx_lt_t1 : fx_lt_ti))); + if (fn_proc(p) == g_geq_xf) return(with_fx(tree, fx_geq_tf)); + if (fn_proc(p) == g_geq_xi) return(with_fx(tree, (integer(caddr(p)) == 0) ? fx_geq_t0 : fx_geq_ti)); + if (fn_proc(p) == g_leq_xi) return(with_fx(tree, fx_leq_ti)); + if (fn_proc(p) == g_greater_xi) return(with_fx(tree, fx_gt_ti)); + if (fx_proc(tree) == fx_leq_si) return(with_fx(tree, fx_leq_ti)); + if (fx_proc(tree) == fx_gt_si) return(with_fx(tree, fx_gt_ti)); + + if (fx_proc(tree) == fx_c_sc_direct) /* p_pp cases */ + { + if ((opt3_direct(cdr(p)) == (s7_pointer)vector_ref_p_pp) && (is_t_integer(caddr(p)))) + return(with_fx(tree, fx_vector_ref_tc)); + if ((opt3_direct(cdr(p)) == (s7_pointer)string_ref_p_pp) && (is_t_integer(caddr(p))) && (integer(caddr(p)) == 0)) + set_opt3_direct(cdr(p), string_ref_p_p0); + return(with_fx(tree, fx_c_tc_direct)); + } + if (fx_proc(tree) == fx_c_si_direct) /* p_pi cases */ + { + if (opt3_direct(cdr(p)) == (s7_pointer)vector_ref_p_pi) + return(with_fx(tree, fx_vector_ref_tc)); + if ((opt3_direct(cdr(p)) == (s7_pointer)string_ref_p_pi) && (integer(caddr(p)) == 0)) + set_opt3_direct(cdr(p), string_ref_p_p0); + return(with_fx(tree, (opt3_direct(cdr(p)) == (s7_pointer)remainder_p_pi) ? fx_c_ti_remainder : fx_c_ti_direct)); + } + + if (fx_proc(tree) == fx_is_eq_sc) return(with_fx(tree, fx_is_eq_tc)); + if (fx_proc(tree) == fx_add_s1) return(with_fx(tree, fx_add_t1)); + if (fx_proc(tree) == fx_add_si) return(with_fx(tree, fx_add_ti)); + if (fx_proc(tree) == fx_subtract_s1) return(with_fx(tree, fx_subtract_t1)); + if (fx_proc(tree) == fx_subtract_si) return(with_fx(tree, fx_subtract_ti)); + if (fx_proc(tree) == fx_subtract_sf) return(with_fx(tree, fx_subtract_tf)); + if (fx_proc(tree) == fx_multiply_sf) return(with_fx(tree, fx_multiply_tf)); + if (fx_proc(tree) == fx_multiply_si) return(with_fx(tree, fx_multiply_ti)); + if (fx_proc(tree) == fx_lt_si) /* is this ever hit? */ + return(with_fx(tree, (integer(caddr(p)) == 2) ? fx_lt_t2 : ((integer(caddr(p)) == 1) ? fx_lt_t1 : fx_lt_ti))); + if (fx_proc(tree) == fx_num_eq_si) return(with_fx(tree, fx_num_eq_ti)); + if (fx_proc(tree) == fx_num_eq_s0) return(with_fx(tree, fx_num_eq_t0)); + if (fx_proc(tree) == fx_memq_sc) return(with_fx(tree, fx_memq_tc)); + return(false); + } + if (cadr(p) == var2) + { + if (fx_proc(tree) == fx_c_sc) return(with_fx(tree, fx_c_uc)); + if (fx_proc(tree) == fx_num_eq_s0) return(with_fx(tree, fx_num_eq_u0)); + if (fx_proc(tree) == fx_num_eq_si) return(with_fx(tree, fx_num_eq_ui)); + if (fx_proc(tree) == fx_add_s1) return(with_fx(tree, fx_add_u1)); + if (fx_proc(tree) == fx_subtract_s1) return(with_fx(tree, fx_subtract_u1)); + if (fx_proc(tree) == fx_subtract_si) return(with_fx(tree, fx_subtract_ui)); + if (fx_proc(tree) == fx_multiply_si) return(with_fx(tree, fx_multiply_ui)); + if (fx_proc(tree) == fx_is_eq_sc) return(with_fx(tree, fx_is_eq_uc)); + if (fx_proc(tree) == fx_leq_si) return(with_fx(tree, fx_leq_ui)); + if (fx_proc(tree) == fx_gt_si) return(with_fx(tree, fx_gt_ui)); + return(false); + } + if (cadr(p) == var3) + { + if (fx_proc(tree) == fx_num_eq_s0) return(with_fx(tree, fx_num_eq_v0)); + if (fx_proc(tree) == fx_num_eq_si) return(with_fx(tree, fx_num_eq_vi)); + if (fx_proc(tree) == fx_add_s1) return(with_fx(tree, fx_add_v1)); + if (fx_proc(tree) == fx_subtract_s1) return(with_fx(tree, fx_subtract_v1)); + if (fx_proc(tree) == fx_leq_si) return(with_fx(tree, fx_leq_vi)); + if (fx_proc(tree) == fx_c_sc) return(with_fx(tree, fx_c_vc)); + return(false); + } + if (!more_vars) + { + if (fx_proc(tree) == fx_num_eq_si) return(with_fx(tree, fx_num_eq_oi)); + if ((fx_proc(tree) == fx_c_sc) && (o_var_ok(cadr(p), var1, var2, var3))) return(with_fx(tree, fx_c_oc)); + } + break; + + case HOP_SAFE_C_CS: + if (caddr(p) == var1) + { + if ((car(p) == sc->cons_symbol) && (is_unchanged_global(sc->cons_symbol))) return(with_fx(tree, fx_cons_ct)); + if (fx_proc(tree) == fx_multiply_is) return(with_fx(tree, fx_multiply_it)); + if (fx_proc(tree) == fx_add_fs) return(with_fx(tree, fx_add_ft)); + if (fx_proc(tree) == fx_c_cs) + { + if (is_global_and_has_func(car(p), s7_p_pp_function)) + { + if (fn_proc(p) == g_tree_set_memq_1) + set_opt3_direct(cdr(p), (s7_pointer)tree_set_memq_direct); + else set_opt3_direct(cdr(p), (s7_pointer)(s7_p_pp_function(global_value(car(p))))); + set_fx_direct(tree, fx_c_ct_direct); + } + else set_fx_direct(tree, fx_c_ct); + return(true); + }} + if ((caddr(p) == var2) && (fx_proc(tree) == fx_c_cs)) return(with_fx(tree, fx_c_cu)); + break; + + case HOP_SAFE_C_SS: + if (cadr(p) == var1) + { + if (fx_proc(tree) == fx_c_ss) return(with_fx(tree, (caddr(p) == var2) ? fx_c_tu : fx_c_ts)); + if (fx_proc(tree) == fx_c_ss_direct) return(with_fx(tree, (caddr(p) == var2) ? fx_c_tu_direct : fx_c_ts_direct)); + if (fx_proc(tree) == fx_add_ss) return(with_fx(tree, (caddr(p) == var2) ? fx_add_tu : fx_add_ts)); + if (fx_proc(tree) == fx_subtract_ss) return(with_fx(tree, (caddr(p) == var2) ? fx_subtract_tu : fx_subtract_ts)); + if (fx_proc(tree) == fx_cons_ss) return(with_fx(tree, (caddr(p) == var2) ? fx_cons_tu : fx_cons_ts)); + if (caddr(p) == var2) + { + if (fx_proc(tree) == fx_gt_ss) return(with_fx(tree, fx_gt_tu)); + if (fx_proc(tree) == fx_lt_ss) return(with_fx(tree, fx_lt_tu)); + if (fx_proc(tree) == fx_leq_ss) return(with_fx(tree, fx_leq_tu)); + if (fx_proc(tree) == fx_geq_ss) return(with_fx(tree, fx_geq_tu)); + if (fx_proc(tree) == fx_multiply_ss) return(with_fx(tree, fx_multiply_tu)); + if (fx_proc(tree) == fx_num_eq_ss) return(with_fx(tree, fx_num_eq_tu)); + if (fx_proc(tree) == fx_memq_ss) return(with_fx(tree, fx_memq_tu)); + } + if (fx_proc(tree) == fx_multiply_ss) return(with_fx(tree, fx_multiply_ts)); + if (fx_proc(tree) == fx_num_eq_ss) + { + if (caddr(p) == var3) return(with_fx(tree, fx_num_eq_tv)); + if (is_global(caddr(p))) return(with_fx(tree, fx_num_eq_tg)); + if ((!more_vars) && (o_var_ok(caddr(p), var1, var2, var3))) return(with_fx(tree, fx_num_eq_to)); + return(with_fx(tree, fx_num_eq_ts)); + } + if (fx_proc(tree) == fx_geq_ss) + { + if ((!more_vars) && (o_var_ok(caddr(p), var1, var2, var3))) return(with_fx(tree, fx_geq_to)); + return(with_fx(tree, fx_geq_ts)); + } + if (fx_proc(tree) == fx_leq_ss) return(with_fx(tree, fx_leq_ts)); + if (fx_proc(tree) == fx_lt_ss) return(with_fx(tree, fx_lt_ts)); + if (fx_proc(tree) == fx_lt_sg) return(with_fx(tree, fx_lt_tg)); + if (fx_proc(tree) == fx_gt_ss) + { + if (is_global(caddr(p))) return(with_fx(tree, fx_gt_tg)); + if ((!more_vars) && (o_var_ok(caddr(p), var1, var2, var3))) return(with_fx(tree, fx_gt_to)); + return(with_fx(tree, fx_gt_ts)); + } + if (fx_proc(tree) == fx_sqr_s) return(with_fx(tree, fx_sqr_t)); + if (fx_proc(tree) == fx_is_eq_ss) + { + if (caddr(p) == var2) return(with_fx(tree, fx_is_eq_tu)); + if ((!more_vars) && (caddr(p) != var3) && (caddr(p) != var1)) return(with_fx(tree, fx_is_eq_to)); + return(with_fx(tree, fx_is_eq_ts)); + } + if (fx_proc(tree) == fx_vref_ss) + { + if (caddr(p) == var2) return(with_fx(tree, fx_vref_tu)); + return(with_fx(tree, fx_vref_ts)); + }} + if (caddr(p) == var1) + { + if (fx_proc(tree) == fx_c_ss) return(with_fx(tree, fx_c_st)); + if (fx_proc(tree) == fx_c_ss_direct) {return(with_fx(tree, (is_global(cadr(p))) ? fx_c_gt_direct : fx_c_st_direct));} + if (fx_proc(tree) == fx_hash_table_ref_ss) return(with_fx(tree, fx_hash_table_ref_st)); + if (fx_proc(tree) == fx_cons_ss) return(with_fx(tree, fx_cons_st)); + if (fx_proc(tree) == fx_vref_ss) + { + if (is_global(cadr(p))) return(with_fx(tree, fx_vref_gt)); + if ((!more_vars) && (cadr(p) != var2) && (cadr(p) != var3)) return(with_fx(tree, fx_vref_ot)); + return(with_fx(tree, fx_vref_st)); + } + if ((fx_proc(tree) == fx_gt_ss) && (cadr(p) == var2)) return(with_fx(tree, fx_gt_ut)); + if ((fx_proc(tree) == fx_lt_ss) && (cadr(p) == var2)) return(with_fx(tree, fx_lt_ut)); + if ((fx_proc(tree) == fx_geq_ss)) + { + if ((!more_vars) && (o_var_ok(cadr(p), var1, var2, var3))) return(with_fx(tree, fx_geq_ot)); + return(with_fx(tree, fx_geq_st)); + }} + if (cadr(p) == var2) + { + if (fx_proc(tree) == fx_num_eq_ss) return(with_fx(tree, (caddr(p) == var1) ? fx_num_eq_ut : fx_num_eq_us)); + if (fx_proc(tree) == fx_geq_ss) return(with_fx(tree, fx_geq_us)); + if (fx_proc(tree) == fx_add_ss) return(with_fx(tree, (caddr(p) == var1) ? fx_add_ut : ((caddr(p) == var3) ? fx_add_uv : fx_add_us))); + if (fx_proc(tree) == fx_subtract_ss) return(with_fx(tree, (caddr(p) == var1) ? fx_subtract_ut : fx_subtract_us)); + if (caddr(p) == var3) return(with_fx(tree, fx_c_uv)); + } + if ((caddr(p) == var2) && (fx_proc(tree) == fx_sref_ss)) return(with_fx(tree, fx_sref_su)); + if (cadr(p) == var3) + { + if (fx_proc(tree) == fx_num_eq_ss) return(with_fx(tree, fx_num_eq_vs)); + if ((fx_proc(tree) == fx_add_ss) && (caddr(p) == var2)) return(with_fx(tree, fx_add_vu)); + if (fx_proc(tree) == fx_geq_ss) return(with_fx(tree, ((!more_vars) && (o_var_ok(caddr(p), var1, var2, var3))) ? fx_geq_vo : fx_geq_vs)); + } + break; + + case HOP_SAFE_C_AS: + if (caddr(p) == var1) return(with_fx(tree, fx_c_at)); + break; + + case HOP_SAFE_C_SA: + if (cadr(p) == var1) + { + if ((fx_proc(cddr(p)) == fx_c_opsq_c) && + (cadadr(caddr(p)) == var1) && + (is_t_integer(caddaddr(p))) && + (integer(caddaddr(p)) == 1) && + (car(p) == sc->string_ref_symbol) && + (caaddr(p) == sc->subtract_symbol) && +#if (!WITH_PURE_S7) + ((caadr(caddr(p)) == sc->string_length_symbol) || (caadr(caddr(p)) == sc->length_symbol))) +#else + (caadr(caddr(p)) == sc->length_symbol)) +#endif + return(with_fx(tree, fx_sref_t_last)); + return(with_fx(tree, fx_c_ta)); + } + if (cadr(p) == var2) return(with_fx(tree, (fx_proc(tree) == fx_c_sa_direct) ? fx_c_ua_direct : fx_c_ua)); + break; + + case HOP_SAFE_C_SCS: + if (cadr(p) == var1) + { + if (fx_proc(tree) == fx_c_scs) return(with_fx(tree, fx_c_tcs)); + if (fx_proc(tree) == fx_c_scs_direct) return(with_fx(tree, (cadddr(p) == var2) ? fx_c_tcu_direct : fx_c_tcs_direct)); + } + break; + + case HOP_SAFE_C_SSC: + if ((cadr(p) == var1) && (caddr(p) == var2)) return(with_fx(tree, fx_c_tuc)); + break; + + case HOP_SAFE_C_CSS: + if ((caddr(p) == var1) && (cadddr(p) == var3)) return(with_fx(tree, fx_c_ctv)); + break; + + case HOP_SAFE_C_SSS: + if ((cadr(p) == var1) && ((caddr(p) == var2) && ((fx_proc(tree) == fx_c_sss) || (fx_proc(tree) == fx_c_sss_direct)))) + return(with_fx(tree, (cadddr(p) == var3) ? ((fx_proc(tree) == fx_c_sss_direct) ? fx_c_tuv_direct : fx_c_tuv) : fx_c_tus)); + if (caddr(p) == var1) + { + if (car(p) == sc->vector_set_symbol) + { + if ((!more_vars) && (o_var_ok(cadr(p), var1, var2, var3)) && (o_var_ok(cadddr(p), var1, var2, var3))) return(with_fx(tree, fx_vset_oto)); + return(with_fx(tree, fx_vset_sts)); + } + return(with_fx(tree, fx_c_sts)); + } + break; + + case HOP_SAFE_C_SSA: + if (cadr(p) == var1) return(with_fx(tree, fx_c_tsa)); /* tua is hit but not called much */ + if (caddr(p) == var1) return(with_fx(tree, fx_c_sta)); + break; + + case HOP_SAFE_C_opSq: + if (cadadr(p) == var1) + { + if (fx_proc(tree) == fx_is_pair_car_s) return(with_fx(tree, fx_is_pair_car_t)); + if (fx_proc(tree) == fx_is_pair_cdr_s) return(with_fx(tree, fx_is_pair_cdr_t)); + if (fx_proc(tree) == fx_is_pair_cadr_s) return(with_fx(tree, fx_is_pair_cadr_t)); + if (fx_proc(tree) == fx_is_symbol_cadr_s) return(with_fx(tree, fx_is_symbol_cadr_t)); + if (fx_proc(tree) == fx_is_pair_cddr_s) return(with_fx(tree, fx_is_pair_cddr_t)); + if (fx_proc(tree) == fx_is_null_cdr_s) return(with_fx(tree, fx_is_null_cdr_t)); + if (fx_proc(tree) == fx_is_null_cadr_s) return(with_fx(tree, fx_is_null_cadr_t)); + if (fx_proc(tree) == fx_is_null_cddr_s) return(with_fx(tree, fx_is_null_cddr_t)); + if (fx_proc(tree) == fx_not_is_pair_s) return(with_fx(tree, fx_not_is_pair_t)); + if (fx_proc(tree) == fx_not_is_null_s) return(with_fx(tree, fx_not_is_null_t)); + if (fx_proc(tree) == fx_not_is_symbol_s) return(with_fx(tree, fx_not_is_symbol_t)); + if (fx_proc(tree) == fx_is_type_car_s) + return(with_fx(tree, (car(p) == sc->is_symbol_symbol) ? fx_is_symbol_car_t : fx_is_type_car_t)); + if (fx_proc(tree) == fx_c_opsq) + { + set_opt1_sym(cdr(p), cadadr(p)); + if ((is_global_and_has_func(car(p), s7_p_p_function)) && + (is_global_and_has_func(caadr(p), s7_p_p_function))) + { + set_opt2_direct(cdr(p), (s7_pointer)(s7_p_p_function(global_value(car(p))))); + set_opt3_direct(cdr(p), (s7_pointer)(s7_p_p_function(global_value(caadr(p))))); + return(with_fx(tree, fx_c_optq_direct)); + } + return(with_fx(tree, fx_c_optq)); + } + if (fx_proc(tree) == fx_c_car_s) return(with_fx(tree, fx_c_car_t)); + if (fx_proc(tree) == fx_c_cdr_s) return(with_fx(tree, fx_c_cdr_t)); + if (fx_proc(tree) == fx_is_type_opsq) return(with_fx(tree, fx_is_type_optq)); + if (fx_proc(tree) == fx_not_opsq) + { + set_opt1_sym(cdr(p), cadadr(p)); + return(with_fx(tree, (caadr(p) == sc->car_symbol) ? fx_not_car_t : fx_not_optq)); + }} + if (cadadr(p) == var2) + { + if (fx_proc(tree) == fx_c_car_s) return(with_fx(tree, fx_c_car_u)); + if (fx_proc(tree) == fx_not_is_null_s) return(with_fx(tree, fx_not_is_null_u)); + if (fx_proc(tree) == fx_not_is_pair_s) return(with_fx(tree, fx_not_is_pair_u)); + if (fx_proc(tree) == fx_is_pair_cdr_s) return(with_fx(tree, fx_is_pair_cdr_u)); + } + if (cadadr(p) == var3) + { + if (fx_proc(tree) == fx_not_is_pair_s) return(with_fx(tree, fx_not_is_pair_v)); + } + break; + + case HOP_SAFE_C_opSq_S: + if (cadadr(p) == var1) + { + if (fx_proc(tree) == fx_c_opsq_s) + { + if ((is_global_and_has_func(car(p), s7_p_pp_function)) && + (is_global_and_has_func(caadr(p), s7_p_p_function))) + { + set_opt2_direct(cdr(p), (s7_pointer)(s7_p_pp_function(global_value(car(p))))); + set_opt3_direct(cdr(p), (s7_pointer)(s7_p_p_function(global_value(caadr(p))))); + return(with_fx(tree, fx_c_optq_s_direct)); + } + return(with_fx(tree, fx_c_optq_s)); + } + if (fx_proc(tree) == fx_c_opsq_s_direct) return(with_fx(tree, fx_c_optq_s_direct)); + if (fx_proc(tree) == fx_cons_car_s_s) + { + set_opt1_sym(cdr(p), var1); + return(with_fx(tree, (caddr(p) == var3) ? fx_cons_car_t_v : fx_cons_car_t_s)); + }} + if (cadadr(p) == var2) + { + if ((fx_proc(tree) == fx_c_opsq_s) && (caddr(p) == var1)) + { + if ((is_global_and_has_func(car(p), s7_p_pp_function)) && + (is_global_and_has_func(caadr(p), s7_p_p_function))) /* (memq (car sequence) items) lint */ + { + set_opt2_direct(cdr(p), (s7_pointer)(s7_p_pp_function(global_value(car(p))))); + set_opt3_direct(cdr(p), (s7_pointer)(s7_p_p_function(global_value(caadr(p))))); + return(with_fx(tree, (car(p) == sc->cons_symbol) ? + ((caadr(p) == sc->car_symbol) ? fx_cons_car_u_t : fx_cons_opuq_t) : fx_c_opuq_t_direct)); + } + return(with_fx(tree, fx_c_opuq_t)); + } + if (((fx_proc(tree) == fx_c_opsq_s_direct) || (fx_proc(tree) == fx_cons_car_s_s)) && + (caddr(p) == var1)) + return(with_fx(tree, (car(p) == sc->cons_symbol) ? + ((caadr(p) == sc->car_symbol) ? fx_cons_car_u_t : fx_cons_opuq_t) : fx_c_opuq_t_direct)); + } + break; + + case HOP_SAFE_C_S_opSq: + if (cadr(p) == var1) + { + if (cadaddr(p) == var2) + { + if (fx_proc(tree) == fx_c_s_car_s) return(with_fx(tree, fx_c_t_car_u)); + if (fx_proc(tree) == fx_c_s_opsq_direct) return(with_fx(tree, fx_c_t_opuq_direct)); + } + if (cadaddr(p) == var3) + { + if (fx_proc(tree) == fx_add_s_car_s) return(with_fx(tree, fx_add_t_car_v)); + if (fx_proc(tree) == fx_c_s_car_s) return(with_fx(tree, fx_c_t_car_v)); /* ideally eq_p_pp not g_is_eq */ + } + if (fx_proc(tree) == fx_c_s_opsq_direct) return(with_fx(tree, fx_c_t_opsq_direct)); + } + if (cadr(p) == var2) + { + if ((fx_proc(tree) == fx_add_s_car_s) && (cadaddr(p) == var1)) return(with_fx(tree, fx_add_u_car_t)); + if ((fx_proc(tree) == fx_c_s_opsq_direct) && (cadaddr(p) == var3)) return(with_fx(tree, fx_c_u_opvq_direct)); + } + if ((cadaddr(p) == var1) && (fx_proc(tree) == fx_c_s_car_s)) return(with_fx(tree, fx_c_s_car_t)); + break; + + case HOP_SAFE_C_opSq_opSq: + if ((fx_proc(tree) == fx_c_opsq_opsq_direct) && (cadadr(p) == var1) && (cadadr(p) == cadaddr(p))) + { + set_opt1_sym(cdr(p), cadadr(p)); + return(with_fx(tree, fx_c_optq_optq_direct)); /* opuq got few hits */ + } + if (((fx_proc(tree) == fx_c_opsq_opsq_direct) || (fx_proc(tree) == fx_car_s_car_s)) && + ((caadr(p) == sc->car_symbol) && (caadr(p) == caaddr(p)))) + { + set_opt1_sym(cdr(p), cadadr(p)); + set_opt2_sym(cdr(p), cadaddr(p)); + return(with_fx(tree, ((cadadr(p) == var1) && (cadaddr(p) == var2)) ? + ((opt3_direct(p) == (s7_pointer)is_eq_p_pp) ? fx_is_eq_car_car_tu : fx_car_t_car_u) : fx_car_s_car_s)); + } + break; + + case HOP_SAFE_C_opSq_C: + if (cadadr(p) == var1) + { + if (fx_proc(tree) == fx_is_eq_car_sq) return(with_fx(tree, fx_is_eq_car_tq)); + if ((fx_proc(tree) == fx_c_opsq_c) || (fx_proc(tree) == fx_c_optq_c)) + { + if (fn_proc(p) != g_lint_let_ref) /* don't step on opt3_sym */ + { + if ((is_global_and_has_func(car(p), s7_p_pp_function)) && + (is_global_and_has_func(caadr(p), s7_p_p_function))) + { + if (fn_proc(p) == g_memq_2) + set_opt3_direct(p, (s7_pointer)memq_2_p_pp); + else set_opt3_direct(p, (s7_pointer)(s7_p_pp_function(global_value(car(p))))); + set_opt3_direct(cdr(p), (s7_pointer)(s7_p_p_function(global_value(caadr(p))))); + set_fx_direct(tree, fx_c_optq_c_direct); + return(true); + } + if ((is_t_integer(caddr(p))) && + (is_global_and_has_func(caadr(p), s7_i_7p_function)) && + (is_global_and_has_func(car(p), s7_p_ii_function))) + { + set_opt3_direct(p, (s7_pointer)(s7_p_ii_function(global_value(car(p))))); + set_opt3_direct(cdr(p), (s7_pointer)(s7_i_7p_function(global_value(caadr(p))))); + set_fx_direct(tree, fx_c_optq_i_direct); + } + else set_fx_direct(tree, fx_c_optq_c); + } + return(true); + }} + break; + + case HOP_SAFE_C_opSSq: + if (fx_proc(tree) == fx_c_opssq) + { + if (caddadr(p) == var1) return(with_fx(tree, fx_c_opstq)); + if ((cadadr(p) == var1) && (caddadr(p) == var2)) return(with_fx(tree, fx_c_optuq)); + } + if (fx_proc(tree) == fx_c_opssq_direct) + { + if ((cadadr(p) == var1) && (caddadr(p) == var2)) return(with_fx(tree, fx_c_optuq_direct)); + if (caddadr(p) == var1) + { + if ((opt2_direct(cdr(p)) == (s7_pointer)is_zero_p_p) && (opt3_direct(cdr(p)) == (s7_pointer)remainder_p_pp) && + (!more_vars) && (o_var_ok(cadadr(p), var1, var2, var3))) + return(with_fx(tree, fx_is_zero_remainder_o)); + return(with_fx(tree, fx_c_opstq_direct)); + }} + if ((cadadr(p) == var2) && (fx_proc(tree) == fx_not_opssq) && (caddadr(p) == var1)) + { + set_fx_direct(tree, (fn_proc(cadr(p)) == g_less_2) ? fx_not_lt_ut : fx_not_oputq); + return(true); + } + break; + + case HOP_SAFE_C_opSCq: + if (cadadr(p) == var1) + { + if ((fn_proc(p) == g_is_zero) && (fn_proc(cadr(p)) == g_remainder) && + (is_t_integer(caddadr(p))) && (integer(caddadr(p)) > 1)) + return(with_fx(tree, fx_is_zero_remainder_ti)); + return(with_fx(tree, fx_c_optcq)); /* there currently isn't any fx_c_opscq_direct */ + } + break; + + case HOP_SAFE_C_opSSq_C: + if ((fx_proc(tree) == fx_c_opssq_c) && (caddadr(p) == var1)) + { + if (is_global_and_has_func(car(p), s7_p_pp_function)) + { + if ((car(p) == sc->is_eq_symbol) && (!is_unspecified(caddr(p))) && (caadr(p) == sc->vector_ref_symbol) && + (!more_vars) && (o_var_ok(cadadr(p), var1, var2, var3))) + return(with_fx(tree, fx_is_eq_vref_opotq_c)); + set_opt3_direct(p, (s7_pointer)(s7_p_pp_function(global_value(car(p))))); + return(with_fx(tree, fx_c_opstq_c_direct)); + } + return(with_fx(tree, fx_c_opstq_c)); + } + break; + + case HOP_SAFE_C_S_opSCq: + if (cadr(p) == var1) + { + if (fx_proc(tree) == fx_c_s_opscq_direct) return(with_fx(tree, (cadaddr(p) == var2) ? fx_c_t_opucq_direct : fx_c_t_opscq_direct)); + if ((fx_proc(tree) == fx_c_s_opsiq_direct) && (!more_vars) && (o_var_ok(cadaddr(p), var1, var2, var3))) return(with_fx(tree, fx_c_t_opoiq_direct)); + } + else + if ((cadr(p) == var2) && (cadaddr(p) == var1)) + { + if (fx_proc(tree) == fx_c_s_opsiq_direct) return(with_fx(tree, fx_c_u_optiq_direct)); + if (fx_proc(tree) == fx_c_s_opscq) return(with_fx(tree, fx_c_u_optcq)); + } + break; + + case HOP_SAFE_C_opSq_CS: + if ((cadadr(p) == var1) && (fx_proc(tree) == fx_c_opsq_cs) && (cadddr(p) == var2)) return(with_fx(tree, fx_c_optq_cu)); + break; + + case HOP_SAFE_C_opSq_opSSq: + if ((fx_proc(tree) == fx_c_opsq_opssq) && (cadaddr(p) == var1) && (caddaddr(p) == var2) && + (is_global_and_has_func(car(p), s7_p_pp_function)) && + (is_global_and_has_func(caadr(p), s7_p_p_function)) && + (is_global_and_has_func(caaddr(p), s7_p_pp_function))) + { + set_opt3_direct(p, (s7_pointer)(s7_p_pp_function(global_value(car(p))))); + set_opt2_direct(cdr(p), (s7_pointer)(s7_p_p_function(global_value(caadr(p))))); + set_opt3_direct(cdr(p), (s7_pointer)(s7_p_pp_function(global_value(caaddr(p))))); + set_opt1_sym(cdr(p), var2); /* caddaddr(p) */ + + set_opt2_sym(cddr(p), var1); + if ((car(p) == sc->num_eq_symbol) && (caadr(p) == sc->car_symbol) && (cadadr(p) == var3)) + { + if (caaddr(p) == sc->add_symbol) return(with_fx(tree, fx_num_eq_car_v_add_tu)); + if (caaddr(p) == sc->subtract_symbol) return(with_fx(tree, fx_num_eq_car_v_subtract_tu)); + } + return(with_fx(tree, fx_c_opsq_optuq_direct)); + } + break; + + case HOP_SAFE_C_opSSq_S: + if (fx_proc(tree) == fx_vref_vref_ss_s) + { + if ((caddr(p) == var1) && (is_global(cadadr(p)))) + { + if ((!more_vars) && (o_var_ok(caddadr(p), var1, var2, var3))) return(with_fx(tree, fx_vref_vref_go_t)); + return(with_fx(tree, fx_vref_vref_gs_t)); + } + if ((cadadr(p) == var1) && (caddadr(p) == var2) && (caddr(p) == var3)) return(with_fx(tree, fx_vref_vref_tu_v)); + } + if ((fx_proc(tree) == fx_gt_add_s) && (cadadr(p) == var1) && (caddadr(p) == var2)) + return(with_fx(tree, fx_gt_add_tu_s)); + if ((fx_proc(tree) == fx_add_sub_s) && (cadadr(p) == var1) && (caddadr(p) == var2)) + return(with_fx(tree, fx_add_sub_tu_s)); + break; + + case HOP_SAFE_C_S_opSSq: + if (caddaddr(p) == var1) + { + if ((fn_proc(p) == g_vector_ref_2) && (is_global(cadr(p)) && (is_global(cadaddr(p))))) + { + set_opt3_pair(p, cdaddr(p)); + return(with_fx(tree, fx_vref_g_vref_gt)); + } + if (fx_proc(tree) == fx_c_s_opssq_direct) return(with_fx(tree, fx_c_s_opstq_direct)); + } + if ((fx_proc(tree) == fx_c_s_opssq_direct) && (cadr(p) == var1) && (caddaddr(p) == var2)) return(with_fx(tree, fx_c_t_opsuq_direct)); + break; + + case HOP_SAFE_C_op_opSq_Sq: + if ((car(p) == sc->not_symbol) && (is_global(sc->not_symbol)) && (var1 == cadr(cadadr(p)))) + return(with_fx(tree, fx_not_op_optq_sq)); + break; + + case HOP_SAFE_C_AC: + if (((fx_proc(tree) == fx_c_ac) || (fx_proc(tree) == fx_c_ac_direct)) && (fn_proc(p) == g_num_eq_xi) && (caddr(p) == int_zero) && + (fx_proc(cdr(p)) == fx_c_opuq_t_direct) && (caadr(p) == sc->remainder_symbol) && (fn_proc(cadadr(p)) == g_car)) + { + set_opt3_sym(p, cadr(cadadr(p))); + set_opt1_sym(cdr(p), caddadr(p)); + return(with_fx(tree, fx_is_zero_remainder_car)); + } + break; + + case HOP_SAFE_CLOSURE_S_A: + if ((cadr(p) == var1) && (fx_proc(tree) == fx_safe_closure_s_a)) return(with_fx(tree, fx_safe_closure_t_a)); + break; + + case OP_IF_S_A_A: + if ((!more_vars) && (o_var_ok(cadr(p), var1, var2, var3))) return(with_fx(tree, fx_if_o_a_a)); + break; + + case OP_AND_3A: + if ((fx_proc(tree) == fx_and_3a) && + (is_pair(cadr(p))) && + (is_pair(cdadr(p))) && (cadadr(p) == var1) && /* so "s" below is "t" */ + (((fx_proc(cdr(p)) == fx_is_pair_t) && (fx_proc(cddr(p)) == fx_is_pair_cdr_t)) || + ((fx_proc(cdr(p)) == fx_is_pair_s) && (fx_proc(cddr(p)) == fx_is_pair_cdr_s)))) + { + set_opt1_sym(cdr(p), cadadr(p)); + if ((fx_proc(cdddr(p)) == fx_is_null_cddr_t) || (fx_proc(cdddr(p)) == fx_is_null_cddr_s)) + return(with_fx(tree, fx_len2_t)); + if ((fx_proc(cdddr(p)) == fx_is_pair_cddr_t) || (fx_proc(cdddr(p)) == fx_is_pair_cddr_s)) + return(with_fx(tree, fx_len3_t)); + } + break; + } +#if 0 + if ((var3) && ((s7_tree_memq(sc, var1, car(tree))) || ((var2) && (s7_tree_memq(sc, var2, car(tree)))) || ((var3) && (s7_tree_memq(sc, var3, car(tree)))))) + fprintf(stderr, "fx_tree_in %s %s %s: %s %s\n", + display(var1), (var2) ? display(var2) : "", (var3) ? display(var3) : "", display_80(car(tree)), op_names[optimize_op(car(tree))]); +#endif + return(false); +} + +static void fx_tree(s7_scheme *sc, s7_pointer tree, s7_pointer var1, s7_pointer var2, s7_pointer var3, bool more_vars) +{ + /* if (is_pair(tree)) fprintf(stderr, "fx_tree %s %d %d\n", display(tree), has_fx(tree), is_syntax(car(tree))); */ + if (!is_pair(tree)) return; + if ((is_symbol(car(tree))) && + (is_definer_or_binder(car(tree)))) + { + if ((car(tree) == sc->let_symbol) && (is_pair(cdr(tree))) && (is_pair(cadr(tree))) && + (is_null(cdadr(tree))) && (is_pair(caadr(tree)))) /* (let (a) ...) */ + fx_tree(sc, cddr(tree), caaadr(tree), NULL, NULL, more_vars); + return; + } + if (is_syntax(car(tree))) return; /* someday let #_when/#_if etc through -- the symbol 'if, for example, is not syntax */ + + if ((!has_fx(tree)) || + (!fx_tree_in(sc, tree, var1, var2, var3, more_vars))) + fx_tree(sc, car(tree), var1, var2, var3, more_vars); + fx_tree(sc, cdr(tree), var1, var2, var3, more_vars); +} + +/* -------------------------------------------------------------------------------- */ +static opt_funcs_t *alloc_semipermanent_opt_func(s7_scheme *sc) +{ + if (sc->alloc_opt_func_k == ALLOC_FUNCTION_SIZE) + { + sc->alloc_opt_func_cells = (opt_funcs_t *)Malloc(ALLOC_FUNCTION_SIZE * sizeof(opt_funcs_t)); + add_saved_pointer(sc, sc->alloc_opt_func_cells); + sc->alloc_opt_func_k = 0; + } + return(&(sc->alloc_opt_func_cells[sc->alloc_opt_func_k++])); +} + +static void add_opt_func(s7_scheme *sc, s7_pointer f, opt_func_t typ, void *func) +{ + opt_funcs_t *op; +#if S7_DEBUGGING + static const char *o_names[] = {"o_d_v", "o_d_vd", "o_d_vdd", "o_d_vid", "o_d_id", "o_d_7pi", "o_d_7pii", "o_d_7piid", "o_d_7piii", "o_d_7piiid", + "o_d_ip", "o_d_pd", "o_d_7pid", "o_d", "o_d_d", "o_d_dd", "o_d_7dd", "o_d_ddd", "o_d_dddd", + "o_i_i", "o_i_7i", "o_i_ii", "o_i_7ii", "o_i_iii", "o_i_7pi", "o_i_7pii", "o_i_7_piii", "o_d_p", + "o_b_p", "o_b_7p", "o_b_pp", "o_b_7pp", "o_b_pp_unchecked", "o_b_pi", "o_b_ii", "o_b_7ii", "o_b_dd", + "o_p", "o_p_p", "o_p_ii", "o_p_d", "o_p_dd", "o_i_7d", "o_i_7p", "o_d_7d", "o_p_pp", "o_p_ppp", "o_p_pi", "o_p_pi_unchecked", + "o_p_ppi", "o_p_i", "o_p_pii", "o_p_pip", "o_p_pip_unchecked", "o_p_piip", "o_b_i", "o_b_d"}; + if (!is_c_function(f)) + { + fprintf(stderr, "%s[%d]: %s is not a c_function\n", __func__, __LINE__, display(f)); + if (sc->stop_at_error) abort(); + } + else + if (c_function_opt_data(f)) + for (opt_funcs_t *p = c_function_opt_data(f); p; p = p->next) + { + if (p->typ == typ) + fprintf(stderr, "%s[%d]: %s has a function of type %d (%s)\n", + __func__, __LINE__, display(f), typ, o_names[typ]); + if (p->func == func) + fprintf(stderr, "%s[%d]: %s already has this function as type %d %s (current: %d %s)\n", + __func__, __LINE__, display(f), p->typ, o_names[p->typ], typ, o_names[typ]); + } +#endif + op = alloc_semipermanent_opt_func(sc); + op->typ = typ; + op->func = func; + op->next = c_function_opt_data(f); + c_function_opt_data(f) = op; +} + +static void *opt_func(s7_pointer f, opt_func_t typ) +{ + if (is_c_function(f)) + for (opt_funcs_t *p = c_function_opt_data(f); p; p = p->next) + if (p->typ == typ) + return(p->func); + return(NULL); +} + +/* clm2xen.c */ +void s7_set_d_function(s7_scheme *sc, s7_pointer f, s7_d_t df) {add_opt_func(sc, f, o_d, (void *)df);} +s7_d_t s7_d_function(s7_pointer f) {return((s7_d_t)opt_func(f, o_d));} + +void s7_set_d_d_function(s7_scheme *sc, s7_pointer f, s7_d_d_t df) {add_opt_func(sc, f, o_d_d, (void *)df);} +s7_d_d_t s7_d_d_function(s7_pointer f) {return((s7_d_d_t)opt_func(f, o_d_d));} + +void s7_set_d_dd_function(s7_scheme *sc, s7_pointer f, s7_d_dd_t df) {add_opt_func(sc, f, o_d_dd, (void *)df);} +s7_d_dd_t s7_d_dd_function(s7_pointer f) {return((s7_d_dd_t)opt_func(f, o_d_dd));} + +void s7_set_d_v_function(s7_scheme *sc, s7_pointer f, s7_d_v_t df) {add_opt_func(sc, f, o_d_v, (void *)df);} +s7_d_v_t s7_d_v_function(s7_pointer f) {return((s7_d_v_t)opt_func(f, o_d_v));} + +void s7_set_d_vd_function(s7_scheme *sc, s7_pointer f, s7_d_vd_t df) {add_opt_func(sc, f, o_d_vd, (void *)df);} +s7_d_vd_t s7_d_vd_function(s7_pointer f) {return((s7_d_vd_t)opt_func(f, o_d_vd));} + +void s7_set_d_vdd_function(s7_scheme *sc, s7_pointer f, s7_d_vdd_t df) {add_opt_func(sc, f, o_d_vdd, (void *)df);} +s7_d_vdd_t s7_d_vdd_function(s7_pointer f) {return((s7_d_vdd_t)opt_func(f, o_d_vdd));} + +void s7_set_d_vid_function(s7_scheme *sc, s7_pointer f, s7_d_vid_t df) {add_opt_func(sc, f, o_d_vid, (void *)df);} +s7_d_vid_t s7_d_vid_function(s7_pointer f) {return((s7_d_vid_t)opt_func(f, o_d_vid));} + +void s7_set_d_id_function(s7_scheme *sc, s7_pointer f, s7_d_id_t df) {add_opt_func(sc, f, o_d_id, (void *)df);} +s7_d_id_t s7_d_id_function(s7_pointer f) {return((s7_d_id_t)opt_func(f, o_d_id));} + +void s7_set_d_7pid_function(s7_scheme *sc, s7_pointer f, s7_d_7pid_t df) {add_opt_func(sc, f, o_d_7pid, (void *)df);} +s7_d_7pid_t s7_d_7pid_function(s7_pointer f) {return((s7_d_7pid_t)opt_func(f, o_d_7pid));} + +void s7_set_d_ip_function(s7_scheme *sc, s7_pointer f, s7_d_ip_t df) {add_opt_func(sc, f, o_d_ip, (void *)df);} +s7_d_ip_t s7_d_ip_function(s7_pointer f) {return((s7_d_ip_t)opt_func(f, o_d_ip));} + +void s7_set_d_pd_function(s7_scheme *sc, s7_pointer f, s7_d_pd_t df) {add_opt_func(sc, f, o_d_pd, (void *)df);} +s7_d_pd_t s7_d_pd_function(s7_pointer f) {return((s7_d_pd_t)opt_func(f, o_d_pd));} + +void s7_set_d_p_function(s7_scheme *sc, s7_pointer f, s7_d_p_t df) {add_opt_func(sc, f, o_d_p, (void *)df);} +s7_d_p_t s7_d_p_function(s7_pointer f) {return((s7_d_p_t)opt_func(f, o_d_p));} + +void s7_set_b_p_function(s7_scheme *sc, s7_pointer f, s7_b_p_t df) {add_opt_func(sc, f, o_b_p, (void *)df);} +s7_b_p_t s7_b_p_function(s7_pointer f) {return((s7_b_p_t)opt_func(f, o_b_p));} + +void s7_set_d_7pi_function(s7_scheme *sc, s7_pointer f, s7_d_7pi_t df) {add_opt_func(sc, f, o_d_7pi, (void *)df);} +s7_d_7pi_t s7_d_7pi_function(s7_pointer f) {return((s7_d_7pi_t)opt_func(f, o_d_7pi));} + +static void s7_set_d_7pii_function(s7_scheme *sc, s7_pointer f, s7_d_7pii_t df) {add_opt_func(sc, f, o_d_7pii, (void *)df);} +static s7_d_7pii_t s7_d_7pii_function(s7_pointer f) {return((s7_d_7pii_t)opt_func(f, o_d_7pii));} + +static void s7_set_d_7piii_function(s7_scheme *sc, s7_pointer f, s7_d_7piii_t df) {add_opt_func(sc, f, o_d_7piii, (void *)df);} +static s7_d_7piii_t s7_d_7piii_function(s7_pointer f) {return((s7_d_7piii_t)opt_func(f, o_d_7piii));} + +void s7_set_i_7p_function(s7_scheme *sc, s7_pointer f, s7_i_7p_t df) {add_opt_func(sc, f, o_i_7p, (void *)df);} +s7_i_7p_t s7_i_7p_function(s7_pointer f) {return((s7_i_7p_t)opt_func(f, o_i_7p));} + +/* cload.scm */ +void s7_set_d_ddd_function(s7_scheme *sc, s7_pointer f, s7_d_ddd_t df) {add_opt_func(sc, f, o_d_ddd, (void *)df);} +s7_d_ddd_t s7_d_ddd_function(s7_pointer f) {return((s7_d_ddd_t)opt_func(f, o_d_ddd));} + +void s7_set_d_dddd_function(s7_scheme *sc, s7_pointer f, s7_d_dddd_t df) {add_opt_func(sc, f, o_d_dddd, (void *)df);} +s7_d_dddd_t s7_d_dddd_function(s7_pointer f) {return((s7_d_dddd_t)opt_func(f, o_d_dddd));} + +void s7_set_i_i_function(s7_scheme *sc, s7_pointer f, s7_i_i_t df) {add_opt_func(sc, f, o_i_i, (void *)df);} +s7_i_i_t s7_i_i_function(s7_pointer f) {return((s7_i_i_t)opt_func(f, o_i_i));} + +void s7_set_i_ii_function(s7_scheme *sc, s7_pointer f, s7_i_ii_t df) {add_opt_func(sc, f, o_i_ii, (void *)df);} +s7_i_ii_t s7_i_ii_function(s7_pointer f) {return((s7_i_ii_t)opt_func(f, o_i_ii));} + +void s7_set_i_7d_function(s7_scheme *sc, s7_pointer f, s7_i_7d_t df) {add_opt_func(sc, f, o_i_7d, (void *)df);} +s7_i_7d_t s7_i_7d_function(s7_pointer f) {return((s7_i_7d_t)opt_func(f, o_i_7d));} + +/* s7test.scm */ +void s7_set_p_d_function(s7_scheme *sc, s7_pointer f, s7_p_d_t df) {add_opt_func(sc, f, o_p_d, (void *)df);} +s7_p_d_t s7_p_d_function(s7_pointer f) {return((s7_p_d_t)opt_func(f, o_p_d));} + +static void s7_set_d_7dd_function(s7_scheme *sc, s7_pointer f, s7_d_7dd_t df) {add_opt_func(sc, f, o_d_7dd, (void *)df);} +static s7_d_7dd_t s7_d_7dd_function(s7_pointer f) {return((s7_d_7dd_t)opt_func(f, o_d_7dd));} + +static void s7_set_i_7i_function(s7_scheme *sc, s7_pointer f, s7_i_7i_t df) {add_opt_func(sc, f, o_i_7i, (void *)df);} +static s7_i_7i_t s7_i_7i_function(s7_pointer f) {return((s7_i_7i_t)opt_func(f, o_i_7i));} + +static void s7_set_i_7ii_function(s7_scheme *sc, s7_pointer f, s7_i_7ii_t df) {add_opt_func(sc, f, o_i_7ii, (void *)df);} +static s7_i_7ii_t s7_i_7ii_function(s7_pointer f) {return((s7_i_7ii_t)opt_func(f, o_i_7ii));} + +static void s7_set_i_iii_function(s7_scheme *sc, s7_pointer f, s7_i_iii_t df) {add_opt_func(sc, f, o_i_iii, (void *)df);} +static s7_i_iii_t s7_i_iii_function(s7_pointer f) {return((s7_i_iii_t)opt_func(f, o_i_iii));} + +static void s7_set_p_pi_function(s7_scheme *sc, s7_pointer f, s7_p_pi_t df) {add_opt_func(sc, f, o_p_pi, (void *)df);} +static s7_p_pi_t s7_p_pi_function(s7_pointer f) {return((s7_p_pi_t)opt_func(f, o_p_pi));} + +static void s7_set_p_ppi_function(s7_scheme *sc, s7_pointer f, s7_p_ppi_t df) {add_opt_func(sc, f, o_p_ppi, (void *)df);} +static s7_p_ppi_t s7_p_ppi_function(s7_pointer f) {return((s7_p_ppi_t)opt_func(f, o_p_ppi));} + +static void s7_set_i_7pi_function(s7_scheme *sc, s7_pointer f, s7_i_7pi_t df) {add_opt_func(sc, f, o_i_7pi, (void *)df);} +static s7_i_7pi_t s7_i_7pi_function(s7_pointer f) {return((s7_i_7pi_t)opt_func(f, o_i_7pi));} + +static void s7_set_i_7pii_function(s7_scheme *sc, s7_pointer f, s7_i_7pii_t df) {add_opt_func(sc, f, o_i_7pii, (void *)df);} +static s7_i_7pii_t s7_i_7pii_function(s7_pointer f) {return((s7_i_7pii_t)opt_func(f, o_i_7pii));} + +static void s7_set_i_7piii_function(s7_scheme *sc, s7_pointer f, s7_i_7piii_t df) {add_opt_func(sc, f, o_i_7piii, (void *)df);} +static s7_i_7piii_t s7_i_7piii_function(s7_pointer f) {return((s7_i_7piii_t)opt_func(f, o_i_7piii));} + +static void s7_set_b_d_function(s7_scheme *sc, s7_pointer f, s7_b_d_t df) {add_opt_func(sc, f, o_b_d, (void *)df);} +static s7_b_d_t s7_b_d_function(s7_pointer f) {return((s7_b_d_t)opt_func(f, o_b_d));} + +static void s7_set_b_i_function(s7_scheme *sc, s7_pointer f, s7_b_i_t df) {add_opt_func(sc, f, o_b_i, (void *)df);} +static s7_b_i_t s7_b_i_function(s7_pointer f) {return((s7_b_i_t)opt_func(f, o_b_i));} + +static void s7_set_b_7p_function(s7_scheme *sc, s7_pointer f, s7_b_7p_t df) {add_opt_func(sc, f, o_b_7p, (void *)df);} +static s7_b_7p_t s7_b_7p_function(s7_pointer f) {return((s7_b_7p_t)opt_func(f, o_b_7p));} + +static void s7_set_b_pp_function(s7_scheme *sc, s7_pointer f, s7_b_pp_t df) {add_opt_func(sc, f, o_b_pp, (void *)df);} +static s7_b_pp_t s7_b_pp_function(s7_pointer f) {return((s7_b_pp_t)opt_func(f, o_b_pp));} + +static void s7_set_b_7pp_function(s7_scheme *sc, s7_pointer f, s7_b_7pp_t df) {add_opt_func(sc, f, o_b_7pp, (void *)df);} +static s7_b_7pp_t s7_b_7pp_function(s7_pointer f) {return((s7_b_7pp_t)opt_func(f, o_b_7pp));} + +static void s7_set_d_7d_function(s7_scheme *sc, s7_pointer f, s7_d_7d_t df) {add_opt_func(sc, f, o_d_7d, (void *)df);} +static s7_d_7d_t s7_d_7d_function(s7_pointer f) {return((s7_d_7d_t)opt_func(f, o_d_7d));} + +static void s7_set_b_pi_function(s7_scheme *sc, s7_pointer f, s7_b_pi_t df) {add_opt_func(sc, f, o_b_pi, (void *)df);} +static s7_b_pi_t s7_b_pi_function(s7_pointer f) {return((s7_b_pi_t)opt_func(f, o_b_pi));} + +static void s7_set_b_ii_function(s7_scheme *sc, s7_pointer f, s7_b_ii_t df) {add_opt_func(sc, f, o_b_ii, (void *)df);} +static s7_b_ii_t s7_b_ii_function(s7_pointer f) {return((s7_b_ii_t)opt_func(f, o_b_ii));} + +static void s7_set_b_7ii_function(s7_scheme *sc, s7_pointer f, s7_b_7ii_t df) {add_opt_func(sc, f, o_b_7ii, (void *)df);} +static s7_b_7ii_t s7_b_7ii_function(s7_pointer f) {return((s7_b_7ii_t)opt_func(f, o_b_7ii));} + +static void s7_set_b_dd_function(s7_scheme *sc, s7_pointer f, s7_b_dd_t df) {add_opt_func(sc, f, o_b_dd, (void *)df);} +static s7_b_dd_t s7_b_dd_function(s7_pointer f) {return((s7_b_dd_t)opt_func(f, o_b_dd));} + +void s7_set_p_p_function(s7_scheme *sc, s7_pointer f, s7_p_p_t df) {add_opt_func(sc, f, o_p_p, (void *)df);} +s7_p_p_t s7_p_p_function(s7_pointer f) {return((s7_p_p_t)opt_func(f, o_p_p));} + +static void s7_set_p_function(s7_scheme *sc, s7_pointer f, s7_p_t df) {add_opt_func(sc, f, o_p, (void *)df);} +static s7_p_t s7_p_function(s7_pointer f) {return((s7_p_t)opt_func(f, o_p));} + +void s7_set_p_pp_function(s7_scheme *sc, s7_pointer f, s7_p_pp_t df) {add_opt_func(sc, f, o_p_pp, (void *)df);} +s7_p_pp_t s7_p_pp_function(s7_pointer f) {return((s7_p_pp_t)opt_func(f, o_p_pp));} + +void s7_set_p_ppp_function(s7_scheme *sc, s7_pointer f, s7_p_ppp_t df) {add_opt_func(sc, f, o_p_ppp, (void *)df);} +s7_p_ppp_t s7_p_ppp_function(s7_pointer f) {return((s7_p_ppp_t)opt_func(f, o_p_ppp));} + +static void s7_set_p_pip_function(s7_scheme *sc, s7_pointer f, s7_p_pip_t df) {add_opt_func(sc, f, o_p_pip, (void *)df);} +static s7_p_pip_t s7_p_pip_function(s7_pointer f) {return((s7_p_pip_t)opt_func(f, o_p_pip));} + +static void s7_set_p_pii_function(s7_scheme *sc, s7_pointer f, s7_p_pii_t df) {add_opt_func(sc, f, o_p_pii, (void *)df);} +static s7_p_pii_t s7_p_pii_function(s7_pointer f) {return((s7_p_pii_t)opt_func(f, o_p_pii));} + +static void s7_set_p_piip_function(s7_scheme *sc, s7_pointer f, s7_p_piip_t df) {add_opt_func(sc, f, o_p_piip, (void *)df);} +static s7_p_piip_t s7_p_piip_function(s7_pointer f) {return((s7_p_piip_t)opt_func(f, o_p_piip));} + +static void s7_set_p_pi_unchecked_function(s7_scheme *sc, s7_pointer f, s7_p_pi_t df) {add_opt_func(sc, f, o_p_pi_unchecked, (void *)df);} +static s7_p_pi_t s7_p_pi_unchecked_function(s7_pointer f) {return((s7_p_pi_t)opt_func(f, o_p_pi_unchecked));} + +static void s7_set_p_pip_unchecked_function(s7_scheme *sc, s7_pointer f, s7_p_pip_t df) {add_opt_func(sc, f, o_p_pip_unchecked, (void *)df);} +static s7_p_pip_t s7_p_pip_unchecked_function(s7_pointer f) {return((s7_p_pip_t)opt_func(f, o_p_pip_unchecked));} + +static void s7_set_b_pp_unchecked_function(s7_scheme *sc, s7_pointer f, s7_b_pp_t df) {add_opt_func(sc, f, o_b_pp_unchecked, (void *)df);} +static s7_b_pp_t s7_b_pp_unchecked_function(s7_pointer f) {return((s7_b_pp_t)opt_func(f, o_b_pp_unchecked));} + +static void s7_set_p_i_function(s7_scheme *sc, s7_pointer f, s7_p_i_t df) {add_opt_func(sc, f, o_p_i, (void *)df);} +static s7_p_i_t s7_p_i_function(s7_pointer f) {return((s7_p_i_t)opt_func(f, o_p_i));} + +static void s7_set_p_ii_function(s7_scheme *sc, s7_pointer f, s7_p_ii_t df) {add_opt_func(sc, f, o_p_ii, (void *)df);} +static s7_p_ii_t s7_p_ii_function(s7_pointer f) {return((s7_p_ii_t)opt_func(f, o_p_ii));} + +static void s7_set_d_7piid_function(s7_scheme *sc, s7_pointer f, s7_d_7piid_t df) {add_opt_func(sc, f, o_d_7piid, (void *)df);} +static s7_d_7piid_t s7_d_7piid_function(s7_pointer f) {return((s7_d_7piid_t)opt_func(f, o_d_7piid));} + +static void s7_set_d_7piiid_function(s7_scheme *sc, s7_pointer f, s7_d_7piiid_t df) {add_opt_func(sc, f, o_d_7piiid, (void *)df);} +static s7_d_7piiid_t s7_d_7piiid_function(s7_pointer f) {return((s7_d_7piiid_t)opt_func(f, o_d_7piiid));} + +static void s7_set_p_dd_function(s7_scheme *sc, s7_pointer f, s7_p_dd_t df) {add_opt_func(sc, f, o_p_dd, (void *)df);} +static s7_p_dd_t s7_p_dd_function(s7_pointer f) {return((s7_p_dd_t)opt_func(f, o_p_dd));} + +static opt_info *alloc_opt_info(s7_scheme *sc) +{ + opt_info *o; + if (sc->pc >= OPTS_SIZE) + sc->pc = OPTS_SIZE - 1; + o = sc->opts[sc->pc++]; + o->v[O_WRAP].fd = NULL; /* see bool_optimize -- this is a kludge */ + return(o); +} + +#define backup_pc(sc) sc->pc-- + +#define OPT_PRINT 0 +#if OPT_PRINT + +#define return_false(Sc, Expr) return(return_false_1(Sc, Expr, __func__, __LINE__)) +static bool return_false_1(s7_scheme *sc, s7_pointer expr, const char *func, int32_t line) +{ + if (expr) + fprintf(stderr, " %s%s[%d]%s: %s\n", bold_text, func, line, unbold_text, display_80(expr)); + else fprintf(stderr, " %s%s[%d]%s: false\n", bold_text, func, line, unbold_text); + return(false); +} + +#define return_true(Sc, Expr) return(return_true_1(Sc, Expr, __func__, __LINE__)) +static bool return_true_1(s7_scheme *sc, s7_pointer expr, const char *func, int32_t line) +{ + if (expr) + fprintf(stderr, " %s%s[%d]%s: %s\n", blue_text, func, line, uncolor_text, display_80(expr)); + else fprintf(stderr, " %s%s[%d]%s: true\n", blue_text, func, line, uncolor_text); + return(true); +} + +#define return_success(Sc, P, Expr) return(return_success_1(Sc, P, Expr, __func__, __LINE__)) +static s7_pfunc return_success_1(s7_scheme *sc, s7_pfunc p, s7_pointer expr, const char *func, int32_t line) +{ + fprintf(stderr, " %s%s[%d]%s: %s\n", bold_text green_text, func, line, unbold_text uncolor_text, display(expr)); + return(p); +} + +#define return_null(Sc, Expr) return(return_null_1(Sc, Expr, __func__, __LINE__)) +static s7_pfunc return_null_1(s7_scheme *sc, s7_pointer expr, const char *func, int32_t line) +{ + fprintf(stderr, " %s%s[%d]%s: %s\n %sfailure%s\n", bold_text, func, line, unbold_text, display_80(expr), bold_text red_text, unbold_text uncolor_text); + return(NULL); +} +#else +#define return_false(Sc, Expr) return(false) +#define return_true(Sc, Expr) return(true) +#define return_success(Sc, P, Expr) return(P) +#define return_null(Sc, Expr) return(NULL) +#endif + +static s7_pointer opt_integer_symbol(s7_scheme *sc, s7_pointer sym) +{ + if (is_symbol(sym)) + { + s7_pointer p = s7_slot(sc, sym); + if ((is_slot(p)) && + (is_t_integer(slot_value(p)))) + return(p); + } + return(NULL); +} + +static s7_pointer opt_real_symbol(s7_scheme *sc, s7_pointer sym) +{ + if (is_symbol(sym)) + { + s7_pointer p = s7_slot(sc, sym); + if ((is_slot(p)) && + (is_small_real(slot_value(p)))) + return(p); + } + return(NULL); +} + +static s7_pointer opt_float_symbol(s7_scheme *sc, s7_pointer sym) +{ + if (is_symbol(sym)) + { + s7_pointer p = s7_slot(sc, sym); + if ((is_slot(p)) && + (is_t_real(slot_value(p)))) + return(p); + } + return(NULL); +} + +static s7_pointer opt_simple_symbol(s7_scheme *sc, s7_pointer sym) +{ + s7_pointer p = s7_slot(sc, sym); + if ((is_slot(p)) && + (!has_methods(slot_value(p)))) + return(p); + return(NULL); +} + +static s7_pointer opt_types_match(s7_scheme *sc, s7_pointer check, s7_pointer sym) +{ + s7_pointer checker = s7_symbol_value(sc, check); + s7_pointer slot = s7_slot(sc, sym); + if (is_slot(slot)) + { + s7_pointer obj = slot_value(slot); + if (s7_apply_function(sc, checker, set_plist_1(sc, obj)) == sc->T) + return(slot); + } + return(NULL); +} + +static s7_pointer opt_bool_any(s7_scheme *sc) {return((sc->opts[0]->v[0].fb(sc->opts[0])) ? sc->T : sc->F);} +static s7_pointer opt_float_any_nv(s7_scheme *sc) {sc->opts[0]->v[0].fd(sc->opts[0]); return(NULL);} +static s7_pointer opt_int_any_nv(s7_scheme *sc) {sc->opts[0]->v[0].fi(sc->opts[0]); return(NULL);} +static s7_pointer opt_bool_any_nv(s7_scheme *sc) {sc->opts[0]->v[0].fb(sc->opts[0]); return(NULL);} +static s7_pointer opt_cell_any_nv(s7_scheme *sc) {return(sc->opts[0]->v[0].fp(sc->opts[0]));} /* this is faster than returning null */ + +static s7_pointer opt_wrap_float(s7_scheme *sc) {return(make_real(sc, sc->opts[0]->v[0].fd(sc->opts[0])));} +static s7_pointer opt_wrap_int(s7_scheme *sc) {return(make_integer(sc, sc->opts[0]->v[0].fi(sc->opts[0])));} +static s7_pointer opt_wrap_cell(s7_scheme *sc) {return(sc->opts[0]->v[0].fp(sc->opts[0]));} +static s7_pointer opt_wrap_bool(s7_scheme *sc) {return((sc->opts[0]->v[0].fb(sc->opts[0])) ? sc->T : sc->F);} + +static bool p_to_b(opt_info *o) {return(o->v[O_WRAP].fp(o) != o->sc->F);} +static s7_pointer d_to_p(opt_info *o) {return(make_real(o->sc, o->v[O_WRAP].fd(o)));} +static s7_pointer d_to_p_nr(opt_info *o) {o->v[O_WRAP].fd(o); return(NULL);} +static s7_pointer i_to_p(opt_info *o) {return(make_integer(o->sc, o->v[O_WRAP].fi(o)));} +static s7_pointer i_to_p_nr(opt_info *o) {o->v[O_WRAP].fi(o); return(NULL);} + + +/* -------------------------------- int opts -------------------------------- */ +static s7_int opt_i_c(opt_info *o) {return(o->v[1].i);} +static s7_int opt_i_s(opt_info *o) {return(integer(slot_value(o->v[1].p)));} + +static bool opt_int_not_pair(s7_scheme *sc, s7_pointer car_x) +{ + opt_info *opc; + s7_pointer p; + if (is_t_integer(car_x)) + { + opc = alloc_opt_info(sc); + opc->v[1].i = integer(car_x); + opc->v[0].fi = opt_i_c; + return_true(sc, car_x); + } + p = opt_integer_symbol(sc, car_x); + if (!p) + return_false(sc, car_x); + opc = alloc_opt_info(sc); + opc->v[1].p = p; + opc->v[0].fi = opt_i_s; + return_true(sc, car_x); +} + +/* -------- i_i|d|p -------- */ +static s7_int opt_i_i_c(opt_info *o) {return(o->v[2].i_i_f(o->v[1].i));} +static s7_int opt_i_i_s(opt_info *o) {return(o->v[2].i_i_f(integer(slot_value(o->v[1].p))));} +static s7_int opt_i_7i_c(opt_info *o) {return(o->v[2].i_7i_f(o->sc, o->v[1].i));} +static s7_int opt_i_7i_s(opt_info *o) {return(o->v[2].i_7i_f(o->sc, integer(slot_value(o->v[1].p))));} +static s7_int opt_i_7i_s_rand(opt_info *o) {return(random_i_7i(o->sc, integer(slot_value(o->v[1].p))));} +static s7_int opt_i_d_c(opt_info *o) {return(o->v[2].i_7d_f(o->sc, o->v[1].x));} +static s7_int opt_i_d_s(opt_info *o) {return(o->v[2].i_7d_f(o->sc, real(slot_value(o->v[1].p))));} + +static s7_int opt_i_i_f(opt_info *o) {return(o->v[2].i_i_f(o->v[4].fi(o->v[3].o1)));} +static s7_int opt_i_7i_f(opt_info *o) {return(o->v[2].i_7i_f(o->sc, o->v[4].fi(o->v[3].o1)));} +static s7_int opt_i_7d_f(opt_info *o) {return(o->v[2].i_7d_f(o->sc, o->v[4].fd(o->v[3].o1)));} +static s7_int opt_i_7p_f(opt_info *o) {return(o->v[2].i_7p_f(o->sc, o->v[4].fp(o->v[3].o1)));} +static s7_int opt_i_7p_f_cint(opt_info *o) {return(char_to_integer_i_7p(o->sc, o->v[4].fp(o->v[3].o1)));} + +static s7_int opt_i_i_s_abs(opt_info *o) {return(abs_i_i(integer(slot_value(o->v[1].p))));} +static s7_int opt_i_i_f_abs(opt_info *o) {return(abs_i_i(o->v[4].fi(o->v[3].o1)));} + +static bool int_optimize(s7_scheme *sc, s7_pointer expr); +static bool float_optimize(s7_scheme *sc, s7_pointer expr); + +static bool i_idp_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + s7_i_i_t func = s7_i_i_function(s_func); + s7_i_7i_t func7 = NULL; + s7_i_7p_t ipf; + s7_pointer p; + int32_t start = sc->pc; + opc->v[3].o1 = sc->opts[start]; + if (!func) + func7 = s7_i_7i_function(s_func); + if ((func) || (func7)) + { + if (func) + opc->v[2].i_i_f = func; + else opc->v[2].i_7i_f = func7; + if (is_t_integer(cadr(car_x))) + { + if (opc->v[2].i_i_f == subtract_i_i) + { + opc->v[1].i = -integer(cadr(car_x)); + opc->v[0].fi = opt_i_c; + } + else + { + opc->v[1].i = integer(cadr(car_x)); + opc->v[0].fi = (func) ? opt_i_i_c : opt_i_7i_c; + } + return_true(sc, car_x); + } + p = opt_integer_symbol(sc, cadr(car_x)); + if (p) + { + opc->v[1].p = p; + opc->v[0].fi = (func) ? ((func == abs_i_i) ? opt_i_i_s_abs : opt_i_i_s) : ((func7 == random_i_7i) ? opt_i_7i_s_rand : opt_i_7i_s); + return_true(sc, car_x); + } + if (int_optimize(sc, cdr(car_x))) + { + opc->v[4].fi = sc->opts[start]->v[0].fi; + opc->v[0].fi = (func) ? ((func == abs_i_i) ? opt_i_i_f_abs : opt_i_i_f) : opt_i_7i_f; + return_true(sc, car_x); + } + sc->pc = start; + } + if (!is_t_ratio(cadr(car_x))) + { + s7_i_7d_t idf = s7_i_7d_function(s_func); + if (idf) + { + opc->v[2].i_7d_f = idf; + if (is_small_real(cadr(car_x))) + { + opc->v[1].x = s7_number_to_real(sc, cadr(car_x)); + opc->v[0].fi = opt_i_d_c; + return_true(sc, car_x); + } + p = opt_float_symbol(sc, cadr(car_x)); + if (p) + { + opc->v[1].p = p; + opc->v[0].fi = opt_i_d_s; + return_true(sc, car_x); + } + if (float_optimize(sc, cdr(car_x))) + { + opc->v[0].fi = opt_i_7d_f; + opc->v[4].fd = sc->opts[start]->v[0].fd; + return_true(sc, car_x); + } + sc->pc = start; + }} + ipf = s7_i_7p_function(s_func); + if (ipf) + { + opc->v[2].i_7p_f = ipf; + if (cell_optimize(sc, cdr(car_x))) + { + opc->v[0].fi = (ipf == char_to_integer_i_7p) ? opt_i_7p_f_cint : opt_i_7p_f; + opc->v[4].fp = sc->opts[start]->v[0].fp; + return_true(sc, car_x); + } + sc->pc = start; + } + return_false(sc, car_x); +} + + +/* -------- i_pi -------- */ + +static s7_int opt_i_7pi_ss(opt_info *o) {return(o->v[3].i_7pi_f(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p))));} +static s7_int opt_7pi_ss_ivref(opt_info *o) {return(int_vector(slot_value(o->v[1].p), integer(slot_value(o->v[2].p))));} +static s7_int opt_7pi_ss_bvref(opt_info *o) {return(byte_vector(slot_value(o->v[1].p), integer(slot_value(o->v[2].p))));} +static s7_int opt_i_7pi_sf(opt_info *o) {return(o->v[3].i_7pi_f(o->sc, slot_value(o->v[1].p), o->v[5].fi(o->v[4].o1)));} + +static bool i_7pi_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + s7_pointer sig; + s7_i_7pi_t pfunc = s7_i_7pi_function(s_func); + if (!pfunc) + return_false(sc, car_x); + sig = c_function_signature(s_func); + if (is_pair(sig)) + { + s7_pointer slot, arg1 = cadr(car_x), arg2 = caddr(car_x); + int32_t start = sc->pc; + if ((is_symbol(cadr(sig))) && + (is_symbol(arg1)) && + (slot = opt_types_match(sc, cadr(sig), arg1))) + { + s7_pointer p; + opc->v[1].p = slot; + if ((s_func == global_value(sc->int_vector_ref_symbol)) && /* ivref etc */ + ((!is_int_vector(slot_value(slot))) || + (vector_rank(slot_value(slot)) > 1))) + return_false(sc, car_x); + if ((s_func == global_value(sc->byte_vector_ref_symbol)) && /* bvref etc */ + ((!is_byte_vector(slot_value(slot))) || + (vector_rank(slot_value(slot)) > 1))) + return_false(sc, car_x); + + opc->v[3].i_7pi_f = pfunc; + p = opt_integer_symbol(sc, arg2); + if (p) + { + opc->v[2].p = p; + opc->v[0].fi = opt_i_7pi_ss; + if ((s_func == global_value(sc->int_vector_ref_symbol)) && + (loop_end_fits(opc->v[2].p, vector_length(slot_value(opc->v[1].p))))) + { + opc->v[0].fi = opt_7pi_ss_ivref; + opc->v[3].i_7pi_f = int_vector_ref_i_7pi_direct; + } + else + if ((s_func == global_value(sc->byte_vector_ref_symbol)) && + (loop_end_fits(opc->v[2].p, vector_length(slot_value(opc->v[1].p))))) + { + opc->v[0].fi = opt_7pi_ss_bvref; + opc->v[3].i_7pi_f = byte_vector_ref_i_7pi_direct; + } + return_true(sc, car_x); + } + opc->v[4].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, cddr(car_x))) + { + opc->v[0].fi = opt_i_7pi_sf; + opc->v[5].fi = opc->v[4].o1->v[0].fi; + return_true(sc, car_x); + } + sc->pc = start; + }} + return_false(sc, car_x); +} + +/* -------- i_ii -------- */ +static s7_int opt_i_ii_cc(opt_info *o) {return(o->v[3].i_ii_f(o->v[1].i, o->v[2].i));} +static s7_int opt_i_ii_cs(opt_info *o) {return(o->v[3].i_ii_f(o->v[1].i, integer(slot_value(o->v[2].p))));} +static s7_int opt_i_ii_cs_mul(opt_info *o) {return(o->v[1].i * integer(slot_value(o->v[2].p)));} +static s7_int opt_i_ii_sc(opt_info *o) {return(o->v[3].i_ii_f(integer(slot_value(o->v[1].p)), o->v[2].i));} +static s7_int opt_i_ii_sc_add(opt_info *o) {return(integer(slot_value(o->v[1].p)) + o->v[2].i);} /* +1 is not faster */ +static s7_int opt_i_ii_sc_sub(opt_info *o) {return(integer(slot_value(o->v[1].p)) - o->v[2].i);} /* -1 is not faster */ +static s7_int opt_i_ii_ss(opt_info *o) {return(o->v[3].i_ii_f(integer(slot_value(o->v[1].p)), integer(slot_value(o->v[2].p))));} +static s7_int opt_i_ii_ss_add(opt_info *o) {return(integer(slot_value(o->v[1].p)) + integer(slot_value(o->v[2].p)));} +static s7_pointer opt_p_ii_ss_add(opt_info *o) {return(make_integer(o->sc, integer(slot_value(o->v[1].p)) + integer(slot_value(o->v[2].p))));} +static s7_int opt_i_ii_cf(opt_info *o) {return(o->v[3].i_ii_f(o->v[1].i, o->v[5].fi(o->v[4].o1)));} +static s7_int opt_i_ii_cf_mul(opt_info *o) {return(o->v[1].i * o->v[5].fi(o->v[4].o1));} +static s7_int opt_i_ii_sf(opt_info *o) {return(o->v[3].i_ii_f(integer(slot_value(o->v[1].p)), o->v[5].fi(o->v[4].o1)));} +static s7_int opt_i_ii_sf_add(opt_info *o) {return(integer(slot_value(o->v[1].p)) + o->v[5].fi(o->v[4].o1));} +static s7_int opt_i_ii_ff(opt_info *o) {return(o->v[3].i_ii_f(o->v[11].fi(o->v[10].o1), o->v[9].fi(o->v[8].o1)));} +static s7_int opt_i_7ii_ff_quo(opt_info *o){return(quotient_i_7ii(o->sc,o->v[11].fi(o->v[10].o1), o->v[9].fi(o->v[8].o1)));} +static s7_int opt_i_ii_fc(opt_info *o) {return(o->v[3].i_ii_f(o->v[11].fi(o->v[10].o1), o->v[2].i));} +static s7_int opt_i_ii_fc_add(opt_info *o) {return(o->v[11].fi(o->v[10].o1) + o->v[2].i);} +static s7_int opt_i_ii_fc_mul(opt_info *o) {return(o->v[11].fi(o->v[10].o1) * o->v[2].i);} +/* returning s7_int so overflow->real is not doable here, so + * (define (func) (do ((x 0) (i 0 (+ i 1))) ((= i 1) x) (set! x (* (lognot 4294967297) 4294967297)))) (func) (func) + * will return -12884901890 rather than -18446744086594454000.0, 4294967297 > sqrt(fixmost) + * This affects all the opt arithmetical functions. Unfortunately the gmp version also gets -12884901890! + * We need to make sure none of these are available in the gmp version. + */ +static s7_int opt_i_7ii_fc(opt_info *o) {return(o->v[3].i_7ii_f(o->sc, o->v[11].fi(o->v[10].o1), o->v[2].i));} +static s7_int opt_i_ii_fco(opt_info *o) {return(o->v[3].i_ii_f(o->v[4].i_7pi_f(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p))), o->v[5].i));} +static s7_int opt_i_ii_fco_ivref_add(opt_info *o){return(int_vector_ref_i_7pi_direct(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p))) + o->v[5].i);} /* tref */ +static s7_int opt_i_7ii_fco(opt_info *o) {return(o->v[3].i_7ii_f(o->sc, o->v[4].i_7pi_f(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p))), o->v[5].i));} + +static bool i_ii_fc_combinable(s7_scheme *sc, opt_info *opc, s7_i_ii_t func) +{ + if ((sc->pc > 1) && + (opc == sc->opts[sc->pc - 2])) + { + opt_info *o1 = sc->opts[sc->pc - 1]; + if ((o1->v[0].fi == opt_i_7pi_ss) || (o1->v[0].fi == opt_7pi_ss_ivref)) + { + opc->v[5].i = opc->v[2].i; /* move v2.i ("c" in fc = arg2) out of the symbols' way */ + opc->v[4].i_7pi_f = o1->v[3].i_7pi_f; + opc->v[1].p = o1->v[1].p; + opc->v[2].p = o1->v[2].p; + if (func) + opc->v[0].fi = ((opc->v[3].i_ii_f == add_i_ii) && (opc->v[4].i_7pi_f == int_vector_ref_i_7pi_direct)) ? opt_i_ii_fco_ivref_add : opt_i_ii_fco; + else opc->v[0].fi = opt_i_7ii_fco; + backup_pc(sc); + return_true(sc, NULL); + }} + return_false(sc, NULL); +} + +static s7_int opt_i_7ii_cc(opt_info *o) {return(o->v[3].i_7ii_f(o->sc, o->v[1].i, o->v[2].i));} +static s7_int opt_i_7ii_cs(opt_info *o) {return(o->v[3].i_7ii_f(o->sc, o->v[1].i, integer(slot_value(o->v[2].p))));} +static s7_int opt_i_7ii_sc(opt_info *o) {return(o->v[3].i_7ii_f(o->sc, integer(slot_value(o->v[1].p)), o->v[2].i));} +static s7_int opt_i_7ii_ss(opt_info *o) {return(o->v[3].i_7ii_f(o->sc, integer(slot_value(o->v[1].p)), integer(slot_value(o->v[2].p))));} +static s7_int opt_i_7ii_cf(opt_info *o) {return(o->v[3].i_7ii_f(o->sc, o->v[1].i, o->v[5].fi(o->v[4].o1)));} +static s7_int opt_i_7ii_sf(opt_info *o) {return(o->v[3].i_7ii_f(o->sc, integer(slot_value(o->v[1].p)), o->v[5].fi(o->v[4].o1)));} + +static s7_int opt_i_7ii_ff(opt_info *o) +{ + s7_int i1 = o->v[11].fi(o->v[10].o1); + s7_int i2 = o->v[9].fi(o->v[8].o1); + return(o->v[3].i_7ii_f(o->sc, i1, i2)); +} + +#if WITH_GMP +static s7_int opt_add_i_random_i(opt_info *o) {return(o->v[1].i + (s7_int)(o->v[2].i * next_random(o->sc)));} +static s7_int opt_subtract_random_i_i(opt_info *o) {return((s7_int)(o->v[1].i * next_random(o->sc)) - o->v[2].i);} +#else +static s7_int opt_add_i_random_i(opt_info *o) {return(o->v[1].i + (s7_int)(o->v[2].i * next_random(o->sc->default_random_state)));} +static s7_int opt_subtract_random_i_i(opt_info *o) {return((s7_int)(o->v[1].i * next_random(o->sc->default_random_state)) - o->v[2].i);} +#endif + +static bool i_ii_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + s7_i_ii_t ifunc = s7_i_ii_function(s_func); + s7_i_7ii_t ifunc7 = NULL; + s7_pointer sig; + + if (!ifunc) + { + ifunc7 = s7_i_7ii_function(s_func); + if (!ifunc7) + return_false(sc, car_x); + } + sig = c_function_signature(s_func); + if (is_pair(sig)) + { + s7_pointer arg1 = cadr(car_x); + s7_pointer arg2 = caddr(car_x); + int32_t start = sc->pc; + s7_pointer p; + if (ifunc) + opc->v[3].i_ii_f = ifunc; + else opc->v[3].i_7ii_f = ifunc7; + + if (is_t_integer(arg1)) + { + opc->v[1].i = integer(arg1); + if (is_t_integer(arg2)) + { + if (opc->v[3].i_ii_f == add_i_ii) + { + opc->v[1].i = integer(arg1) + integer(arg2); /* no overflow check for sc_add case above */ + opc->v[0].fi = opt_i_c; + } + else + { + opc->v[2].i = integer(arg2); + opc->v[0].fi = (ifunc) ? opt_i_ii_cc : opt_i_7ii_cc; + } + return_true(sc, car_x); + } + p = opt_integer_symbol(sc, arg2); + if (p) + { + opc->v[2].p = p; + if (ifunc) + opc->v[0].fi = (opc->v[3].i_ii_f == multiply_i_ii) ? opt_i_ii_cs_mul : opt_i_ii_cs; + else opc->v[0].fi = opt_i_7ii_cs; + return_true(sc, car_x); + } + opc->v[4].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, cddr(car_x))) + { + if (ifunc) + { + opc->v[0].fi = opt_i_ii_cf; /* sc->opts[start]->v[0].fi -> opt_i_7i_c -> same_opt->v[2].i_7i_f = random_i_7i tmap */ + if ((ifunc == add_i_ii) && (opc == sc->opts[sc->pc - 2]) && + (sc->opts[start]->v[0].fi == opt_i_7i_c) && + (sc->opts[start]->v[2].i_7i_f == random_i_7i)) + { + opc->v[0].fi = opt_add_i_random_i; + opc->v[2].i = sc->opts[start]->v[1].i; + backup_pc(sc); + } + else if (ifunc == multiply_i_ii) opc->v[0].fi = opt_i_ii_cf_mul; + } + else opc->v[0].fi = opt_i_7ii_cf; + opc->v[5].fi = opc->v[4].o1->v[0].fi; + return_true(sc, car_x); + } + sc->pc = start; + return_false(sc, car_x); + } + + /* arg1 not integer */ + p = opt_integer_symbol(sc, arg1); + if (p) + { + opc->v[1].p = p; + if (is_t_integer(arg2)) + { + opc->v[2].i = integer(arg2); + if (ifunc) + { + if (opc->v[3].i_ii_f == add_i_ii) + opc->v[0].fi = opt_i_ii_sc_add; + else opc->v[0].fi = (opc->v[3].i_ii_f == subtract_i_ii) ? opt_i_ii_sc_sub : opt_i_ii_sc; /* add1/sub1 are not faster */ + } + else opc->v[0].fi = opt_i_7ii_sc; + if ((car(car_x) == sc->modulo_symbol) && + (integer(arg2) > 1)) + opc->v[3].i_ii_f = modulo_i_ii_unchecked; + else + { + if (car(car_x) == sc->ash_symbol) + { + if (opc->v[2].i < 0) + { + opc->v[3].i_ii_f = (opc->v[2].i == -1) ? rsh_i_i2_direct : rsh_i_ii_unchecked; + opc->v[0].fi = opt_i_ii_sc; + } + else + if (opc->v[2].i < S7_INT_BITS) + { + opc->v[3].i_ii_f = lsh_i_ii_unchecked; + opc->v[0].fi = opt_i_ii_sc; + }} + else + if (opc->v[2].i > 0) + { + /* these assume vunion is a union, not a struct; i_7ii_f otherwise might be leftover from a previous use */ + if (opc->v[3].i_7ii_f == quotient_i_7ii) + { + opc->v[3].i_ii_f = quotient_i_ii_unchecked; + opc->v[0].fi = opt_i_ii_sc; + } + else + if ((opc->v[2].i > 1) && (opc->v[3].i_7ii_f == remainder_i_7ii)) + { + opc->v[3].i_ii_f = remainder_i_ii_unchecked; + opc->v[0].fi = opt_i_ii_sc; + }}} + return_true(sc, car_x); + } + + /* arg2 not integer, arg1 is int symbol */ + p = opt_integer_symbol(sc, arg2); + if (p) + { + opc->v[2].p = p; + if (ifunc) + opc->v[0].fi = (opc->v[3].i_ii_f == add_i_ii) ? opt_i_ii_ss_add : opt_i_ii_ss; + else opc->v[0].fi = opt_i_7ii_ss; + return_true(sc, car_x); + } + if (int_optimize(sc, cddr(car_x))) + { + opc->v[4].o1 = sc->opts[start]; + opc->v[5].fi = sc->opts[start]->v[0].fi; + if (ifunc) + opc->v[0].fi = (opc->v[3].i_ii_f == add_i_ii) ? opt_i_ii_sf_add : opt_i_ii_sf; + else opc->v[0].fi = opt_i_7ii_sf; + return_true(sc, car_x); + } + sc->pc = start; + return_false(sc, car_x); + } + + /* arg1 not int symbol */ + if (is_t_integer(arg2)) + { + opc->v[2].i = integer(arg2); + opc->v[10].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, cdr(car_x))) + { + opc->v[11].fi = opc->v[10].o1->v[0].fi; + if (!i_ii_fc_combinable(sc, opc, ifunc)) + { + if (ifunc) + { + if (opc->v[3].i_ii_f == add_i_ii) {opc->v[0].fi = opt_i_ii_fc_add; return_true(sc, car_x);} + if (opc->v[3].i_ii_f == multiply_i_ii) {opc->v[0].fi = opt_i_ii_fc_mul; return_true(sc, car_x);} + opc->v[0].fi = opt_i_ii_fc; + + if ((opc->v[3].i_ii_f == subtract_i_ii) && (opc == sc->opts[sc->pc - 2]) && + (sc->opts[start]->v[0].fi == opt_i_7i_c) && + (sc->opts[start]->v[2].i_7i_f == random_i_7i)) + { + opc->v[0].fi = opt_subtract_random_i_i; + opc->v[1].i = sc->opts[start]->v[1].i; + backup_pc(sc); + }} + else opc->v[0].fi = opt_i_7ii_fc; + if (opc->v[2].i > 0) + { + if (opc->v[3].i_7ii_f == quotient_i_7ii) + { + opc->v[3].i_ii_f = quotient_i_ii_unchecked; + opc->v[0].fi = opt_i_ii_fc; + } + else + if ((opc->v[2].i > 1) && (opc->v[3].i_7ii_f == remainder_i_7ii)) + { + opc->v[3].i_ii_f = remainder_i_ii_unchecked; + opc->v[0].fi = opt_i_ii_fc; + }}} + return_true(sc, car_x); + } + sc->pc = start; + return_false(sc, car_x); + } + + /* arg1 not integer or symbol, arg2 not integer */ + opc->v[10].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, cdr(car_x))) + { + opc->v[11].fi = opc->v[10].o1->v[0].fi; + opc->v[8].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, cddr(car_x))) + { + opc->v[9].fi = opc->v[8].o1->v[0].fi; + opc->v[0].fi = (ifunc) ? opt_i_ii_ff : ((opc->v[3].i_7ii_f == quotient_i_7ii) ? opt_i_7ii_ff_quo : opt_i_7ii_ff); + return_true(sc, car_x); + } + sc->pc = start; + }} + return_false(sc, car_x); +} + +/* -------- i_iii -------- */ +static s7_int opt_i_iii_fff(opt_info *o) +{ + s7_int i1 = o->v[11].fi(o->v[10].o1); + s7_int i2 = o->v[9].fi(o->v[8].o1); + s7_int i3 = o->v[5].fi(o->v[4].o1); + return(o->v[3].i_iii_f(i1, i2, i3)); +} + +static bool i_iii_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + int32_t start = sc->pc; + s7_i_iii_t ifunc = s7_i_iii_function(s_func); + if (!ifunc) + return_false(sc, car_x); + opc->v[10].o1 = sc->opts[start]; + if (int_optimize(sc, cdr(car_x))) + { + opc->v[8].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, cddr(car_x))) + { + opc->v[4].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, cdddr(car_x))) + { + opc->v[3].i_iii_f = ifunc; + opc->v[0].fi = opt_i_iii_fff; + opc->v[11].fi = opc->v[10].o1->v[0].fi; + opc->v[9].fi = opc->v[8].o1->v[0].fi; + opc->v[5].fi = opc->v[4].o1->v[0].fi; + return_true(sc, car_x); + }}} + sc->pc = start; + return_false(sc, car_x); +} + +/* -------- i_7pii -------- */ +static s7_int opt_i_7pii_ssf(opt_info *o) {return(o->v[3].i_7pii_f(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), o->v[5].fi(o->v[4].o1)));} +static s7_int opt_i_7pii_ssf_vset(opt_info *o) {return(int_vector_set_i_7pii_direct(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), o->v[5].fi(o->v[4].o1)));} +static s7_int opt_i_7pii_ssc(opt_info *o) {return(o->v[3].i_7pii_f(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), o->v[4].i));} +static s7_int opt_i_7pii_sss(opt_info *o) {return(o->v[4].i_7pii_f(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), integer(slot_value(o->v[3].p))));} +static s7_int opt_i_7pii_sif(opt_info *o) {return(o->v[3].i_7pii_f(o->sc, slot_value(o->v[1].p), o->v[12].i, o->v[9].fi(o->v[8].o1)));} + +static s7_int opt_i_pii_sss_ivref_unchecked(opt_info *o) +{ + s7_pointer v = slot_value(o->v[1].p); + return(int_vector(v, ((integer(slot_value(o->v[2].p)) * vector_offset(v, 0)) + integer(slot_value(o->v[3].p))))); +} + +static s7_int opt_i_7pii_sff(opt_info *o) +{ + s7_int i1 = o->v[11].fi(o->v[10].o1); + s7_int i2 = o->v[9].fi(o->v[8].o1); + return(o->v[3].i_7pii_f(o->sc, slot_value(o->v[1].p), i1, i2)); +} + +/* -------- i_7piii -------- */ +static s7_int opt_i_7piii_sssf(opt_info *o) +{ + return(o->v[5].i_7piii_f(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), integer(slot_value(o->v[3].p)), o->v[11].fi(o->v[10].o1))); +} + +static s7_int opt_i_piii_sssf_ivset_unchecked(opt_info *o) +{ + s7_pointer v = slot_value(o->v[1].p); + s7_int val = o->v[11].fi(o->v[10].o1); + int_vector(v, ((integer(slot_value(o->v[2].p)) * vector_offset(v, 0)) + integer(slot_value(o->v[3].p)))) = val; + return(val); +} + +static s7_int opt_i_7piii_sssc(opt_info *o) +{ + return(o->v[5].i_7piii_f(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), integer(slot_value(o->v[3].p)), o->v[4].i)); +} + +static s7_int opt_i_7piii_ssss(opt_info *o) +{ + return(o->v[5].i_7piii_f(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), integer(slot_value(o->v[3].p)), integer(slot_value(o->v[4].p)))); +} + +static s7_int opt_i_7piii_sfff(opt_info *o) +{ + s7_int i1 = o->v[11].fi(o->v[10].o1); + s7_int i2 = o->v[9].fi(o->v[8].o1); + s7_int i3 = o->v[6].fi(o->v[4].o1); + return(o->v[5].i_7piii_f(o->sc, slot_value(o->v[1].p), i1, i2, i3)); +} + +static bool opt_i_7piii_args(s7_scheme *sc, opt_info *opc, s7_pointer indexp1, s7_pointer indexp2, s7_pointer valp) +{ + /* opc->v[5] is the called function (int-vector-set! etc) */ + s7_pointer slot = opt_integer_symbol(sc, car(indexp2)); + if (slot) + { + opc->v[3].p = slot; + slot = opt_integer_symbol(sc, car(indexp1)); + if (slot) + { + opc->v[2].p = slot; + if (is_t_integer(car(valp))) + { + opc->v[0].fi = opt_i_7piii_sssc; + opc->v[4].i = integer(car(valp)); + return_true(sc, NULL); + } + slot = opt_integer_symbol(sc, car(valp)); + if (slot) + { + opc->v[4].p = slot; + opc->v[0].fi = opt_i_7piii_ssss; + return_true(sc, NULL); + } + opc->v[10].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, valp)) + { + opc->v[11].fi = opc->v[10].o1->v[0].fi; + opc->v[0].fi = opt_i_7piii_sssf; + if ((opc->v[5].i_7piii_f == int_vector_set_i_7piii) && + (loop_end_fits(opc->v[2].p, vector_dimension(slot_value(opc->v[1].p), 0))) && + (loop_end_fits(opc->v[3].p, vector_dimension(slot_value(opc->v[1].p), 1)))) + opc->v[0].fi = opt_i_piii_sssf_ivset_unchecked; + return_true(sc, NULL); + }} + return_false(sc, NULL); + } + opc->v[10].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, indexp1)) + { + opc->v[8].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, indexp2)) + { + opc->v[4].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, valp)) + { + opc->v[0].fi = opt_i_7piii_sfff; + opc->v[11].fi = opc->v[10].o1->v[0].fi; + opc->v[9].fi = opc->v[8].o1->v[0].fi; + opc->v[6].fi = opc->v[4].o1->v[0].fi; /* v[5] is in use */ + return_true(sc, NULL); + }}} + return_false(sc, indexp1); +} + +static bool opt_int_vector_set(s7_scheme *sc, int32_t otype, opt_info *opc, s7_pointer v, s7_pointer indexp1, s7_pointer indexp2, s7_pointer valp) +{ + s7_pointer settee = s7_slot(sc, v); + if ((is_slot(settee)) && + (!is_immutable(slot_value(settee)))) + { + s7_pointer slot, vect = slot_value(settee); + bool int_case = (is_int_vector(vect)); + opc->v[1].p = settee; + if ((int_case) || (is_byte_vector(vect))) + { + if ((otype >= 0) && (otype != ((int_case) ? 1 : 0))) + return_false(sc, indexp1); + if ((!indexp2) && + (vector_rank(vect) == 1)) + { + opc->v[3].i_7pii_f = (int_case) ? int_vector_set_i_7pii : byte_vector_set_i_7pii; + slot = opt_integer_symbol(sc, car(indexp1)); + if (slot) + { + int32_t start = sc->pc; + opc->v[2].p = slot; + if (loop_end_fits(opc->v[2].p, vector_length(vect))) + opc->v[3].i_7pii_f = (int_case) ? int_vector_set_i_7pii_direct : byte_vector_set_i_7pii_direct; + if ((is_pair(valp)) && + (is_null(cdr(valp))) && + (is_t_integer(car(valp)))) + { + opc->v[4].i = integer(car(valp)); + opc->v[0].fi = opt_i_7pii_ssc; + return_true(sc, NULL); + } + if (!int_optimize(sc, valp)) + return_false(sc, NULL); + opc->v[0].fi = (opc->v[3].i_7pii_f == int_vector_set_i_7pii_direct) ? opt_i_7pii_ssf_vset : opt_i_7pii_ssf; + opc->v[4].o1 = sc->opts[start]; + opc->v[5].fi = sc->opts[start]->v[0].fi; + return_true(sc, NULL); + } + opc->v[10].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, indexp1)) + { + opc->v[8].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, valp)) + { + opc->v[11].fi = opc->v[10].o1->v[0].fi; + opc->v[9].fi = opc->v[8].o1->v[0].fi; + if (opc->v[11].fi == opt_i_c) /* (int-vector-set! v 0 (floor (sqrt i))) */ + { + opc->v[0].fi = opt_i_7pii_sif; + opc->v[12].i = opc->v[10].o1->v[1].i; + } + else opc->v[0].fi = opt_i_7pii_sff; + return_true(sc, NULL); + }} + return_false(sc, NULL); + } + if ((indexp2) && + (vector_rank(vect) == 2)) + { + opc->v[5].i_7piii_f = (int_case) ? int_vector_set_i_7piii : byte_vector_set_i_7piii; + return(opt_i_7piii_args(sc, opc, indexp1, indexp2, valp)); + }}} + return_false(sc, v); +} + +static bool is_target_or_its_alias(const s7_pointer symbol, const s7_pointer symfunc, s7_pointer target) +{ + return((symbol == target) || (symfunc == initial_value(target))); +} + +static bool i_7pii_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + s7_pointer sig; + s7_i_7pii_t pfunc = s7_i_7pii_function(s_func); + if (!pfunc) + return_false(sc, car_x); + sig = c_function_signature(s_func); + if ((is_pair(sig)) && + (is_symbol(cadr(car_x)))) + { + s7_pointer slot, fname = car(car_x); + + if ((is_target_or_its_alias(fname, s_func, sc->int_vector_set_symbol)) || + (is_target_or_its_alias(fname, s_func, sc->byte_vector_set_symbol))) + return(opt_int_vector_set(sc, (fname == sc->int_vector_set_symbol) ? 1 : 0, opc, cadr(car_x), cddr(car_x), NULL, cdddr(car_x))); + + slot = opt_types_match(sc, cadr(sig), cadr(car_x)); + if (slot) + { + s7_pointer arg2, p; + int32_t start = sc->pc; + opc->v[1].p = slot; + + if (((is_target_or_its_alias(fname, s_func, sc->int_vector_ref_symbol)) || + (is_target_or_its_alias(fname, s_func, sc->byte_vector_ref_symbol))) && + (vector_rank(slot_value(slot)) != 2)) + return_false(sc, car_x); + + arg2 = caddr(car_x); + p = opt_integer_symbol(sc, arg2); + if (p) + { + opc->v[2].p = p; + p = opt_integer_symbol(sc, cadddr(car_x)); + if (p) + { + opc->v[3].p = p; + opc->v[4].i_7pii_f = pfunc; + opc->v[0].fi = opt_i_7pii_sss; + if ((pfunc == int_vector_ref_i_7pii) && + (loop_end_fits(opc->v[2].p, vector_dimension(slot_value(opc->v[1].p), 0))) && + (loop_end_fits(opc->v[3].p, vector_dimension(slot_value(opc->v[1].p), 1)))) + opc->v[0].fi = opt_i_pii_sss_ivref_unchecked; + return_true(sc, car_x); + } + if (int_optimize(sc, cdddr(car_x))) + { + opc->v[3].i_7pii_f = pfunc; + opc->v[0].fi = opt_i_7pii_ssf; + opc->v[4].o1 = sc->opts[start]; + opc->v[5].fi = sc->opts[start]->v[0].fi; + return_true(sc, car_x); + } + return_false(sc, car_x); + } + opc->v[10].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, cddr(car_x))) + { + opc->v[8].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, cdddr(car_x))) + { + opc->v[3].i_7pii_f = pfunc; + opc->v[0].fi = opt_i_7pii_sff; + opc->v[11].fi = opc->v[10].o1->v[0].fi; + opc->v[9].fi = opc->v[8].o1->v[0].fi; + return_true(sc, car_x); + }} + sc->pc = start; + }} + return_false(sc, car_x); +} + +static bool i_7piii_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + s7_i_7piii_t f = s7_i_7piii_function(s_func); + if ((f) && + (is_symbol(cadr(car_x)))) + { + s7_pointer settee; + if ((is_target_or_its_alias(car(car_x), s_func, sc->int_vector_set_symbol)) || + (is_target_or_its_alias(car(car_x), s_func, sc->byte_vector_set_symbol))) + return(opt_int_vector_set(sc, (car(car_x) == sc->int_vector_set_symbol) ? 1 : 0, opc, cadr(car_x), cddr(car_x), cdddr(car_x), cddddr(car_x))); + + settee = s7_slot(sc, cadr(car_x)); + if (is_slot(settee)) + { + s7_pointer vect = slot_value(settee); + if ((is_int_vector(vect)) && (vector_rank(vect) == 3)) + { + opc->v[5].i_7piii_f = f; + opc->v[1].p = settee; + return(opt_i_7piii_args(sc, opc, cddr(car_x), cdddr(car_x), cddddr(car_x))); + }}} + return_false(sc, car_x); +} + +/* -------- i_add|multiply_any -------- */ +static s7_int opt_i_add_any_f(opt_info *o) +{ + s7_int sum = 0; + for (s7_int i = 0; i < o->v[1].i; i++) + { + opt_info *o1 = o->v[i + 2].o1; + sum += o1->v[0].fi(o1); + } + return(sum); +} + +static s7_int opt_i_add2(opt_info *o) +{ + s7_int sum = o->v[6].fi(o->v[2].o1); + return(sum + o->v[7].fi(o->v[3].o1)); +} + +static s7_int opt_i_mul2(opt_info *o) +{ + s7_int sum = o->v[6].fi(o->v[2].o1); + return(sum * o->v[7].fi(o->v[3].o1)); +} + +static s7_int opt_i_add3(opt_info *o) +{ + s7_int sum = o->v[6].fi(o->v[2].o1); + sum += o->v[7].fi(o->v[3].o1); + return(sum + o->v[8].fi(o->v[4].o1)); +} + +static s7_int opt_i_mul3(opt_info *o) +{ + s7_int sum = o->v[6].fi(o->v[2].o1); + sum *= o->v[7].fi(o->v[3].o1); + return(sum * o->v[8].fi(o->v[4].o1)); +} + +static s7_int opt_i_add4(opt_info *o) +{ + s7_int sum = o->v[6].fi(o->v[2].o1); + sum += o->v[7].fi(o->v[3].o1); + sum += o->v[8].fi(o->v[4].o1); + return(sum + o->v[9].fi(o->v[5].o1)); +} + +static s7_int opt_i_mul4(opt_info *o) +{ + s7_int sum = o->v[6].fi(o->v[2].o1); + sum *= o->v[7].fi(o->v[3].o1); + sum *= o->v[8].fi(o->v[4].o1); + return(sum * o->v[9].fi(o->v[5].o1)); +} + +static s7_int opt_i_multiply_any_f(opt_info *o) +{ + s7_int sum = 1; + for (s7_int i = 0; i < o->v[1].i; i++) + { + opt_info *o1 = o->v[i + 2].o1; + sum *= o1->v[0].fi(o1); + } + return(sum); +} + +static bool i_add_any_ok(s7_scheme *sc, opt_info *opc, s7_pointer car_x) +{ + s7_pointer p, head = car(car_x); + int32_t cur_len, start = sc->pc; + for (cur_len = 0, p = cdr(car_x); (is_pair(p)) && (cur_len < 12); p = cdr(p), cur_len++) + { + opc->v[cur_len + 2].o1 = sc->opts[sc->pc]; + if (!int_optimize(sc, p)) + break; + } + if (is_null(p)) + { + opc->v[1].i = cur_len; + if (cur_len <= 4) + for (int32_t i = 0; i < cur_len; i++) + opc->v[i + 6].fi = opc->v[i + 2].o1->v[0].fi; + if (cur_len == 2) + opc->v[0].fi = (head == sc->add_symbol) ? opt_i_add2 : opt_i_mul2; + else + if (cur_len == 3) + opc->v[0].fi = (head == sc->add_symbol) ? opt_i_add3 : opt_i_mul3; + else + if (cur_len == 4) + opc->v[0].fi = (head == sc->add_symbol) ? opt_i_add4 : opt_i_mul4; + else opc->v[0].fi = (head == sc->add_symbol) ? opt_i_add_any_f : opt_i_multiply_any_f; + return_true(sc, car_x); + } + sc->pc = start; + return_false(sc, car_x); +} + + +/* -------- set_i_i -------- */ +static s7_int opt_set_i_i_f(opt_info *o) +{ + s7_int x = o->v[3].fi(o->v[2].o1); + slot_set_value(o->v[1].p, make_integer(o->sc, x)); + return(x); +} + +static s7_int opt_set_i_i_fm(opt_info *o) /* called in increment: (set! sum (+ sum (...))) where are all ints */ +{ + s7_int x = o->v[3].fi(o->v[2].o1); + integer(slot_value(o->v[1].p)) = x; + return(x); +} + +static s7_int opt_set_i_i_fo(opt_info *o) +{ + s7_int x = integer(slot_value(o->v[3].p)) + o->v[2].i; + slot_set_value(o->v[1].p, make_integer(o->sc, x)); + return(x); +} + +static bool set_i_i_f_combinable(s7_scheme *sc, opt_info *opc) +{ + if ((sc->pc > 1) && + (opc == sc->opts[sc->pc - 2])) + { + opt_info *o1 = sc->opts[sc->pc - 1]; + if (o1->v[0].fi == opt_i_ii_sc_add) + { + /* opc->v[4].i_ii_f = o1->v[3].i_ii_f; */ + opc->v[3].p = o1->v[1].p; + opc->v[2].i = o1->v[2].i; + opc->v[0].fi = opt_set_i_i_fo; + backup_pc(sc); + return_true(sc, NULL); /* ii_sc v[1].p is a slot */ + }} + return_false(sc, NULL); +} + +static bool i_syntax_ok(s7_scheme *sc, s7_pointer car_x, int32_t len) +{ + if ((car(car_x) == sc->set_symbol) && + (len == 3)) + { + opt_info *opc = alloc_opt_info(sc); + if (is_symbol(cadr(car_x))) /* (set! i 3) */ + { + s7_pointer settee; + if (is_immutable(cadr(car_x))) + return_false(sc, car_x); + settee = s7_slot(sc, cadr(car_x)); + if ((is_slot(settee)) && + (is_t_integer(slot_value(settee))) && + (!is_immutable_slot(settee)) && + ((!slot_has_setter(settee)) || + ((is_c_function(slot_setter(settee))) && + ((slot_setter(settee) == initial_value(sc->is_integer_symbol)) || + (c_function_call(slot_setter(settee)) == b_is_integer_setter))))) + /* opt set! won't change type, and it is an integer now (and we might not hit opt_cell_set) */ + { + opt_info *o1 = sc->opts[sc->pc]; + opc->v[1].p = settee; + if (int_optimize(sc, cddr(car_x))) + { + if (set_i_i_f_combinable(sc, opc)) + return_true(sc, car_x); + opc->v[0].fi = (is_mutable_integer(slot_value(opc->v[1].p))) ? opt_set_i_i_fm : opt_set_i_i_f; + opc->v[2].o1 = o1; + opc->v[3].fi = o1->v[0].fi; + return_true(sc, car_x); /* or OO_I? */ + }}} + else + if ((is_pair(cadr(car_x))) && /* if is_pair(settee) get setter */ + (is_symbol(caadr(car_x))) && + (is_pair(cdadr(car_x)))) + { + if (is_null(cddadr(car_x))) + return(opt_int_vector_set(sc, -1, opc, caadr(car_x), cdadr(car_x), NULL, cddr(car_x))); + if (is_null(cdddr(cadr(car_x)))) + return(opt_int_vector_set(sc, -1, opc, caadr(car_x), cdadr(car_x), cddadr(car_x), cddr(car_x))); + }} + return_false(sc, car_x); +} + +static bool i_implicit_ok(s7_scheme *sc, s7_pointer s_slot, s7_pointer car_x, int32_t len) +{ + s7_pointer obj = slot_value(s_slot); + if ((is_int_vector(obj)) || (is_byte_vector(obj))) + { + bool int_case = is_int_vector(obj); + s7_pointer slot; + + if ((len == 2) && + (vector_rank(obj) == 1)) + { + opt_info *opc = alloc_opt_info(sc); + opc->v[1].p = s_slot; + slot = opt_integer_symbol(sc, cadr(car_x)); + if (slot) + { + opc->v[0].fi = opt_i_7pi_ss; + opc->v[3].i_7pi_f = (int_case) ? int_vector_ref_i_7pi : byte_vector_ref_i_7pi; + opc->v[2].p = slot; + if (loop_end_fits(opc->v[2].p, vector_length(obj))) + opc->v[3].i_7pi_f = (int_case) ? int_vector_ref_i_7pi_direct : byte_vector_ref_i_7pi_direct; + /* not opc->v[0].fi = opt_7pi_ss_ivref -- this causes a huge slowdown in dup.scm?? */ + return_true(sc, car_x); + } + opc->v[4].o1 = sc->opts[sc->pc]; + if (!int_optimize(sc, cdr(car_x))) + return_false(sc, car_x); + opc->v[0].fi = opt_i_7pi_sf; + opc->v[3].i_7pi_f = (int_case) ? int_vector_ref_i_7pi : byte_vector_ref_i_7pi; + opc->v[5].fi = opc->v[4].o1->v[0].fi; + return_true(sc, car_x); + } + if ((len == 3) && + (vector_rank(obj) == 2)) + { + opt_info *opc = alloc_opt_info(sc); + opc->v[1].p = s_slot; + slot = opt_integer_symbol(sc, cadr(car_x)); + if (slot) + { + opc->v[2].p = slot; + slot = opt_integer_symbol(sc, caddr(car_x)); + if (!slot) + return_false(sc, car_x); + opc->v[4].i_7pii_f = (int_case) ? int_vector_ref_i_7pii : byte_vector_ref_i_7pii; + opc->v[3].p = slot; + opc->v[0].fi = opt_i_7pii_sss; + if ((int_case) && + (loop_end_fits(opc->v[2].p, vector_dimension(obj, 0))) && + (loop_end_fits(opc->v[3].p, vector_dimension(obj, 1)))) + opc->v[0].fi = opt_i_pii_sss_ivref_unchecked; + return_true(sc, car_x); + } + opc->v[10].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, cdr(car_x))) + { + opc->v[8].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, cddr(car_x))) + { + opc->v[3].i_7pii_f = (int_case) ? int_vector_ref_i_7pii : byte_vector_ref_i_7pii; + opc->v[0].fi = opt_i_7pii_sff; + opc->v[11].fi = opc->v[10].o1->v[0].fi; + opc->v[9].fi = opc->v[8].o1->v[0].fi; + return_true(sc, car_x); + }}}} + return_false(sc, car_x); +} + + +/* ------------------------------------- float opts ------------------------------------------- */ +static s7_double opt_d_c(opt_info *o) {return(o->v[1].x);} +static s7_double opt_d_s(opt_info *o) {return(real(slot_value(o->v[1].p)));} + +static s7_double opt_D_s(opt_info *o) +{ + s7_pointer x = slot_value(o->v[1].p); + return((is_t_integer(x)) ? (s7_double)(integer(x)) : s7_number_to_real(o->sc, x)); +} + +static bool opt_float_not_pair(s7_scheme *sc, s7_pointer car_x) +{ + s7_pointer p; + if (is_small_real(car_x)) + { + opt_info *opc = alloc_opt_info(sc); + opc->v[1].x = s7_number_to_real(sc, car_x); + opc->v[0].fd = opt_d_c; + return_true(sc, car_x); + } + p = opt_real_symbol(sc, car_x); + if (p) + { + opt_info *opc = alloc_opt_info(sc); + opc->v[1].p = p; + opc->v[0].fd = (is_t_real(slot_value(p))) ? opt_d_s : opt_D_s; + return_true(sc, car_x); + } + return_false(sc, car_x); +} + +/* -------- d -------- */ +static s7_double opt_d_f(opt_info *o) {return(o->v[1].d_f());} + +static bool d_ok(s7_scheme *unused_sc, opt_info *opc, s7_pointer s_func) /* (f): (mus-srate) */ +{ + s7_d_t func = s7_d_function(s_func); + if (!func) + return_false(sc, NULL); + opc->v[0].fd = opt_d_f; + opc->v[1].d_f = func; + return_true(sc, NULL); +} + +/* -------- d_d -------- */ +static s7_double opt_d_d_c(opt_info *o) {return(o->v[3].d_d_f(o->v[1].x));} +static s7_double opt_d_d_s(opt_info *o) {return(o->v[3].d_d_f(real(slot_value(o->v[1].p))));} +static s7_double opt_d_d_s_abs(opt_info *o) {return(abs_d_d(real(slot_value(o->v[1].p))));} +static s7_double opt_d_7d_c(opt_info *o) {return(o->v[3].d_7d_f(o->sc, o->v[1].x));} +static s7_double opt_d_7d_s(opt_info *o) {return(o->v[3].d_7d_f(o->sc, real(slot_value(o->v[1].p))));} +static s7_double opt_d_d_f(opt_info *o) {return(o->v[3].d_d_f(o->v[5].fd(o->v[4].o1)));} +static s7_double opt_d_d_f_abs(opt_info *o) {return(abs_d_d(o->v[5].fd(o->v[4].o1)));} +static s7_double opt_d_d_f_sin(opt_info *o) {return(sin_d_d(o->v[5].fd(o->v[4].o1)));} +static s7_double opt_d_d_f_cos(opt_info *o) {return(cos_d_d(o->v[5].fd(o->v[4].o1)));} +static s7_double opt_d_7d_f(opt_info *o) {return(o->v[3].d_7d_f(o->sc, o->v[5].fd(o->v[4].o1)));} +static s7_double opt_d_7d_f_divide(opt_info *o) {return(divide_d_7d(o->sc, o->v[5].fd(o->v[4].o1)));} + +static s7_double opt_d_7pi_ss_fvref_direct(opt_info *o); +static s7_double opt_abs_d_ss_fvref(opt_info *o) +{ + opt_info *o1 = o->v[4].o1; + return(abs_d_d(float_vector(slot_value(o1->v[1].p), integer(slot_value(o1->v[2].p))))); +} + +static bool d_d_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + s7_d_7d_t func7 = NULL; + int32_t start = sc->pc; + s7_d_d_t func = s7_d_d_function(s_func); + if (!func) func7 = s7_d_7d_function(s_func); + if ((func) || (func7)) + { + s7_pointer p; + if (func) + opc->v[3].d_d_f = func; + else opc->v[3].d_7d_f = func7; + if (is_small_real(cadr(car_x))) + { + if ((!is_t_real(cadr(car_x))) && /* (random 1) != (random 1.0) */ + ((car(car_x) == sc->random_symbol) || + (car(car_x) == sc->sin_symbol) || (car(car_x) == sc->cos_symbol))) + return_false(sc, car_x); + opc->v[1].x = s7_number_to_real(sc, cadr(car_x)); + opc->v[0].fd = (func) ? opt_d_d_c : opt_d_7d_c; + return_true(sc, car_x); + } + p = opt_float_symbol(sc, cadr(car_x)); + if ((p) && + (!has_methods(slot_value(p)))) + { + opc->v[1].p = p; + opc->v[0].fd = (func) ? ((func == abs_d_d) ? opt_d_d_s_abs : opt_d_d_s) : opt_d_7d_s; + return_true(sc, car_x); + } + opc->v[4].o1 = sc->opts[sc->pc]; + if (float_optimize(sc, cdr(car_x))) + { + opc->v[0].fd = (func) ? ((func == abs_d_d) ? opt_d_d_f_abs : ((func == sin_d_d) ? opt_d_d_f_sin : + ((func == cos_d_d) ? opt_d_d_f_cos : opt_d_d_f))) : + ((func7 == divide_d_7d) ? opt_d_7d_f_divide : opt_d_7d_f); + opc->v[5].fd = opc->v[4].o1->v[0].fd; + if ((func == abs_d_d) && (opc->v[5].fd == opt_d_7pi_ss_fvref_direct)) + opc->v[0].fd = opt_abs_d_ss_fvref; + return_true(sc, car_x); + } + sc->pc = start; + } + return_false(sc, car_x); +} + +/* -------- d_v -------- */ +static s7_double opt_d_v(opt_info *o) {return(o->v[3].d_v_f(o->v[5].obj));} + +static bool d_v_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + s7_pointer sig; + s7_d_v_t flt_func = s7_d_v_function(s_func); + if (!flt_func) + return_false(sc, car_x); + sig = c_function_signature(s_func); + if ((is_pair(sig)) && + (is_symbol(cadr(sig))) && + (is_symbol(cadr(car_x)))) /* look for (oscil g) */ + { + s7_pointer slot = opt_types_match(sc, cadr(sig), cadr(car_x)); + if (slot) + { + opc->v[1].p = slot; + opc->v[5].obj = (void *)c_object_value(slot_value(slot)); + opc->v[3].d_v_f = flt_func; + opc->v[0].fd = opt_d_v; + return_true(sc, car_x); + }} + return_false(sc, car_x); +} + +/* -------- d_p -------- */ +static s7_double opt_d_p_s(opt_info *o) {return(o->v[3].d_p_f(slot_value(o->v[1].p)));} +static s7_double opt_d_p_f(opt_info *o) {return(o->v[3].d_p_f(o->v[5].fp(o->v[4].o1)));} + +static bool d_p_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + int32_t start = sc->pc; + s7_d_p_t dpf = s7_d_p_function(s_func); /* mostly clm gens */ + if (!dpf) + return_false(sc, car_x); + opc->v[3].d_p_f = dpf; + if (is_symbol(cadr(car_x))) + { + s7_pointer slot = opt_simple_symbol(sc, cadr(car_x)); + if (!slot) + return_false(sc, car_x); + opc->v[1].p = slot; + opc->v[0].fd = opt_d_p_s; + return_true(sc, car_x); + } + opc->v[4].o1 = sc->opts[sc->pc]; + if (cell_optimize(sc, cdr(car_x))) + { + opc->v[0].fd = opt_d_p_f; + opc->v[5].fp = opc->v[4].o1->v[0].fp; + return_true(sc, car_x); + } + sc->pc = start; + return_false(sc, car_x); +} + +/* -------- d_7pi -------- */ + +static s7_double opt_d_7pi_sc(opt_info *o) {return(o->v[3].d_7pi_f(o->sc, slot_value(o->v[1].p), o->v[2].i));} +static s7_double opt_d_7pi_ss(opt_info *o) {return(o->v[3].d_7pi_f(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p))));} +static s7_double opt_d_7pi_sf(opt_info *o) {return(o->v[3].d_7pi_f(o->sc, slot_value(o->v[1].p), o->v[11].fi(o->v[10].o1)));} +static s7_double opt_d_7pi_ss_fvref(opt_info *o) {return(float_vector_ref_d_7pi(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p))));} +static s7_double opt_d_7pi_ss_fvref_direct(opt_info *o) {return(float_vector(slot_value(o->v[1].p), integer(slot_value(o->v[2].p))));} + +static s7_double opt_d_7pi_ff(opt_info *o) +{ + s7_pointer seq = o->v[5].fp(o->v[4].o1); + return(o->v[3].d_7pi_f(o->sc, seq, o->v[9].fi(o->v[8].o1))); +} + +static bool d_7pi_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + /* float-vector-ref is checked for a 1D float-vector arg, but other callers should do type checking */ + int32_t start = sc->pc; + s7_d_7pi_t ifunc = s7_d_7pi_function(s_func); /* ifunc: float_vector_ref_d_7pi, s_func: global_value(sc->float_vector_ref_symbol) */ + if (!ifunc) + return_false(sc, car_x); + opc->v[3].d_7pi_f = ifunc; + if (is_symbol(cadr(car_x))) /* (float-vector-ref v i) */ + { + s7_pointer arg2, p, obj; + opc->v[1].p = s7_slot(sc, cadr(car_x)); + if (!is_slot(opc->v[1].p)) + return_false(sc, car_x); + + obj = slot_value(opc->v[1].p); + if ((is_target_or_its_alias(car(car_x), s_func, sc->float_vector_ref_symbol)) && + ((!is_float_vector(obj)) || /* if it's float-vector-ref, make sure obj is a float-vector */ + (vector_rank(obj) > 1))) + return_false(sc, car_x); /* but if it's e.g. (block-ref...), go on */ + + arg2 = caddr(car_x); + if (!is_pair(arg2)) + { + if (is_t_integer(arg2)) + { + opc->v[2].i = integer(arg2); + opc->v[0].fd = opt_d_7pi_sc; + return_true(sc, car_x); + } + p = opt_integer_symbol(sc, arg2); + if (!p) + return_false(sc, car_x); + opc->v[2].p = p; + opc->v[0].fd = opt_d_7pi_ss; + if (is_target_or_its_alias(car(car_x), s_func, sc->float_vector_ref_symbol)) + opc->v[0].fd = (loop_end_fits(opc->v[2].p, vector_length(obj))) ? opt_d_7pi_ss_fvref_direct : opt_d_7pi_ss_fvref; + return_true(sc, car_x); + } + if (int_optimize(sc, cddr(car_x))) + { + opc->v[0].fd = opt_d_7pi_sf; + opc->v[10].o1 = sc->opts[start]; + opc->v[11].fi = opc->v[10].o1->v[0].fi; + return_true(sc, car_x); + } + sc->pc = start; + return_false(sc, car_x); + } + + if ((is_target_or_its_alias(car(car_x), s_func, sc->float_vector_ref_symbol)) && + ((!is_float_vector(cadr(car_x))) || + (vector_rank(cadr(car_x)) > 1))) /* (float-vector-ref #r2d((.1 .2) (.3 .4)) 3) */ + return_false(sc, car_x); + + if (cell_optimize(sc, cdr(car_x))) + { + opt_info *o2 = sc->opts[sc->pc]; + if (int_optimize(sc, cddr(car_x))) + { + opc->v[0].fd = opt_d_7pi_ff; + opc->v[4].o1 = sc->opts[start]; + opc->v[5].fp = sc->opts[start]->v[0].fp; + opc->v[8].o1 = o2; + opc->v[9].fi = o2->v[0].fi; + return_true(sc, car_x); + }} + sc->pc = start; + return_false(sc, car_x); +} + +/* -------- d_ip -------- */ +static s7_double opt_d_ip_ss(opt_info *o) {return(o->v[3].d_ip_f(integer(slot_value(o->v[1].p)), slot_value(o->v[2].p)));} + +static bool d_ip_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + s7_d_ip_t pfunc = s7_d_ip_function(s_func); + if ((pfunc) && + (is_symbol(caddr(car_x)))) + { + s7_pointer p = opt_integer_symbol(sc, cadr(car_x)); + if (p) + { + opc->v[3].d_ip_f = pfunc; + opc->v[1].p = p; + opc->v[2].p = s7_slot(sc, caddr(car_x)); + if (is_slot(opc->v[2].p)) /* (with-sound (:reverb jc-reverb) (fm-violin 0 .1 440 .4 :reverb-amount .5)) */ + { + opc->v[0].fd = opt_d_ip_ss; + return_true(sc, car_x); + }}} + return_false(sc, car_x); +} + +/* -------- d_pd -------- */ +static s7_double opt_d_pd_sf(opt_info *o) {return(o->v[3].d_pd_f(slot_value(o->v[1].p), o->v[11].fd(o->v[10].o1)));} +static s7_double opt_d_pd_ss(opt_info *o) {return(o->v[3].d_pd_f(slot_value(o->v[1].p), real(slot_value(o->v[2].p))));} + +static bool d_pd_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + if (is_symbol(cadr(car_x))) + { + s7_d_pd_t func = s7_d_pd_function(s_func); + if (func) + { + s7_pointer p, arg2 = caddr(car_x); + int32_t start = sc->pc; + opc->v[3].d_pd_f = func; + opc->v[1].p = s7_slot(sc, cadr(car_x)); + if (!is_slot(opc->v[1].p)) + return_false(sc, car_x); + p = opt_float_symbol(sc, arg2); + if (p) + { + opc->v[2].p = p; + opc->v[0].fd = opt_d_pd_ss; + return_true(sc, car_x); + } + opc->v[10].o1 = sc->opts[sc->pc]; + if (float_optimize(sc, cddr(car_x))) + { + opc->v[0].fd = opt_d_pd_sf; + opc->v[11].fd = opc->v[10].o1->v[0].fd; + return_true(sc, car_x); + } + sc->pc = start; + }} + return_false(sc, car_x); +} + +/* -------- d_vd -------- */ +static s7_double opt_d_vd_c(opt_info *o) {return(o->v[3].d_vd_f(o->v[5].obj, o->v[2].x));} +static s7_double opt_d_vd_s(opt_info *o) {return(o->v[3].d_vd_f(o->v[5].obj, real(slot_value(o->v[2].p))));} +static s7_double opt_d_vd_f(opt_info *o) {return(o->v[3].d_vd_f(o->v[5].obj, o->v[9].fd(o->v[8].o1)));} +static s7_double opt_d_vd_o(opt_info *o) {return(o->v[3].d_vd_f(o->v[5].obj, o->v[4].d_v_f(o->v[6].obj)));} +static s7_double opt_d_vd_o1_mul(opt_info *o) {return(o->v[3].d_vd_f(o->v[5].obj, real(slot_value(o->v[2].p)) * o->v[11].fd(o->v[10].o1)));} +static s7_double opt_d_vd_o1(opt_info *o) {return(o->v[3].d_vd_f(o->v[5].obj, o->v[4].d_dd_f(real(slot_value(o->v[2].p)), o->v[11].fd(o->v[10].o1))));} +static s7_double opt_d_vd_o2(opt_info *o) {return(o->v[4].d_vd_f(o->v[6].obj, o->v[5].d_vd_f(o->v[2].obj, real(slot_value(o->v[3].p)))));} +static s7_double opt_d_vd_o3(opt_info *o) {return(o->v[3].d_vd_f(o->v[5].obj, o->v[4].d_dd_f(o->v[6].x, real(slot_value(o->v[2].p)))));} +static s7_double opt_d_vd_ff(opt_info *o) {return(o->v[3].d_vd_f(o->v[5].obj, o->v[2].d_vd_f(o->v[4].obj, o->v[11].fd(o->v[10].o1))));} + +static s7_double opt_d_dd_cs(opt_info *o); +static s7_double opt_d_dd_sf_mul(opt_info *o); +static s7_double opt_d_dd_sf_add(opt_info *o); +static s7_double opt_d_dd_sf(opt_info *o); + +static bool d_vd_f_combinable(s7_scheme *sc, int32_t start) +{ + opt_info *opc = sc->opts[start - 1], *o1 = sc->opts[start]; + if (o1->v[0].fd == opt_d_v) + { + opc->v[2].p = o1->v[1].p; + opc->v[6].obj = o1->v[5].obj; + opc->v[4].d_v_f = o1->v[3].d_v_f; + opc->v[0].fd = opt_d_vd_o; + backup_pc(sc); + return_true(sc, NULL); + } + if (o1->v[0].fd == opt_d_vd_s) + { + opc->v[6].obj = opc->v[5].obj; + opc->v[4].d_vd_f = opc->v[3].d_vd_f; /* room for symbols */ + opc->v[2].obj = o1->v[5].obj; + opc->v[5].d_vd_f = o1->v[3].d_vd_f; + opc->v[3].p = o1->v[2].p; + opc->v[7].p = o1->v[1].p; + opc->v[0].fd = opt_d_vd_o2; + backup_pc(sc); + return_true(sc, NULL); + } + if (o1->v[0].fd == opt_d_dd_cs) + { + opc->v[4].d_dd_f = o1->v[3].d_dd_f; + opc->v[6].x = o1->v[2].x; + opc->v[2].p = o1->v[1].p; + opc->v[0].fd = opt_d_vd_o3; + backup_pc(sc); + return_true(sc, NULL); + } + if ((o1->v[0].fd == opt_d_dd_sf_mul) || (o1->v[0].fd == opt_d_dd_sf) || (o1->v[0].fd == opt_d_dd_sf_add)) + { + opc->v[2].p = o1->v[1].p; + opc->v[4].d_dd_f = o1->v[3].d_dd_f; + opc->v[0].fd = (o1->v[0].fd == opt_d_dd_sf_mul) ? opt_d_vd_o1_mul : opt_d_vd_o1; + opc->v[11].fd = o1->v[5].fd; + opc->v[10].o1 = o1->v[4].o1; + return_true(sc, NULL); + } + if (o1->v[0].fd == opt_d_vd_f) + { + opc->v[2].d_vd_f = o1->v[3].d_vd_f; + opc->v[4].obj = o1->v[5].obj; + opc->v[6].p = o1->v[1].p; + opc->v[0].fd = opt_d_vd_ff; + opc->v[11].fd = o1->v[9].fd; + opc->v[10].o1 = o1->v[8].o1; + return_true(sc, NULL); + } + return_false(sc, NULL); +} + +static bool d_vd_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + s7_pointer sig; + s7_d_vd_t vfunc; + if (!is_symbol(cadr(car_x))) + return_false(sc, car_x); + vfunc = s7_d_vd_function(s_func); + if (!vfunc) + return_false(sc, car_x); + sig = c_function_signature(s_func); + if ((is_pair(sig)) && + (is_symbol(cadr(sig)))) + { + s7_pointer slot = opt_types_match(sc, cadr(sig), cadr(car_x)); + if (slot) + { + s7_pointer arg2 = caddr(car_x); + int32_t start = sc->pc; + opc->v[3].d_vd_f = vfunc; + if (!is_pair(arg2)) + { + opc->v[1].p = slot; + opc->v[5].obj = (void *)c_object_value(slot_value(slot)); + if (is_small_real(arg2)) + { + opc->v[2].x = s7_number_to_real(sc, arg2); + opc->v[0].fd = opt_d_vd_c; + return_true(sc, car_x); + } + opc->v[2].p = s7_slot(sc, arg2); + if (is_slot(opc->v[2].p)) + { + if (is_t_real(slot_value(opc->v[2].p))) + { + opc->v[0].fd = opt_d_vd_s; + return_true(sc, car_x); + } + if (!float_optimize(sc, cddr(car_x))) + return_false(sc, car_x); + if (d_vd_f_combinable(sc, start)) + return_true(sc, car_x); + opc->v[0].fd = opt_d_vd_f; + opc->v[8].o1 = sc->opts[start]; + opc->v[9].fd = sc->opts[start]->v[0].fd; + return_true(sc, car_x); + }} + else /* is pair arg2 */ + { + if (float_optimize(sc, cddr(car_x))) + { + opc->v[1].p = slot; + opc->v[5].obj = (void *)c_object_value(slot_value(slot)); + if (d_vd_f_combinable(sc, start)) + return_true(sc, car_x); + opc->v[0].fd = opt_d_vd_f; + opc->v[8].o1 = sc->opts[start]; + opc->v[9].fd = sc->opts[start]->v[0].fd; + return_true(sc, car_x); + } + sc->pc = start; + }}} + return_false(sc, car_x); +} + +/* -------- d_id -------- */ +static s7_double opt_d_id_sf(opt_info *o) {return(o->v[3].d_id_f(integer(slot_value(o->v[1].p)), o->v[5].fd(o->v[4].o1)));} +static s7_double opt_d_id_sc(opt_info *o) {return(o->v[3].d_id_f(integer(slot_value(o->v[1].p)), o->v[2].x));} +static s7_double opt_d_id_sfo1(opt_info *o) {return(o->v[3].d_id_f(integer(slot_value(o->v[1].p)), o->v[5].d_v_f(o->v[2].obj)));} +static s7_double opt_d_id_sfo(opt_info *o) {return(o->v[4].d_id_f(integer(slot_value(o->v[1].p)), o->v[5].d_vd_f(o->v[6].obj, real(slot_value(o->v[3].p)))));} +static s7_double opt_d_id_cf(opt_info *o) {return(o->v[3].d_id_f(o->v[1].i, o->v[5].fd(o->v[4].o1)));} + +static s7_double opt_d_id_ff(opt_info *o) +{ + s7_int x1 = o->v[9].fi(o->v[8].o1); + return(o->v[3].d_id_f(x1, o->v[11].fd(o->v[10].o1))); +} + +static bool d_id_sf_combinable(s7_scheme *sc, opt_info *opc) +{ + if ((sc->pc > 1) && + (opc == sc->opts[sc->pc - 2])) + { + opt_info *o1 = sc->opts[sc->pc - 1]; + if (o1->v[0].fd == opt_d_vd_s) + { + opc->v[4].d_id_f = opc->v[3].d_id_f; + opc->v[2].p = o1->v[1].p; + opc->v[6].obj = o1->v[5].obj; + opc->v[5].d_vd_f = o1->v[3].d_vd_f; + opc->v[3].p = o1->v[2].p; + opc->v[0].fd = opt_d_id_sfo; + backup_pc(sc); + return_true(sc, NULL); + } + if (o1->v[0].fd == opt_d_v) + { + opc->v[6].p = o1->v[1].p; + opc->v[2].obj = o1->v[5].obj; + opc->v[5].d_v_f = o1->v[3].d_v_f; + opc->v[0].fd = opt_d_id_sfo1; + backup_pc(sc); + return_true(sc, NULL); + }} + return_false(sc, NULL); +} + +static bool d_id_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + s7_pointer p; + int32_t start = sc->pc; + s7_d_id_t flt_func = s7_d_id_function(s_func); + if (!flt_func) + return_false(sc, car_x); + opc->v[3].d_id_f = flt_func; + p = opt_integer_symbol(sc, cadr(car_x)); + if (p) + { + opc->v[1].p = p; + if (is_t_real(caddr(car_x))) + { + opc->v[0].fd = opt_d_id_sc; + opc->v[2].x = real(caddr(car_x)); + return_true(sc, car_x); + } + if (float_optimize(sc, cddr(car_x))) + { + if (d_id_sf_combinable(sc, opc)) + return_true(sc, car_x); + opc->v[0].fd = opt_d_id_sf; + opc->v[4].o1 = sc->opts[start]; + opc->v[5].fd = sc->opts[start]->v[0].fd; + return_true(sc, car_x); + } + sc->pc = start; + } + if (is_t_integer(cadr(car_x))) + { + if (float_optimize(sc, cddr(car_x))) + { + opc->v[0].fd = opt_d_id_cf; + opc->v[1].i = integer(cadr(car_x)); + opc->v[4].o1 = sc->opts[start]; + opc->v[5].fd = sc->opts[start]->v[0].fd; + return_true(sc, car_x); + } + sc->pc = start; + } + opc->v[8].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, cdr(car_x))) + { + opc->v[9].fi = opc->v[8].o1->v[0].fi; + opc->v[10].o1 = sc->opts[sc->pc]; + if (float_optimize(sc, cddr(car_x))) + { + opc->v[11].fd = opc->v[10].o1->v[0].fd; + opc->v[0].fd = opt_d_id_ff; + return_true(sc, car_x); + } + sc->pc = start; + } + return_false(sc, car_x); +} + +/* -------- d_dd -------- */ + +static s7_double opt_d_dd_cc(opt_info *o) {return(o->v[3].d_dd_f(o->v[1].x, o->v[2].x));} +static s7_double opt_d_dd_cs(opt_info *o) {return(o->v[3].d_dd_f(o->v[2].x, real(slot_value(o->v[1].p))));} +static s7_double opt_d_dd_sc(opt_info *o) {return(o->v[3].d_dd_f(real(slot_value(o->v[1].p)), o->v[2].x));} +static s7_double opt_d_dd_sc_sub(opt_info *o) {return(real(slot_value(o->v[1].p)) - o->v[2].x);} +static s7_double opt_d_dd_ss(opt_info *o) {return(o->v[3].d_dd_f(real(slot_value(o->v[1].p)), real(slot_value(o->v[2].p))));} +static s7_double opt_d_dd_ss_add(opt_info *o) {return(real(slot_value(o->v[1].p)) + real(slot_value(o->v[2].p)));} +static s7_double opt_d_dd_ss_mul(opt_info *o) {return(real(slot_value(o->v[1].p)) * real(slot_value(o->v[2].p)));} + +static s7_double opt_d_dd_cf(opt_info *o) {return(o->v[3].d_dd_f(o->v[1].x, o->v[5].fd(o->v[4].o1)));} +static s7_double opt_d_dd_1f_subtract(opt_info *o) {return(1.0 - o->v[5].fd(o->v[4].o1));} +static s7_double opt_d_dd_fc(opt_info *o) {return(o->v[3].d_dd_f(o->v[5].fd(o->v[4].o1), o->v[2].x));} + +#if WITH_GMP +static s7_double opt_subtract_random_f_f(opt_info *o) {return(o->v[1].x * next_random(o->sc) - o->v[2].x);} +#else +static s7_double opt_subtract_random_f_f(opt_info *o) {return(o->v[1].x * next_random(o->sc->default_random_state) - o->v[2].x);} +#endif + +static s7_double opt_d_dd_fc_add(opt_info *o) {return(o->v[5].fd(o->v[4].o1) + o->v[2].x);} +static s7_double opt_d_dd_fc_fvref_add(opt_info *o) {return(o->v[2].x + float_vector(slot_value(o->v[4].o1->v[1].p), integer(slot_value(o->v[4].o1->v[2].p))));} +static s7_double opt_d_dd_fc_subtract(opt_info *o) {return(o->v[5].fd(o->v[4].o1) - o->v[2].x);} +static s7_double opt_d_dd_sf(opt_info *o) {return(o->v[3].d_dd_f(real(slot_value(o->v[1].p)), o->v[5].fd(o->v[4].o1)));} +static s7_double opt_d_dd_sf_mul(opt_info *o) {return(real(slot_value(o->v[1].p)) * o->v[5].fd(o->v[4].o1));} +static s7_double opt_d_dd_sf_add(opt_info *o) {return(real(slot_value(o->v[1].p)) + o->v[5].fd(o->v[4].o1));} +static s7_double opt_d_dd_sf_sub(opt_info *o) {return(real(slot_value(o->v[1].p)) - o->v[5].fd(o->v[4].o1));} + +static s7_double opt_d_7dd_cc(opt_info *o) {return(o->v[3].d_7dd_f(o->sc, o->v[1].x, o->v[2].x));} +static s7_double opt_d_7dd_cs(opt_info *o) {return(o->v[3].d_7dd_f(o->sc, o->v[2].x, real(slot_value(o->v[1].p))));} +static s7_double opt_d_7dd_sc(opt_info *o) {return(o->v[3].d_7dd_f(o->sc, real(slot_value(o->v[1].p)), o->v[2].x));} +static s7_double opt_d_7dd_ss(opt_info *o) {return(o->v[3].d_7dd_f(o->sc, real(slot_value(o->v[1].p)), real(slot_value(o->v[2].p))));} +static s7_double opt_d_7dd_cf(opt_info *o) {return(o->v[3].d_7dd_f(o->sc, o->v[1].x, o->v[5].fd(o->v[4].o1)));} +static s7_double opt_d_7dd_fc(opt_info *o) {return(o->v[3].d_7dd_f(o->sc, o->v[5].fd(o->v[4].o1), o->v[2].x));} +static s7_double opt_d_7dd_sf(opt_info *o) {return(o->v[3].d_7dd_f(o->sc, real(slot_value(o->v[1].p)), o->v[5].fd(o->v[4].o1)));} + +static s7_double opt_d_dd_sf_mul_fvref(opt_info *o) +{ + opt_info *o1 = o->v[4].o1; + return(real(slot_value(o->v[1].p)) * float_vector_ref_d_7pii(o1->sc, slot_value(o1->v[1].p), o1->v[2].i, integer(slot_value(o1->v[3].p)))); +} + +static s7_double opt_d_dd_sfo(opt_info *o) +{ + return(o->v[4].d_dd_f(real(slot_value(o->v[1].p)), o->v[5].d_7pi_f(o->sc, slot_value(o->v[2].p), integer(slot_value(o->v[3].p))))); +} + +static s7_double opt_d_7dd_sfo(opt_info *o) +{ + return(o->v[4].d_7dd_f(o->sc, real(slot_value(o->v[1].p)), o->v[5].d_7pi_f(o->sc, slot_value(o->v[2].p), integer(slot_value(o->v[3].p))))); +} + +static bool d_dd_sf_combinable(s7_scheme *sc, opt_info *opc, s7_d_dd_t func) +{ + if ((sc->pc > 1) && + (opc == sc->opts[sc->pc - 2])) + { + opt_info *o1 = sc->opts[sc->pc - 1]; + if ((o1->v[0].fd == opt_d_7pi_ss) || (o1->v[0].fd == opt_d_7pi_ss_fvref) || (o1->v[0].fd == opt_d_7pi_ss_fvref_direct)) + { + if (func) + { + opc->v[4].d_dd_f = opc->v[3].d_dd_f; /* need room for 3 symbols */ + opc->v[0].fd = opt_d_dd_sfo; + } + else + { + opc->v[4].d_7dd_f = opc->v[3].d_7dd_f; /* need room for 3 symbols */ + opc->v[0].fd = opt_d_7dd_sfo; + } + opc->v[2].p = o1->v[1].p; + opc->v[3].p = o1->v[2].p; + opc->v[5].d_7pi_f = o1->v[3].d_7pi_f; + backup_pc(sc); + return_true(sc, NULL); + }} + return_false(sc, NULL); +} + +static s7_double opt_d_dd_fs(opt_info *o) {return(o->v[3].d_dd_f(o->v[5].fd(o->v[4].o1), real(slot_value(o->v[1].p))));} +static s7_double opt_d_dd_fs_mul(opt_info *o) {return(o->v[5].fd(o->v[4].o1) * real(slot_value(o->v[1].p)));} +static s7_double opt_d_dd_fs_add(opt_info *o) {return(o->v[5].fd(o->v[4].o1) + real(slot_value(o->v[1].p)));} +static s7_double opt_d_dd_fs_sub(opt_info *o) {return(o->v[5].fd(o->v[4].o1) - real(slot_value(o->v[1].p)));} +static s7_double opt_d_7dd_fs(opt_info *o) {return(o->v[3].d_7dd_f(o->sc, o->v[5].fd(o->v[4].o1), real(slot_value(o->v[1].p))));} + +static s7_double opt_d_dd_fs_add_fvref(opt_info *o) +{ + opt_info *o1 = o->v[4].o1; + return(real(slot_value(o->v[1].p)) + float_vector_ref_d_7pii(o1->sc, slot_value(o1->v[1].p), o1->v[2].i, integer(slot_value(o1->v[3].p)))); +} + +static s7_double opt_d_dd_fso(opt_info *o) +{ + return(o->v[4].d_dd_f(o->v[5].d_7pi_f(o->sc, slot_value(o->v[2].p), integer(slot_value(o->v[3].p))), real(slot_value(o->v[1].p)))); +} + +static s7_double opt_d_7dd_fso(opt_info *o) +{ + return(o->v[4].d_7dd_f(o->sc, o->v[5].d_7pi_f(o->sc, slot_value(o->v[2].p), integer(slot_value(o->v[3].p))), real(slot_value(o->v[1].p)))); +} + +static bool d_dd_fs_combinable(s7_scheme *sc, opt_info *opc, s7_d_dd_t func) +{ + if ((sc->pc > 1) && + (opc == sc->opts[sc->pc - 2])) + { + opt_info *o1 = sc->opts[sc->pc - 1]; + if ((o1->v[0].fd == opt_d_7pi_ss) || (o1->v[0].fd == opt_d_7pi_ss_fvref) || (o1->v[0].fd == opt_d_7pi_ss_fvref_direct)) + { + if (func) + { + opc->v[4].d_dd_f = opc->v[3].d_dd_f; /* need room for 3 symbols */ + opc->v[0].fd = opt_d_dd_fso; + } + else + { + opc->v[4].d_7dd_f = opc->v[3].d_7dd_f; + opc->v[0].fd = opt_d_7dd_fso; + } + opc->v[2].p = o1->v[1].p; + opc->v[3].p = o1->v[2].p; + opc->v[5].d_7pi_f = o1->v[3].d_7pi_f; + backup_pc(sc); + return_true(sc, NULL); + }} + return_false(sc, NULL); +} + +static s7_double opt_d_dd_ff(opt_info *o) +{ + s7_double x1 = o->v[9].fd(o->v[8].o1); + return(o->v[3].d_dd_f(x1, o->v[11].fd(o->v[10].o1))); +} + +static s7_double opt_d_dd_ff_mul(opt_info *o) +{ + s7_double x1 = o->v[9].fd(o->v[8].o1); + return(x1 * o->v[11].fd(o->v[10].o1)); +} + +static s7_double opt_d_dd_ff_square(opt_info *o) +{ + s7_double x1 = o->v[9].fd(o->v[8].o1); + return(x1 * x1); +} + +static s7_double opt_d_dd_ff_add(opt_info *o) +{ + s7_double x1 = o->v[5].fd(o->v[4].o1); + return(x1 + o->v[11].fd(o->v[10].o1)); +} + +static s7_double opt_d_dd_ff_add_mul(opt_info *o) +{ + s7_double x1 = o->v[5].fd(o->v[4].o1); + s7_double x2 = o->v[9].fd(o->v[8].o1); + return(x1 + (x2 * o->v[11].fd(o->v[10].o1))); +} + +static s7_double opt_d_dd_ff_add_fv_ref(opt_info *o) +{ + s7_double x1 = o->v[5].fd(o->v[4].o1); + return(x1 + float_vector_ref_d_7pi(o->sc, slot_value(o->v[6].p), o->v[9].fi(o->v[8].o1))); +} + +static s7_double opt_d_dd_ff_sub(opt_info *o) +{ + s7_double x1 = o->v[5].fd(o->v[4].o1); + return(x1 - o->v[11].fd(o->v[10].o1)); +} + +static s7_double opt_d_7dd_ff(opt_info *o) +{ + s7_double x1 = o->v[9].fd(o->v[8].o1); + return(o->v[3].d_7dd_f(o->sc, x1, o->v[11].fd(o->v[10].o1))); +} + +static s7_double opt_d_dd_ff_o1(opt_info *o) +{ + s7_double x1 = o->v[2].d_v_f(o->v[1].obj); + return(o->v[3].d_dd_f(x1, o->v[11].fd(o->v[10].o1))); +} + +static s7_double opt_d_dd_ff_mul1(opt_info *o) {return(o->v[2].d_v_f(o->v[1].obj) * o->v[11].fd(o->v[10].o1));} + +static s7_double opt_d_dd_ff_o2(opt_info *o) +{ + s7_double x1 = o->v[4].d_v_f(o->v[1].obj); + return(o->v[3].d_dd_f(x1, o->v[5].d_v_f(o->v[2].obj))); +} + +static s7_double opt_d_dd_ff_mul2(opt_info *o) {return(o->v[4].d_v_f(o->v[1].obj) * o->v[5].d_v_f(o->v[2].obj));} + +static s7_double opt_d_dd_ff_o3(opt_info *o) +{ + s7_double x1 = o->v[5].d_v_f(o->v[1].obj); + return(o->v[4].d_dd_f(x1, o->v[6].d_vd_f(o->v[2].obj, real(slot_value(o->v[3].p))))); +} + +static s7_double opt_d_dd_fff(opt_info *o) +{ + s7_double x1 = o->v[3+4].d_dd_f(o->v[3+5].d_7pi_f(o->sc, slot_value(o->v[3+2].p), integer(slot_value(o->v[3+3].p))), real(slot_value(o->v[3+1].p))); /* dd_fso */ + s7_double x2 = o->v[8+4].d_dd_f(o->v[8+5].d_7pi_f(o->sc, slot_value(o->v[8+2].p), integer(slot_value(o->v[8+3].p))), real(slot_value(o->v[8+1].p))); /* dd_fso */ + return(o->v[3].d_dd_f(x1, x2)); +} + +static s7_double opt_d_mm_fff(opt_info *o) +{ + s7_double x1 = float_vector_ref_d_7pi(o->sc, slot_value(o->v[3+2].p), integer(slot_value(o->v[3+3].p))) * real(slot_value(o->v[3+1].p)); + s7_double x2 = float_vector_ref_d_7pi(o->sc, slot_value(o->v[8+2].p), integer(slot_value(o->v[8+3].p))) * real(slot_value(o->v[8+1].p)); + return(o->v[3].d_dd_f(x1, x2)); +} + +static s7_double opt_d_dd_fff_rev(opt_info *o) /* faster with o->sc? */ +{ + s7_double x1 = o->v[3+4].d_dd_f(real(slot_value(o->v[3+1].p)), o->v[3+5].d_7pi_f(o->sc, slot_value(o->v[3+2].p), integer(slot_value(o->v[3+3].p)))); + s7_double x2 = o->v[8+4].d_dd_f(real(slot_value(o->v[8+1].p)), o->v[8+5].d_7pi_f(o->sc, slot_value(o->v[8+2].p), integer(slot_value(o->v[8+3].p)))); + return(o->v[3].d_dd_f(x1, x2)); +} + +static s7_double opt_d_dd_ff_o4(opt_info *o) +{ + s7_double x1 = o->v[2].d_v_f(o->v[1].obj); + return(o->v[3].d_dd_f(x1, o->v[7].d_vd_f(o->v[5].obj, o->v[4].d_v_f(o->v[6].obj)))); +} + +static s7_double opt_d_dd_ff_mul4(opt_info *o) {return(o->v[2].d_v_f(o->v[1].obj) * o->v[7].d_vd_f(o->v[5].obj, o->v[4].d_v_f(o->v[6].obj)));} + +static s7_double opt_d_7pii_sss(opt_info *o); +static s7_double opt_d_dd_ff_mul_sss(opt_info *o) +{ + opt_info *o1 = o->v[8].o1; + s7_pointer v = slot_value(o1->v[1].p); + s7_int i1 = integer(slot_value(o1->v[2].p)); + s7_int i2 = integer(slot_value(o1->v[3].p)); + s7_double x1 = float_vector_ref_d_7pii(o1->sc, v, i1, i2); + o1 = o->v[10].o1; + v = slot_value(o1->v[1].p); + i1 = integer(slot_value(o1->v[2].p)); /* in (* (A i j) (B j k)) we could reuse i2->i1 (flipping args below) */ + i2 = integer(slot_value(o1->v[3].p)); + return(x1 * float_vector_ref_d_7pii(o1->sc, v, i1, i2)); +} + +static bool finish_dd_fso(opt_info *opc, opt_info *o1, opt_info *o2) +{ + opc->v[3+1].p = o1->v[1].p; + opc->v[3+2].p = o1->v[2].p; + opc->v[3+3].p = o1->v[3].p; + opc->v[3+4].d_dd_f = o1->v[4].d_dd_f; + opc->v[3+5].d_7pi_f = o1->v[5].d_7pi_f; + opc->v[8+1].p = o2->v[1].p; + opc->v[8+2].p = o2->v[2].p; + opc->v[8+3].p = o2->v[3].p; + opc->v[8+4].d_dd_f = o2->v[4].d_dd_f; + opc->v[8+5].d_7pi_f = o2->v[5].d_7pi_f; + return(true); +} + +static s7_double opt_d_7dd_ff_div_add(opt_info *o) +{ + opt_info *o2 = o->v[10].o1; + s7_double x1 = o->v[9].fd(o->v[8].o1); + s7_double x2 = o2->v[5].fd(o2->v[4].o1); + x2 += float_vector_ref_d_7pi(o2->sc, slot_value(o2->v[6].p), o2->v[9].fi(o2->v[8].o1)); + return(divide_d_7dd(o->sc, x1, x2)); +} + +static bool d_dd_ff_combinable(s7_scheme *sc, opt_info *opc, int32_t start) +{ + opt_info *o1 = opc->v[8].o1, *o2 = opc->v[10].o1; + if (o1->v[0].fd == opt_d_v) + { + /* opc->v[3] is in use */ + if ((o2->v[0].fd == opt_d_v) && + (sc->pc == start + 2)) + { + opc->v[1].obj = o1->v[5].obj; + opc->v[6].p = o1->v[1].p; + opc->v[4].d_v_f = o1->v[3].d_v_f; + opc->v[2].obj = o2->v[5].obj; + opc->v[7].p = o2->v[1].p; + opc->v[5].d_v_f = o2->v[3].d_v_f; + opc->v[0].fd = (opc->v[3].d_dd_f == multiply_d_dd) ? opt_d_dd_ff_mul2 : opt_d_dd_ff_o2; + sc->pc -= 2; + return_true(sc, NULL); + } + if ((o2->v[0].fd == opt_d_vd_s) && + (sc->pc == start + 2)) + { + opc->v[4].d_dd_f = opc->v[3].d_dd_f; /* make room for symbols */ + opc->v[1].obj = o1->v[5].obj; + opc->v[7].p = o1->v[1].p; + opc->v[5].d_v_f = o1->v[3].d_v_f; + opc->v[2].obj = o2->v[5].obj; + opc->v[8].p = o2->v[1].p; + opc->v[6].d_vd_f = o2->v[3].d_vd_f; + opc->v[3].p = o2->v[2].p; + opc->v[0].fd = opt_d_dd_ff_o3; + sc->pc -= 2; + return_true(sc, NULL); + } + if ((o2->v[0].fd == opt_d_vd_o) && + (sc->pc == start + 2)) + { + opc->v[1].obj = o1->v[5].obj; + opc->v[8].p = o1->v[1].p; + opc->v[2].d_v_f = o1->v[3].d_v_f; + opc->v[7].d_vd_f = o2->v[3].d_vd_f; + opc->v[4].d_v_f = o2->v[4].d_v_f; + opc->v[5].obj = o2->v[5].obj; + opc->v[9].p = o2->v[1].p; + opc->v[6].obj = o2->v[6].obj; + opc->v[10].p = o2->v[2].p; + opc->v[0].fd = (opc->v[3].d_dd_f == multiply_d_dd) ? opt_d_dd_ff_mul4 : opt_d_dd_ff_o4; + sc->pc -= 2; + return_true(sc, NULL); + } + opc->v[1].obj = o1->v[5].obj; + opc->v[4].p = o1->v[1].p; + opc->v[2].d_v_f = o1->v[3].d_v_f; + opc->v[0].fd = (opc->v[3].d_dd_f == multiply_d_dd) ? opt_d_dd_ff_mul1 : opt_d_dd_ff_o1; + return_true(sc, NULL); + } + if (o1->v[0].fd == opt_d_dd_fso) + { + if (o2->v[0].fd == opt_d_dd_fso) + { + if ((o1->v[4].d_dd_f == multiply_d_dd) && + (o2->v[4].d_dd_f == multiply_d_dd) && + (o1->v[5].d_7pi_f == float_vector_ref_d_7pi) && + (o2->v[5].d_7pi_f == float_vector_ref_d_7pi)) + opc->v[0].fd = opt_d_mm_fff; /* a placeholder (never called) */ + else opc->v[0].fd = opt_d_dd_fff; + return(finish_dd_fso(opc, o1, o2)); + }} + if (o1->v[0].fd == opt_d_dd_sfo) + { + if (o2->v[0].fd == opt_d_dd_sfo) + { + if ((o1->v[4].d_dd_f == multiply_d_dd) && + (o2->v[4].d_dd_f == multiply_d_dd) && + (o1->v[5].d_7pi_f == float_vector_ref_d_7pi) && + (o2->v[5].d_7pi_f == float_vector_ref_d_7pi)) + opc->v[0].fd = opt_d_mm_fff; /* multiply is commutative */ + else opc->v[0].fd = opt_d_dd_fff_rev; + return(finish_dd_fso(opc, o1, o2)); + }} + return_false(sc, NULL); +} + +static s7_double opt_d_dd_cfo(opt_info *o) {return(o->v[3].d_dd_f(o->v[2].x, o->v[4].d_v_f(o->v[1].obj)));} +static s7_double opt_d_7dd_cfo(opt_info *o) {return(o->v[3].d_7dd_f(o->sc, o->v[2].x, o->v[4].d_v_f(o->v[1].obj)));} +static s7_double opt_d_dd_cfo1(opt_info *o) {return(o->v[3].d_dd_f(o->v[4].x, o->v[5].d_vd_f(o->v[6].obj, real(slot_value(o->v[2].p)))));} +static s7_double opt_d_7dd_cfo1(opt_info *o){return(o->v[3].d_7dd_f(o->sc, o->v[4].x, o->v[5].d_vd_f(o->v[6].obj, real(slot_value(o->v[2].p)))));} + +static bool d_dd_call_combinable(s7_scheme *sc, opt_info *opc, s7_d_dd_t func) +{ + if ((sc->pc > 1) && + (opc == sc->opts[sc->pc - 2])) + { + opt_info *o1 = sc->opts[sc->pc - 1]; + if (o1->v[0].fd == opt_d_v) + { + opc->v[2].x = opc->v[1].x; + opc->v[6].p = o1->v[1].p; + opc->v[1].obj = o1->v[5].obj; + opc->v[4].d_v_f = o1->v[3].d_v_f; + opc->v[0].fd = (func) ? opt_d_dd_cfo : opt_d_7dd_cfo; + backup_pc(sc); + return_true(sc, NULL); + } + if (o1->v[0].fd == opt_d_vd_s) + { + opc->v[4].x = opc->v[1].x; + opc->v[1].p = o1->v[1].p; + opc->v[6].obj = o1->v[5].obj; + opc->v[2].p = o1->v[2].p; + opc->v[5].d_vd_f = o1->v[3].d_vd_f; + opc->v[0].fd = (func) ? opt_d_dd_cfo1 : opt_d_7dd_cfo1; + backup_pc(sc); + return_true(sc, NULL); + }} + return_false(sc, NULL); +} + +static s7_double opt_d_7pii_scs(opt_info *o); + +static bool d_dd_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + s7_pointer slot, arg1 = cadr(car_x), arg2 = caddr(car_x); + int32_t start = sc->pc; + opt_info *o1; + s7_d_7dd_t func7 = NULL; + s7_d_dd_t func = s7_d_dd_function(s_func); + if (!func) + { + func7 = s7_d_7dd_function(s_func); + if (!func7) return_false(sc, car_x); + } + if (func) + opc->v[3].d_dd_f = func; + else opc->v[3].d_7dd_f = func7; + + /* arg1 = real constant */ + if (is_small_real(arg1)) + { + if (is_small_real(arg2)) + { + if ((!is_t_real(arg1)) && (!is_t_real(arg2))) + return_false(sc, car_x); + opc->v[1].x = s7_number_to_real(sc, arg1); + opc->v[2].x = s7_number_to_real(sc, arg2); + opc->v[0].fd = (func) ? opt_d_dd_cc : opt_d_7dd_cc; + return_true(sc, car_x); + } + slot = opt_float_symbol(sc, arg2); + if (slot) + { + opc->v[1].p = slot; + opc->v[2].x = s7_number_to_real(sc, arg1); /* move arg1? */ + opc->v[0].fd = (func) ? opt_d_dd_cs : opt_d_7dd_cs; + return_true(sc, car_x); + } + if (float_optimize(sc, cddr(car_x))) + { + opc->v[1].x = s7_number_to_real(sc, arg1); + if (d_dd_call_combinable(sc, opc, func)) + return_true(sc, car_x); + opc->v[4].o1 = sc->opts[start]; + opc->v[5].fd = sc->opts[start]->v[0].fd; + opc->v[0].fd = (func) ? opt_d_dd_cf : opt_d_7dd_cf; + if ((opc->v[1].x == 1.0) && (func == subtract_d_dd)) opc->v[0].fd = opt_d_dd_1f_subtract; + return_true(sc, car_x); + } + sc->pc = start; + return_false(sc, car_x); + } + + /* arg1 = float symbol */ + slot = opt_float_symbol(sc, arg1); + if (slot) + { + opc->v[1].p = slot; + if (is_small_real(arg2)) + { + opc->v[2].x = s7_number_to_real(sc, arg2); + if (func) + opc->v[0].fd = (func == subtract_d_dd) ? opt_d_dd_sc_sub : opt_d_dd_sc; + else opc->v[0].fd = opt_d_7dd_sc; + return_true(sc, car_x); + } + slot = opt_float_symbol(sc, arg2); + if (slot) + { + opc->v[2].p = slot; + if (func) + { + if (func == multiply_d_dd) + opc->v[0].fd = opt_d_dd_ss_mul; + else opc->v[0].fd = (func == add_d_dd) ? opt_d_dd_ss_add : opt_d_dd_ss; + } + else opc->v[0].fd = opt_d_7dd_ss; + return_true(sc, car_x); + } + if (float_optimize(sc, cddr(car_x))) + { + if (d_dd_sf_combinable(sc, opc, func)) + return_true(sc, car_x); + opc->v[4].o1 = sc->opts[start]; + opc->v[5].fd = sc->opts[start]->v[0].fd; + if (func) + { + opc->v[0].fd = (func == multiply_d_dd) ? opt_d_dd_sf_mul : + ((func == add_d_dd) ? opt_d_dd_sf_add : + ((func == subtract_d_dd) ? opt_d_dd_sf_sub : opt_d_dd_sf)); + if ((func == multiply_d_dd) && (opc->v[5].fd == opt_d_7pii_scs)) + opc->v[0].fd = opt_d_dd_sf_mul_fvref; + } + else opc->v[0].fd = opt_d_7dd_sf; + return_true(sc, car_x); + } + sc->pc = start; + return_false(sc, car_x); + } + + /* arg1 = float expr or non-float */ + o1 = sc->opts[sc->pc]; + if (float_optimize(sc, cdr(car_x))) + { + int32_t start2 = sc->pc; + if (is_small_real(arg2)) + { + opc->v[2].x = s7_number_to_real(sc, arg2); + opc->v[4].o1 = sc->opts[start]; + opc->v[5].fd = sc->opts[start]->v[0].fd; + if (func) + { + if (func == add_d_dd) + { + opc->v[0].fd = (opc->v[5].fd == opt_d_7pi_ss_fvref_direct) ? opt_d_dd_fc_fvref_add : opt_d_dd_fc_add; + return_true(sc, car_x); + } + if (func == subtract_d_dd) + { + opc->v[0].fd = opt_d_dd_fc_subtract; + /* if o1->v[0].fd = opt_d_7d_c and its o->v[3].d_7d_f = random_d_7d it's (- (random f1) f2) */ + if ((opc == sc->opts[sc->pc - 2]) && + (sc->opts[start]->v[0].fd == opt_d_7d_c) && + (sc->opts[start]->v[3].d_7d_f == random_d_7d)) + { + opc->v[0].fd = opt_subtract_random_f_f; + opc->v[1].x = sc->opts[start]->v[1].x; /* random arg */ + backup_pc(sc); + }} + else opc->v[0].fd = opt_d_dd_fc; + } + else opc->v[0].fd = opt_d_7dd_fc; + return_true(sc, car_x); + } + slot = opt_float_symbol(sc, arg2); + if (slot) + { + opc->v[1].p = slot; + if (d_dd_fs_combinable(sc, opc, func)) + return_true(sc, car_x); + opc->v[4].o1 = sc->opts[start]; + opc->v[5].fd = sc->opts[start]->v[0].fd; + if (func) + { + opc->v[0].fd = (func == multiply_d_dd) ? opt_d_dd_fs_mul : + ((func == add_d_dd) ? opt_d_dd_fs_add : + ((func == subtract_d_dd) ? opt_d_dd_fs_sub : opt_d_dd_fs)); + if ((func == add_d_dd) && (opc->v[5].fd == opt_d_7pii_scs)) + opc->v[0].fd = opt_d_dd_fs_add_fvref; + } + else opc->v[0].fd = opt_d_7dd_fs; + return_true(sc, car_x); + } + opc->v[10].o1 = sc->opts[sc->pc]; + if (float_optimize(sc, cddr(car_x))) + { + opt_info *o2; + opc->v[8].o1 = o1; + opc->v[9].fd = o1->v[0].fd; + opc->v[11].fd = opc->v[10].o1->v[0].fd; + if (func) + { + if (d_dd_ff_combinable(sc, opc, start)) + return_true(sc, car_x); + opc->v[0].fd = opt_d_dd_ff; + if (func == multiply_d_dd) + { + if (arg1 == arg2) + opc->v[0].fd = opt_d_dd_ff_square; + else + if ((opc->v[9].fd == opt_d_7pii_sss) && (opc->v[11].fd == opt_d_7pii_sss) && + (o1->v[4].d_7pii_f == float_vector_ref_d_7pii)) /* currently redundant */ + opc->v[0].fd = opt_d_dd_ff_mul_sss; + else opc->v[0].fd = opt_d_dd_ff_mul; + return_true(sc, car_x); + } + o2 = sc->opts[start2]; /* this is opc->v[10].o1 */ + if (func == add_d_dd) + { + if (o2->v[0].fd == opt_d_dd_ff_mul) + { + opc->v[0].fd = opt_d_dd_ff_add_mul; + opc->v[4].o1 = o1; /* add first arg */ + opc->v[5].fd = o1->v[0].fd; + opc->v[8].o1 = o2->v[8].o1; /* mul first arg */ + opc->v[9].fd = o2->v[9].fd; + opc->v[10].o1 = o2->v[10].o1; /* mul second arg */ + opc->v[11].fd = o2->v[11].fd; + return_true(sc, car_x); + } + if ((o2->v[0].fd == opt_d_7pi_sf) && + (o2->v[3].d_7pi_f == float_vector_ref_d_7pi)) + { + opc->v[0].fd = opt_d_dd_ff_add_fv_ref; + opc->v[6].p = o2->v[1].p; + opc->v[8].o1 = o2->v[10].o1; /* sc->opts[start2 + 1]; */ + opc->v[9].fi = o2->v[11].fi; /* sc->opts[start2 + 1]->v[0].fi; */ + } + else + { + opc->v[0].fd = opt_d_dd_ff_add; + opc->v[10].o1 = o2; + opc->v[11].fd = o2->v[0].fd; + } + opc->v[4].o1 = o1; /* sc->opts[start]; */ + opc->v[5].fd = o1->v[0].fd; /* sc->opts[start]->v[0].fd; */ + return_true(sc, car_x); + } + if (func == subtract_d_dd) + { + opc->v[0].fd = opt_d_dd_ff_sub; + opc->v[4].o1 = o1; /* sc->opts[start]; */ + opc->v[5].fd = o1->v[0].fd; /* sc->opts[start]->v[0].fd; */ + opc->v[10].o1 = o2; + opc->v[11].fd = o2->v[0].fd; + return_true(sc, car_x); + }} + else + { + opc->v[0].fd = opt_d_7dd_ff; + if ((opc->v[11].fd == opt_d_dd_ff_add_fv_ref) && + (opc->v[3].d_7dd_f == divide_d_7dd)) + opc->v[0].fd = opt_d_7dd_ff_div_add; + } + return_true(sc, car_x); + }} + sc->pc = start; + return_false(sc, car_x); +} + +/* -------- d_ddd -------- */ +static s7_double opt_d_ddd_sss(opt_info *o) {return(o->v[4].d_ddd_f(real(slot_value(o->v[1].p)), real(slot_value(o->v[2].p)), real(slot_value(o->v[3].p))));} +static s7_double opt_d_ddd_ssf(opt_info *o) {return(o->v[4].d_ddd_f(real(slot_value(o->v[1].p)), real(slot_value(o->v[2].p)), o->v[11].fd(o->v[10].o1)));} + +static s7_double opt_d_ddd_sff(opt_info *o) +{ + s7_double x1 = o->v[11].fd(o->v[10].o1); + s7_double x2 = o->v[9].fd(o->v[8].o1); + return(o->v[4].d_ddd_f(real(slot_value(o->v[1].p)), x1, x2)); +} + +static s7_double opt_d_ddd_fff(opt_info *o) +{ + s7_double x1 = o->v[11].fd(o->v[10].o1); + s7_double x2 = o->v[9].fd(o->v[8].o1); + s7_double x3 = o->v[6].fd(o->v[5].o1); + return(o->v[4].d_ddd_f(x1, x2, x3)); +} + +static s7_double opt_d_ddd_fff1(opt_info *o) +{ + s7_double x1 = o->v[1].d_v_f(o->v[2].obj); + s7_double x2 = o->v[3].d_v_f(o->v[4].obj); + s7_double x3 = o->v[5].d_v_f(o->v[6].obj); + return(o->v[7].d_ddd_f(x1, x2, x3)); +} + +static s7_double opt_d_ddd_fff2(opt_info *o) +{ + s7_double x1 = o->v[1].d_v_f(o->v[2].obj); + s7_double x2 = o->v[9].fd(o->v[12].o1); + s7_double x3 = o->v[6].fd(o->v[5].o1); + return(o->v[7].d_ddd_f(x1, x2, x3)); +} + +static s7_double opt_d_ddd_fff_mul(opt_info *o) +{ + s7_double x1 = opt_D_s(o->v[10].o1); + s7_double x2 = opt_D_s(o->v[8].o1); + s7_double x3 = opt_d_s(o->v[5].o1); + return(multiply_d_ddd(x1, x2, x3)); +} + +static bool d_ddd_fff_combinable(s7_scheme *sc, opt_info *opc, int32_t start) +{ + opt_info *o1; + if (sc->opts[start]->v[0].fd != opt_d_v) + return_false(sc, NULL); + opc->v[12].o1 = opc->v[8].o1; + opc->v[7].d_ddd_f = opc->v[4].d_ddd_f; + o1 = sc->opts[start]; + opc->v[1].d_v_f = o1->v[3].d_v_f; + opc->v[2].obj = o1->v[5].obj; + opc->v[8].p = o1->v[1].p; + if ((sc->opts[start + 1]->v[0].fd == opt_d_v) && + (sc->opts[start + 2]->v[0].fd == opt_d_v)) + { + opc->v[0].fd = opt_d_ddd_fff1; + o1 = sc->opts[start + 1]; + opc->v[3].d_v_f = o1->v[3].d_v_f; + opc->v[4].obj = o1->v[5].obj; + opc->v[9].p = o1->v[1].p; + o1 = sc->opts[start + 2]; + opc->v[5].d_v_f = o1->v[3].d_v_f; + opc->v[6].obj = o1->v[5].obj; + opc->v[10].p = o1->v[1].p; + sc->pc -= 3; + return_true(sc, NULL); + } + opc->v[0].fd = opt_d_ddd_fff2; + opc->v[9].fd = opc->v[12].o1->v[0].fd; + opc->v[6].fd = opc->v[5].o1->v[0].fd; + return_true(sc, NULL); +} + +static bool d_ddd_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + int32_t start = sc->pc; + s7_pointer slot, arg1 = cadr(car_x), arg2 = caddr(car_x); + s7_d_ddd_t f = s7_d_ddd_function(s_func); + if (!f) + return_false(sc, car_x); + opc->v[4].d_ddd_f = f; + slot = opt_float_symbol(sc, arg1); + opc->v[10].o1 = sc->opts[start]; + if (slot) + { + opc->v[1].p = slot; + slot = opt_float_symbol(sc, arg2); + if (slot) + { + s7_pointer arg3 = cadddr(car_x); + opc->v[2].p = slot; + slot = opt_float_symbol(sc, arg3); + if (slot) + { + opc->v[3].p = slot; + opc->v[0].fd = opt_d_ddd_sss; + return_true(sc, car_x); + } + if (float_optimize(sc, cdddr(car_x))) + { + opc->v[11].fd = opc->v[10].o1->v[0].fd; + opc->v[0].fd = opt_d_ddd_ssf; + return_true(sc, car_x); + } + sc->pc = start; + } + if (float_optimize(sc, cddr(car_x))) + { + opc->v[8].o1 = sc->opts[sc->pc]; + if (float_optimize(sc, cdddr(car_x))) + { + opc->v[0].fd = opt_d_ddd_sff; + opc->v[11].fd = opc->v[10].o1->v[0].fd; + opc->v[9].fd = opc->v[8].o1->v[0].fd; + return_true(sc, car_x); + }} + sc->pc = start; + } + if (float_optimize(sc, cdr(car_x))) + { + opc->v[8].o1 = sc->opts[sc->pc]; + if (float_optimize(sc, cddr(car_x))) + { + opc->v[5].o1 = sc->opts[sc->pc]; + if (float_optimize(sc, cdddr(car_x))) + { + if (d_ddd_fff_combinable(sc, opc, start)) + return_true(sc, car_x); + opc->v[0].fd = opt_d_ddd_fff; /* tfft: (* xout xin iw) (+ (* xout xin iw) (* yout yin ih) (* zout zin id)) */ + opc->v[11].fd = opc->v[10].o1->v[0].fd; + opc->v[9].fd = opc->v[8].o1->v[0].fd; + opc->v[6].fd = opc->v[5].o1->v[0].fd; + if ((f == multiply_d_ddd) && (opc->v[11].fd == opt_D_s) && (opc->v[9].fd == opt_D_s) && (opc->v[6].fd == opt_d_s)) + opc->v[0].fd = opt_d_ddd_fff_mul; + return_true(sc, car_x); + }}} + sc->pc = start; + return_false(sc, car_x); +} + +/* -------- d_7pid -------- */ +static s7_double opt_d_7pid_ssf(opt_info *o) +{ + return(o->v[4].d_7pid_f(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), o->v[11].fd(o->v[10].o1))); +} + +static s7_pointer opt_d_7pid_ssf_nr(opt_info *o) +{ + o->v[4].d_7pid_f(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), o->v[11].fd(o->v[10].o1)); + return(NULL); +} + +static s7_double opt_d_7pid_sss(opt_info *o) +{ + return(o->v[4].d_7pid_f(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), real(slot_value(o->v[3].p)))); +} + +static s7_double opt_d_7pid_ssc(opt_info *o) +{ + return(o->v[4].d_7pid_f(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), o->v[3].x)); +} + +static s7_double opt_d_7pid_sff(opt_info *o) +{ + s7_int pos = o->v[11].fi(o->v[10].o1); + return(o->v[4].d_7pid_f(o->sc, slot_value(o->v[1].p), pos, o->v[9].fd(o->v[8].o1))); +} + +static s7_double opt_d_7pid_sff_fvset(opt_info *o) +{ + s7_int pos = o->v[11].fi(o->v[10].o1); + return(float_vector_set_d_7pid(o->sc, slot_value(o->v[1].p), pos, o->v[9].fd(o->v[8].o1))); +} + +static s7_double opt_d_7pid_sso(opt_info *o) +{ + return(o->v[4].d_7pid_f(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), o->v[5].d_v_f(o->v[3].obj))); +} + +static s7_double opt_d_7pid_ss_ss(opt_info *o) +{ + return(o->v[4].d_7pid_f(o->sc, slot_value(o->v[1].p), + integer(slot_value(o->v[2].p)), + o->v[3].d_7pi_f(o->sc, slot_value(o->v[5].p), integer(slot_value(o->v[6].p))))); +} + +static s7_double opt_d_7pid_ssfo(opt_info *o) +{ + s7_pointer fv = slot_value(o->v[1].p); + return(o->v[4].d_7pid_f(o->sc, fv, integer(slot_value(o->v[2].p)), + o->v[6].d_dd_f(o->v[5].d_7pi_f(o->sc, fv, integer(slot_value(o->v[3].p))), real(slot_value(o->v[8].p))))); +} + +static s7_double opt_d_7pid_ssfo_fv(opt_info *o) +{ + s7_double *els = float_vector_floats(slot_value(o->v[1].p)); + s7_double val = o->v[6].d_dd_f(els[integer(slot_value(o->v[3].p))], real(slot_value(o->v[8].p))); + els[integer(slot_value(o->v[2].p))] = val; + return(val); +} + +static s7_pointer opt_d_7pid_ssfo_fv_nr(opt_info *o) /* these next are variations on (float-vector-set! s (float-vector-ref s...)) */ +{ + s7_double *els = float_vector_floats(slot_value(o->v[1].p)); + els[integer(slot_value(o->v[2].p))] = o->v[6].d_dd_f(els[integer(slot_value(o->v[3].p))], real(slot_value(o->v[8].p))); + return(NULL); +} + +static s7_pointer opt_d_7pid_ssfo_fv_add_nr(opt_info *o) +{ + s7_double *els = float_vector_floats(slot_value(o->v[1].p)); + els[integer(slot_value(o->v[2].p))] = els[integer(slot_value(o->v[3].p))] + real(slot_value(o->v[8].p)); + return(NULL); +} + +static s7_pointer opt_d_7pid_ssfo_fv_sub_nr(opt_info *o) +{ + s7_double *els = float_vector_floats(slot_value(o->v[1].p)); + els[integer(slot_value(o->v[2].p))] = els[integer(slot_value(o->v[3].p))] - real(slot_value(o->v[8].p)); + return(NULL); +} + +static bool d_7pid_ssf_combinable(s7_scheme *sc, opt_info *opc) +{ + if ((sc->pc > 1) && + (opc == sc->opts[sc->pc - 2])) + { + opt_info *o1 = sc->opts[sc->pc - 1]; + if (o1->v[0].fd == opt_d_v) + { + opc->v[6].p = o1->v[1].p; + opc->v[3].obj = o1->v[5].obj; + opc->v[5].d_v_f = o1->v[3].d_v_f; + opc->v[0].fd = opt_d_7pid_sso; + backup_pc(sc); + return_true(sc, NULL); + } + if ((o1->v[0].fd == opt_d_7pi_ss) || (o1->v[0].fd == opt_d_7pi_ss_fvref) || (o1->v[0].fd == opt_d_7pi_ss_fvref_direct)) + { + opc->v[3].d_7pi_f = o1->v[3].d_7pi_f; + opc->v[5].p = o1->v[1].p; + opc->v[6].p = o1->v[2].p; + opc->v[0].fd = opt_d_7pid_ss_ss; + backup_pc(sc); + return_true(sc, NULL); + } + if ((o1->v[0].fd == opt_d_dd_fso) && + (opc->v[1].p == o1->v[2].p)) + { + /* opc: pid_ssf: o->v[4].d_7pid_f(slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), o1->v[0].fd(o1)) + * o1: d_dd_fso: o->v[4].d_dd_f(o->v[5].d_7pi_f(slot_value(o->v[2].p), integer(slot_value(o->v[3].p))), real(slot_value(o->v[1].p)))) + */ + opc->v[6].d_dd_f = o1->v[4].d_dd_f; + opc->v[5].d_7pi_f = o1->v[5].d_7pi_f; + opc->v[3].p = o1->v[3].p; + opc->v[8].p = o1->v[1].p; + opc->v[0].fd = opt_d_7pid_ssfo; + if ((opc->v[5].d_7pi_f == float_vector_ref_d_7pi) && + ((opc->v[4].d_7pid_f == float_vector_set_d_7pid_direct) || (opc->v[4].d_7pid_f == float_vector_set_d_7pid))) + opc->v[0].fd = opt_d_7pid_ssfo_fv; /* actually if either is *_d, we need to check the indices */ + backup_pc(sc); + return_true(sc, NULL); + }} + return_false(sc, NULL); +} + +static bool opt_float_vector_set(s7_scheme *sc, opt_info *opc, s7_pointer v, s7_pointer indexp1, s7_pointer indexp2, s7_pointer indexp3, s7_pointer valp); + +static bool d_7pid_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + s7_d_7pid_t f = s7_d_7pid_function(s_func); + if ((f) && + (is_symbol(cadr(car_x)))) + { + s7_pointer slot, head = car(car_x); + int32_t start = sc->pc; + opc->v[4].d_7pid_f = f; + + if (is_target_or_its_alias(head, s_func, sc->float_vector_set_symbol)) + return(opt_float_vector_set(sc, opc, cadr(car_x), cddr(car_x), NULL, NULL, cdddr(car_x))); + + opc->v[1].p = s7_slot(sc, cadr(car_x)); + opc->v[10].o1 = sc->opts[start]; + if (is_slot(opc->v[1].p)) + { + slot = opt_integer_symbol(sc, caddr(car_x)); + if (slot) + { + opc->v[2].p = slot; + slot = opt_float_symbol(sc, cadddr(car_x)); + if (slot) + { + opc->v[3].p = slot; + opc->v[0].fd = opt_d_7pid_sss; + return_true(sc, car_x); + } + if (float_optimize(sc, cdddr(car_x))) + { + opc->v[11].fd = sc->opts[start]->v[0].fd; + if (d_7pid_ssf_combinable(sc, opc)) + return_true(sc, car_x); + opc->v[0].fd = opt_d_7pid_ssf; + return_true(sc, car_x); + } + sc->pc = start; + } + if (int_optimize(sc, cddr(car_x))) + { + opc->v[8].o1 = sc->opts[sc->pc]; + if (float_optimize(sc, cdddr(car_x))) + { + opc->v[0].fd = opt_d_7pid_sff; + opc->v[11].fi = opc->v[10].o1->v[0].fi; + opc->v[9].fd = opc->v[8].o1->v[0].fd; + return_true(sc, car_x); + }} + sc->pc = start; + }} + return_false(sc, car_x); +} + +/* -------- d_7pii -------- */ +/* currently this can only be float_vector_ref_d_7pii (d_7pii is not exported at this time) */ + +static s7_double opt_d_7pii_sss(opt_info *o) +{ /* o->v[4].d_7pii_f */ + return(float_vector_ref_d_7pii(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), integer(slot_value(o->v[3].p)))); +} + +static s7_double opt_d_7pii_sss_unchecked(opt_info *o) +{ + s7_pointer v = slot_value(o->v[1].p); + return(float_vector(v, ((integer(slot_value(o->v[2].p)) * vector_offset(v, 0)) + integer(slot_value(o->v[3].p))))); +} + +static s7_double opt_d_7pii_scs(opt_info *o) +{ + return(float_vector_ref_d_7pii(o->sc, slot_value(o->v[1].p), o->v[2].i, integer(slot_value(o->v[3].p)))); +} + +static s7_double opt_d_7pii_sff(opt_info *o) +{ + return(float_vector_ref_d_7pii(o->sc, slot_value(o->v[1].p), o->v[11].fi(o->v[10].o1), o->v[9].fi(o->v[8].o1))); +} + +static bool d_7pii_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + s7_d_7pii_t ifunc = s7_d_7pii_function(s_func); + if ((ifunc == float_vector_ref_d_7pii) && + (is_symbol(cadr(car_x)))) + { + s7_pointer slot; + int32_t start = sc->pc; + opc->v[1].p = s7_slot(sc, cadr(car_x)); + if ((!is_slot(opc->v[1].p)) || + (!is_float_vector(slot_value(opc->v[1].p))) || + (vector_rank(slot_value(opc->v[1].p)) != 2)) + return_false(sc, car_x); + + opc->v[4].d_7pii_f = ifunc; /* currently pointless */ + slot = opt_integer_symbol(sc, cadddr(car_x)); + if (slot) + { + opc->v[3].p = slot; + slot = opt_integer_symbol(sc, caddr(car_x)); + if (slot) + { + opc->v[2].p = slot; + opc->v[0].fd = opt_d_7pii_sss; + if ((loop_end_fits(opc->v[2].p, vector_dimension(slot_value(opc->v[1].p), 0))) && + (loop_end_fits(opc->v[3].p, vector_dimension(slot_value(opc->v[1].p), 1)))) + opc->v[0].fd = opt_d_7pii_sss_unchecked; + return_true(sc, car_x); + } + if (is_t_integer(caddr(car_x))) + { + opc->v[2].i = integer(caddr(car_x)); + opc->v[0].fd = opt_d_7pii_scs; + return_true(sc, car_x); + }} + opc->v[10].o1 = sc->opts[start]; + if (int_optimize(sc, cddr(car_x))) + { + opc->v[8].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, cdddr(car_x))) + { + opc->v[0].fd = opt_d_7pii_sff; + opc->v[11].fi = opc->v[10].o1->v[0].fi; + opc->v[9].fi = opc->v[8].o1->v[0].fi; + return_true(sc, car_x); + }} + sc->pc = start; + } + return_false(sc, car_x); +} + +/* -------- d_7piid -------- */ +/* currently only float_vector_set */ + +static s7_double opt_d_7piid_sssf(opt_info *o) +{ /* o->v[5].d_7piid_f and below */ + return(float_vector_set_d_7piid(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), integer(slot_value(o->v[3].p)), o->v[9].fd(o->v[8].o1))); +} + +static s7_double opt_d_7piid_sssc(opt_info *o) +{ + return(float_vector_set_d_7piid(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), integer(slot_value(o->v[3].p)), o->v[4].x)); +} + +static s7_double opt_d_7piid_scsf(opt_info *o) +{ + return(float_vector_set_d_7piid(o->sc, slot_value(o->v[1].p), o->v[2].i, integer(slot_value(o->v[3].p)), o->v[11].fd(o->v[10].o1))); +} + +static s7_double opt_d_7piid_sfff(opt_info *o) +{ + s7_int i1 = o->v[11].fi(o->v[10].o1); + s7_int i2 = o->v[9].fi(o->v[8].o1); + return(float_vector_set_d_7piid(o->sc, slot_value(o->v[1].p), i1, i2, o->v[4].fd(o->v[3].o1))); +} + +static s7_double opt_d_7piid_sssf_unchecked(opt_info *o) /* this could be subsumed by the call above if we were using o->v[5] or o->v[0].fd */ +{ + s7_int i1 = integer(slot_value(o->v[2].p)), i2 = integer(slot_value(o->v[3].p)); + s7_pointer vect = slot_value(o->v[1].p); + s7_double val = o->v[9].fd(o->v[8].o1); + float_vector(vect, (i1 * (vector_offset(vect, 0)) + i2)) = val; + return(val); +} + +static bool d_7piid_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + s7_d_7piid_t f = s7_d_7piid_function(s_func); + if ((f) && + (is_symbol(cadr(car_x)))) + { + opc->v[4].d_7piid_f = f; + if (is_target_or_its_alias(car(car_x), s_func, sc->float_vector_set_symbol)) + return(opt_float_vector_set(sc, opc, cadr(car_x), cddr(car_x), cdddr(car_x), NULL, cddddr(car_x))); + } + return_false(sc, car_x); +} + +/* -------- d_7piii -------- */ +static s7_double opt_d_7piii_ssss(opt_info *o) +{ + return(float_vector_ref_d_7piii(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), + integer(slot_value(o->v[3].p)), integer(slot_value(o->v[5].p)))); +} + +static s7_double opt_d_7piii_ssss_unchecked(opt_info *o) +{ + s7_pointer v = slot_value(o->v[1].p); + s7_int i1 = integer(slot_value(o->v[2].p)) * vector_offset(v, 0); + s7_int i2 = integer(slot_value(o->v[3].p)) * vector_offset(v, 1); /* offsets accumulate */ + return(float_vector(v, (i1 + i2 + integer(slot_value(o->v[5].p))))); +} + +static bool d_7piii_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + s7_d_7piii_t ifunc = s7_d_7piii_function(s_func); + if ((ifunc == float_vector_ref_d_7piii) && + (is_symbol(cadr(car_x)))) + { + s7_pointer slot; + opc->v[1].p = s7_slot(sc, cadr(car_x)); + if ((!is_slot(opc->v[1].p)) || + (!is_float_vector(slot_value(opc->v[1].p))) || + (vector_rank(slot_value(opc->v[1].p)) != 3)) + return_false(sc, car_x); + + opc->v[4].d_7piii_f = ifunc; /* currently ignored */ + slot = opt_integer_symbol(sc, car(cddddr(car_x))); + if (slot) + { + opc->v[5].p = slot; + slot = opt_integer_symbol(sc, cadddr(car_x)); + if (slot) + { + opc->v[3].p = slot; + slot = opt_integer_symbol(sc, caddr(car_x)); + if (slot) + { + s7_pointer vect = slot_value(opc->v[1].p); + opc->v[2].p = slot; + opc->v[0].fd = opt_d_7piii_ssss; + if ((loop_end_fits(opc->v[2].p, vector_dimension(vect, 0))) && + (loop_end_fits(opc->v[3].p, vector_dimension(vect, 1))) && + (loop_end_fits(opc->v[5].p, vector_dimension(vect, 2)))) + opc->v[0].fd = opt_d_7piii_ssss_unchecked; + return_true(sc, car_x); + }}}} + return_false(sc, car_x); +} + +/* -------- d_7piiid -------- */ +static s7_double opt_d_7piiid_ssssf(opt_info *o) +{ + return(float_vector_set_d_7piiid(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), + integer(slot_value(o->v[3].p)), integer(slot_value(o->v[5].p)), o->v[11].fd(o->v[10].o1))); +} + +static s7_double opt_d_7piiid_ssssf_unchecked(opt_info *o) +{ + s7_pointer vect = slot_value(o->v[1].p); + s7_int i1 = integer(slot_value(o->v[2].p)) * vector_offset(vect, 0); + s7_int i2 = integer(slot_value(o->v[3].p)) * vector_offset(vect, 1); + s7_int i3 = integer(slot_value(o->v[5].p)); + s7_double val = o->v[11].fd(o->v[10].o1); + float_vector(vect, (i1 + i2 + i3)) = val; + return(val); +} + +static bool d_7piiid_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + s7_d_7piiid_t f = s7_d_7piiid_function(s_func); + if ((f == float_vector_set_d_7piiid) && + (is_symbol(cadr(car_x)))) + { + opc->v[4].d_7piiid_f = f; + if (is_target_or_its_alias(car(car_x), s_func, sc->float_vector_set_symbol)) + return(opt_float_vector_set(sc, opc, cadr(car_x), cddr(car_x), cdddr(car_x), cddddr(car_x), cdr(cddddr(car_x)))); + } + return(false); +} + +static bool opt_float_vector_set(s7_scheme *sc, opt_info *opc, s7_pointer v, s7_pointer indexp1, s7_pointer indexp2, s7_pointer indexp3, s7_pointer valp) +{ + s7_pointer settee = s7_slot(sc, v); + if ((is_slot(settee)) && + (!is_immutable(slot_value(settee)))) + { + s7_pointer slot, vect = slot_value(settee); + int32_t start = sc->pc; + opc->v[1].p = settee; + if (!is_float_vector(vect)) + return_false(sc, NULL); + opc->v[10].o1 = sc->opts[start]; + if ((!indexp2) && + (vector_rank(vect) == 1)) + { + opc->v[4].d_7pid_f = float_vector_set_d_7pid; + slot = opt_integer_symbol(sc, car(indexp1)); + if (slot) + { + opc->v[2].p = slot; + if (loop_end_fits(opc->v[2].p, vector_length(vect))) + opc->v[4].d_7pid_f = float_vector_set_d_7pid_direct; + slot = opt_float_symbol(sc, car(valp)); + if (slot) + { + opc->v[3].p = slot; + opc->v[0].fd = opt_d_7pid_sss; + return_true(sc, NULL); + } + if (is_small_real(car(valp))) + { + opc->v[3].x = s7_real(car(valp)); + opc->v[0].fd = opt_d_7pid_ssc; + return_true(sc, NULL); + } + if (float_optimize(sc, valp)) + { + opc->v[11].fd = sc->opts[start]->v[0].fd; + if (d_7pid_ssf_combinable(sc, opc)) + return_true(sc, NULL); + opc->v[0].fd = opt_d_7pid_ssf; + return_true(sc, NULL); + } + sc->pc = start; + } + if (int_optimize(sc, indexp1)) + { + opc->v[8].o1 = sc->opts[sc->pc]; + if (float_optimize(sc, valp)) + { + opc->v[0].fd = (opc->v[4].d_7pid_f == float_vector_set_d_7pid) ? opt_d_7pid_sff_fvset : opt_d_7pid_sff; + opc->v[11].fi = opc->v[10].o1->v[0].fi; + opc->v[9].fd = opc->v[8].o1->v[0].fd; + return_true(sc, NULL); + }} + return_false(sc, NULL); + } + if ((indexp2) && (!indexp3) && + (vector_rank(vect) == 2)) + { + opc->v[5].d_7piid_f = float_vector_set_d_7piid; + /* could check for loop_end/end-ok here for both indices, but the d_7pii* functions currently assume fv_d_7piid + * perhaps set a different fd? so opc->v[0].fd = fvset_unchecked_d_7piid or whatever + */ + slot = opt_integer_symbol(sc, car(indexp2)); + if (slot) + { + opc->v[3].p = slot; + if (is_t_integer(car(indexp1))) + { + if (!float_optimize(sc, valp)) + return_false(sc, NULL); + opc->v[0].fd = opt_d_7piid_scsf; + opc->v[2].i = integer(car(indexp1)); + opc->v[11].fd = opc->v[10].o1->v[0].fd; + return_true(sc, NULL); + } + slot = opt_integer_symbol(sc, car(indexp1)); + if (slot) + { + opc->v[2].p = slot; + if (is_small_real(car(valp))) + { + opc->v[0].fd = opt_d_7piid_sssc; + opc->v[4].x = s7_real(car(valp)); + return_true(sc, NULL); + } + opc->v[8].o1 = sc->opts[sc->pc]; + if (float_optimize(sc, valp)) + { + opc->v[0].fd = opt_d_7piid_sssf; + opc->v[9].fd = opc->v[8].o1->v[0].fd; + + if ((loop_end_fits(opc->v[2].p, vector_dimension(vect, 0))) && + (loop_end_fits(opc->v[3].p, vector_dimension(vect, 1)))) + opc->v[0].fd = opt_d_7piid_sssf_unchecked; + return_true(sc, NULL); + } + sc->pc = start; + }} + if (int_optimize(sc, indexp1)) + { + opc->v[8].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, indexp2)) + { + opc->v[3].o1 = sc->opts[sc->pc]; + if (float_optimize(sc, valp)) + { + opc->v[0].fd = opt_d_7piid_sfff; + opc->v[11].fi = opc->v[10].o1->v[0].fi; + opc->v[9].fi = opc->v[8].o1->v[0].fi; + opc->v[4].fd = opc->v[3].o1->v[0].fd; + return_true(sc, NULL); + }}} + return_false(sc, NULL); + } + if ((indexp3) && + (vector_rank(vect) == 3)) + { + opc->v[4].d_7piiid_f = float_vector_set_d_7piiid; + slot = opt_integer_symbol(sc, car(indexp3)); + if (slot) + { + opc->v[5].p = slot; + slot = opt_integer_symbol(sc, car(indexp2)); + if (slot) + { + opc->v[3].p = slot; + slot = opt_integer_symbol(sc, car(indexp1)); + if (slot) + { + opc->v[2].p = slot; + if (float_optimize(sc, valp)) + { + opc->v[0].fd = opt_d_7piiid_ssssf; + opc->v[11].fd = sc->opts[start]->v[0].fd; + if ((loop_end_fits(opc->v[2].p, vector_dimension(vect, 0))) && + (loop_end_fits(opc->v[3].p, vector_dimension(vect, 1))) && + (loop_end_fits(opc->v[5].p, vector_dimension(vect, 2)))) + opc->v[0].fd = opt_d_7piiid_ssssf_unchecked; + return_true(sc, NULL); + }}}}}} + return_false(sc, NULL); +} + + +/* -------- d_vid -------- */ +static s7_double opt_d_vid_ssf(opt_info *o) {return(o->v[4].d_vid_f(o->v[5].obj, integer(slot_value(o->v[2].p)), o->v[11].fd(o->v[10].o1)));} + +static inline s7_double opt_fmv(opt_info *o) +{ + /* d_vid_ssf -> d_dd_ff_o1 -> d_vd_o1 -> d_dd_ff_o3, this is a placeholder */ + opt_info *o1 = o->v[12].o1; + opt_info *o2 = o->v[13].o1; + opt_info *o3 = o->v[14].o1; + s7_double amp_env = o1->v[2].d_v_f(o1->v[1].obj); + s7_double vib = real(slot_value(o2->v[2].p)); + s7_double index_env = o3->v[5].d_v_f(o3->v[1].obj); + return(o->v[4].d_vid_f(o->v[5].obj, + integer(slot_value(o->v[2].p)), + amp_env * o2->v[3].d_vd_f(o2->v[5].obj, + vib + (index_env * o3->v[6].d_vd_f(o3->v[2].obj, vib))))); +} + +static bool d_vid_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + if ((is_symbol(cadr(car_x))) && + (is_symbol(caddr(car_x)))) + { + s7_pointer sig; + s7_d_vid_t flt = s7_d_vid_function(s_func); + if (!flt) + return_false(sc, car_x); + opc->v[4].d_vid_f = flt; + sig = c_function_signature(s_func); + if (is_pair(sig)) + { + int32_t start = sc->pc; + s7_pointer vslot = opt_types_match(sc, cadr(sig), cadr(car_x)); + if (vslot) + { + s7_pointer slot; + opc->v[0].fd = opt_d_vid_ssf; + opc->v[1].p = vslot; + opc->v[10].o1 = sc->opts[start]; + slot = opt_integer_symbol(sc, caddr(car_x)); + if ((slot) && + (float_optimize(sc, cdddr(car_x)))) + { + opt_info *o2; + opc->v[2].p = slot; + opc->v[5].obj = (void *)c_object_value(slot_value(vslot)); + opc->v[11].fd = opc->v[10].o1->v[0].fd; + o2 = sc->opts[start]; + if (o2->v[0].fd == opt_d_dd_ff_mul1) + { + opt_info *o3 = sc->opts[start + 2]; + if (o3->v[0].fd == opt_d_vd_o1) + { + opt_info *o1 = sc->opts[start + 4]; + if ((o1->v[0].fd == opt_d_dd_ff_o3) && + (o1->v[4].d_dd_f == multiply_d_dd) && + (o3->v[4].d_dd_f == add_d_dd)) + { + opc->v[0].fd = opt_fmv; /* a placeholder -- see below */ + opc->v[12].o1 = o2; + opc->v[13].o1 = o3; + opc->v[14].o1 = o1; + }}} + return_true(sc, car_x); + }} + sc->pc = start; + }} + return_false(sc, car_x); +} + +/* -------- d_vdd -------- */ +static s7_double opt_d_vdd_ff(opt_info *o) +{ + s7_double x1 = o->v[11].fd(o->v[10].o1); + s7_double x2 = o->v[9].fd(o->v[8].o1); + return(o->v[4].d_vdd_f(o->v[5].obj, x1, x2)); +} + +static bool d_vdd_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + s7_d_vdd_t flt = s7_d_vdd_function(s_func); + if (flt) + { + s7_pointer sig = c_function_signature(s_func); + opc->v[4].d_vdd_f = flt; + if (is_pair(sig)) + { + s7_pointer slot = opt_types_match(sc, cadr(sig), cadr(car_x)); + if (slot) + { + int32_t start = sc->pc; + opc->v[10].o1 = sc->opts[start]; + if (float_optimize(sc, cddr(car_x))) + { + opc->v[8].o1 = sc->opts[sc->pc]; + if (float_optimize(sc, cdddr(car_x))) + { + opc->v[11].fd = opc->v[10].o1->v[0].fd; + opc->v[9].fd = opc->v[8].o1->v[0].fd; + opc->v[1].p = slot; + opc->v[5].obj = (void *)c_object_value(slot_value(slot)); + opc->v[0].fd = opt_d_vdd_ff; + return_true(sc, car_x); + }} + sc->pc = start; + }}} + return_false(sc, car_x); +} + + +/* -------- d_dddd -------- */ +static s7_double opt_d_dddd_ffff(opt_info *o) +{ + s7_double x1 = o->v[11].fd(o->v[10].o1); + s7_double x2 = o->v[9].fd(o->v[8].o1); + s7_double x3 = o->v[5].fd(o->v[4].o1); + s7_double x4 = o->v[3].fd(o->v[2].o1); + return(o->v[1].d_dddd_f(x1, x2, x3, x4)); +} + +static bool d_dddd_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + s7_d_dddd_t f = s7_d_dddd_function(s_func); + if (!f) + return_false(sc, car_x); + opc->v[10].o1 = sc->opts[sc->pc]; + if (float_optimize(sc, cdr(car_x))) + { + opc->v[8].o1 = sc->opts[sc->pc]; + if (float_optimize(sc, cddr(car_x))) + { + opc->v[4].o1 = sc->opts[sc->pc]; + if (float_optimize(sc, cdddr(car_x))) + { + opc->v[2].o1 = sc->opts[sc->pc]; + if (float_optimize(sc, cddddr(car_x))) + { + opc->v[1].d_dddd_f = f; + opc->v[0].fd = opt_d_dddd_ffff; + opc->v[11].fd = opc->v[10].o1->v[0].fd; + opc->v[9].fd = opc->v[8].o1->v[0].fd; + opc->v[5].fd = opc->v[4].o1->v[0].fd; + opc->v[3].fd = opc->v[2].o1->v[0].fd; + return_true(sc, car_x); + }}}} + return_false(sc, car_x); +} + +/* -------- d_add|multiply|subtract_any ------- */ +static s7_double opt_d_add_any_f(opt_info *o) +{ + s7_double sum = 0.0; + for (s7_int i = 0; i < o->v[1].i; i++) + { + opt_info *o1 = o->v[i + 2].o1; + sum += o1->v[0].fd(o1); + } + return(sum); +} + +static s7_double opt_d_multiply_any_f(opt_info *o) +{ + s7_double sum = 1.0; + for (s7_int i = 0; i < o->v[1].i; i++) + { + opt_info *o1 = o->v[i + 2].o1; + sum *= o1->v[0].fd(o1); + } + return(sum); +} + +static bool d_add_any_ok(s7_scheme *sc, opt_info *opc, s7_pointer car_x) +{ + s7_pointer head = car(car_x); + int32_t start = sc->pc; + if ((head == sc->add_symbol) || + (head == sc->multiply_symbol)) + { + s7_pointer p; + int32_t cur_len; + for (cur_len = 0, p = cdr(car_x); (is_pair(p)) && (cur_len < 12); p = cdr(p), cur_len++) + { + opc->v[cur_len + 2].o1 = sc->opts[sc->pc]; + if (!float_optimize(sc, p)) + break; + } + if (is_null(p)) + { + opc->v[1].i = cur_len; + opc->v[0].fd = (head == sc->add_symbol) ? opt_d_add_any_f : opt_d_multiply_any_f; + return_true(sc, car_x); + }} + sc->pc = start; + return_false(sc, car_x); +} + + +/* -------- d_syntax -------- */ +static s7_double opt_set_d_d_f(opt_info *o) +{ + s7_double x = o->v[3].fd(o->v[2].o1); + slot_set_value(o->v[1].p, make_real(o->sc, x)); + return(x); +} + +static s7_double opt_set_d_d_fm(opt_info *o) +{ + s7_double x = o->v[3].fd(o->v[2].o1); + real(slot_value(o->v[1].p)) = x; + return(x); +} + +static bool d_syntax_ok(s7_scheme *sc, s7_pointer car_x, int32_t len) +{ + if ((len == 3) && + (car(car_x) == sc->set_symbol)) + { + opt_info *opc = alloc_opt_info(sc); + if (is_symbol(cadr(car_x))) + { + s7_pointer settee; + if (is_immutable(cadr(car_x))) + return_false(sc, car_x); + settee = s7_slot(sc, cadr(car_x)); + if ((is_slot(settee)) && + (is_t_real(slot_value(settee))) && + (!is_immutable_slot(settee)) && + ((!slot_has_setter(settee)) || + ((is_c_function(slot_setter(settee))) && + ((slot_setter(settee) == initial_value(sc->is_float_symbol)) || + (c_function_call(slot_setter(settee)) == b_is_float_setter))))) + { + opt_info *o1 = sc->opts[sc->pc]; + opc->v[1].p = settee; + if ((!is_t_integer(caddr(car_x))) && + (float_optimize(sc, cddr(car_x)))) + { + opc->v[0].fd = (is_mutable_number(slot_value(opc->v[1].p))) ? opt_set_d_d_fm : opt_set_d_d_f; + opc->v[2].o1 = o1; + opc->v[3].fd = o1->v[0].fd; + return_true(sc, car_x); + }}} + else /* if is_pair(settee) get setter */ + if ((is_pair(cadr(car_x))) && + (is_symbol(caadr(car_x))) && + (is_pair(cdadr(car_x)))) + { + if (is_null(cddadr(car_x))) + return(opt_float_vector_set(sc, opc, caadr(car_x), cdadr(car_x), NULL, NULL, cddr(car_x))); + if (is_null(cdddr(cadr(car_x)))) + return(opt_float_vector_set(sc, opc, caadr(car_x), cdadr(car_x), cddadr(car_x), NULL, cddr(car_x))); + }} + return_false(sc, car_x); +} + +static bool d_implicit_ok(s7_scheme *sc, s7_pointer s_slot, s7_pointer car_x, int32_t len) +{ + s7_pointer slot, obj = slot_value(s_slot); + if (is_float_vector(obj)) + { + /* implicit float-vector-ref */ + if ((len == 2) && + (vector_rank(obj) == 1)) + { + opt_info *opc = alloc_opt_info(sc); + opc->v[1].p = s_slot; + opc->v[3].d_7pi_f = float_vector_ref_d_7pi; + slot = opt_integer_symbol(sc, cadr(car_x)); + if (slot) + { + opc->v[2].p = slot; + if (loop_end_fits(opc->v[2].p, vector_length(obj))) + opc->v[0].fd = opt_d_7pi_ss_fvref_direct; + else opc->v[0].fd = opt_d_7pi_ss_fvref; + return_true(sc, car_x); + } + opc->v[10].o1 = sc->opts[sc->pc]; + if (!int_optimize(sc, cdr(car_x))) + return_false(sc, car_x); + opc->v[11].fi = opc->v[10].o1->v[0].fi; + opc->v[0].fd = opt_d_7pi_sf; + return_true(sc, car_x); + } + if ((len == 3) && + (vector_rank(obj) == 2)) + { + opt_info *opc = alloc_opt_info(sc); + opc->v[1].p = s_slot; + opc->v[4].d_7pii_f = float_vector_ref_d_7pii; + slot = opt_integer_symbol(sc, cadr(car_x)); + if (slot) + { + opc->v[2].p = slot; + slot = opt_integer_symbol(sc, caddr(car_x)); + if (slot) + { + opc->v[3].p = slot; + opc->v[0].fd = opt_d_7pii_sss; + if ((loop_end_fits(opc->v[2].p, vector_dimension(obj, 0))) && + (loop_end_fits(opc->v[3].p, vector_dimension(obj, 1)))) + opc->v[0].fd = opt_d_7pii_sss_unchecked; + return_true(sc, car_x); + }} + opc->v[10].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, cdr(car_x))) + { + opc->v[8].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, cddr(car_x))) + { + opc->v[0].fd = opt_d_7pii_sff; + opc->v[11].fi = opc->v[10].o1->v[0].fi; + opc->v[9].fi = opc->v[8].o1->v[0].fi; + return_true(sc, car_x); + }}} + if ((len == 4) && + (vector_rank(obj) == 3)) + { + opt_info *opc = alloc_opt_info(sc); + opc->v[1].p = s_slot; + opc->v[4].d_7piii_f = float_vector_ref_d_7piii; + slot = opt_integer_symbol(sc, cadr(car_x)); + if (slot) + { + opc->v[2].p = slot; + slot = opt_integer_symbol(sc, caddr(car_x)); + if (slot) + { + opc->v[3].p = slot; + slot = opt_integer_symbol(sc, cadddr(car_x)); + if (slot) + { + opc->v[5].p = slot; + opc->v[0].fd = opt_d_7piii_ssss; + if ((loop_end_fits(opc->v[2].p, vector_dimension(obj, 0))) && + (loop_end_fits(opc->v[3].p, vector_dimension(obj, 1))) && + (loop_end_fits(opc->v[5].p, vector_dimension(obj, 2)))) + opc->v[0].fd = opt_d_7piii_ssss_unchecked; + return_true(sc, car_x); + }}}}} + if ((is_c_object(obj)) && + (len == 2)) + { + s7_pointer getf = c_object_getf(sc, obj); + if (is_c_function(getf)) /* default is #f */ + { + s7_d_7pi_t func = s7_d_7pi_function(getf); + if (func) + { + opt_info *opc = alloc_opt_info(sc); + opc->v[1].p = s_slot; + opc->v[4].obj = (void *)c_object_value(obj); + opc->v[3].d_7pi_f = func; + slot = opt_integer_symbol(sc, cadr(car_x)); + if (slot) + { + opc->v[0].fd = opt_d_7pi_ss; + opc->v[2].p = slot; + return_true(sc, car_x); + } + opc->v[10].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, cdr(car_x))) + { + opc->v[11].fi = opc->v[10].o1->v[0].fi; + opc->v[0].fd = opt_d_7pi_sf; + return_true(sc, car_x); + }}}} + return_false(sc, car_x); +} + + +/* -------------------------------- bool opts -------------------------------- */ +static bool opt_b_s(opt_info *o) {return(slot_value(o->v[1].p) != o->sc->F);} + +static bool opt_bool_not_pair(s7_scheme *sc, s7_pointer car_x) +{ + s7_pointer p; + if (!is_symbol(car_x)) + return_false(sc, car_x); /* i.e. use cell_optimize */ + p = opt_simple_symbol(sc, car_x); + if ((p) && + (is_boolean(slot_value(p)))) + { + opt_info *opc = alloc_opt_info(sc); + opc->v[1].p = p; + opc->v[0].fb = opt_b_s; + return_true(sc, car_x); + } + return_false(sc, car_x); +} + +/* -------- b_idp -------- */ +static bool opt_b_i_s(opt_info *o) {return(o->v[2].b_i_f(integer(slot_value(o->v[1].p))));} +static bool opt_b_i_f(opt_info *o) {return(o->v[2].b_i_f(o->v[11].fi(o->v[10].o1)));} +static bool opt_b_d_s(opt_info *o) {return(o->v[2].b_d_f(real(slot_value(o->v[1].p))));} +static bool opt_b_d_f(opt_info *o) {return(o->v[2].b_d_f(o->v[11].fd(o->v[10].o1)));} +static bool opt_b_p_s(opt_info *o) {return(o->v[2].b_p_f(slot_value(o->v[1].p)));} +static bool opt_b_p_f(opt_info *o) {return(o->v[2].b_p_f(o->v[4].fp(o->v[3].o1)));} +static bool opt_b_7p_s(opt_info *o) {return(o->v[2].b_7p_f(o->sc, slot_value(o->v[1].p)));} +static bool opt_b_7p_s_not(opt_info *o) {return(slot_value(o->v[1].p) == o->sc->F);} +static bool opt_b_7p_f(opt_info *o) {return(o->v[2].b_7p_f(o->sc, o->v[4].fp(o->v[3].o1)));} +static bool opt_b_d_s_is_positive(opt_info *o) {return(real(slot_value(o->v[1].p)) > 0.0);} +static bool opt_b_p_s_is_integer(opt_info *o) {return(s7_is_integer(slot_value(o->v[1].p)));} +static bool opt_b_p_s_is_pair(opt_info *o) {return(is_pair(slot_value(o->v[1].p)));} +static bool opt_b_p_f_is_string(opt_info *o) {return(s7_is_string(o->v[4].fp(o->v[3].o1)));} +static bool opt_b_7p_s_iter_at_end(opt_info *o) {return(iterator_is_at_end(slot_value(o->v[1].p)));} +static bool opt_b_7p_f_not(opt_info *o) {return((o->v[4].fp(o->v[3].o1)) == o->sc->F);} + +static bool opt_zero_mod(opt_info *o) +{ + s7_int x = integer(slot_value(o->v[1].p)); + return((x % o->v[2].i) == 0); +} + +static bool b_idp_ok(s7_scheme *sc, s7_pointer s_func, s7_pointer car_x, const s7_pointer arg_type) +{ + s7_b_p_t bpf = NULL; + s7_b_7p_t bpf7 = NULL; + opt_info *opc = alloc_opt_info(sc); + int32_t cur_index = sc->pc; + + if (arg_type == sc->is_integer_symbol) + { + s7_b_i_t bif = s7_b_i_function(s_func); + if (bif) + { + opc->v[2].b_i_f = bif; + if (is_symbol(cadr(car_x))) + { + opc->v[1].p = s7_slot(sc, cadr(car_x)); + opc->v[0].fb = opt_b_i_s; + return_true(sc, car_x); + } + opc->v[10].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, cdr(car_x))) + { + opt_info *o1 = sc->opts[sc->pc - 1]; + if ((car(car_x) == sc->is_zero_symbol) && + (o1->v[0].fi == opt_i_ii_sc) && + (o1->v[3].i_ii_f == modulo_i_ii_unchecked)) + { + opc->v[0].fb = opt_zero_mod; + opc->v[1].p = o1->v[1].p; + opc->v[2].i = o1->v[2].i; + backup_pc(sc); + return_true(sc, car_x); + } + opc->v[0].fb = opt_b_i_f; + opc->v[11].fi = opc->v[10].o1->v[0].fi; + return_true(sc, car_x); + }}} + else + if (arg_type == sc->is_float_symbol) + { + s7_b_d_t bdf = s7_b_d_function(s_func); + if (bdf) + { + opc->v[2].b_d_f = bdf; + if (is_symbol(cadr(car_x))) + { + opc->v[1].p = s7_slot(sc, cadr(car_x)); + opc->v[0].fb = (bdf == is_positive_d) ? opt_b_d_s_is_positive : opt_b_d_s; + return_true(sc, car_x); + } + opc->v[10].o1 = sc->opts[sc->pc]; + if (float_optimize(sc, cdr(car_x))) + { + opc->v[0].fb = opt_b_d_f; + opc->v[11].fd = opc->v[10].o1->v[0].fd; + return_true(sc, car_x); + }}} + sc->pc = cur_index; + + bpf = s7_b_p_function(s_func); + if (!bpf) bpf7 = s7_b_7p_function(s_func); + if ((bpf) || (bpf7)) + { + if (bpf) + opc->v[2].b_p_f = bpf; + else opc->v[2].b_7p_f = bpf7; + if (is_symbol(cadr(car_x))) + { + s7_pointer p = opt_simple_symbol(sc, cadr(car_x)); + if (!p) return_false(sc, car_x); + opc->v[1].p = p; + opc->v[0].fb = (bpf) ? ((bpf == s7_is_integer) ? opt_b_p_s_is_integer : ((bpf == s7_is_pair) ? opt_b_p_s_is_pair : opt_b_p_s)) : + (((bpf7 == iterator_is_at_end_b_7p) && (is_iterator(slot_value(p)))) ? opt_b_7p_s_iter_at_end : + ((bpf7 == not_b_7p) ? opt_b_7p_s_not : opt_b_7p_s)); + return_true(sc, car_x); + } + opc->v[3].o1 = sc->opts[sc->pc]; + if (cell_optimize(sc, cdr(car_x))) + { + opc->v[0].fb = (bpf) ? ((bpf == s7_is_string) ? opt_b_p_f_is_string : opt_b_p_f) : (bpf7 == not_b_7p) ? opt_b_7p_f_not : opt_b_7p_f; + opc->v[4].fp = opc->v[3].o1->v[0].fp; + return_true(sc, car_x); + }} + return_false(sc, car_x); +} + + +/* -------- b_pp -------- */ +static s7_pointer opt_arg_type(s7_scheme *sc, s7_pointer argp) +{ + s7_pointer slot, arg = car(argp); + if (is_pair(arg)) + { + if (is_symbol(car(arg))) + { + if ((is_global(car(arg))) || + ((is_slot(global_slot(car(arg)))) && + (s7_slot(sc, car(arg)) == global_slot(car(arg))))) + { + s7_pointer a_func = global_value(car(arg)); + if (is_c_function(a_func)) + { + s7_pointer sig = c_function_signature(a_func); + if (is_pair(sig)) + { + if ((car(sig) == sc->is_integer_symbol) || + ((is_pair(car(sig))) && (direct_memq(sc->is_integer_symbol, car(sig))))) + return(sc->is_integer_symbol); + if ((car(sig) == sc->is_float_symbol) || + ((is_pair(car(sig))) && (direct_memq(sc->is_float_symbol, car(sig))))) + return(sc->is_float_symbol); + if ((car(sig) == sc->is_real_symbol) || + (car(sig) == sc->is_number_symbol)) + { + int32_t start = sc->pc; + if (int_optimize(sc, argp)) + { + sc->pc = start; + return(sc->is_integer_symbol); + } + if (float_optimize(sc, argp)) + { + sc->pc = start; + return(sc->is_float_symbol); + } + sc->pc = start; + } + return(car(sig)); /* we want the function's return type in this context */ + } + return(sc->T); + } + if ((car(arg) == sc->quote_symbol) && + (is_pair(cdr(arg)))) + return(s7_type_of(sc, cadr(arg))); + } + slot = s7_slot(sc, car(arg)); + if ((is_slot(slot)) && + (is_sequence(slot_value(slot)))) + { + s7_pointer sig = s7_signature(sc, slot_value(slot)); + if (is_pair(sig)) + return(car(sig)); + }} + return(sc->T); + } + if (is_symbol(arg)) + { + slot = opt_simple_symbol(sc, arg); + if (!slot) return(sc->T); +#if WITH_GMP + if (is_big_number(slot_value(slot))) + return(sc->T); + if ((is_t_integer(slot_value(slot))) && + (integer(slot_value(slot)) > QUOTIENT_INT_LIMIT)) + return(sc->T); + if ((is_t_real(slot_value(slot))) && + (real(slot_value(slot)) > QUOTIENT_FLOAT_LIMIT)) + return(sc->T); +#endif + return(s7_type_of(sc, slot_value(slot))); + } + return(s7_type_of(sc, arg)); +} + +static bool opt_b_pp_ff(opt_info *o) +{ + s7_pointer p1 = o->v[9].fp(o->v[8].o1); + return(o->v[3].b_pp_f(p1, o->v[11].fp(o->v[10].o1))); +} + +static bool opt_b_7pp_ff(opt_info *o) +{ + s7_pointer p1 = o->v[9].fp(o->v[8].o1); + return(o->v[3].b_7pp_f(o->sc, p1, o->v[11].fp(o->v[10].o1))); +} + +static bool opt_b_pp_sf(opt_info *o) {return(o->v[3].b_pp_f(slot_value(o->v[1].p), o->v[11].fp(o->v[10].o1)));} +static bool opt_b_pp_fs(opt_info *o) {return(o->v[3].b_pp_f(o->v[11].fp(o->v[10].o1), slot_value(o->v[1].p)));} +static bool opt_b_pp_ss(opt_info *o) {return(o->v[3].b_pp_f(slot_value(o->v[1].p), slot_value(o->v[2].p)));} +static bool opt_b_pp_sc(opt_info *o) {return(o->v[3].b_pp_f(slot_value(o->v[1].p), o->v[2].p));} +static bool opt_b_pp_sfo(opt_info *o) {return(o->v[3].b_pp_f(slot_value(o->v[1].p), o->v[4].p_p_f(o->sc, slot_value(o->v[2].p))));} +static bool opt_b_7pp_sf(opt_info *o) {return(o->v[3].b_7pp_f(o->sc, slot_value(o->v[1].p), o->v[11].fp(o->v[10].o1)));} +static bool opt_b_7pp_fs(opt_info *o) {return(o->v[3].b_7pp_f(o->sc, o->v[11].fp(o->v[10].o1), slot_value(o->v[1].p)));} +static bool opt_b_7pp_ss(opt_info *o) {return(o->v[3].b_7pp_f(o->sc, slot_value(o->v[1].p), slot_value(o->v[2].p)));} +static bool opt_b_7pp_ss_lt(opt_info *o) {return(lt_b_7pp(o->sc, slot_value(o->v[1].p), slot_value(o->v[2].p)));} +static bool opt_b_7pp_ss_gt(opt_info *o) {return(gt_b_7pp(o->sc, slot_value(o->v[1].p), slot_value(o->v[2].p)));} +static bool opt_b_7pp_ss_char_lt(opt_info *o) {return(char_lt_b_7pp(o->sc, slot_value(o->v[1].p), slot_value(o->v[2].p)));} +static bool opt_b_7pp_sc(opt_info *o) {return(o->v[3].b_7pp_f(o->sc, slot_value(o->v[1].p), o->v[2].p));} +static bool opt_b_7pp_sfo(opt_info *o) {return(o->v[3].b_7pp_f(o->sc, slot_value(o->v[1].p), o->v[4].p_p_f(o->sc, slot_value(o->v[2].p))));} +static bool opt_is_equal_sfo(opt_info *o) {return(s7_is_equal(o->sc, slot_value(o->v[1].p), o->v[4].p_p_f(o->sc, slot_value(o->v[2].p))));} +static bool opt_is_equivalent_sfo(opt_info *o) {return(is_equivalent_1(o->sc, slot_value(o->v[1].p), o->v[4].p_p_f(o->sc, slot_value(o->v[2].p)), NULL));} +static bool opt_b_pp_sf_char_eq(opt_info *o) {return(slot_value(o->v[1].p) == o->v[11].fp(o->v[10].o1));} /* lt above checks for char args */ +static bool opt_b_pp_ff_char_eq(opt_info *o) {return(o->v[9].fp(o->v[8].o1) == o->v[11].fp(o->v[10].o1));} + +static bool opt_car_equal_sf(opt_info *o) +{ + s7_pointer p = slot_value(o->v[2].p); + return(s7_is_equal(o->sc, slot_value(o->v[1].p), (is_pair(p)) ? car(p) : g_car(o->sc, set_plist_1(o->sc, p)))); +} + +static bool opt_car_equivalent_sf(opt_info *o) +{ + s7_pointer p = slot_value(o->v[2].p); + return(is_equivalent_1(o->sc, slot_value(o->v[1].p), (is_pair(p)) ? car(p) : g_car(o->sc, set_plist_1(o->sc, p)), NULL)); +} + +static bool opt_b_7pp_car_sf(opt_info *o) +{ + s7_pointer p = slot_value(o->v[2].p); + return(o->v[3].b_7pp_f(o->sc, slot_value(o->v[1].p), (is_pair(p)) ? car(p) : g_car(o->sc, set_plist_1(o->sc, p)))); +} + +static s7_pointer opt_p_substring_uncopied_ssf(opt_info *o) /* "inline" here rather than copying below is much slower? */ +{ + return(substring_uncopied_p_pii(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), o->v[6].fi(o->v[5].o1))); +} + +static bool opt_substring_equal_sf(opt_info *o) {return(scheme_strings_are_equal(slot_value(o->v[1].p), opt_p_substring_uncopied_ssf(o->v[10].o1)));} + +static s7_pointer opt_p_p_s(opt_info *o); + +static bool b_pp_sf_combinable(s7_scheme *sc, opt_info *opc, bool bpf_case) +{ + if ((sc->pc > 1) && + (opc == sc->opts[sc->pc - 2])) + { + opt_info *o1 = sc->opts[sc->pc - 1]; + if (o1->v[0].fp == opt_p_p_s) + { + opc->v[2].p = o1->v[1].p; + opc->v[4].p_p_f = o1->v[2].p_p_f; + if (bpf_case) + opc->v[0].fb = opt_b_pp_sfo; + else + if (opc->v[4].p_p_f == car_p_p) + opc->v[0].fb = ((opc->v[3].b_7pp_f == s7_is_equal) ? opt_car_equal_sf : + ((opc->v[3].b_7pp_f == s7_is_equivalent) ? opt_car_equivalent_sf : opt_b_7pp_car_sf)); + else opc->v[0].fb = ((opc->v[3].b_7pp_f == s7_is_equal) ? opt_is_equal_sfo : + ((opc->v[3].b_7pp_f == s7_is_equivalent) ? opt_is_equivalent_sfo : opt_b_7pp_sfo)); + backup_pc(sc); + return_true(sc, NULL); + }} + return_false(sc, NULL); +} + +static bool opt_b_pp_ffo(opt_info *o) +{ + s7_pointer b1 = o->v[4].p_p_f(o->sc, slot_value(o->v[1].p)); + return(o->v[3].b_pp_f(b1, o->v[5].p_p_f(o->sc, slot_value(o->v[2].p)))); +} + +static bool opt_b_pp_ffo_is_eq(opt_info *o) +{ + s7_pointer b1 = o->v[4].p_p_f(o->sc, slot_value(o->v[1].p)); + s7_pointer b2 = o->v[5].p_p_f(o->sc, slot_value(o->v[2].p)); + return((b1 == b2) || ((is_unspecified(b1)) && (is_unspecified(b2)))); +} + +static bool opt_b_7pp_ffo(opt_info *o) +{ + s7_pointer b1 = o->v[4].p_p_f(o->sc, slot_value(o->v[1].p)); + return(o->v[3].b_7pp_f(o->sc, b1, o->v[5].p_p_f(o->sc, slot_value(o->v[2].p)))); +} + +static bool opt_b_cadr_cadr(opt_info *o) +{ + s7_pointer p1 = slot_value(o->v[1].p); + s7_pointer p2 = slot_value(o->v[2].p); + p1 = ((is_pair(p1)) && (is_pair(cdr(p1)))) ? cadr(p1) : g_cadr(o->sc, set_plist_1(o->sc, p1)); + p2 = ((is_pair(p2)) && (is_pair(cdr(p2)))) ? cadr(p2) : g_cadr(o->sc, set_plist_1(o->sc, p2)); + return(o->v[3].b_7pp_f(o->sc, p1, p2)); +} + +static bool b_pp_ff_combinable(s7_scheme *sc, opt_info *opc, bool bpf_case) +{ + if ((sc->pc > 2) && + (opc == sc->opts[sc->pc - 3])) + { + opt_info *o1 = sc->opts[sc->pc - 2], *o2 = sc->opts[sc->pc - 1]; + if ((o1->v[0].fp == opt_p_p_s) && (o2->v[0].fp == opt_p_p_s)) + { + opc->v[1].p = o1->v[1].p; + opc->v[4].p_p_f = o1->v[2].p_p_f; + opc->v[2].p = o2->v[1].p; + opc->v[5].p_p_f = o2->v[2].p_p_f; + opc->v[0].fb = (bpf_case) ? ((opc->v[3].b_pp_f == s7_is_eq) ? opt_b_pp_ffo_is_eq : opt_b_pp_ffo) : + (((opc->v[4].p_p_f == cadr_p_p) && (opc->v[5].p_p_f == cadr_p_p)) ? opt_b_cadr_cadr : opt_b_7pp_ffo); + sc->pc -= 2; + return_true(sc, NULL); + }} + return_false(sc, NULL); +} + +static void check_b_types(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x, bool (*fb)(opt_info *o)) +{ + if (s7_b_pp_unchecked_function(s_func)) + { + s7_pointer call_sig = c_function_signature(s_func); + s7_pointer arg1_type = opt_arg_type(sc, cdr(car_x)); + s7_pointer arg2_type = opt_arg_type(sc, cddr(car_x)); + if ((cadr(call_sig) == arg1_type) && /* not car(arg1_type) here: (string>? (string) (read-line)) */ + (caddr(call_sig) == arg2_type)) + { + opc->v[0].fb = fb; + opc->v[3].b_pp_f = s7_b_pp_unchecked_function(s_func); + }} +#if 0 + if ((arg2_type == sc->is_integer_symbol) && s7_b_pi_function(s_func)) + { + /* opc->v[0].fb = opt_b_pi */ + fprintf(stderr, " pi: %s\n", display(car_x)); + } +#endif +} + +static bool b_pp_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x, s7_pointer arg1, s7_pointer arg2, bool bpf_case) +{ + int32_t cur_index = sc->pc; + opt_info *o1; + /* v[3] is set when we get here */ + if ((is_symbol(arg1)) && + (is_symbol(arg2))) + { + opc->v[1].p = opt_simple_symbol(sc, arg1); + opc->v[2].p = opt_simple_symbol(sc, arg2); + if ((opc->v[1].p) && + (opc->v[2].p)) + { + s7_b_7pp_t b7f = (bpf_case) ? NULL : opc->v[3].b_7pp_f; + opc->v[0].fb = (bpf_case) ? opt_b_pp_ss : + ((b7f == lt_b_7pp) ? opt_b_7pp_ss_lt : ((b7f == gt_b_7pp) ? opt_b_7pp_ss_gt : + ((b7f == char_lt_b_7pp) ? opt_b_7pp_ss_char_lt : opt_b_7pp_ss))); + return_true(sc, car_x); + }} + if (is_symbol(arg1)) + { + opc->v[1].p = opt_simple_symbol(sc, arg1); + if (!opc->v[1].p) + return_false(sc, car_x); + if ((!is_symbol(arg2)) && + (!is_pair(arg2))) + { + opc->v[2].p = arg2; + opc->v[0].fb = (bpf_case) ? opt_b_pp_sc : opt_b_7pp_sc; + check_b_types(sc, opc, s_func, car_x, opt_b_pp_sc); + return_true(sc, car_x); + } + if (cell_optimize(sc, cddr(car_x))) + { + if (!b_pp_sf_combinable(sc, opc, bpf_case)) + { + opc->v[10].o1 = sc->opts[cur_index]; + opc->v[11].fp = opc->v[10].o1->v[0].fp; + opc->v[0].fb = (bpf_case) ? opt_b_pp_sf : opt_b_7pp_sf; + check_b_types(sc, opc, s_func, car_x, opt_b_pp_sf); /* this finds b_pp_unchecked cases */ + if ((opc->v[11].fp == opt_p_substring_uncopied_ssf) && (opc->v[3].b_pp_f == string_eq_b_unchecked)) + opc->v[0].fb = opt_substring_equal_sf; + else if (opc->v[3].b_pp_f == char_eq_b_unchecked) opc->v[0].fb = opt_b_pp_sf_char_eq; + } + return_true(sc, car_x); + } + sc->pc = cur_index; + } + else + if ((is_symbol(arg2)) && + (is_pair(arg1))) + { + opc->v[10].o1 = sc->opts[sc->pc]; + if (cell_optimize(sc, cdr(car_x))) + { + opc->v[1].p = s7_slot(sc, arg2); + if ((!is_slot(opc->v[1].p)) || + (has_methods(slot_value(opc->v[1].p)))) + return_false(sc, car_x); + opc->v[11].fp = opc->v[10].o1->v[0].fp; + opc->v[0].fb = (bpf_case) ? opt_b_pp_fs : opt_b_7pp_fs; + check_b_types(sc, opc, s_func, car_x, opt_b_pp_fs); + return_true(sc, car_x); + } + sc->pc = cur_index; + } + o1 = sc->opts[sc->pc]; + if (cell_optimize(sc, cdr(car_x))) + { + opc->v[10].o1 = sc->opts[sc->pc]; + if (cell_optimize(sc, cddr(car_x))) + { + if (b_pp_ff_combinable(sc, opc, bpf_case)) + return_true(sc, car_x); + opc->v[0].fb = (bpf_case) ? opt_b_pp_ff : opt_b_7pp_ff; + opc->v[8].o1 = o1; + opc->v[9].fp = o1->v[0].fp; + opc->v[11].fp = opc->v[10].o1->v[0].fp; + check_b_types(sc, opc, s_func, car_x, opt_b_pp_ff); + if (opc->v[3].b_pp_f == char_eq_b_unchecked) opc->v[0].fb = opt_b_pp_ff_char_eq; + return_true(sc, car_x); + }} + return_false(sc, car_x); +} + +/* -------- b_pi -------- */ +static bool opt_b_pi_fs(opt_info *o) {return(o->v[2].b_pi_f(o->sc, o->v[11].fp(o->v[10].o1), integer(slot_value(o->v[1].p))));} +static bool opt_b_pi_fs_num_eq(opt_info *o) {return(num_eq_b_pi(o->sc, o->v[11].fp(o->v[10].o1), integer(slot_value(o->v[1].p))));} +static bool opt_b_pi_fi(opt_info *o) {return(o->v[2].b_pi_f(o->sc, o->v[11].fp(o->v[10].o1), o->v[1].i));} +static bool opt_b_pi_ff(opt_info *o) {s7_pointer p1 = o->v[11].fp(o->v[10].o1); return(o->v[2].b_pi_f(o->sc, p1, o->v[9].fi(o->v[8].o1)));} + +static bool b_pi_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x, s7_pointer arg2) +{ + s7_b_pi_t bpif = s7_b_pi_function(s_func); /* perhaps add vector-ref/equal? */ + if (bpif) + { + opc->v[10].o1 = sc->opts[sc->pc]; + if (cell_optimize(sc, cdr(car_x))) + { + opt_info *o1 = sc->opts[sc->pc]; + opc->v[2].b_pi_f = bpif; + opc->v[11].fp = opc->v[10].o1->v[0].fp; + if (is_symbol(arg2)) + { + opc->v[1].p = s7_slot(sc, arg2); /* slot checked in opt_arg_type */ + opc->v[0].fb = (bpif == num_eq_b_pi) ? opt_b_pi_fs_num_eq : opt_b_pi_fs; + return_true(sc, car_x); + } + if (is_t_integer(arg2)) + { + opc->v[1].i = integer(arg2); + opc->v[0].fb = opt_b_pi_fi; + return_true(sc, car_x); + } + if (int_optimize(sc, cddr(car_x))) + { + opc->v[0].fb = opt_b_pi_ff; + opc->v[8].o1 = o1; + opc->v[9].fp = o1->v[0].fp; + return_true(sc, car_x); + }}} + return_false(sc, car_x); +} + + +/* -------- b_dd -------- */ +static bool opt_b_dd_ss(opt_info *o) {return(o->v[3].b_dd_f(real(slot_value(o->v[1].p)), real(slot_value(o->v[2].p))));} +static bool opt_b_dd_ss_lt(opt_info *o) {return(real(slot_value(o->v[1].p)) < real(slot_value(o->v[2].p)));} +static bool opt_b_dd_ss_gt(opt_info *o) {return(real(slot_value(o->v[1].p)) > real(slot_value(o->v[2].p)));} + +static bool opt_b_dd_sc(opt_info *o) {return(o->v[3].b_dd_f(real(slot_value(o->v[1].p)), o->v[2].x));} +static bool opt_b_dd_sc_lt(opt_info *o) {return(real(slot_value(o->v[1].p)) < o->v[2].x);} +static bool opt_b_dd_sc_geq(opt_info *o) {return(real(slot_value(o->v[1].p)) >= o->v[2].x);} +static bool opt_b_dd_sc_eq(opt_info *o) {return(real(slot_value(o->v[1].p)) == o->v[2].x);} + +static bool opt_b_dd_sf(opt_info *o) {return(o->v[3].b_dd_f(real(slot_value(o->v[1].p)), o->v[11].fd(o->v[10].o1)));} +static bool opt_b_dd_fs(opt_info *o) {return(o->v[3].b_dd_f(o->v[11].fd(o->v[10].o1), real(slot_value(o->v[1].p))));} +static bool opt_b_dd_fs_gt(opt_info *o) {return(o->v[11].fd(o->v[10].o1) > real(slot_value(o->v[1].p)));} +static bool opt_b_dd_fc(opt_info *o) {return(o->v[3].b_dd_f(o->v[11].fd(o->v[10].o1), o->v[1].x));} +static bool opt_b_dd_fc_gt(opt_info *o) {return(o->v[11].fd(o->v[10].o1) > o->v[1].x);} + +static bool opt_b_dd_ff(opt_info *o) +{ + s7_double x1 = o->v[11].fd(o->v[10].o1); + s7_double x2 = o->v[9].fd(o->v[8].o1); + return(o->v[3].b_dd_f(x1, x2)); +} + +static bool b_dd_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x, s7_pointer arg1, s7_pointer arg2) +{ + s7_b_dd_t bif = s7_b_dd_function(s_func); + int32_t cur_index = sc->pc; + if (!bif) + return_false(sc, car_x); + opc->v[3].b_dd_f = bif; + if (is_symbol(arg1)) + { + opc->v[1].p = s7_slot(sc, arg1); + if (is_symbol(arg2)) + { + opc->v[2].p = s7_slot(sc, arg2); + opc->v[0].fb = (bif == lt_b_dd) ? opt_b_dd_ss_lt : ((bif == gt_b_dd) ? opt_b_dd_ss_gt : opt_b_dd_ss); + return_true(sc, car_x); + } + if (is_t_real(arg2)) + { + opc->v[2].x = s7_number_to_real(sc, arg2); + opc->v[0].fb = (bif == lt_b_dd) ? opt_b_dd_sc_lt : ((bif == geq_b_dd) ? opt_b_dd_sc_geq : ((bif == num_eq_b_dd) ? opt_b_dd_sc_eq : opt_b_dd_sc)); + return_true(sc, car_x); + } + opc->v[10].o1 = sc->opts[sc->pc]; + if (float_optimize(sc, cddr(car_x))) + { + opc->v[11].fd = opc->v[10].o1->v[0].fd; + opc->v[0].fb = opt_b_dd_sf; + return_true(sc, car_x); + }} + sc->pc = cur_index; + opc->v[10].o1 = sc->opts[sc->pc]; + if (float_optimize(sc, cdr(car_x))) + { + opc->v[11].fd = opc->v[10].o1->v[0].fd; + if (is_symbol(arg2)) + { + opc->v[1].p = s7_slot(sc, arg2); + opc->v[0].fb = (bif == gt_b_dd) ? opt_b_dd_fs_gt : opt_b_dd_fs; + return_true(sc, car_x); + } + if (is_small_real(arg2)) + { + opc->v[1].x = s7_number_to_real(sc, arg2); + opc->v[0].fb = (bif == gt_b_dd) ? opt_b_dd_fc_gt : opt_b_dd_fc; + return_true(sc, car_x); + } + opc->v[8].o1 = sc->opts[sc->pc]; + if (float_optimize(sc, cddr(car_x))) + { + opc->v[9].fd = opc->v[8].o1->v[0].fd; + opc->v[0].fb = opt_b_dd_ff; + return_true(sc, car_x); + }} + sc->pc = cur_index; + return_false(sc, car_x); +} + + +/* -------- b_ii -------- */ +static bool opt_b_ii_ss(opt_info *o) {return(o->v[3].b_ii_f(integer(slot_value(o->v[1].p)), integer(slot_value(o->v[2].p))));} +static bool opt_b_ii_ss_lt(opt_info *o) {return(integer(slot_value(o->v[1].p)) < integer(slot_value(o->v[2].p)));} +static bool opt_b_ii_ss_gt(opt_info *o) {return(integer(slot_value(o->v[1].p)) > integer(slot_value(o->v[2].p)));} +static bool opt_b_ii_ss_leq(opt_info *o) {return(integer(slot_value(o->v[1].p)) <= integer(slot_value(o->v[2].p)));} +static bool opt_b_ii_ss_geq(opt_info *o) {return(integer(slot_value(o->v[1].p)) >= integer(slot_value(o->v[2].p)));} +static bool opt_b_ii_ss_eq(opt_info *o) {return(integer(slot_value(o->v[1].p)) == integer(slot_value(o->v[2].p)));} +static bool opt_b_ii_sc(opt_info *o) {return(o->v[3].b_ii_f(integer(slot_value(o->v[1].p)), o->v[2].i));} +static bool opt_b_ii_sc_lt(opt_info *o) {return(integer(slot_value(o->v[1].p)) < o->v[2].i);} +static bool opt_b_ii_sc_leq(opt_info *o) {return(integer(slot_value(o->v[1].p)) <= o->v[2].i);} +static bool opt_b_ii_sc_gt(opt_info *o) {return(integer(slot_value(o->v[1].p)) > o->v[2].i);} +static bool opt_b_ii_sc_geq(opt_info *o) {return(integer(slot_value(o->v[1].p)) >= o->v[2].i);} +static bool opt_b_ii_sc_eq(opt_info *o) {return(integer(slot_value(o->v[1].p)) == o->v[2].i);} +static bool opt_b_ii_sc_lt_2(opt_info *o) {return(integer(slot_value(o->v[1].p)) < 2);} +static bool opt_b_ii_sc_lt_1(opt_info *o) {return(integer(slot_value(o->v[1].p)) < 1);} +static bool opt_b_ii_sc_lt_0(opt_info *o) {return(integer(slot_value(o->v[1].p)) < 0);} +static bool opt_b_ii_sc_leq_0(opt_info *o){return(integer(slot_value(o->v[1].p)) <= 0);} +static bool opt_b_ii_sc_gt_0(opt_info *o) {return(integer(slot_value(o->v[1].p)) > 0);} +static bool opt_b_ii_sc_geq_0(opt_info *o){return(integer(slot_value(o->v[1].p)) >= 0);} +static bool opt_b_ii_sc_eq_0(opt_info *o) {return(integer(slot_value(o->v[1].p)) == 0);} +static bool opt_b_ii_sc_eq_1(opt_info *o) {return(integer(slot_value(o->v[1].p)) == 1);} + +static bool opt_b_7ii_ss(opt_info *o) {return(o->v[3].b_7ii_f(o->sc, integer(slot_value(o->v[1].p)), integer(slot_value(o->v[2].p))));} +static bool opt_b_7ii_sc(opt_info *o) {return(o->v[3].b_7ii_f(o->sc, integer(slot_value(o->v[1].p)), o->v[2].i));} +static bool opt_b_7ii_sc_bit(opt_info *o) {return((integer(slot_value(o->v[1].p)) & ((int64_t)(1LL << o->v[2].i))) != 0);} + +static bool opt_b_ii_ff(opt_info *o) +{ + s7_int i1 = o->v[11].fi(o->v[10].o1); + s7_int i2 = o->v[9].fi(o->v[8].o1); + return(o->v[3].b_ii_f(i1, i2)); +} + +static bool opt_b_ii_fs(opt_info *o) {return(o->v[3].b_ii_f(o->v[11].fi(o->v[10].o1), integer(slot_value(o->v[2].p))));} +static bool opt_b_ii_sf(opt_info *o) {return(o->v[3].b_ii_f(integer(slot_value(o->v[1].p)), o->v[11].fi(o->v[10].o1)));} +static bool opt_b_ii_sf_eq(opt_info *o) {return(integer(slot_value(o->v[1].p)) == o->v[11].fi(o->v[10].o1));} +static bool opt_b_ii_fc(opt_info *o) {return(o->v[3].b_ii_f(o->v[11].fi(o->v[10].o1), o->v[2].i));} +static bool opt_b_ii_fc_eq(opt_info *o) {return(o->v[11].fi(o->v[10].o1) == o->v[2].i);} + +static bool b_ii_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x, s7_pointer arg1, s7_pointer arg2) +{ + s7_b_ii_t bif = s7_b_ii_function(s_func); + s7_b_7ii_t b7if = NULL; + if (!bif) + { + b7if = s7_b_7ii_function(s_func); + if (!b7if) + return_false(sc, car_x); + } + if (bif) opc->v[3].b_ii_f = bif; else opc->v[3].b_7ii_f = b7if; + if (is_symbol(arg1)) + { + opc->v[1].p = s7_slot(sc, arg1); + if (is_symbol(arg2)) + { + opc->v[2].p = s7_slot(sc, arg2); + + opc->v[0].fb = (bif == lt_b_ii) ? opt_b_ii_ss_lt : + ((bif == leq_b_ii) ? opt_b_ii_ss_leq : + ((bif == gt_b_ii) ? opt_b_ii_ss_gt : + ((bif == geq_b_ii) ? opt_b_ii_ss_geq : + ((bif == num_eq_b_ii) ? opt_b_ii_ss_eq : + ((bif) ? opt_b_ii_ss : opt_b_7ii_ss))))); + return_true(sc, car_x); + } + if (is_t_integer(arg2)) + { + s7_int i2 = integer(arg2); + opc->v[2].i = i2; + opc->v[0].fb = (bif == num_eq_b_ii) ? ((i2 == 0) ? opt_b_ii_sc_eq_0 : ((i2 == 1) ? opt_b_ii_sc_eq_1 : opt_b_ii_sc_eq)) : + ((bif == lt_b_ii) ? ((i2 == 0) ? opt_b_ii_sc_lt_0 : ((i2 == 1) ? opt_b_ii_sc_lt_1 : ((i2 == 2) ? opt_b_ii_sc_lt_2 : opt_b_ii_sc_lt))) : + ((bif == gt_b_ii) ? ((i2 == 0) ? opt_b_ii_sc_gt_0 : opt_b_ii_sc_gt) : + ((bif == leq_b_ii) ? ((i2 == 0) ? opt_b_ii_sc_leq_0 : opt_b_ii_sc_leq) : + ((bif == geq_b_ii) ? ((i2 == 0) ? opt_b_ii_sc_geq_0 : opt_b_ii_sc_geq) : + (((b7if == logbit_b_7ii) && (i2 >= 0) && (i2 < S7_INT_BITS)) ? opt_b_7ii_sc_bit : + ((bif) ? opt_b_ii_sc : opt_b_7ii_sc)))))); + return_true(sc, car_x); + } + opc->v[10].o1 = sc->opts[sc->pc]; + if ((bif) && (int_optimize(sc, cddr(car_x)))) + { + opc->v[0].fb = (bif == num_eq_b_ii) ? opt_b_ii_sf_eq : opt_b_ii_sf; + opc->v[11].fi = opc->v[10].o1->v[0].fi; + return_true(sc, car_x); + } + return_false(sc, car_x); + } + if (!bif) return_false(sc, car_x); + + if (is_symbol(arg2)) + { + opc->v[10].o1 = sc->opts[sc->pc]; + if (!int_optimize(sc, cdr(car_x))) + return_false(sc, car_x); + opc->v[11].fi = opc->v[10].o1->v[0].fi; + opc->v[2].p = s7_slot(sc, arg2); + opc->v[0].fb = opt_b_ii_fs; + return_true(sc, car_x); + } + opc->v[10].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, cdr(car_x))) + { + opc->v[11].fi = opc->v[10].o1->v[0].fi; + if (is_t_integer(arg2)) + { + opc->v[2].i = integer(arg2); + opc->v[0].fb = (bif == num_eq_b_ii) ? opt_b_ii_fc_eq : opt_b_ii_fc; + return_true(sc, car_x); + } + opc->v[8].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, cddr(car_x))) + { + opc->v[9].fi = opc->v[8].o1->v[0].fi; + opc->v[0].fb = opt_b_ii_ff; + return_true(sc, car_x); + }} + return_false(sc, car_x); +} + +/* -------- b_or|and -------- */ +static bool opt_and_bb(opt_info *o) {return((o->v[3].fb(o->v[2].o1)) && (o->v[11].fb(o->v[10].o1)));} + +static bool opt_and_any_b(opt_info *o) +{ + for (s7_int i = 0; i < o->v[1].i; i++) + { + opt_info *o1 = o->v[i + 3].o1; + if (!o1->v[0].fb(o1)) + return(false); + } + return(true); +} + +static bool opt_or_bb(opt_info *o) {return((o->v[3].fb(o->v[2].o1)) || o->v[11].fb(o->v[10].o1));} + +static bool opt_or_any_b(opt_info *o) +{ + for (s7_int i = 0; i < o->v[1].i; i++) + { + opt_info *o1 = o->v[i + 3].o1; + if (o1->v[0].fb(o1)) + return(true); + } + return(false); +} + +static bool opt_b_or_and(s7_scheme *sc, s7_pointer car_x, int32_t len, int32_t is_and) +{ + opt_info *opc = alloc_opt_info(sc); + s7_pointer p = cdr(car_x); + if (len == 3) + { + opt_info *o1 = sc->opts[sc->pc]; + if (bool_optimize_nw(sc, cdr(car_x))) + { + opt_info *o2 = sc->opts[sc->pc]; + if (bool_optimize_nw(sc, cddr(car_x))) + { + opc->v[10].o1 = o2; + opc->v[11].fb = o2->v[0].fb; + opc->v[0].fb = (is_and) ? opt_and_bb : opt_or_bb; + opc->v[2].o1 = o1; + opc->v[3].fb = o1->v[0].fb; + return_true(sc, car_x); + }} + return_false(sc, car_x); + } + opc->v[1].i = (len - 1); + for (int32_t i = 0; (is_pair(p)) && (i < 12); i++, p = cdr(p)) + { + opc->v[i + 3].o1 = sc->opts[sc->pc]; + if (!bool_optimize_nw(sc, p)) + break; + } + if (!is_null(p)) + return_false(sc, car_x); + opc->v[0].fb = (is_and) ? opt_and_any_b : opt_or_any_b; + return_true(sc, car_x); +} + +static bool opt_b_and(s7_scheme *sc, s7_pointer car_x, int32_t len) {return(opt_b_or_and(sc, car_x, len, true));} +static bool opt_b_or(s7_scheme *sc, s7_pointer car_x, int32_t len) {return(opt_b_or_and(sc, car_x, len, false));} + + +/* ---------------------------------------- cell opts ---------------------------------------- */ +static s7_pointer opt_p_c(opt_info *o) {return(o->v[1].p);} +static s7_pointer opt_p_s(opt_info *o) {return(slot_value(o->v[1].p));} + +static bool opt_cell_not_pair(s7_scheme *sc, s7_pointer car_x) +{ + s7_pointer p; + opt_info *opc; + if (!is_symbol(car_x)) + { + opc = alloc_opt_info(sc); + opc->v[1].p = car_x; + opc->v[0].fp = opt_p_c; + return_true(sc, car_x); + } + p = opt_simple_symbol(sc, car_x); + if (!p) + return_false(sc, car_x); + opc = alloc_opt_info(sc); + opc->v[1].p = p; + opc->v[0].fp = opt_p_s; + return_true(sc, car_x); +} + +/* -------- p -------- */ +#define is_opt_safe(P) ((optimize_op(P) >= OP_SAFE_C_S) && (!is_unknown_op(optimize_op(P)))) + +#define cf_call(Sc, Car_x, S_func, Num) \ + (((is_optimized(Car_x)) && (is_opt_safe(Car_x))) ? fn_proc(Car_x) : c_function_call(c_function_chooser(S_func)(Sc, S_func, Num, Car_x, false))) + +static s7_pointer opt_p_f(opt_info *o) {return(o->v[1].p_f(o->sc));} +static s7_pointer opt_p_call(opt_info *o) {return(o->v[1].call(o->sc, o->sc->nil));} + +static bool p_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + s7_p_t func = s7_p_function(s_func); + if (func) + { + opc->v[1].p_f = func; + opc->v[0].fp = opt_p_f; + return_true(sc, car_x); + } + if ((is_safe_procedure(s_func)) && + (c_function_min_args(s_func) == 0)) + { + opc->v[1].call = cf_call(sc, car_x, s_func, 0); + opc->v[0].fp = opt_p_call; + return_true(sc, car_x); + } + return_false(sc, car_x); +} + +/* -------- p_p -------- */ +static s7_pointer opt_p_p_c(opt_info *o) {return(o->v[2].p_p_f(o->sc, o->v[1].p));} +static s7_pointer opt_p_i_c(opt_info *o) {return(make_integer(o->sc, o->v[2].i_i_f(o->v[1].i)));} +static s7_pointer opt_p_7i_c(opt_info *o) {return(make_integer(o->sc, o->v[2].i_7i_f(o->sc, o->v[1].i)));} +static s7_pointer opt_p_d_c(opt_info *o) {return(make_real(o->sc, o->v[2].d_d_f(o->v[1].x)));} +static s7_pointer opt_p_7d_c(opt_info *o) {return(make_real(o->sc, o->v[2].d_7d_f(o->sc, o->v[1].x)));} +static s7_pointer opt_p_p_s(opt_info *o) {return(o->v[2].p_p_f(o->sc, slot_value(o->v[1].p)));} +static s7_pointer opt_p_p_s_abs(opt_info *o) {return(abs_p_p(o->sc, slot_value(o->v[1].p)));} +static s7_pointer opt_p_p_s_cdr(opt_info *o) {s7_pointer p = slot_value(o->v[1].p); return((is_pair(p)) ? cdr(p) : cdr_p_p(o->sc, p));} +static s7_pointer opt_p_p_f(opt_info *o) {return(o->v[2].p_p_f(o->sc, o->v[4].fp(o->v[3].o1)));} +static s7_pointer opt_p_p_f1(opt_info *o) {return(o->v[2].p_p_f(o->sc, o->v[3].p_p_f(o->sc, slot_value(o->v[1].p))));} +static s7_pointer opt_p_p_f_exp(opt_info *o) {return(exp_p_p(o->sc, o->v[4].fp(o->v[3].o1)));} +static s7_pointer opt_p_7d_c_random(opt_info *o) {return(make_real(o->sc, random_d_7d(o->sc, o->v[1].x)));} +static s7_pointer opt_p_p_s_iterate(opt_info *o) {return(iterate_p_p(o->sc, slot_value(o->v[1].p)));} +static s7_pointer opt_p_p_f_iterate(opt_info *o) {return(iterate_p_p(o->sc, o->v[4].fp(o->v[3].o1)));} +static s7_pointer opt_p_p_f_string_to_number(opt_info *o) {return(string_to_number_p_p(o->sc, o->v[4].fp(o->v[3].o1)));} +static s7_pointer opt_p_p_s_iterate_unchecked(opt_info *o) {s7_pointer iter = slot_value(o->v[1].p); return(iterator_next(iter)(o->sc, iter));} + +static s7_pointer opt_p_pi_ss_vref_direct(opt_info *o); +static s7_pointer opt_p_pi_ss_fvref_direct(opt_info *o); +static s7_pointer opt_p_p_fvref(opt_info *o) {return(o->v[2].p_p_f(o->sc, opt_p_pi_ss_fvref_direct(o->v[3].o1)));} /* unwrap to fvref is not faster */ +static s7_pointer opt_p_p_vref(opt_info *o) {return(o->v[2].p_p_f(o->sc, opt_p_pi_ss_vref_direct(o->v[3].o1)));} + +static bool p_p_f_combinable(s7_scheme *sc, opt_info *opc) +{ + if ((sc->pc > 1) && + (opc == sc->opts[sc->pc - 2])) + { + opt_info *o1 = sc->opts[sc->pc - 1]; + if (o1->v[0].fp == opt_p_p_s) + { + opc->v[3].p_p_f = o1->v[2].p_p_f; + opc->v[1].p = o1->v[1].p; + opc->v[0].fp = opt_p_p_f1; + backup_pc(sc); + return_true(sc, NULL); + }} + return_false(sc, NULL); +} + +static s7_pointer opt_p_call_f(opt_info *o) {return(o->v[2].call(o->sc, set_plist_1(o->sc, o->v[5].fp(o->v[4].o1))));} +static s7_pointer opt_p_call_s(opt_info *o) {return(o->v[2].call(o->sc, set_plist_1(o->sc, slot_value(o->v[1].p))));} +static s7_pointer opt_p_call_c(opt_info *o) {return(o->v[2].call(o->sc, set_plist_1(o->sc, o->v[1].p)));} + +static bool p_p_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + s7_p_p_t ppf; + int32_t start = sc->pc; + if (is_t_integer(cadr(car_x))) + { + s7_i_i_t iif = s7_i_i_function(s_func); + s7_i_7i_t i7if; + opc->v[1].i = integer(cadr(car_x)); + if (iif) + { + opc->v[2].i_i_f = iif; + opc->v[0].fp = opt_p_i_c; + return_true(sc, car_x); + } + i7if = s7_i_7i_function(s_func); + if (i7if) + { + opc->v[2].i_7i_f = i7if; + opc->v[0].fp = opt_p_7i_c; + return_true(sc, car_x); + }} + if (is_t_real(cadr(car_x))) + { + s7_d_d_t ddf = s7_d_d_function(s_func); + s7_d_7d_t d7df; + opc->v[1].x = real(cadr(car_x)); + if (ddf) + { + opc->v[2].d_d_f = ddf; + opc->v[0].fp = opt_p_d_c; + return_true(sc, car_x); + } + d7df = s7_d_7d_function(s_func); + if (d7df) + { + opc->v[2].d_7d_f = d7df; + opc->v[0].fp = (d7df == random_d_7d) ? opt_p_7d_c_random : opt_p_7d_c; + return_true(sc, car_x); + }} + ppf = s7_p_p_function(s_func); + if (ppf) + { + opt_info *o1; + opc->v[2].p_p_f = ppf; + if ((ppf == symbol_to_string_p_p) && + (is_optimized(car_x)) && + (fn_proc(car_x) == g_symbol_to_string_uncopied)) + opc->v[2].p_p_f = symbol_to_string_uncopied_p; + + if (is_symbol(cadr(car_x))) + { + opc->v[1].p = opt_simple_symbol(sc, cadr(car_x)); + if (!opc->v[1].p) + return_false(sc, car_x); + opc->v[0].fp = (ppf == abs_p_p) ? opt_p_p_s_abs : ((ppf == cdr_p_p) ? opt_p_p_s_cdr : + ((ppf == iterate_p_p) ? ((is_iterator(slot_value(opc->v[1].p))) ? opt_p_p_s_iterate_unchecked : opt_p_p_s_iterate) : opt_p_p_s)); + return_true(sc, car_x); + } + if (!is_pair(cadr(car_x))) + { + if (opc->v[2].p_p_f == s7_length) + { + opc->v[1].p = s7_length(sc, cadr(car_x)); + opc->v[0].fp = opt_p_c; + } + else + { + opc->v[1].p = cadr(car_x); + opc->v[0].fp = opt_p_p_c; + } + return_true(sc, car_x); + } + o1 = sc->opts[sc->pc]; + if (cell_optimize(sc, cdr(car_x))) + { + if (!p_p_f_combinable(sc, opc)) + { + s7_pointer (*fp)(opt_info *o); + opc->v[0].fp = (ppf == exp_p_p) ? opt_p_p_f_exp : ((ppf == iterate_p_p) ? opt_p_p_f_iterate : + ((ppf == string_to_number_p_p) ? opt_p_p_f_string_to_number : opt_p_p_f)); + if (caadr(car_x) == sc->string_ref_symbol) + { + if (opc->v[2].p_p_f == char_upcase_p_p) + opc->v[2].p_p_f = char_upcase_p_p_unchecked; + else + if (opc->v[2].p_p_f == is_char_whitespace_p_p) + opc->v[2].p_p_f = is_char_whitespace_p_p_unchecked; + } + opc->v[3].o1 = o1; + fp = o1->v[0].fp; + opc->v[4].fp = fp; + if (fp == opt_p_pi_ss_fvref_direct) opc->v[0].fp = opt_p_p_fvref; + else if (fp == opt_p_pi_ss_vref_direct) opc->v[0].fp = opt_p_p_vref; + } + return_true(sc, car_x); + }} + + sc->pc = start; + if ((is_safe_procedure(s_func)) && (c_function_is_aritable(s_func, 1))) + { + opc->v[2].call = cf_call(sc, car_x, s_func, 1); + if (is_symbol(cadr(car_x))) + { + s7_pointer slot = opt_simple_symbol(sc, cadr(car_x)); + if (slot) + { + opc->v[1].p = slot; + opc->v[0].fp = opt_p_call_s; + return_true(sc, car_x); + }} + else + { + opt_info *o1; + if (!is_pair(cadr(car_x))) + { + opc->v[1].p = cadr(car_x); + opc->v[0].fp = opt_p_call_c; + return_true(sc, car_x); + } + o1 = sc->opts[sc->pc]; + if (cell_optimize(sc, cdr(car_x))) + { + opc->v[0].fp = opt_p_call_f; + opc->v[4].o1 = o1; + opc->v[5].fp = o1->v[0].fp; + return_true(sc, car_x); + }}} + return_false(sc, car_x); +} + +/* -------- p_i -------- */ +static s7_pointer opt_p_i_s(opt_info *o) {return(o->v[2].p_i_f(o->sc, integer(slot_value(o->v[1].p))));} /* number_to_string_p_i expanded here doesn't gain much */ +static s7_pointer opt_p_i_f(opt_info *o) {return(o->v[2].p_i_f(o->sc, o->v[4].fi(o->v[3].o1)));} +static s7_pointer opt_p_i_f_intc(opt_info *o) {return(integer_to_char_p_i(o->sc, o->v[4].fi(o->v[3].o1)));} + +static bool p_i_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x, int32_t pstart) +{ + s7_pointer p; + s7_p_i_t ifunc = s7_p_i_function(s_func); + if (!ifunc) + return_false(sc, car_x); + p = opt_integer_symbol(sc, cadr(car_x)); + if (p) + { + opc->v[1].p = p; + opc->v[2].p_i_f = ifunc; + opc->v[0].fp = opt_p_i_s; + return_true(sc, car_x); + } + if (int_optimize(sc, cdr(car_x))) + { + opc->v[2].p_i_f = ifunc; + opc->v[0].fp = (ifunc == integer_to_char_p_i) ? opt_p_i_f_intc : opt_p_i_f; + opc->v[3].o1 = sc->opts[pstart]; + opc->v[4].fi = sc->opts[pstart]->v[0].fi; + return_true(sc, car_x); + } + sc->pc = pstart; + return_false(sc, car_x); +} + +/* -------- p_ii -------- */ +static s7_pointer opt_p_ii_ss(opt_info *o) {return(o->v[3].p_ii_f(o->sc, integer(slot_value(o->v[1].p)), integer(slot_value(o->v[2].p))));} +static s7_pointer opt_p_ii_fs(opt_info *o) {return(o->v[3].p_ii_f(o->sc, o->v[11].fi(o->v[10].o1), integer(slot_value(o->v[2].p))));} +static s7_pointer opt_p_ii_ff_divide(opt_info *o) {return(make_ratio_with_div_check(o->sc, o->sc->divide_symbol, o->v[11].fi(o->v[10].o1), o->v[9].fi(o->v[8].o1)));} + +static s7_pointer opt_p_ii_ff(opt_info *o) +{ + s7_int i1 = o->v[11].fi(o->v[10].o1); + return(o->v[3].p_ii_f(o->sc, i1, o->v[9].fi(o->v[8].o1))); +} + +static bool p_ii_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x, int32_t pstart) +{ + s7_pointer p2; + s7_p_ii_t ifunc = s7_p_ii_function(s_func); + if (!ifunc) + return_false(sc, car_x); + p2 = opt_integer_symbol(sc, caddr(car_x)); + if (p2) + { + s7_pointer p1 = opt_integer_symbol(sc, cadr(car_x)); + if (p1) + { + opc->v[1].p = p1; + opc->v[2].p = p2; + opc->v[3].p_ii_f = ifunc; + opc->v[0].fp = opt_p_ii_ss; + return_true(sc, car_x); + } + opc->v[10].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, cdr(car_x))) + { + opc->v[11].fi = opc->v[10].o1->v[0].fi; + opc->v[2].p = p2; + opc->v[3].p_ii_f = ifunc; + opc->v[0].fp = opt_p_ii_fs; + return_true(sc, car_x); + } + sc->pc = pstart; + return_false(sc, car_x); + } + opc->v[10].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, cdr(car_x))) + { + opc->v[8].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, cddr(car_x))) + { + opc->v[11].fi = opc->v[10].o1->v[0].fi; + opc->v[9].fi = opc->v[8].o1->v[0].fi; + opc->v[3].p_ii_f = ifunc; + opc->v[0].fp = (ifunc == divide_p_ii) ? opt_p_ii_ff_divide : opt_p_ii_ff; + return_true(sc, car_x); + }} + sc->pc = pstart; + return_false(sc, car_x); +} + +/* -------- p_d -------- */ +static s7_pointer opt_p_d_s(opt_info *o) {return(o->v[2].p_d_f(o->sc, real_to_double(o->sc, slot_value(o->v[1].p), __func__)));} +static s7_pointer opt_p_d_f(opt_info *o) {return(o->v[2].p_d_f(o->sc, o->v[4].fd(o->v[3].o1)));} +/* static s7_pointer opt_p_d_fvref(opt_info *o) {return(o->v[2].p_d_f(o->sc, float_vector(slot_value(o->v[1].p), integer(slot_value(o->v[2].p)))));} */ + +static bool p_d_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x, int32_t pstart) +{ + s7_pointer p; + opt_info *o1; + s7_p_d_t ifunc = s7_p_d_function(s_func); + + if (!ifunc) + return_false(sc, car_x); + p = opt_float_symbol(sc, cadr(car_x)); + if (p) + { + opc->v[1].p = p; + opc->v[2].p_d_f = ifunc; + opc->v[0].fp = opt_p_d_s; + return_true(sc, car_x); + } + if ((is_number(cadr(car_x))) && (!is_t_real(cadr(car_x)))) + return_false(sc, car_x); + o1 = sc->opts[sc->pc]; + if (float_optimize(sc, cdr(car_x))) + { + opc->v[2].p_d_f = ifunc; + opc->v[0].fp = opt_p_d_f; + opc->v[3].o1 = o1; + opc->v[4].fd = o1->v[0].fd; + return_true(sc, car_x); + } + sc->pc = pstart; + return_false(sc, car_x); +} + +/* -------- p_dd -------- */ +static s7_pointer opt_p_dd_sc(opt_info *o) {return(o->v[3].p_dd_f(o->sc, real_to_double(o->sc, slot_value(o->v[1].p), __func__), o->v[2].x));} +static s7_pointer opt_p_dd_cs(opt_info *o) {return(o->v[3].p_dd_f(o->sc, o->v[2].x, real_to_double(o->sc, slot_value(o->v[1].p), __func__)));} +static s7_pointer opt_p_dd_cc(opt_info *o) {return(o->v[3].p_dd_f(o->sc, o->v[1].x, o->v[2].x));} + +static bool p_dd_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x, int32_t pstart) +{ + s7_pointer slot, arg1 = cadr(car_x), arg2 = caddr(car_x); + s7_p_dd_t ifunc = s7_p_dd_function(s_func); + if (!ifunc) + return_false(sc, car_x); + if (is_t_real(arg2)) + { + if (is_t_real(arg1)) + { + opc->v[1].x = real(arg1); + opc->v[2].x = real(arg2); + opc->v[3].p_dd_f = ifunc; + opc->v[0].fp = opt_p_dd_cc; + return_true(sc, car_x); + } + slot = opt_real_symbol(sc, arg1); + if (slot) + { + opc->v[2].x = real(arg2); + opc->v[1].p = slot; + opc->v[3].p_dd_f = ifunc; + opc->v[0].fp = opt_p_dd_sc; + return_true(sc, car_x); + }} + if (is_t_real(arg1)) + { + slot = opt_real_symbol(sc, arg2); + if (slot) + { + opc->v[2].x = real(arg1); + opc->v[1].p = slot; + opc->v[3].p_dd_f = ifunc; + opc->v[0].fp = opt_p_dd_cs; + return_true(sc, car_x); + }} + sc->pc = pstart; + return_false(sc, car_x); +} + +/* -------- p_pi -------- */ +static s7_pointer opt_p_pi_ss(opt_info *o) {return(o->v[3].p_pi_f(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p))));} +static s7_pointer opt_p_pi_ss_sref(opt_info *o) {return(string_ref_p_pi_unchecked(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p))));} +static s7_pointer opt_p_pi_ss_sref_direct(opt_info *o) {return(string_ref_p_pi_direct(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p))));} +static s7_pointer opt_p_pi_ss_vref(opt_info *o) {return(normal_vector_ref_p_pi_unchecked(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p))));} +static s7_pointer opt_p_pi_ss_vref_direct(opt_info *o) {return(normal_vector_ref_p_pi_direct(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p))));} +static s7_pointer opt_p_pi_ss_fvref_direct(opt_info *o) {return(float_vector_ref_p_pi_direct(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p))));} +static s7_pointer opt_p_pi_ss_ivref_direct(opt_info *o) {return(int_vector_ref_p_pi_direct(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p))));} +static s7_pointer opt_p_pi_ss_lref(opt_info *o) {return(list_ref_p_pi_unchecked(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p))));} +static s7_pointer opt_p_pi_sc(opt_info *o) {return(o->v[3].p_pi_f(o->sc, slot_value(o->v[1].p), o->v[2].i));} +static s7_pointer opt_p_pi_sc_lref(opt_info *o) {return(list_ref_p_pi_unchecked(o->sc, slot_value(o->v[1].p), o->v[2].i));} +static s7_pointer opt_p_pi_sf(opt_info *o) {return(o->v[3].p_pi_f(o->sc, slot_value(o->v[1].p), o->v[5].fi(o->v[4].o1)));} +static s7_pointer opt_p_pi_sf_sref(opt_info *o) {return(string_ref_p_pi_unchecked(o->sc, slot_value(o->v[1].p), o->v[5].fi(o->v[4].o1)));} +static s7_pointer opt_p_pi_sf_sref_direct(opt_info *o) {return(string_ref_p_pi_direct(o->sc, slot_value(o->v[1].p), o->v[5].fi(o->v[4].o1)));} +static s7_pointer opt_p_pi_fc(opt_info *o) {return(o->v[3].p_pi_f(o->sc, o->v[5].fp(o->v[4].o1), o->v[2].i));} + +/* use a unique name (in this code) for this use of denominator -- this is a kludge -- we don't have anywhere in the slot + * to store the loop end, but the slot_value can be a small_int (or any unheaped integer), so we're assuming there + * aren't collisions? Each use is a single (uncomplicated) do loop, set up before each call? + */ +#if S7_DEBUGGING +static s7_pointer check_loop_end_ref(s7_pointer p, const char *func, int32_t line) +{ + uint8_t typ = unchecked_type(T_Slt(p)); + if (!has_loop_end(p)) complain("%s%s[%d]: loop_end not set, %s (%s)%s\n", p, func, line, typ); + return(T_Int(slot_value(p))); +} +#define loop_end(A) denominator(check_loop_end_ref(A, __func__, __LINE__)) +#else +#define loop_end(A) denominator(T_Int(slot_value(A))) +#endif +#define set_loop_end(A, B) denominator(T_Int(slot_value(A))) = B + +static void check_unchecked(s7_scheme *sc, s7_pointer obj, s7_pointer slot, opt_info *opc, s7_pointer expr) +{ + switch (type(obj)) /* can't use funcs here (opc->v[3].p_pi_f et al) because there are so many, and copy depends on this choice */ + { + case T_STRING: + if (((!expr) || (car(expr) == sc->string_ref_symbol)) && (loop_end(slot) <= string_length(obj))) + opc->v[3].p_pi_f = string_ref_p_pi_direct; + break; + case T_BYTE_VECTOR: + if (((!expr) || (car(expr) == sc->byte_vector_ref_symbol) || (car(expr) == sc->vector_ref_symbol)) && + (loop_end(slot) <= byte_vector_length(obj))) + opc->v[3].p_pi_f = byte_vector_ref_p_pi_direct; + break; + case T_VECTOR: + if (((!expr) || (car(expr) == sc->vector_ref_symbol)) && (loop_end(slot) <= vector_length(obj))) + opc->v[3].p_pi_f = normal_vector_ref_p_pi_direct; + break; + case T_FLOAT_VECTOR: + if (((!expr) || (car(expr) == sc->float_vector_ref_symbol) || (car(expr) == sc->vector_ref_symbol)) && + (loop_end(slot) <= vector_length(obj))) + opc->v[3].p_pi_f = float_vector_ref_p_pi_direct; + break; + case T_INT_VECTOR: + if (((!expr) || (car(expr) == sc->int_vector_ref_symbol) || (car(expr) == sc->vector_ref_symbol)) && + (loop_end(slot) <= vector_length(obj))) + opc->v[3].p_pi_f = int_vector_ref_p_pi_direct; + break; + } +} + +static void fixup_p_pi_ss(opt_info *opc) +{ + opc->v[0].fp = (opc->v[3].p_pi_f == string_ref_p_pi_unchecked) ? opt_p_pi_ss_sref : + ((opc->v[3].p_pi_f == string_ref_p_pi_direct) ? opt_p_pi_ss_sref_direct : + ((opc->v[3].p_pi_f == normal_vector_ref_p_pi_unchecked) ? opt_p_pi_ss_vref : + ((opc->v[3].p_pi_f == float_vector_ref_p_pi_direct) ? opt_p_pi_ss_fvref_direct : + ((opc->v[3].p_pi_f == int_vector_ref_p_pi_direct) ? opt_p_pi_ss_ivref_direct : + ((opc->v[3].p_pi_f == normal_vector_ref_p_pi_direct) ? opt_p_pi_ss_vref_direct : + ((opc->v[3].p_pi_f == list_ref_p_pi_unchecked) ? opt_p_pi_ss_lref : opt_p_pi_ss)))))); +} + +static bool p_pi_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer sig, s7_pointer car_x) +{ + s7_pointer obj = NULL, slot1, checker = NULL; + opt_info *o1; + s7_p_pi_t func = s7_p_pi_function(s_func); + if (!func) + return_false(sc, car_x); + /* here we know cadr is a symbol */ + slot1 = opt_simple_symbol(sc, cadr(car_x)); + if (!slot1) + return_false(sc, car_x); + if ((is_any_vector(slot_value(slot1))) && + (vector_rank(slot_value(slot1)) > 1)) + return_false(sc, car_x); + + opc->v[3].p_pi_f = func; + opc->v[1].p = slot1; + + if (is_symbol(cadr(sig))) + checker = cadr(sig); + + if ((s7_p_pi_unchecked_function(s_func)) && + (checker)) + { + obj = slot_value(opc->v[1].p); + if ((is_string(obj)) || + (is_pair(obj)) || + (is_any_vector(obj))) + { + if (((is_string(obj)) && (checker == sc->is_string_symbol)) || + ((is_any_vector(obj)) && (checker == sc->is_vector_symbol)) || + ((is_pair(obj)) && (checker == sc->is_pair_symbol)) || + ((is_byte_vector(obj)) && (checker == sc->is_byte_vector_symbol))) + opc->v[3].p_pi_f = (is_normal_vector(obj)) ? normal_vector_ref_p_pi_unchecked : s7_p_pi_unchecked_function(s_func); + }} + slot1 = opt_integer_symbol(sc, caddr(car_x)); + if (slot1) + { + opc->v[2].p = slot1; + if ((obj) && + (has_loop_end(slot1))) + check_unchecked(sc, obj, slot1, opc, car_x); + fixup_p_pi_ss(opc); + return_true(sc, car_x); + } + if (is_t_integer(caddr(car_x))) + { + opc->v[2].i = integer(caddr(car_x)); + opc->v[0].fp = (opc->v[3].p_pi_f == list_ref_p_pi_unchecked) ? opt_p_pi_sc_lref : opt_p_pi_sc; + return_true(sc, car_x); + } + o1 = sc->opts[sc->pc]; + if (int_optimize(sc, cddr(car_x))) + { + opc->v[0].fp = (opc->v[3].p_pi_f == string_ref_p_pi_unchecked) ? opt_p_pi_sf_sref : + ((opc->v[3].p_pi_f == string_ref_p_pi_direct) ? opt_p_pi_sf_sref_direct : opt_p_pi_sf); + opc->v[4].o1 = o1; + opc->v[5].fi = o1->v[0].fi; + return_true(sc, car_x); + } + return_false(sc, car_x); +} + +static s7_pointer opt_p_pi_fco(opt_info *o) {return(o->v[3].p_pi_f(o->sc, o->v[4].p_p_f(o->sc, slot_value(o->v[1].p)), o->v[2].i));} + +static bool p_pi_fc_combinable(s7_scheme *sc, opt_info *opc) +{ + if ((sc->pc > 1) && + (opc == sc->opts[sc->pc - 2])) + { + opt_info *o1 = sc->opts[sc->pc - 1]; + if (o1->v[0].fp == opt_p_p_s) + { + opc->v[4].p_p_f = o1->v[2].p_p_f; + opc->v[1].p = o1->v[1].p; + opc->v[0].fp = opt_p_pi_fco; + backup_pc(sc); + return_true(sc, NULL); + }} + return_false(sc, NULL); +} + +/* -------- p_pp -------- */ +static s7_pointer opt_p_pp_ss(opt_info *o) {return(o->v[3].p_pp_f(o->sc, slot_value(o->v[1].p), slot_value(o->v[2].p)));} +static s7_pointer opt_p_pp_sc(opt_info *o) {return(o->v[3].p_pp_f(o->sc, slot_value(o->v[1].p), o->v[2].p));} +static s7_pointer opt_p_pp_cs(opt_info *o) {return(o->v[3].p_pp_f(o->sc, o->v[2].p, slot_value(o->v[1].p)));} +static s7_pointer opt_p_pp_sf(opt_info *o) {return(o->v[3].p_pp_f(o->sc, slot_value(o->v[1].p), o->v[5].fp(o->v[4].o1)));} +static s7_pointer opt_p_pp_fs(opt_info *o) {return(o->v[3].p_pp_f(o->sc, o->v[5].fp(o->v[4].o1), slot_value(o->v[1].p)));} +static s7_pointer opt_p_pp_fc(opt_info *o) {return(o->v[3].p_pp_f(o->sc, o->v[5].fp(o->v[4].o1), o->v[2].p));} +static s7_pointer opt_p_pp_cc(opt_info *o) {return(o->v[3].p_pp_f(o->sc, o->v[1].p, o->v[2].p));} +static s7_pointer opt_p_pp_cc_make_list(opt_info *o) {return(make_list(o->sc, o->v[1].i, o->v[2].p));} +static s7_pointer opt_set_car_pp_ss(opt_info *o) {return(inline_set_car(o->sc, slot_value(o->v[1].p), slot_value(o->v[2].p)));} +static s7_pointer opt_p_pp_ss_href(opt_info *o) {return(s7_hash_table_ref(o->sc, slot_value(o->v[1].p), slot_value(o->v[2].p)));} +static s7_pointer opt_p_pp_sf_add(opt_info *o) {return(add_p_pp(o->sc, slot_value(o->v[1].p), o->v[5].fp(o->v[4].o1)));} +static s7_pointer opt_p_pp_sf_sub(opt_info *o) {return(subtract_p_pp(o->sc, slot_value(o->v[1].p), o->v[5].fp(o->v[4].o1)));} +static s7_pointer opt_p_pp_sf_mul(opt_info *o) {return(multiply_p_pp(o->sc, slot_value(o->v[1].p), o->v[5].fp(o->v[4].o1)));} +static s7_pointer opt_p_pp_sf_set_car(opt_info *o) {return(inline_set_car(o->sc, slot_value(o->v[1].p), o->v[5].fp(o->v[4].o1)));} +static s7_pointer opt_p_pp_sf_set_cdr(opt_info *o) {return(inline_set_cdr(o->sc, slot_value(o->v[1].p), o->v[5].fp(o->v[4].o1)));} +static s7_pointer opt_p_pp_sf_href(opt_info *o) {return(s7_hash_table_ref(o->sc, slot_value(o->v[1].p), o->v[5].fp(o->v[4].o1)));} +static s7_pointer opt_p_pp_fs_vref(opt_info *o) {return(vector_ref_p_pp(o->sc, o->v[5].fp(o->v[4].o1), slot_value(o->v[1].p)));} +static s7_pointer opt_p_pp_fs_cons(opt_info *o) {return(cons(o->sc, o->v[5].fp(o->v[4].o1), slot_value(o->v[1].p)));} +static s7_pointer opt_p_pp_fs_add(opt_info *o) {return(add_p_pp(o->sc, o->v[5].fp(o->v[4].o1), slot_value(o->v[1].p)));} +static s7_pointer opt_p_pp_fs_sub(opt_info *o) {return(subtract_p_pp(o->sc, o->v[5].fp(o->v[4].o1), slot_value(o->v[1].p)));} + +static s7_pointer opt_p_pp_ff(opt_info *o) +{ + s7_scheme *sc = o->sc; + s7_pointer result; + gc_protect_2_via_stack(sc, o->v[11].fp(o->v[10].o1), o->v[9].fp(o->v[8].o1)); /* we do need to protect both */ + result = o->v[3].p_pp_f(sc, stack_protected1(sc), stack_protected2(sc)); + unstack(sc); + return(result); +} + +static s7_pointer opt_p_pp_ff_add_mul_mul_1(opt_info *o, bool add_case) /* (+|- (* s1 f2) (* s3 f4)) */ +{ + opt_info *o1 = o->v[10].o1, *o2 = o->v[8].o1; + s7_pointer f4; + s7_scheme *sc = o->sc; + s7_pointer s1 = slot_value(o1->v[1].p); + s7_pointer s3 = slot_value(o2->v[1].p); + s7_pointer f2 = o1->v[5].fp(o1->v[4].o1); + if ((is_t_real(f2)) && (is_t_real(s1)) && (is_t_real(s3))) + { + s7_double r2 = real(f2); + f4 = o2->v[5].fp(o2->v[4].o1); + if (is_t_real(f4)) + return(make_real(sc, (add_case) ? ((real(s1) * r2) + (real(s3) * real(f4))) : ((real(s1) * r2) - (real(s3) * real(f4))))); + gc_protect_via_stack(sc, f2); + } + else + { + gc_protect_via_stack(sc, f2); + f4 = o2->v[5].fp(o2->v[4].o1); + } + set_stack_protected2(sc, f4); + set_stack_protected2(sc, multiply_p_pp(sc, s3, f4)); + set_stack_protected1(sc, multiply_p_pp(sc, s1, f2)); + s3 = (add_case) ? add_p_pp(sc, stack_protected1(sc), stack_protected2(sc)) : subtract_p_pp(sc, stack_protected1(sc), stack_protected2(sc)); + unstack(sc); + return(s3); +} + +static s7_pointer opt_p_pp_ff_add_mul_mul(opt_info *o) {return(opt_p_pp_ff_add_mul_mul_1(o, true));} +static s7_pointer opt_p_pp_ff_sub_mul_mul(opt_info *o) {return(opt_p_pp_ff_add_mul_mul_1(o, false));} + +static bool p_pp_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x, int32_t pstart) +{ + s7_pointer slot; + s7_p_pp_t func = s7_p_pp_function(s_func); + if (!func) + return_false(sc, car_x); + + opc->v[3].p_pp_f = func; + if (is_symbol(cadr(car_x))) + { + slot = opt_simple_symbol(sc, cadr(car_x)); + if (!slot) + { + sc->pc = pstart; + return_false(sc, car_x); + } + if ((is_any_vector(slot_value(slot))) && + (vector_rank(slot_value(slot)) > 1)) + { + sc->pc = pstart; + return_false(sc, car_x); + } + opc->v[1].p = slot; + + if ((func == hash_table_ref_p_pp) && (is_hash_table(slot_value(slot)))) + opc->v[3].p_pp_f = s7_hash_table_ref; + + if (is_symbol(caddr(car_x))) + { + opc->v[2].p = opt_simple_symbol(sc, caddr(car_x)); + if (opc->v[2].p) + { + opc->v[0].fp = (func == set_car_p_pp) ? opt_set_car_pp_ss : ((opc->v[3].p_pp_f == s7_hash_table_ref) ? opt_p_pp_ss_href : opt_p_pp_ss); + return_true(sc, car_x); + } + sc->pc = pstart; + return_false(sc, car_x); + } + if ((!is_pair(caddr(car_x))) || + (is_proper_quote(sc, caddr(car_x)))) + { + opc->v[2].p = (!is_pair(caddr(car_x))) ? caddr(car_x) : cadaddr(car_x); + opc->v[0].fp = opt_p_pp_sc; + return_true(sc, car_x); + } + if (cell_optimize(sc, cddr(car_x))) + { + opc->v[0].fp = (func == add_p_pp) ? opt_p_pp_sf_add : ((func == subtract_p_pp) ? opt_p_pp_sf_sub : ((func == multiply_p_pp) ? opt_p_pp_sf_mul : + ((func == set_car_p_pp) ? opt_p_pp_sf_set_car : ((func == set_cdr_p_pp) ? opt_p_pp_sf_set_cdr : + ((opc->v[3].p_pp_f == s7_hash_table_ref) ? opt_p_pp_sf_href : opt_p_pp_sf))))); + opc->v[4].o1 = sc->opts[pstart]; + opc->v[5].fp = sc->opts[pstart]->v[0].fp; + return_true(sc, car_x); + }} + else + { + opt_info *o1 = sc->opts[sc->pc]; + if ((!is_pair(cadr(car_x))) || + (is_proper_quote(sc, cadr(car_x)))) + { + opc->v[1].p = (!is_pair(cadr(car_x))) ? cadr(car_x) : cadadr(car_x); + if ((!is_symbol(caddr(car_x))) && + ((!is_pair(caddr(car_x))) || + (is_proper_quote(sc, caddr(car_x))))) + { + opc->v[2].p = (!is_pair(caddr(car_x))) ? caddr(car_x) : cadaddr(car_x); + if ((opc->v[3].p_pp_f == make_list_p_pp) && + (is_t_integer(opc->v[1].p)) && (integer(opc->v[1].p) >= 0) && (integer(opc->v[1].p) < sc->max_list_length)) + { + opc->v[0].fp = opt_p_pp_cc_make_list; + opc->v[1].i = integer(opc->v[1].p); + } + else opc->v[0].fp = opt_p_pp_cc; + return_true(sc, car_x); + } + if (is_symbol(caddr(car_x))) + { + opc->v[2].p = opc->v[1].p; + opc->v[1].p = opt_simple_symbol(sc, caddr(car_x)); + if (opc->v[1].p) + { + opc->v[0].fp = opt_p_pp_cs; + if (is_pair(slot_value(opc->v[1].p))) + { + if (func == assq_p_pp) opc->v[3].p_pp_f = s7_assq; + else + if (func == memq_p_pp) opc->v[3].p_pp_f = s7_memq; + else + if ((func == member_p_pp) && (is_simple(opc->v[2].p))) opc->v[3].p_pp_f = s7_memq; + else + if (func == assoc_p_pp) + { + if (is_simple(opc->v[2].p)) opc->v[3].p_pp_f = s7_assq; + else if (is_pair(car(slot_value(opc->v[1].p)))) opc->v[3].p_pp_f = assoc_1; + }} + return_true(sc, car_x); + } + sc->pc = pstart; + return_false(sc, car_x); + }} + if (cell_optimize(sc, cdr(car_x))) + { + if (is_symbol(caddr(car_x))) + { + opc->v[1].p = opt_simple_symbol(sc, caddr(car_x)); + if (opc->v[1].p) + { + opc->v[0].fp = (func == add_p_pp) ? opt_p_pp_fs_add : ((func == subtract_p_pp) ? opt_p_pp_fs_sub : + ((func == vector_ref_p_pp) ? opt_p_pp_fs_vref : ((func == cons_p_pp) ? opt_p_pp_fs_cons : opt_p_pp_fs))); + opc->v[4].o1 = o1; + opc->v[5].fp = o1->v[0].fp; + return_true(sc, car_x); + } + sc->pc = pstart; + return_false(sc, car_x); + } + if ((!is_pair(caddr(car_x))) || + (is_proper_quote(sc, caddr(car_x)))) + { + if (is_t_integer(caddr(car_x))) + { + s7_p_pi_t ifunc = s7_p_pi_function(s_func); + if (ifunc) + { + opc->v[2].i = integer(caddr(car_x)); + opc->v[3].p_pi_f = ifunc; + if (!p_pi_fc_combinable(sc, opc)) + { + opc->v[0].fp = opt_p_pi_fc; + opc->v[4].o1 = o1; + opc->v[5].fp = o1->v[0].fp; + } + return_true(sc, car_x); + }} + opc->v[2].p = (!is_pair(caddr(car_x))) ? caddr(car_x) : cadaddr(car_x); + opc->v[0].fp = opt_p_pp_fc; + opc->v[4].o1 = o1; + opc->v[5].fp = o1->v[0].fp; + return_true(sc, car_x); + } + opc->v[8].o1 = sc->opts[sc->pc]; + if (cell_optimize(sc, cddr(car_x))) + { + opc->v[10].o1 = o1; + opc->v[11].fp = o1->v[0].fp; + opc->v[9].fp = opc->v[8].o1->v[0].fp; + opc->v[0].fp = opt_p_pp_ff; + + if ((opc->v[9].fp == opt_p_pp_sf_mul) && (opc->v[11].fp == opt_p_pp_sf_mul)) + { + if (func == add_p_pp) opc->v[0].fp = opt_p_pp_ff_add_mul_mul; + else if (func == subtract_p_pp) opc->v[0].fp = opt_p_pp_ff_sub_mul_mul; + } + + return_true(sc, car_x); + }}} + sc->pc = pstart; + return_false(sc, car_x); +} + +/* -------- p_call_pp -------- */ +static s7_pointer opt_p_call_ff(opt_info *o) +{ + s7_pointer po2; + s7_scheme *sc = o->sc; + gc_protect_via_stack(sc, o->v[11].fp(o->v[10].o1)); + po2 = o->v[9].fp(o->v[8].o1); + po2 = o->v[3].call(sc, set_plist_2(sc, stack_protected1(sc), po2)); + unstack(sc); + return(po2); +} + +static s7_pointer opt_p_call_fs(opt_info *o) +{ + s7_pointer po1 = o->v[11].fp(o->v[10].o1); + return(o->v[3].call(o->sc, set_plist_2(o->sc, po1, slot_value(o->v[1].p)))); +} + +static s7_pointer opt_p_call_sf(opt_info *o) +{ + s7_pointer po1 = o->v[11].fp(o->v[10].o1); + return(o->v[3].call(o->sc, set_plist_2(o->sc, slot_value(o->v[1].p), po1))); +} + +static s7_pointer opt_p_call_sc(opt_info *o) {return(o->v[3].call(o->sc, set_plist_2(o->sc, slot_value(o->v[1].p), o->v[2].p)));} +static s7_pointer opt_p_call_ss(opt_info *o) {return(o->v[3].call(o->sc, set_plist_2(o->sc, slot_value(o->v[1].p), slot_value(o->v[2].p))));} + +static bool p_call_pp_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x, int32_t pstart) +{ + if ((is_safe_procedure(s_func)) && (c_function_is_aritable(s_func, 2))) + { + /* if optimized, we want to use the current fn_proc (to take advantage of fixups like substring_temp), + * but those same fixups are incorrect for this context if op_safe_c_c related. + */ + opc->v[3].call = cf_call(sc, car_x, s_func, 2); + if (is_symbol(cadr(car_x))) + { + opc->v[1].p = s7_slot(sc, cadr(car_x)); + if ((is_slot(opc->v[1].p)) && + (!has_methods(slot_value(opc->v[1].p)))) + { + if (is_symbol(caddr(car_x))) + { + opc->v[2].p = opt_simple_symbol(sc, caddr(car_x)); + if (opc->v[2].p) + { + opc->v[0].fp = opt_p_call_ss; + return_true(sc, car_x); + } + sc->pc = pstart; + return_false(sc, car_x); + } + if (!is_pair(caddr(car_x))) + { + opc->v[2].p = caddr(car_x); + opc->v[0].fp = opt_p_call_sc; + return_true(sc, car_x); + } + if (cell_optimize(sc, cddr(car_x))) + { + opc->v[10].o1 = sc->opts[pstart]; + opc->v[11].fp = opc->v[10].o1->v[0].fp; + opc->v[0].fp = opt_p_call_sf; + return_true(sc, car_x); + }} + else + { + sc->pc = pstart; + return_false(sc, car_x); + }} + opc->v[10].o1 = sc->opts[sc->pc]; + if (cell_optimize(sc, cdr(car_x))) + { + opc->v[11].fp = opc->v[10].o1->v[0].fp; + if (is_symbol(caddr(car_x))) + { + opc->v[1].p = opt_simple_symbol(sc, caddr(car_x)); + if (opc->v[1].p) + { + opc->v[0].fp = opt_p_call_fs; + return_true(sc, car_x); + } + sc->pc = pstart; + return_false(sc, car_x); + } + opc->v[8].o1 = sc->opts[sc->pc]; + if (cell_optimize(sc, cddr(car_x))) + { + opc->v[9].fp = opc->v[8].o1->v[0].fp; + opc->v[0].fp = opt_p_call_ff; + return_true(sc, car_x); + }}} + sc->pc = pstart; + return_false(sc, car_x); +} + + +/* -------- p_pip --------*/ + +static s7_pointer opt_p_pip_ssf(opt_info *o) {return(o->v[3].p_pip_f(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), o->v[5].fp(o->v[4].o1)));} +static s7_pointer opt_p_pip_ssf_sset(opt_info *o) {return(string_set_p_pip_direct(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), o->v[5].fp(o->v[4].o1)));} +static s7_pointer opt_p_pip_ssf_vset(opt_info *o) {return(vector_set_p_pip_unchecked(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), o->v[5].fp(o->v[4].o1)));} +static s7_pointer opt_p_pip_sss(opt_info *o) {return(o->v[4].p_pip_f(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), slot_value(o->v[3].p)));} +static s7_pointer opt_p_pip_sss_vset(opt_info *o) {return(vector_set_p_pip_unchecked(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), slot_value(o->v[3].p)));} +static s7_pointer opt_p_pip_ssc(opt_info *o) {return(o->v[3].p_pip_f(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), o->v[4].p));} +static s7_pointer opt_p_pip_c(opt_info *o) {return(o->v[3].p_pip_f(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), o->v[5].p_p_f(o->sc, o->v[4].p)));} + +static s7_pointer opt_p_pip_sff(opt_info *o) +{ + s7_int i1 = o->v[11].fi(o->v[10].o1); + return(o->v[3].p_pip_f(o->sc, slot_value(o->v[1].p), i1, o->v[9].fp(o->v[8].o1))); +} + +static s7_pointer opt_p_pip_sff_lset(opt_info *o) +{ + s7_int i1 = o->v[11].fi(o->v[10].o1); + return(list_set_p_pip_unchecked(o->sc, slot_value(o->v[1].p), i1, o->v[9].fp(o->v[8].o1))); +} + +static s7_pointer opt_p_pip_sso(opt_info *o) +{ + return(o->v[5].p_pip_f(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), + o->v[6].p_pi_f(o->sc, slot_value(o->v[3].p), integer(slot_value(o->v[4].p))))); +} + +static s7_pointer opt_p_pip_ssf1(opt_info *o) +{ + return(o->v[3].p_pip_f(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), o->v[4].p_p_f(o->sc, o->v[6].fp(o->v[5].o1)))); +} + +static bool p_pip_ssf_combinable(s7_scheme *sc, opt_info *opc, int32_t start) +{ + opt_info *o1; + if ((sc->pc > 1) && + (opc == sc->opts[sc->pc - 2])) + { + o1 = sc->opts[sc->pc - 1]; + if ((o1->v[0].fp == opt_p_pi_ss) || (o1->v[0].fp == opt_p_pi_ss_sref) || (o1->v[0].fp == opt_p_pi_ss_vref) || + (o1->v[0].fp == opt_p_pi_ss_sref_direct) || (o1->v[0].fp == opt_p_pi_ss_vref_direct) || + (o1->v[0].fp == opt_p_pi_ss_fvref_direct) || (o1->v[0].fp == opt_p_pi_ss_ivref_direct) || + (o1->v[0].fp == opt_p_pi_ss_lref)) + { + opc->v[5].p_pip_f = opc->v[3].p_pip_f; + opc->v[6].p_pi_f = o1->v[3].p_pi_f; + opc->v[3].p = o1->v[1].p; + opc->v[4].p = o1->v[2].p; + opc->v[0].fp = opt_p_pip_sso; + backup_pc(sc); + return_true(sc, NULL); + } + if (o1->v[0].fp == opt_p_p_c) + { + opc->v[5].p_p_f = o1->v[2].p_p_f; + opc->v[4].p = o1->v[1].p; + backup_pc(sc); + opc->v[0].fp = opt_p_pip_c; + return_true(sc, NULL); + }} + o1 = sc->opts[start]; + if (o1->v[0].fp != opt_p_p_f) + return_false(sc, NULL); + opc->v[4].p_p_f = o1->v[2].p_p_f; + opc->v[5].o1 = sc->opts[start + 1]; + opc->v[6].fp = sc->opts[start + 1]->v[0].fp; + opc->v[0].fp = opt_p_pip_ssf1; + return_true(sc, NULL); +} + +static bool p_pip_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + s7_pointer obj, slot1, sig, checker = NULL; + s7_p_pip_t func = s7_p_pip_function(s_func); + if (!func) + return_false(sc, car_x); + + sig = c_function_signature(s_func); + if ((is_pair(sig)) && + (is_pair(cdr(sig))) && + (is_symbol(cadr(sig)))) + checker = cadr(sig); + + /* here we know cadr is a symbol */ + slot1 = s7_slot(sc, cadr(car_x)); + if ((!is_slot(slot1)) || + (has_methods(slot_value(slot1))) || + (is_immutable(slot_value(slot1)))) + return_false(sc, car_x); + if ((is_any_vector(slot_value(slot1))) && + (vector_rank(slot_value(slot1)) > 1)) + return_false(sc, car_x); + + opc->v[1].p = slot1; + obj = slot_value(opc->v[1].p); + opc->v[3].p_pip_f = func; + if ((s7_p_pip_unchecked_function(s_func)) && + (checker)) + { + if ((is_normal_vector(obj)) && (checker == sc->is_vector_symbol)) + opc->v[3].p_pip_f = (is_typed_vector(obj)) ? typed_vector_set_p_pip_unchecked : vector_set_p_pip_unchecked; + else + if ((is_pair(obj)) && (checker == sc->is_pair_symbol)) /* avoid dumb mismatch in val_type and sig below, #t integer:any? and integer? integer:any? */ + opc->v[3].p_pip_f = s7_p_pip_unchecked_function(s_func); + else + { + s7_pointer val_type = opt_arg_type(sc, cdddr(car_x)); + if ((val_type == cadddr(sig)) && + (((is_string(obj)) && (checker == sc->is_string_symbol)) || + ((is_float_vector(obj)) && (checker == sc->is_float_vector_symbol)) || + ((is_int_vector(obj)) && (checker == sc->is_int_vector_symbol)) || + ((is_byte_vector(obj)) && (checker == sc->is_byte_vector_symbol)))) + opc->v[3].p_pip_f = s7_p_pip_unchecked_function(s_func); + }} + if (is_symbol(caddr(car_x))) + { + int32_t start = sc->pc; + s7_pointer slot2 = opt_integer_symbol(sc, caddr(car_x)); + if (slot2) + { + opc->v[2].p = slot2; + if (has_loop_end(slot2)) + switch (type(obj)) + { + case T_VECTOR: + if (loop_end(slot2) <= vector_length(obj)) + opc->v[3].p_pip_f = (is_typed_vector(obj)) ? typed_normal_vector_set_p_pip_direct : normal_vector_set_p_pip_direct; + break; + case T_INT_VECTOR: + if (loop_end(slot2) <= vector_length(obj)) + opc->v[3].p_pip_f = int_vector_set_p_pip_direct; + break; + case T_FLOAT_VECTOR: + if (loop_end(slot2) <= vector_length(obj)) + opc->v[3].p_pip_f = float_vector_set_p_pip_direct; + break; + case T_STRING: + if (loop_end(slot2) <= string_length(obj)) + opc->v[3].p_pip_f = string_set_p_pip_direct; + break; + case T_BYTE_VECTOR: + if (loop_end(slot2) <= vector_length(obj)) + opc->v[3].p_pip_f = byte_vector_set_p_pip_direct; + break; + } /* T_PAIR here would require list_length check which sort of defeats the purpose */ + + if (is_symbol(cadddr(car_x))) + { + s7_pointer val_slot = opt_simple_symbol(sc, cadddr(car_x)); + if (val_slot) + { + opc->v[4].p_pip_f = opc->v[3].p_pip_f; + opc->v[3].p = val_slot; + opc->v[0].fp = (opc->v[4].p_pip_f == vector_set_p_pip_unchecked) ? opt_p_pip_sss_vset : opt_p_pip_sss; + return_true(sc, car_x); + }} + else + if ((!is_pair(cadddr(car_x))) || + (is_proper_quote(sc, cadddr(car_x)))) + { + opc->v[4].p = (is_pair(cadddr(car_x))) ? cadr(cadddr(car_x)) : cadddr(car_x); + opc->v[0].fp = opt_p_pip_ssc; + return_true(sc, car_x); + } + if (cell_optimize(sc, cdddr(car_x))) + { + if (p_pip_ssf_combinable(sc, opc, start)) + return_true(sc, car_x); + opc->v[0].fp = (opc->v[3].p_pip_f == string_set_p_pip_direct) ? opt_p_pip_ssf_sset : + ((opc->v[3].p_pip_f == vector_set_p_pip_unchecked) ? opt_p_pip_ssf_vset : opt_p_pip_ssf); + opc->v[4].o1 = sc->opts[start]; + opc->v[5].fp = sc->opts[start]->v[0].fp; + return_true(sc, car_x); + }}} + else /* not symbol caddr */ + { + opc->v[10].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, cddr(car_x))) + { + opc->v[8].o1 = sc->opts[sc->pc]; + if (cell_optimize(sc, cdddr(car_x))) + { + opc->v[0].fp = (opc->v[3].p_pip_f == list_set_p_pip_unchecked) ? opt_p_pip_sff_lset : opt_p_pip_sff; + opc->v[11].fi = opc->v[10].o1->v[0].fi; + opc->v[9].fp = opc->v[8].o1->v[0].fp; + return_true(sc, car_x); + }}} + return_false(sc, car_x); +} + +/* -------- p_piip -------- */ +static s7_pointer opt_p_piip_sssf(opt_info *o) +{ + return(o->v[5].p_piip_f(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), integer(slot_value(o->v[3].p)), o->v[11].fp(o->v[10].o1))); +} + +static s7_pointer vector_set_piip_sssf_unchecked(opt_info *o) +{ + s7_pointer v = slot_value(o->v[1].p); + s7_pointer val = o->v[11].fp(o->v[10].o1); + vector_element(v, ((integer(slot_value(o->v[2].p)) * vector_offset(v, 0)) + integer(slot_value(o->v[3].p)))) = val; + return(val); +} + +static s7_pointer opt_p_piip_sssc(opt_info *o) +{ + return(o->v[5].p_piip_f(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), integer(slot_value(o->v[3].p)), o->v[4].p)); +} + +static s7_pointer opt_p_piip_sfff(opt_info *o) +{ + s7_int i1 = o->v[11].fi(o->v[10].o1); + s7_int i2 = o->v[9].fi(o->v[8].o1); + return(o->v[5].p_piip_f(o->sc, slot_value(o->v[1].p), i1, i2, o->v[3].fp(o->v[4].o1))); /* v[3] because v[5] is already in use */ +} + +static bool p_piip_to_sx(s7_scheme *sc, opt_info *opc, s7_pointer indexp1, s7_pointer indexp2, s7_pointer valp, s7_pointer obj) +{ + s7_pointer slot = opt_integer_symbol(sc, car(indexp2)); + if (!slot) + return_false(sc, indexp1); + opc->v[3].p = slot; + slot = opt_integer_symbol(sc, car(indexp1)); + if (slot) + { + opc->v[2].p = slot; + if ((is_symbol(car(valp))) || + (is_unquoted_pair(car(valp)))) + { + opc->v[10].o1 = sc->opts[sc->pc]; + if (!cell_optimize(sc, valp)) + return_false(sc, indexp1); + opc->v[11].fp = opc->v[10].o1->v[0].fp; + opc->v[0].fp = opt_p_piip_sssf; + if ((is_normal_vector(obj)) && + (loop_end_fits(opc->v[2].p, vector_dimension(obj, 0))) && + (loop_end_fits(opc->v[3].p, vector_dimension(obj, 1)))) + opc->v[0].fp = vector_set_piip_sssf_unchecked; + return_true(sc, NULL); + } + opc->v[0].fp = opt_p_piip_sssc; + opc->v[4].p = (is_pair(car(valp))) ? cadar(valp) : car(valp); + return_true(sc, NULL); + } + opc->v[10].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, indexp1)) + { + opc->v[8].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, indexp2)) + { + opc->v[4].o1 = sc->opts[sc->pc]; + if (cell_optimize(sc, valp)) + { + opc->v[11].fi = opc->v[10].o1->v[0].fi; + opc->v[9].fi = opc->v[8].o1->v[0].fi; + opc->v[3].fp = opc->v[4].o1->v[0].fp; + opc->v[0].fp = opt_p_piip_sfff; + return_true(sc, NULL); + }}} + return_false(sc, NULL); +} + +static bool p_piip_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + s7_p_piip_t func = s7_p_piip_function(s_func); + if ((func) && (s_func == global_value(sc->vector_set_symbol)) && (is_symbol(cadr(car_x)))) + { + s7_pointer obj; + s7_pointer slot1 = s7_slot(sc, cadr(car_x)); + if (!is_slot(slot1)) + return_false(sc, car_x); + obj = slot_value(slot1); + if ((has_methods(obj)) || (is_immutable(obj))) + return_false(sc, car_x); + if ((is_any_vector(obj)) && /* vector_set_p_piip calls vector_setter(obj) */ + (vector_rank(obj) == 2)) + { + opc->v[1].p = slot1; + opc->v[5].p_piip_f = vector_set_p_piip; + return(p_piip_to_sx(sc, opc, cddr(car_x), cdddr(car_x), cddddr(car_x), obj)); + }} + return_false(sc, car_x); +} + +/* -------- p_pii -------- */ +static s7_pointer opt_p_pii_sss(opt_info *o) +{ + return(o->v[4].p_pii_f(o->sc, slot_value(o->v[1].p), integer(slot_value(o->v[2].p)), integer(slot_value(o->v[3].p)))); +} + +static s7_pointer opt_p_pii_sff(opt_info *o) +{ + s7_int i1 = o->v[11].fi(o->v[10].o1); + s7_int i2 = o->v[9].fi(o->v[8].o1); + return(o->v[4].p_pii_f(o->sc, slot_value(o->v[1].p), i1, i2)); +} + +static s7_pointer vector_ref_pii_sss_unchecked(opt_info *o) +{ + s7_pointer v = slot_value(o->v[1].p); + return(vector_element(v, ((integer(slot_value(o->v[2].p)) * vector_offset(v, 0)) + integer(slot_value(o->v[3].p))))); +} + +static bool p_pii_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + s7_p_pii_t func = s7_p_pii_function(s_func); + if ((func) && + (is_symbol(cadr(car_x)))) + { + s7_pointer obj; + s7_pointer slot1 = s7_slot(sc, cadr(car_x)); + if (!is_slot(slot1)) + return_false(sc, car_x); + obj = slot_value(slot1); + if ((has_methods(obj)) || (is_immutable(obj))) + return_false(sc, car_x); + if ((is_normal_vector(obj)) && + (vector_rank(obj) == 2)) + { + s7_pointer slot, indexp1 = cddr(car_x), indexp2 = cdddr(car_x); + opc->v[1].p = slot1; + opc->v[4].p_pii_f = vector_ref_p_pii; + slot = opt_integer_symbol(sc, car(indexp2)); + if (slot) + { + opc->v[3].p = slot; + slot = opt_integer_symbol(sc, car(indexp1)); + if (slot) + { + opc->v[2].p = slot; + opc->v[0].fp = opt_p_pii_sss; + /* normal vector rank 2 (see above) */ + if ((loop_end_fits(opc->v[2].p, vector_dimension(slot_value(opc->v[1].p), 0))) && + (loop_end_fits(opc->v[3].p, vector_dimension(slot_value(opc->v[1].p), 1)))) + opc->v[0].fp = vector_ref_pii_sss_unchecked; + return_true(sc, car_x); + }} + opc->v[10].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, indexp1)) + { + opc->v[8].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, indexp2)) + { + opc->v[0].fp = opt_p_pii_sff; + opc->v[11].fi = opc->v[10].o1->v[0].fi; + opc->v[9].fi = opc->v[8].o1->v[0].fi; + return_true(sc, car_x); + }}}} + return_false(sc, car_x); +} + +/* -------- p_ppi -------- */ +static s7_pointer opt_p_ppi_psf(opt_info *o) {return(o->v[3].p_ppi_f(o->sc, o->v[2].p, slot_value(o->v[1].p), o->v[5].fi(o->v[4].o1)));} +static s7_pointer opt_p_ppi_psf_cpos(opt_info *o) {return(char_position_p_ppi(o->sc, o->v[2].p, slot_value(o->v[1].p), o->v[5].fi(o->v[4].o1)));} + +static bool p_ppi_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + int32_t start = sc->pc; + s7_p_ppi_t ifunc = s7_p_ppi_function(s_func); + if (!ifunc) + return_false(sc, car_x); + opc->v[3].p_ppi_f = ifunc; + if ((is_character(cadr(car_x))) && + (is_symbol(caddr(car_x))) && + (int_optimize(sc, cdddr(car_x)))) + { + s7_pointer slot = opt_simple_symbol(sc, caddr(car_x)); + if (slot) + { + opc->v[2].p = cadr(car_x); + opc->v[1].p = slot; + opc->v[0].fp = (ifunc == char_position_p_ppi) ? opt_p_ppi_psf_cpos : opt_p_ppi_psf; + opc->v[4].o1 = sc->opts[start]; + opc->v[5].fi = sc->opts[start]->v[0].fi; + return_true(sc, car_x); + }} + sc->pc = start; + return_false(sc, car_x); +} + +/* -------- p_ppp -------- */ +static s7_pointer opt_p_ppp_ssf(opt_info *o) {return(o->v[3].p_ppp_f(o->sc, slot_value(o->v[1].p), slot_value(o->v[2].p), o->v[5].fp(o->v[4].o1)));} +static s7_pointer opt_p_ppp_hash_table_increment(opt_info *o) {return(fx_hash_table_increment_1(o->sc, slot_value(o->v[1].p), slot_value(o->v[2].p), o->v[5].p));} +static s7_pointer opt_p_ppp_sfs(opt_info *o) {return(o->v[3].p_ppp_f(o->sc, slot_value(o->v[1].p), o->v[5].fp(o->v[4].o1), slot_value(o->v[2].p)));} +static s7_pointer opt_p_ppp_scs(opt_info *o) {return(o->v[3].p_ppp_f(o->sc, slot_value(o->v[1].p), o->v[4].p, slot_value(o->v[2].p)));} +static s7_pointer opt_p_ppp_scs_eset(opt_info *o) {return(let_set_1(o->sc, slot_value(o->v[1].p), o->v[4].p, slot_value(o->v[2].p)));} +static s7_pointer opt_p_ppp_sss(opt_info *o) {return(o->v[4].p_ppp_f(o->sc, slot_value(o->v[1].p), slot_value(o->v[2].p), slot_value(o->v[3].p)));} +static s7_pointer opt_p_ppp_sss_mul(opt_info *o) {return(multiply_p_ppp(o->sc, slot_value(o->v[1].p), slot_value(o->v[2].p), slot_value(o->v[3].p)));} +static s7_pointer opt_p_ppp_sss_hset(opt_info *o) {return(s7_hash_table_set(o->sc, slot_value(o->v[1].p), slot_value(o->v[2].p), slot_value(o->v[3].p)));} +static s7_pointer opt_p_ppp_ssc(opt_info *o) {return(o->v[3].p_ppp_f(o->sc, slot_value(o->v[1].p), slot_value(o->v[2].p), o->v[4].p));} +static s7_pointer opt_list_3c(opt_info *o) {s7_scheme *sc = o->sc; return(list_3(sc, o->v[10].p, o->v[8].p, o->v[4].p));} + +static s7_pointer opt_p_ppp_sff(opt_info *o) +{ + s7_pointer res; + s7_scheme *sc = o->sc; + gc_protect_2_via_stack(sc, T_Ext(o->v[11].fp(o->v[10].o1)), T_Ext(o->v[9].fp(o->v[8].o1))); + res = o->v[3].p_ppp_f(o->sc, slot_value(o->v[1].p), stack_protected1(sc), stack_protected2(sc)); + unstack(sc); + return(res); +} + +static s7_pointer opt_p_ppp_fff(opt_info *o) +{ + s7_pointer res; + s7_scheme *sc = o->sc; + gc_protect_2_via_stack(sc, T_Ext(o->v[11].fp(o->v[10].o1)), T_Ext(o->v[9].fp(o->v[8].o1))); + res = o->v[3].p_ppp_f(sc, stack_protected1(sc), stack_protected2(sc), o->v[5].fp(o->v[4].o1)); + unstack(sc); + return(res); +} + +static bool p_ppp_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + s7_pointer arg1 = cadr(car_x); + s7_pointer arg2 = caddr(car_x); + s7_pointer arg3 = cadddr(car_x); + int32_t start = sc->pc; + s7_p_ppp_t func = s7_p_ppp_function(s_func); + if (!func) + return_false(sc, car_x); + opc->v[3].p_ppp_f = func; + if (is_symbol(arg1)) /* dealt with at the top -> p1 */ + { + s7_pointer obj; + opt_info *o1; + s7_pointer slot = s7_slot(sc, arg1); + if ((!is_slot(slot)) || + (has_methods(slot_value(slot)))) + return_false(sc, car_x); + + obj = slot_value(slot); + if ((is_any_vector(obj)) && + (vector_rank(obj) > 1)) + return_false(sc, car_x); + + if (is_target_or_its_alias(car(car_x), s_func, sc->hash_table_set_symbol)) + { + if ((!is_hash_table(obj)) || (is_immutable_hash_table(obj))) + return_false(sc, car_x); + } + else + if ((is_target_or_its_alias(car(car_x), s_func, sc->let_set_symbol)) && + ((!is_let(obj)) || (is_immutable(obj)))) + return_false(sc, car_x); + + opc->v[1].p = slot; + + if ((func == hash_table_set_p_ppp) && (is_hash_table(obj))) + opc->v[3].p_ppp_f = s7_hash_table_set; + + if (is_symbol(arg2)) + { + slot = opt_simple_symbol(sc, arg2); + if (slot) + { + opc->v[2].p = slot; + if (is_symbol(arg3)) + { + slot = opt_simple_symbol(sc, arg3); + if (slot) + { + s7_p_ppp_t func1 = opc->v[3].p_ppp_f; + opc->v[4].p_ppp_f = func1; + opc->v[3].p = slot; + opc->v[0].fp = (func1 == multiply_p_ppp) ? opt_p_ppp_sss_mul : ((func1 == s7_hash_table_set) ? opt_p_ppp_sss_hset : opt_p_ppp_sss); + return_true(sc, car_x); + }} + else + if ((!is_pair(arg3)) || + (is_proper_quote(sc, arg3))) + { + opc->v[4].p = (is_pair(arg3)) ? cadr(arg3) : arg3; + opc->v[0].fp = opt_p_ppp_ssc; + return_true(sc, car_x); + } + if (optimize_op(car_x) == HOP_HASH_TABLE_INCREMENT) + { + opc->v[0].fp = opt_p_ppp_hash_table_increment; + opc->v[5].p = car_x; + return_true(sc, car_x); + } + if (cell_optimize(sc, cdddr(car_x))) + { + opc->v[4].o1 = sc->opts[start]; + opc->v[5].fp = opc->v[4].o1->v[0].fp; + opc->v[0].fp = opt_p_ppp_ssf; + return_true(sc, car_x); + } + sc->pc = start; + }} + if ((is_proper_quote(sc, arg2)) && + (is_symbol(arg3))) + { + s7_pointer val_slot = opt_simple_symbol(sc, arg3); + if (val_slot) + { + opc->v[4].p = cadr(arg2); + opc->v[2].p = val_slot; + opc->v[0].fp = opt_p_ppp_scs; + if (opc->v[3].p_ppp_f == let_set) + { + if (is_symbol(cadr(arg2))) /* checked is_let, has_methods and is_immutable above */ + opc->v[0].fp = opt_p_ppp_scs_eset; + else return_false(sc, car_x); + } + return_true(sc, car_x); + }} + o1 = sc->opts[sc->pc]; + if (cell_optimize(sc, cddr(car_x))) + { + opt_info *o2 = sc->opts[sc->pc]; + if (is_symbol(arg3)) + { + s7_pointer val_slot = opt_simple_symbol(sc, arg3); + if (val_slot) + { + opc->v[2].p = val_slot; + opc->v[0].fp = opt_p_ppp_sfs; /* hset case goes through the case below */ + opc->v[4].o1 = o1; + opc->v[5].fp = o1->v[0].fp; + return_true(sc, car_x); + }} + if (cell_optimize(sc, cdddr(car_x))) + { + opc->v[0].fp = opt_p_ppp_sff; + opc->v[10].o1 = o1; + opc->v[11].fp = o1->v[0].fp; + opc->v[8].o1 = o2; + opc->v[9].fp = o2->v[0].fp; + return_true(sc, car_x); + }}} + else + { + opc->v[10].o1 = sc->opts[start]; + if (cell_optimize(sc, cdr(car_x))) + { + opc->v[8].o1 = sc->opts[sc->pc]; + if (cell_optimize(sc, cddr(car_x))) + { + opc->v[4].o1 = sc->opts[sc->pc]; + if (cell_optimize(sc, cdddr(car_x))) + { + opc->v[0].fp = opt_p_ppp_fff; + opc->v[11].fp = opc->v[10].o1->v[0].fp; + opc->v[9].fp = opc->v[8].o1->v[0].fp; + opc->v[5].fp = opc->v[4].o1->v[0].fp; + if ((opc->v[3].p_ppp_f == list_p_ppp) && + (opc->v[5].fp == opt_p_c) && (opc->v[9].fp == opt_p_c) && (opc->v[11].fp == opt_p_c)) + { + opc->v[0].fp = opt_list_3c; + opc->v[4].p = opc->v[4].o1->v[1].p; + opc->v[8].p = opc->v[8].o1->v[1].p; + opc->v[10].p = opc->v[10].o1->v[1].p; + } + return_true(sc, car_x); + }}}} + sc->pc = start; + return_false(sc, car_x); +} + +/* -------- p_call_ppp -------- */ +static s7_pointer opt_p_call_sss(opt_info *o) +{ + return(o->v[4].call(o->sc, set_plist_3(o->sc, slot_value(o->v[1].p), slot_value(o->v[2].p), slot_value(o->v[3].p)))); +} + +static s7_pointer opt_p_call_css(opt_info *o) +{ + return(o->v[4].call(o->sc, set_plist_3(o->sc, o->v[1].p, slot_value(o->v[2].p), slot_value(o->v[3].p)))); +} + +static s7_pointer opt_p_call_ssf(opt_info *o) +{ + return(o->v[4].call(o->sc, set_plist_3(o->sc, slot_value(o->v[1].p), slot_value(o->v[2].p), o->v[6].fp(o->v[5].o1)))); +} + +static s7_pointer opt_p_call_ppp(opt_info *o) +{ + s7_pointer res; + s7_scheme *sc = o->sc; + gc_protect_2_via_stack(sc, o->v[4].fp(o->v[3].o1), o->v[6].fp(o->v[5].o1)); + res = o->v[11].fp(o->v[10].o1); /* not combinable into next */ + res = o->v[2].call(sc, set_plist_3(sc, stack_protected1(sc), stack_protected2(sc), res)); + unstack(sc); + return(res); +} + +static bool p_call_ppp_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x) +{ + int32_t start = sc->pc; + if ((is_safe_procedure(s_func)) && (c_function_is_aritable(s_func, 3)) && + (s_func != global_value(sc->hash_table_ref_symbol)) && (s_func != global_value(sc->list_ref_symbol))) + { + s7_pointer slot, arg = cadr(car_x); + opt_info *o1 = sc->opts[sc->pc]; + + if (!is_pair(arg)) + { + if (is_symbol(arg)) + { + slot = opt_simple_symbol(sc, arg); + if (slot) + { + opc->v[1].p = slot; + if ((s_func == global_value(sc->vector_ref_symbol)) && + (is_normal_vector(slot_value(slot))) && (vector_rank(slot_value(slot)) != 2)) + return_false(sc, car_x); + } + else return_false(sc, car_x); /* no need for sc->pc = start here, I think */ + } + else + { + opc->v[1].p = arg; + if (s_func == global_value(sc->vector_ref_symbol)) + return_false(sc, car_x); + } + arg = caddr(car_x); + if (is_symbol(arg)) + { + slot = opt_simple_symbol(sc, arg); + if (slot) + { + opc->v[2].p = slot; + arg = cadddr(car_x); + if (is_symbol(arg)) + { + slot = opt_simple_symbol(sc, arg); + if (slot) + { + opc->v[3].p = slot; + opc->v[4].call = cf_call(sc, car_x, s_func, 3); + opc->v[0].fp = (is_slot(opc->v[1].p)) ? opt_p_call_sss : opt_p_call_css; + return_true(sc, car_x); + }} + else + if (is_slot(opc->v[1].p)) + { + int32_t start1 = sc->pc; + if ((cf_call(sc, car_x, s_func, 3) == g_substring_uncopied) && /* opc->v[4].call is unsafe -- might not be set */ + (is_t_integer(slot_value(opc->v[2].p))) && + (is_string(slot_value(opc->v[1].p))) && + (int_optimize(sc, cdddr(car_x)))) + { + opc->v[0].fp = opt_p_substring_uncopied_ssf; + opc->v[5].o1 = o1; + opc->v[6].fi = o1->v[0].fi; + return_true(sc, car_x); + } + sc->pc = start1; + if (cell_optimize(sc, cdddr(car_x))) + { + opc->v[4].call = cf_call(sc, car_x, s_func, 3); + opc->v[0].fp = opt_p_call_ssf; + opc->v[5].o1 = o1; + opc->v[6].fp = o1->v[0].fp; + return_true(sc, car_x); + }}}}} + if (s_func == global_value(sc->vector_ref_symbol)) + return_false(sc, car_x); + if (cell_optimize(sc, cdr(car_x))) + { + opt_info *o2 = sc->opts[sc->pc]; + if (cell_optimize(sc, cddr(car_x))) + { + opt_info *o3 = sc->opts[sc->pc]; + if (cell_optimize(sc, cdddr(car_x))) + { + opc->v[2].call = cf_call(sc, car_x, s_func, 3); + opc->v[0].fp = opt_p_call_ppp; + opc->v[3].o1 = o1; + opc->v[4].fp = o1->v[0].fp; + opc->v[5].o1 = o2; + opc->v[6].fp = o2->v[0].fp; + opc->v[10].o1 = o3; + opc->v[11].fp = o3->v[0].fp; + return_true(sc, car_x); + }}}} + sc->pc = start; + return_false(sc, car_x); +} + + +/* -------- p_call_any -------- */ +#define P_CALL_O1 3 + +static s7_pointer opt_p_call_any(opt_info *o) +{ + s7_scheme *sc = o->sc; + s7_pointer val = safe_list_if_possible(sc, o->v[1].i); + s7_pointer arg = val; + if (in_heap(val)) gc_protect_via_stack(sc, val); + for (s7_int i = 0; i < o->v[1].i; i++, arg = cdr(arg)) + { + opt_info *o1 = o->v[i + P_CALL_O1].o1; + set_car(arg, o1->v[0].fp(o1)); + } + arg = o->v[2].call(sc, val); + if (in_heap(val)) unstack(sc); + else clear_list_in_use(val); + return(arg); +} + +static bool p_call_any_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x, int32_t len) +{ + if ((len < (NUM_VUNIONS - P_CALL_O1)) && + (is_safe_procedure(s_func)) && + (c_function_is_aritable(s_func, len - 1))) + { + s7_pointer p = cdr(car_x); /* (vector-set! v k i 2) gets here */ + opc->v[1].i = (len - 1); + for (int32_t pctr = P_CALL_O1; is_pair(p); pctr++, p = cdr(p)) + { + opc->v[pctr].o1 = sc->opts[sc->pc]; + if (!cell_optimize(sc, p)) + break; + } + if (is_null(p)) + { + opc->v[0].fp = opt_p_call_any; + opc->v[2].call = cf_call(sc, car_x, s_func, len - 1); + return_true(sc, car_x); + }} + return_false(sc, car_x); +} + + +/* -------- p_fx_any -------- */ + +static s7_pointer opt_p_fx_any(opt_info *o) {return(o->v[1].call(o->sc, o->v[2].p));} + +static bool p_fx_any_ok(s7_scheme *sc, opt_info *opc, s7_pointer x) +{ + s7_function f = (has_fx(x)) ? fx_proc(x) : fx_choose(sc, x, sc->curlet, let_symbol_is_safe); + if (!f) + return_false(sc, x); + opc->v[0].fp = opt_p_fx_any; + opc->v[1].call = f; + opc->v[2].p = car(x); + return_true(sc, x); +} + + +/* -------- p_implicit -------- */ + +static bool p_implicit_ok(s7_scheme *sc, s7_pointer s_slot, s7_pointer car_x, int32_t len) +{ + s7_pointer obj = slot_value(s_slot); + opt_info *opc; + int32_t start; + + if ((!is_sequence(obj)) || (len < 2)) + return_false(sc, car_x); + + opc = alloc_opt_info(sc); + opc->v[1].p = s_slot; + start = sc->pc; + if (len == 2) + { + switch (type(obj)) + { + case T_PAIR: opc->v[3].p_pi_f = list_ref_p_pi_unchecked; break; + case T_HASH_TABLE: opc->v[3].p_pp_f = s7_hash_table_ref; break; + case T_LET: opc->v[3].p_pp_f = let_ref; break; + case T_STRING: opc->v[3].p_pi_f = string_ref_p_pi_unchecked; break; + case T_C_OBJECT: return_false(sc, car_x); /* no pi_ref because ref assumes pp */ + + case T_VECTOR: + if (vector_rank(obj) != 1) + return_false(sc, car_x); + opc->v[3].p_pi_f = normal_vector_ref_p_pi_unchecked; + break; + + case T_BYTE_VECTOR: + case T_INT_VECTOR: + case T_FLOAT_VECTOR: + if (vector_rank(obj) != 1) + return_false(sc, car_x); + opc->v[3].p_pi_f = vector_ref_p_pi_unchecked; + break; + + default: + return_false(sc, car_x); + } + /* now v3.p_pi|pp.f is set */ + if (is_symbol(cadr(car_x))) + { + s7_pointer slot = s7_slot(sc, cadr(car_x)); + if (is_slot(slot)) + { + opc->v[2].p = slot; + if ((!is_hash_table(obj)) && /* these because opt_int below */ + (!is_let(obj))) + { + if (!is_t_integer(slot_value(slot))) + return_false(sc, car_x); /* I think this reflects that a non-int index is an error for list-ref et al */ + opc->v[0].fp = opt_p_pi_ss; + if (has_loop_end(opc->v[2].p)) + check_unchecked(sc, obj, opc->v[2].p, opc, NULL); + fixup_p_pi_ss(opc); + return_true(sc, car_x); + } + opc->v[0].fp = opt_p_pp_ss; + return_true(sc, car_x); + }} + else + { + if ((!is_hash_table(obj)) && + (!is_let(obj))) + { + opt_info *o1; + if (is_t_integer(cadr(car_x))) + { + opc->v[2].i = integer(cadr(car_x)); + opc->v[0].fp = opt_p_pi_sc; + return_true(sc, car_x); + } + o1 = sc->opts[sc->pc]; + if (!int_optimize(sc, cdr(car_x))) + return_false(sc, car_x); + opc->v[0].fp = opt_p_pi_sf; + opc->v[4].o1 = o1; + opc->v[5].fi = o1->v[0].fi; + return_true(sc, car_x); + } + if (cell_optimize(sc, cdr(car_x))) + { + opc->v[0].fp = opt_p_pp_sf; + opc->v[4].o1 = sc->opts[start]; + opc->v[5].fp = sc->opts[start]->v[0].fp; + return_true(sc, car_x); + }}} /* len==2 */ + else + { /* len > 2 */ + if ((is_normal_vector(obj)) && (len == 3) && (vector_rank(obj) == 2)) + { + s7_pointer slot = opt_integer_symbol(sc, caddr(car_x)); + if (slot) + { + opc->v[3].p = slot; + slot = opt_integer_symbol(sc, cadr(car_x)); + if (slot) + { + opc->v[2].p = slot; + opc->v[4].p_pii_f = vector_ref_p_pii; + opc->v[0].fp = opt_p_pii_sss; + if ((loop_end_fits(opc->v[2].p, vector_dimension(obj, 0))) && + (loop_end_fits(opc->v[3].p, vector_dimension(obj, 1)))) + opc->v[0].fp = vector_ref_pii_sss_unchecked; + return_true(sc, car_x); + }} + opc->v[10].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, cdr(car_x))) + { + opc->v[8].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, cddr(car_x))) + { + opc->v[0].fp = opt_p_pii_sff; + opc->v[11].fi = opc->v[10].o1->v[0].fi; + opc->v[9].fi = opc->v[8].o1->v[0].fi; + /* opc->v[1].p set above */ + opc->v[4].p_pii_f = vector_ref_p_pii_direct; + return_true(sc, car_x); + }} + sc->pc = start; + } + + #define P_IMPLICIT_CALL_O1 4 + if (len < (NUM_VUNIONS - P_IMPLICIT_CALL_O1)) /* mimic p_call_any_ok */ + { + s7_pointer p = car_x; + opc->v[1].i = len; + for (int32_t pctr = (P_IMPLICIT_CALL_O1 - 1); is_pair(p); pctr++, p = cdr(p)) + { + opc->v[pctr].o1 = sc->opts[sc->pc]; + if (!cell_optimize(sc, p)) + break; + } + if (is_null(p)) + { + /* here we know the vector rank/type, probably can handle the new value type, and maybe indices/dimensions, + * so at least forgo the vec type/rank + immutable checks, the *_set cases are from p_call_any_ok called in cell_optimize, + * but this is called very rarely mainly because hi-rank implicit refs are rare, and check_type_uncertainty is unhappy + * if there are multiple sets of a var. + * hash-tables, lets, lists, and vectors with extra (implicit) args can't be handled because we have no way to tell + * what the implicit call will do, and in the opt_* context, everything must be "safe" (i.e. no defines or + * hidden multiple-values, etc). + */ + if ((!is_any_vector(obj)) || (vector_rank(obj) != (len - 1))) return_false(sc, car_x); + opc->v[0].fp = opt_p_call_any; + switch (type(obj)) /* string can't happen here (no multidimensional strings), for pair/hash/let see above */ + { + case T_INT_VECTOR: opc->v[2].call = g_int_vector_ref; break; + case T_BYTE_VECTOR: opc->v[2].call = g_byte_vector_ref; break; + case T_FLOAT_VECTOR: opc->v[2].call = g_float_vector_ref; break; + case T_VECTOR: opc->v[2].call = g_vector_ref; break; + default: return_false(sc, car_x); + } + return_true(sc, car_x); + }}} + return_false(sc, car_x); +} + +/* -------- cell_quote -------- */ +static bool opt_cell_quote(s7_scheme *sc, s7_pointer car_x) +{ + opt_info *opc; + if (!is_null(cddr(car_x))) + return_false(sc, car_x); + opc = alloc_opt_info(sc); + opc->v[1].p = cadr(car_x); + opc->v[0].fp = opt_p_c; + return_true(sc, car_x); +} + +/* -------- cell_set -------- */ +static s7_pointer opt_set_p_p_f(opt_info *o) +{ + s7_pointer x = o->v[4].fp(o->v[3].o1); + slot_set_value(o->v[1].p, x); + return(x); +} + +static s7_pointer opt_set_p_p_f_with_setter(opt_info *o) +{ + s7_pointer x = o->v[4].fp(o->v[3].o1); + call_c_function_setter(o->sc, slot_setter(o->v[1].p), slot_symbol(o->v[1].p), x); + slot_set_value(o->v[1].p, x); /* symbol_increment?? */ + return(x); +} + +static s7_pointer opt_set_p_i_s(opt_info *o) +{ + s7_pointer val = slot_value(o->v[2].p); + if (is_mutable_integer(val)) + val = make_integer(o->sc, integer(val)); + slot_set_value(o->v[1].p, val); + return(val); +} + +static s7_pointer opt_set_p_i_f(opt_info *o) +{ + s7_pointer x = make_integer(o->sc, o->v[6].fi(o->v[5].o1)); + slot_set_value(o->v[1].p, x); + return(x); +} + +static s7_pointer opt_set_p_d_s(opt_info *o) +{ + s7_pointer val = slot_value(o->v[2].p); + if (is_mutable_number(val)) + val = make_real(o->sc, real(val)); + slot_set_value(o->v[1].p, val); + return(val); +} + +static s7_pointer opt_set_p_d_f(opt_info *o) +{ + s7_pointer x = make_real(o->sc, o->v[5].fd(o->v[4].o1)); + slot_set_value(o->v[1].p, x); + return(x); +} + +static s7_pointer opt_set_p_d_f_sf_add(opt_info *o) +{ + s7_pointer x = make_real(o->sc, opt_d_dd_sf_add(o->v[4].o1)); + slot_set_value(o->v[1].p, x); + return(x); +} + +static s7_pointer opt_set_p_d_f_mm_add(opt_info *o) +{ + s7_double x1 = float_vector_ref_d_7pi(o->sc, slot_value(o->v[4].p), integer(slot_value(o->v[5].p))) * real(slot_value(o->v[3].p)); + s7_double x2 = float_vector_ref_d_7pi(o->sc, slot_value(o->v[10].p), integer(slot_value(o->v[11].p))) * real(slot_value(o->v[9].p)); + slot_set_value(o->v[1].p, make_real(o->sc, x1 + x2)); + return(slot_value(o->v[1].p)); +} + +static s7_pointer opt_set_p_d_f_mm_subtract(opt_info *o) +{ + s7_double x1 = float_vector_ref_d_7pi(o->sc, slot_value(o->v[4].p), integer(slot_value(o->v[5].p))) * real(slot_value(o->v[3].p)); + s7_double x2 = float_vector_ref_d_7pi(o->sc, slot_value(o->v[10].p), integer(slot_value(o->v[11].p))) * real(slot_value(o->v[9].p)); + slot_set_value(o->v[1].p, make_real(o->sc, x1 - x2)); + return(slot_value(o->v[1].p)); +} + +static s7_pointer opt_set_p_c(opt_info *o) +{ + slot_set_value(o->v[1].p, o->v[2].p); + return(o->v[2].p); +} + +static s7_pointer opt_set_p_i_fo(opt_info *o) +{ + s7_int i = o->v[4].i_ii_f(integer(slot_value(o->v[2].p)), integer(slot_value(o->v[3].p))); + s7_pointer x = make_integer(o->sc, i); + slot_set_value(o->v[1].p, x); + return(x); +} + +static s7_pointer opt_set_p_i_fo_add(opt_info *o) +{ + s7_int i = integer(slot_value(o->v[2].p)) + integer(slot_value(o->v[3].p)); + s7_pointer x = make_integer(o->sc, i); + slot_set_value(o->v[1].p, x); + return(x); +} + +static s7_pointer opt_set_p_i_fo1(opt_info *o) +{ + s7_int i = o->v[4].i_ii_f(integer(slot_value(o->v[2].p)), o->v[3].i); + s7_pointer x = make_integer(o->sc, i); + slot_set_value(o->v[1].p, x); + return(x); +} + +static s7_pointer opt_set_p_i_fo1_add(opt_info *o) +{ + s7_int i = integer(slot_value(o->v[2].p)) + o->v[3].i; + s7_pointer x = make_integer(o->sc, i); + slot_set_value(o->v[1].p, x); + return(x); +} + +static bool set_p_i_f_combinable(s7_scheme *sc, opt_info *opc) +{ + if ((sc->pc > 1) && + (opc == sc->opts[sc->pc - 2])) + { + opt_info *o1 = sc->opts[sc->pc - 1]; + if ((o1->v[0].fi == opt_i_ii_ss) || + (o1->v[0].fi == opt_i_ii_ss_add)) + { + opc->v[4].i_ii_f = o1->v[3].i_ii_f; + opc->v[2].p = o1->v[1].p; + opc->v[3].p = o1->v[2].p; + opc->v[0].fp = (o1->v[0].fi == opt_i_ii_ss_add) ? opt_set_p_i_fo_add : opt_set_p_i_fo; + backup_pc(sc); + return_true(sc, NULL); + } + if ((o1->v[0].fi == opt_i_ii_sc) || (o1->v[0].fi == opt_i_ii_sc_add) || (o1->v[0].fi == opt_i_ii_sc_sub)) + { + opc->v[4].i_ii_f = o1->v[3].i_ii_f; + opc->v[2].p = o1->v[1].p; + opc->v[3].i = o1->v[2].i; + opc->v[0].fp = (o1->v[0].fi == opt_i_ii_sc_add) ? opt_set_p_i_fo1_add : opt_set_p_i_fo1; + backup_pc(sc); + return_true(sc, NULL); + }} + return_false(sc, NULL); +} + +static bool set_p_d_f_combinable(s7_scheme *sc, opt_info *opc) +{ + if ((sc->pc > 3) && + (opc == sc->opts[sc->pc - 4])) + { + opt_info *o1 = sc->opts[sc->pc - 3]; + if ((o1->v[0].fd == opt_d_mm_fff) && + ((o1->v[3].d_dd_f == add_d_dd) || (o1->v[3].d_dd_f == subtract_d_dd))) + { + opc->v[0].fp = (o1->v[3].d_dd_f == add_d_dd) ? opt_set_p_d_f_mm_add : opt_set_p_d_f_mm_subtract; + o1 = sc->opts[sc->pc - 2]; + opc->v[3].p = o1->v[1].p; + opc->v[4].p = o1->v[2].p; + opc->v[5].p = o1->v[3].p; + o1 = sc->opts[sc->pc - 1]; + opc->v[9].p = o1->v[1].p; + opc->v[10].p = o1->v[2].p; + opc->v[11].p = o1->v[3].p; + sc->pc -= 3; + return_true(sc, NULL); + }} + return_false(sc, NULL); +} + +static bool is_some_number(s7_scheme *sc, const s7_pointer tp) +{ + return((tp == sc->is_integer_symbol) || + (tp == sc->is_float_symbol) || + (tp == sc->is_real_symbol) || + (tp == sc->is_complex_symbol) || + (tp == sc->is_number_symbol) || + (tp == sc->is_byte_symbol) || + (tp == sc->is_rational_symbol)); +} + +static bool check_type_uncertainty(s7_scheme *sc, s7_pointer target, s7_pointer car_x, opt_info *opc, int32_t start_pc) +{ + s7_pointer code = sc->code; + /* if we're optimizing do, sc->code is (sometimes) ((vars...) (end...) car_x) where car_x is the do body, but it can also be for-each etc */ + + /* maybe the type uncertainty is not a problem */ + if ((is_pair(code)) && /* t101-aux-14: (vector-set! !v! 0 (do ((x (list 1 2 3) (cdr x)) (j -1)) ((null? x) j) (set! j (car x)))) */ + (is_pair(car(code))) && + (is_pair(cdr(code))) && /* weird that code sometimes has nothing to do with car_x -- tree_memq below for reality check */ + (is_pair(cadr(code)))) + { + s7_int counts; + if ((!has_low_count(code)) && /* only set below */ + (s7_tree_memq(sc, car_x, code))) + { + if (is_pair(caar(code))) + { + counts = tree_count(sc, target, car(code), 0) + + tree_count(sc, target, caadr(code), 0) + + tree_count(sc, target, cddr(code), 0); + for (s7_pointer p = car(code); is_pair(p); p = cdr(p)) + { + s7_pointer var = car(p); + if ((is_proper_list_2(sc, var)) && + (car(var) == target)) + counts--; + }} + else counts = tree_count(sc, target, code, 0); + } + else counts = 2; + /* can be from lambda: (lambda (n)...): ((n) (set! sum (+ sum n))) etc */ + if (counts <= 2) + { + set_has_low_count(code); + sc->pc = start_pc; + if (cell_optimize(sc, cddr(car_x))) + { + opc->v[0].fp = opt_set_p_p_f; + opc->v[3].o1 = sc->opts[start_pc]; + opc->v[4].fp = sc->opts[start_pc]->v[0].fp; + return_true(sc, car_x); + }}} + return_false(sc, car_x); +} + +static bool opt_cell_set(s7_scheme *sc, s7_pointer car_x) /* len == 3 here (p_syntax) */ +{ + opt_info *opc = alloc_opt_info(sc); + s7_pointer target = cadr(car_x); + if (is_symbol(target)) + { + s7_pointer settee; + if ((is_constant_symbol(sc, target)) || + ((is_slot(global_slot(target))) && (slot_has_setter(global_slot(target))))) + return_false(sc, car_x); + + settee = s7_slot(sc, target); + if ((is_slot(settee)) && + (!is_immutable_slot(settee)) && + (!is_syntax(slot_value(settee)))) + { + int32_t start_pc = sc->pc; + s7_pointer stype = s7_type_of(sc, slot_value(settee)); + s7_pointer atype; + opc->v[1].p = settee; + if (slot_has_setter(settee)) + { + if ((is_c_function(slot_setter(settee))) && + (is_bool_function(slot_setter(settee))) && + (stype == opt_arg_type(sc, cddr(car_x))) && + (cell_optimize(sc, cddr(car_x)))) + { + opc->v[1].p = settee; + opc->v[0].fp = opt_set_p_p_f_with_setter; + opc->v[3].o1 = sc->opts[start_pc]; + opc->v[4].fp = sc->opts[start_pc]->v[0].fp; + return_true(sc, car_x); + } + return_false(sc, car_x); + } + + if (stype == sc->is_integer_symbol) + { + if (is_symbol(caddr(car_x))) + { + s7_pointer val_slot = opt_integer_symbol(sc, caddr(car_x)); + if (val_slot) + { + opc->v[2].p = val_slot; + opc->v[0].fp = opt_set_p_i_s; + return_true(sc, car_x); + }} + else + { + opc->v[5].o1 = sc->opts[sc->pc]; + if (!int_optimize(sc, cddr(car_x))) + return(check_type_uncertainty(sc, target, car_x, opc, start_pc)); + if (!set_p_i_f_combinable(sc, opc)) + { + opc->v[0].fp = opt_set_p_i_f; + opc->v[6].fi = opc->v[5].o1->v[0].fi; + } + return_true(sc, car_x); + }} + if (stype == sc->is_float_symbol) + { + if (is_t_real(caddr(car_x))) + { + opc->v[2].p = caddr(car_x); + opc->v[0].fp = opt_set_p_c; + return_true(sc, car_x); + } + if (is_symbol(caddr(car_x))) + { + s7_pointer val_slot = opt_float_symbol(sc, caddr(car_x)); + if (val_slot) + { + opc->v[2].p = val_slot; + opc->v[0].fp = opt_set_p_d_s; + return_true(sc, car_x); + }} + else + { + if ((is_pair(caddr(car_x))) && + (float_optimize(sc, cddr(car_x)))) + { + if (!set_p_d_f_combinable(sc, opc)) + { + opc->v[4].o1 = sc->opts[start_pc]; + opc->v[5].fd = sc->opts[start_pc]->v[0].fd; + opc->v[0].fp = (opc->v[5].fd == opt_d_dd_sf_add) ? opt_set_p_d_f_sf_add : opt_set_p_d_f; + } + return_true(sc, car_x); + } + return(check_type_uncertainty(sc, target, car_x, opc, start_pc)); + }} + + atype = opt_arg_type(sc, cddr(car_x)); + if ((is_some_number(sc, atype)) && + (!is_some_number(sc, stype))) + return_false(sc, car_x); + if (cell_optimize(sc, cddr(car_x))) + { + if ((stype != atype) && + (is_symbol(stype)) && + (((t_sequence_p[symbol_type(stype)]) && + (stype != sc->is_null_symbol) && + (stype != sc->is_pair_symbol)) || /* compatible with is_proper_list! */ + (stype == sc->is_iterator_symbol))) + return_false(sc, car_x); + opc->v[0].fp = opt_set_p_p_f; + opc->v[3].o1 = sc->opts[start_pc]; + opc->v[4].fp = sc->opts[start_pc]->v[0].fp; + return_true(sc, car_x); + }} + return_false(sc, car_x); + } + if ((is_pair(target)) && + (is_symbol(car(target))) && + (is_pair(cdr(target))) && + ((is_null(cddr(target))) || (is_null(cdddr(target))) || (is_null(cddddr(target))))) + { + s7_pointer obj, index, s_slot = s7_slot(sc, car(target)); + if (!is_slot(s_slot)) + return_false(sc, car_x); + + obj = slot_value(s_slot); + opc->v[1].p = s_slot; + if (!is_mutable_sequence(obj)) + return_false(sc, car_x); + + index = cadr(target); + switch (type(obj)) + { + case T_STRING: + { + s7_pointer val_type; + if (is_pair(cddr(target))) return_false(sc, car_x); + val_type = opt_arg_type(sc, cddr(car_x)); + if (val_type != sc->is_char_symbol) + return_false(sc, car_x); + opc->v[3].p_pip_f = string_set_p_pip_unchecked; + } + break; + + case T_VECTOR: + /* is_t_integer below to handle the index */ + if (is_null(cddr(target))) + { + if (vector_rank(obj) != 1) return_false(sc, car_x); + opc->v[3].p_pip_f = (is_typed_vector(obj)) ? typed_vector_set_p_pip_unchecked : vector_set_p_pip_unchecked; + } + else + { + if (vector_rank(obj) != 2) + return_false(sc, car_x); + opc->v[5].p_piip_f = (is_typed_vector(obj)) ? typed_vector_set_p_piip_direct : vector_set_p_piip_direct; + return(p_piip_to_sx(sc, opc, cdr(target), cddr(target), cddr(car_x), obj)); + } + break; + + case T_FLOAT_VECTOR: + if (opt_float_vector_set(sc, opc, car(target), cdr(target), + (is_null(cddr(target))) ? NULL : cddr(target), + ((!is_pair(cddr(target))) || (is_null(cdddr(target)))) ? NULL : cdddr(target), + cddr(car_x))) + { + opc->v[O_WRAP].fd = opc->v[0].fd; + opc->v[0].fp = d_to_p; + return_true(sc, car_x); + } + return_false(sc, car_x); + + case T_BYTE_VECTOR: + case T_INT_VECTOR: + if (opt_int_vector_set(sc, -1, opc, car(target), cdr(target), (is_null(cddr(target))) ? NULL : cddr(target), cddr(car_x))) + { + opc->v[O_WRAP].fi = opc->v[0].fi; + opc->v[0].fp = i_to_p; + return_true(sc, car_x); + } + return_false(sc, car_x); + + case T_C_OBJECT: + if ((is_null(cddr(target))) && + (is_c_function(c_object_setf(sc, obj)))) + { + /* d_7pid_ok assumes cadr is the target, not car etc */ + s7_d_7pid_t func = s7_d_7pid_function(c_object_setf(sc, obj)); + if (func) + { + s7_pointer slot = opt_integer_symbol(sc, cadr(target)); + opc->v[4].d_7pid_f = func; + opc->v[10].o1 = sc->opts[sc->pc]; + if (slot) + { + if (float_optimize(sc, cddr(car_x))) + { + opc->v[O_WRAP].fd = opt_d_7pid_ssf; + opc->v[0].fp = d_to_p; /* cell_optimize, so need to return s7_pointer */ + opc->v[2].p = slot; + opc->v[11].fd = opc->v[10].o1->v[0].fd; + return_true(sc, car_x); + }} + else + if (int_optimize(sc, cdr(target))) + { + opc->v[8].o1 = sc->opts[sc->pc]; + if (float_optimize(sc, cddr(car_x))) + { + opc->v[O_WRAP].fd = opt_d_7pid_sff; + opc->v[11].fi = opc->v[10].o1->v[0].fi; + opc->v[9].fd = opc->v[8].o1->v[0].fd; + opc->v[0].fp = d_to_p; + return_true(sc, car_x); + }}}} + return_false(sc, car_x); + + case T_PAIR: + if (is_pair(cddr(target))) return_false(sc, car_x); + opc->v[3].p_pip_f = list_set_p_pip_unchecked; + + /* an experiment -- is this ever hit in normal code? */ + { + s7_pointer val = caddr(car_x); + if ((is_pair(val)) && (car(val) == sc->add_symbol) && (is_t_integer(caddr(val))) && (is_null(cdddr(val))) && (is_symbol(cadr(target))) && + (car(target) == (caadr(val))) && (is_pair(cdadr(val))) && (is_null(cddadr(val))) && (cadr(target) == cadadr(val))) + { + s7_pointer slot = opt_simple_symbol(sc, index); + if ((slot) && (is_t_integer(slot_value(slot)))) + { + opc->v[2].p = slot; + opc->v[3].p = caddr(val); + opc->v[0].fp = list_increment_p_pip_unchecked; + return_true(sc, car_x); + }}} + break; + + case T_HASH_TABLE: + if (is_pair(cddr(target))) return_false(sc, car_x); + opc->v[3].p_ppp_f = s7_hash_table_set; + break; + + case T_LET: + /* here we know the let is a covered mutable let -- ?? not true if s7-optimize called explicitly */ + if ((is_pair(cddr(target))) || (is_openlet(obj))) + return_false(sc, car_x); + if ((is_symbol_and_keyword(cadr(target))) || + ((is_quoted_symbol(cadr(target))))) + opc->v[3].p_ppp_f = let_set_1; + else opc->v[3].p_ppp_f = let_set_p_ppp_2; + break; + + default: + return_false(sc, car_x); + } + if (is_symbol(index)) + { + int32_t start = sc->pc; + s7_pointer slot = opt_simple_symbol(sc, index); + if (slot) + { + opc->v[2].p = slot; + if ((is_t_integer(slot_value(slot))) && + (has_loop_end(opc->v[2].p))) + { + if (is_string(obj)) + { + if (loop_end(opc->v[2].p) <= string_length(obj)) + opc->v[3].p_pip_f = string_set_p_pip_direct; + } + else + if (is_byte_vector(obj)) + { + if (loop_end(opc->v[2].p) <= byte_vector_length(obj)) + opc->v[3].p_pip_f = byte_vector_set_p_pip_direct; + } + else + if (is_any_vector(obj)) /* true for all 3 vectors */ + { + if ((is_any_vector(obj)) && + (loop_end(opc->v[2].p) <= vector_length(obj))) + { + if ((is_normal_vector(obj)) && (is_typed_vector(obj))) + opc->v[3].p_pip_f = typed_normal_vector_set_p_pip_direct; + else opc->v[3].p_pip_f = normal_vector_set_p_pip_direct; + }}} + if (is_symbol(caddr(car_x))) + { + s7_pointer val_slot = opt_simple_symbol(sc, caddr(car_x)); + if (val_slot) + { + s7_p_ppp_t func1; + if ((is_string(obj)) || + (is_any_vector(obj)) || + (is_pair(obj))) + { + opc->v[4].p_pip_f = opc->v[3].p_pip_f; + opc->v[3].p = val_slot; + opc->v[0].fp = opt_p_pip_sss; + return_true(sc, car_x); + } + func1 = opc->v[3].p_ppp_f; + opc->v[4].p_ppp_f = func1; + opc->v[3].p = val_slot; + opc->v[0].fp = (func1 == multiply_p_ppp) ? opt_p_ppp_sss_mul : ((func1 == s7_hash_table_set) ? opt_p_ppp_sss_hset : opt_p_ppp_sss); + return_true(sc, car_x); + }} + else + if ((!is_pair(caddr(car_x))) || + (is_proper_quote(sc, caddr(car_x)))) + { + if (!is_pair(caddr(car_x))) + opc->v[4].p = caddr(car_x); + else opc->v[4].p = cadaddr(car_x); + if ((is_string(obj)) || + (is_any_vector(obj)) || + (is_pair(obj))) + { + opc->v[0].fp = opt_p_pip_ssc; + return_true(sc, car_x); + } + opc->v[0].fp = opt_p_ppp_ssc; + return_true(sc, car_x); + } + if (cell_optimize(sc, cddr(car_x))) + { + opc->v[4].o1 = sc->opts[start]; + opc->v[5].fp = sc->opts[start]->v[0].fp; + if ((is_string(obj)) || + (is_any_vector(obj)) || + (is_pair(obj))) + { + if (p_pip_ssf_combinable(sc, opc, start)) + return_true(sc, car_x); + opc->v[0].fp = opt_p_pip_ssf; + return_true(sc, car_x); + } + opc->v[0].fp = opt_p_ppp_ssf; + return_true(sc, car_x); + }}} + else /* index not a symbol */ + { + opt_info *o1; + if ((is_string(obj)) || + (is_pair(obj)) || + (is_any_vector(obj))) + { + opc->v[10].o1 = sc->opts[sc->pc]; + if (int_optimize(sc, cdr(target))) + { + opc->v[8].o1 = sc->opts[sc->pc]; + if (cell_optimize(sc, cddr(car_x))) + { + opc->v[0].fp = opt_p_pip_sff; + opc->v[11].fi = opc->v[10].o1->v[0].fi; + opc->v[9].fp = opc->v[8].o1->v[0].fp; + return_true(sc, car_x); + }} + return_false(sc, car_x); + } + if ((is_proper_quote(sc, cadr(target))) && + (is_symbol(caddr(car_x)))) + { + s7_pointer val_slot = opt_simple_symbol(sc, caddr(car_x)); + if (val_slot) + { + opc->v[4].p = cadadr(target); + opc->v[2].p = val_slot; + opc->v[0].fp = (opc->v[3].p_ppp_f == let_set_1) ? opt_p_ppp_scs_eset : opt_p_ppp_scs; + return_true(sc, car_x); + }} + o1 = sc->opts[sc->pc]; + if (cell_optimize(sc, cdr(target))) + { + opt_info *o2; + if (is_symbol(caddr(car_x))) + { + s7_pointer val_slot = opt_simple_symbol(sc, caddr(car_x)); + if (val_slot) + { + opc->v[2].p = val_slot; + opc->v[0].fp = opt_p_ppp_sfs; + opc->v[4].o1 = o1; + opc->v[5].fp = o1->v[0].fp; + return_true(sc, car_x); + }} + o2 = sc->opts[sc->pc]; + if (cell_optimize(sc, cddr(car_x))) + { + opc->v[0].fp = opt_p_ppp_sff; + opc->v[10].o1 = o1; + opc->v[11].fp = o1->v[0].fp; + opc->v[8].o1 = o2; + opc->v[9].fp = o2->v[0].fp; + return_true(sc, car_x); + }}}} + return_false(sc, car_x); +} + + +/* -------- cell_begin -------- */ +static s7_pointer opt_begin_p(opt_info *o) +{ + opt_info *o1; + s7_int i, len = o->v[1].i; /* len = 1 if 2 exprs, etc */ + for (i = 0; i < len; i++) + { + o1 = o->v[i + 2].o1; + o1->v[0].fp(o1); + } + o1 = o->v[i + 2].o1; + return(o1->v[0].fp(o1)); +} + +static s7_pointer opt_begin_p_1(opt_info *o) +{ + o->v[3].fp(o->v[2].o1); + return(o->v[5].fp(o->v[4].o1)); +} + +static void oo_idp_nr_fixup(opt_info *start) +{ + if (start->v[0].fp == d_to_p) + { + start->v[0].fp = d_to_p_nr; + if (start->v[O_WRAP].fd == opt_d_7pid_ssf) + start->v[0].fp = opt_d_7pid_ssf_nr; + else + if (start->v[O_WRAP].fd == opt_d_7pid_ssfo_fv) + { + start->v[0].fp = opt_d_7pid_ssfo_fv_nr; + if (start->v[6].d_dd_f == add_d_dd) + start->v[0].fp = opt_d_7pid_ssfo_fv_add_nr; + else + if (start->v[6].d_dd_f == subtract_d_dd) + start->v[0].fp = opt_d_7pid_ssfo_fv_sub_nr; + }} + else + if (start->v[0].fp == i_to_p) + start->v[0].fp = i_to_p_nr; +} + +static bool opt_cell_begin(s7_scheme *sc, s7_pointer car_x, int32_t len) +{ + int32_t i; + opt_info *opc; + s7_pointer p; + if (len > (NUM_VUNIONS - 3)) + return_false(sc, car_x); + opc = alloc_opt_info(sc); + for (i = 2, p = cdr(car_x); is_pair(p); i++, p = cdr(p)) + { + opt_info *start = sc->opts[sc->pc]; + if (!cell_optimize(sc, p)) + return_false(sc, car_x); + if (is_pair(cdr(p))) + oo_idp_nr_fixup(start); + opc->v[i].o1 = start; + } + opc->v[1].i = len - 2; + if (len == 3) + { + opc->v[0].fp = opt_begin_p_1; + opc->v[4].o1 = opc->v[3].o1; + opc->v[5].fp = opc->v[4].o1->v[0].fp; + opc->v[3].fp = opc->v[2].o1->v[0].fp; + } + else opc->v[0].fp = opt_begin_p; + return_true(sc, car_x); +} + +/* -------- cell_when|unless -------- */ +static s7_pointer opt_when_p_2(opt_info *o) +{ + if (o->v[4].fb(o->v[3].o1)) + { + o->v[6].fp(o->v[5].o1); + return(o->v[8].fp(o->v[7].o1)); + } + return(o->sc->unspecified); +} + +static s7_pointer opt_when_p(opt_info *o) +{ + if (o->v[4].fb(o->v[3].o1)) + { + s7_int i, len = o->v[1].i - 1; + opt_info *o1; + for (i = 0; i < len; i++) + { + o1 = o->v[i + 5].o1; + o1->v[0].fp(o1); + } + o1 = o->v[i + 5].o1; + return(o1->v[0].fp(o1)); + } + return(o->sc->unspecified); +} + +static s7_pointer opt_when_p_1(opt_info *o) +{ + opt_info *o1; + if (!o->v[4].fb(o->v[3].o1)) + return(o->sc->unspecified); + o1 = o->v[5].o1; + return(o1->v[0].fp(o1)); +} + +static s7_pointer opt_unless_p(opt_info *o) +{ + opt_info *o1; + s7_int i, len; + if (o->v[4].fb(o->v[3].o1)) + return(o->sc->unspecified); + len = o->v[1].i - 1; + for (i = 0; i < len; i++) + { + o1 = o->v[i + 5].o1; + o1->v[0].fp(o1); + } + o1 = o->v[i + 5].o1; + return(o1->v[0].fp(o1)); +} + +static s7_pointer opt_unless_p_1(opt_info *o) +{ + opt_info *o1; + if (o->v[4].fb(o->v[3].o1)) + return(o->sc->unspecified); + o1 = o->v[5].o1; + return(o1->v[0].fp(o1)); +} + +static bool opt_cell_when(s7_scheme *sc, s7_pointer car_x, int32_t len) +{ + s7_pointer p; + int32_t k; + opt_info *opc; + if (len > (NUM_VUNIONS - 6)) + return_false(sc, car_x); + opc = alloc_opt_info(sc); + opc->v[3].o1 = sc->opts[sc->pc]; + if (!bool_optimize(sc, cdr(car_x))) + return_false(sc, car_x); + for (k = 5, p = cddr(car_x); is_pair(p); k++, p = cdr(p)) + { + opt_info *start = sc->opts[sc->pc]; + if (!cell_optimize(sc, p)) + return_false(sc, car_x); + if (is_pair(cdr(p))) + oo_idp_nr_fixup(start); + opc->v[k].o1 = start; + } + opc->v[4].fb = opc->v[3].o1->v[0].fb; + opc->v[1].i = len - 2; + if (car(car_x) == sc->when_symbol) + { + if (len == 3) + opc->v[0].fp = opt_when_p_1; + else + if (len == 4) + { + opc->v[0].fp = opt_when_p_2; + opc->v[7].o1 = opc->v[6].o1; + opc->v[8].fp = opc->v[7].o1->v[0].fp; + opc->v[6].fp = opc->v[5].o1->v[0].fp; + } + else opc->v[0].fp = opt_when_p; + } + else opc->v[0].fp = (len == 3) ? opt_unless_p_1 : opt_unless_p; + return_true(sc, car_x); +} + +/* -------- cell_cond -------- */ + +#define COND_O1 3 +#define COND_CLAUSE_O1 5 + +static s7_pointer cond_value(opt_info *o) +{ + opt_info *o1; + s7_int i, len = o->v[1].i - 1; + for (i = 0; i < len; i++) + { + o1 = o->v[i + COND_CLAUSE_O1].o1; + o1->v[0].fp(o1); + } + o1 = o->v[i + COND_CLAUSE_O1].o1; + return(o1->v[0].fp(o1)); +} + +static s7_pointer opt_cond(opt_info *top) +{ + s7_int len = top->v[2].i; + for (s7_int clause = 0; clause < len; clause++) + { + opt_info *o1 = top->v[clause + COND_O1].o1; + opt_info *o2 = o1->v[4].o1; + if (o2->v[0].fb(o2)) + { + s7_pointer res = cond_value(o1); + return(res); + }} + return(top->sc->unspecified); +} + +static s7_pointer opt_cond_1(opt_info *o) {return((o->v[5].fb(o->v[4].o1)) ? cond_value(o->v[6].o1) : o->sc->unspecified);} /* cond as when */ +static s7_pointer opt_cond_1b(opt_info *o) {return((o->v[4].o1->v[O_WRAP].fp(o->v[4].o1) != o->sc->F) ? cond_value(o->v[6].o1) : o->sc->unspecified);} + +static s7_pointer opt_cond_2(opt_info *o) /* 2 branches, results 1 expr, else */ +{ + opt_info *o1 = (o->v[5].fb(o->v[4].o1)) ? o->v[6].o1 : o->v[7].o1; + s7_pointer res = o1->v[0].fp(o1); + return(res); +} + +static bool opt_cell_cond(s7_scheme *sc, s7_pointer car_x) +{ + /* top->v[1].i is end index, clause->v[3].i is end of current clause, clause->v[1].i = clause result len */ + s7_pointer last_clause = NULL; + int32_t branches = 0, max_blen = 0; + opt_info *top = alloc_opt_info(sc); + int32_t start_pc = sc->pc; + for (s7_pointer p = cdr(car_x); is_pair(p); p = cdr(p), branches++) + { + opt_info *opc; + s7_pointer clause = car(p), cp; + int32_t blen; + if ((branches >= (NUM_VUNIONS - COND_O1)) || + (!is_pair(clause)) || + (!is_pair(cdr(clause))) || /* leave the test->result case for later */ + (cadr(clause) == sc->feed_to_symbol)) + return_false(sc, clause); + + last_clause = clause; + top->v[branches + COND_O1].o1 = sc->opts[sc->pc]; + opc = alloc_opt_info(sc); + opc->v[4].o1 = sc->opts[sc->pc]; + if (!bool_optimize(sc, clause)) + return_false(sc, clause); + + for (blen = 0, cp = cdr(clause); is_pair(cp); blen++, cp = cdr(cp)) + { + if (blen >= NUM_VUNIONS - COND_CLAUSE_O1) + return_false(sc, cp); + opc->v[blen + COND_CLAUSE_O1].o1 = sc->opts[sc->pc]; + if (!cell_optimize(sc, cp)) + return_false(sc, cp); + } + if (!is_null(cp)) + return_false(sc, cp); + opc->v[1].i = blen; + if (max_blen < blen) max_blen = blen; + opc->v[0].fp = opt_cond; /* a placeholder */ + } + if (branches == 1) + { + opt_info *o1 = sc->opts[start_pc + 1]; + top->v[0].fp = (o1->v[0].fb == p_to_b) ? opt_cond_1b : opt_cond_1; + top->v[4].o1 = o1; + top->v[5].fb = o1->v[0].fb; + top->v[6].o1 = sc->opts[start_pc]; + return_true(sc, car_x); + } + if (branches == 2) + { + if ((max_blen == 1) && + ((car(last_clause) == sc->else_symbol) || (car(last_clause) == sc->T))) + { + opt_info *o1; + top->v[6].o1 = top->v[COND_O1].o1->v[COND_CLAUSE_O1].o1; + top->v[7].o1 = top->v[COND_O1 + 1].o1->v[COND_CLAUSE_O1].o1; + + o1 = sc->opts[start_pc + 1]; + top->v[4].o1 = o1; + top->v[5].fb = o1->v[0].fb; + top->v[0].fp = opt_cond_2; + return_true(sc, car_x); + }} + top->v[2].i = branches; + top->v[0].fp = opt_cond; + return_true(sc, car_x); +} + +/* -------- cell_and|or -------- */ +static s7_pointer opt_and_pp(opt_info *o) {return((o->v[11].fp(o->v[10].o1) == o->sc->F) ? o->sc->F : o->v[9].fp(o->v[8].o1));} + +static s7_pointer opt_and_any_p(opt_info *o) +{ + s7_pointer val = o->sc->T; /* (and) -> #t */ + for (s7_int i = 0; i < o->v[1].i; i++) + { + opt_info *o1 = o->v[i + 3].o1; + val = o1->v[0].fp(o1); + if (val == o->sc->F) + return(o->sc->F); + } + return(val); +} + +static s7_pointer opt_or_pp(opt_info *o) +{ + s7_pointer val = o->v[11].fp(o->v[10].o1); + return((val != o->sc->F) ? val : o->v[9].fp(o->v[8].o1)); +} + +static s7_pointer opt_or_any_p(opt_info *o) +{ + for (s7_int i = 0; i < o->v[1].i; i++) + { + opt_info *o1 = o->v[i + 3].o1; + s7_pointer val = o1->v[0].fp(o1); + if (val != o->sc->F) + return(val); + } + return(o->sc->F); +} + +static bool opt_cell_and(s7_scheme *sc, s7_pointer car_x, int32_t len) +{ + opt_info *opc = alloc_opt_info(sc); + if (len == 3) + { + opc->v[0].fp = ((car(car_x) == sc->or_symbol) ? opt_or_pp : opt_and_pp); + opc->v[10].o1 = sc->opts[sc->pc]; + if (!cell_optimize(sc, cdr(car_x))) + return_false(sc, car_x); + opc->v[11].fp = opc->v[10].o1->v[0].fp; + opc->v[8].o1 = sc->opts[sc->pc]; + if (!cell_optimize(sc, cddr(car_x))) + return_false(sc, car_x); + opc->v[9].fp = opc->v[8].o1->v[0].fp; + return_true(sc, car_x); + } + if ((len > 1) && (len < (NUM_VUNIONS - 4))) + { + s7_pointer p = cdr(car_x); + opc->v[1].i = (len - 1); + opc->v[0].fp = ((car(car_x) == sc->or_symbol) ? opt_or_any_p : opt_and_any_p); + for (int32_t i = 3; is_pair(p); i++, p = cdr(p)) + { + opc->v[i].o1 = sc->opts[sc->pc]; + if (!cell_optimize(sc, p)) + return_false(sc, car_x); + } + return_true(sc, car_x); + } + return_false(sc, car_x); +} + +/* -------- cell_if -------- */ +static s7_pointer opt_if_bp(opt_info *o) {return((o->v[3].fb(o->v[2].o1)) ? o->v[5].fp(o->v[4].o1) : o->sc->unspecified);} +static s7_pointer opt_if_b7p(opt_info *o) {return((opt_b_7p_f(o->v[2].o1)) ? o->v[5].fp(o->v[4].o1) : o->sc->unspecified);} /* expanded not faster */ +static s7_pointer opt_if_nbp(opt_info *o) {return((o->v[5].fb(o->v[4].o1)) ? o->sc->unspecified : o->v[11].fp(o->v[10].o1));} +static s7_pointer opt_if_bp_and(opt_info *o) {return((opt_and_bb(o->v[2].o1)) ? o->v[5].fp(o->v[4].o1) : o->sc->unspecified);} + +static s7_pointer opt_if_bp_pb(opt_info *o) /* p_to_b at outer, p_to_b expanded and moved to o[3] */ +{ + return((o->v[3].fp(o->v[2].o1) != o->sc->F) ? o->v[5].fp(o->v[4].o1) : o->sc->unspecified); +} + +static s7_pointer opt_if_bp_ii_fc(opt_info *o) +{ + return((o->v[3].b_ii_f(o->v[11].fi(o->v[10].o1), o->v[2].i)) ? o->v[5].fp(o->v[4].o1) : o->sc->unspecified); +} + +static s7_pointer opt_if_nbp_s(opt_info *o) +{ + return((o->v[2].b_p_f(slot_value(o->v[3].p))) ? o->sc->unspecified : o->v[11].fp(o->v[10].o1)); +} + +static s7_pointer opt_if_nbp_sc(opt_info *o) /* b_pp_sc */ +{ + return((o->v[3].b_pp_f(slot_value(o->v[2].p), o->v[4].p)) ? o->sc->unspecified : o->v[11].fp(o->v[10].o1)); +} + +static s7_pointer opt_if_nbp_7sc(opt_info *o) /* b_7pp_sc */ +{ + return((o->v[3].b_7pp_f(o->sc, slot_value(o->v[2].p), o->v[4].p)) ? o->sc->unspecified : o->v[11].fp(o->v[10].o1)); +} + +static s7_pointer opt_if_nbp_ss(opt_info *o) /* b_ii_ss */ +{ + return((o->v[3].b_ii_f(integer(slot_value(o->v[2].p)), integer(slot_value(o->v[4].p)))) ? o->sc->unspecified : o->v[11].fp(o->v[10].o1)); +} + +static s7_pointer opt_if_num_eq_ii_ss(opt_info *o) /* b_ii_ss */ +{ + return((integer(slot_value(o->v[2].p)) == integer(slot_value(o->v[4].p))) ? o->sc->unspecified : o->v[11].fp(o->v[10].o1)); +} + +static s7_pointer opt_if_nbp_fs(opt_info *o) /* b_pi_fs */ +{ + return((o->v[2].b_pi_f(o->sc, o->v[5].fp(o->v[4].o1), integer(slot_value(o->v[3].p)))) ? o->sc->unspecified : o->v[11].fp(o->v[10].o1)); +} + +static s7_pointer opt_if_nbp_sf(opt_info *o) /* b_pp_sf */ +{ + return((o->v[2].b_pp_f(slot_value(o->v[3].p), o->v[5].fp(o->v[4].o1))) ? o->sc->unspecified : o->v[11].fp(o->v[10].o1)); +} + +static s7_pointer opt_if_nbp_7sf(opt_info *o) /* b_7pp_sf */ +{ + return((o->v[2].b_7pp_f(o->sc, slot_value(o->v[3].p), o->v[5].fp(o->v[4].o1))) ? o->sc->unspecified : o->v[11].fp(o->v[10].o1)); +} + +static s7_pointer opt_if_bpp(opt_info *o) {return((o->v[5].fb(o->v[4].o1)) ? o->v[9].fp(o->v[8].o1) : o->v[11].fp(o->v[10].o1));} +static s7_pointer opt_if_bpp_bit(opt_info *o) {return((opt_b_7ii_sc_bit(o->v[4].o1)) ? o->v[9].fp(o->v[8].o1) : o->v[11].fp(o->v[10].o1));} + +static bool opt_cell_if(s7_scheme *sc, s7_pointer car_x, int32_t len) +{ + opt_info *opc = alloc_opt_info(sc); + opt_info *bop = sc->opts[sc->pc]; + if (len == 3) + { + if ((is_proper_list_2(sc, cadr(car_x))) && /* (not arg) */ + (caadr(car_x) == sc->not_symbol)) + { + if (bool_optimize(sc, cdadr(car_x))) + { + opt_info *top = sc->opts[sc->pc]; + if (cell_optimize(sc, cddr(car_x))) + { + opc->v[10].o1 = top; + opc->v[11].fp = top->v[0].fp; + if (bop->v[0].fb == opt_b_p_s) + { + opc->v[2].b_p_f = bop->v[2].b_p_f; + opc->v[3].p = bop->v[1].p; + opc->v[0].fp = opt_if_nbp_s; + return_true(sc, car_x); + } + if ((bop->v[0].fb == opt_b_pi_fs) || (bop->v[0].fb == opt_b_pi_fs_num_eq)) + { + opc->v[2].b_pi_f = bop->v[2].b_pi_f; + opc->v[3].p = bop->v[1].p; + opc->v[4].o1 = bop->v[10].o1; + opc->v[5].fp = bop->v[11].fp; + opc->v[0].fp = opt_if_nbp_fs; + return_true(sc, car_x); + } + if ((bop->v[0].fb == opt_b_pp_sf) || + (bop->v[0].fb == opt_b_7pp_sf)) + { + opc->v[4].o1 = bop->v[10].o1; + opc->v[5].fp = bop->v[11].fp; + if (bop->v[0].fb == opt_b_pp_sf) + { + opc->v[2].b_pp_f = bop->v[3].b_pp_f; + opc->v[0].fp = opt_if_nbp_sf; + } + else + { + opc->v[2].b_7pp_f = bop->v[3].b_7pp_f; + opc->v[0].fp = opt_if_nbp_7sf; + } + opc->v[3].p = bop->v[1].p; + return_true(sc, car_x); + } + if ((bop->v[0].fb == opt_b_pp_sc) || + (bop->v[0].fb == opt_b_7pp_sc)) + { + if (bop->v[0].fb == opt_b_pp_sc) + { + opc->v[3].b_pp_f = bop->v[3].b_pp_f; + opc->v[0].fp = opt_if_nbp_sc; + } + else + { + opc->v[3].b_7pp_f = bop->v[3].b_7pp_f; + opc->v[0].fp = opt_if_nbp_7sc; + } + opc->v[2].p = bop->v[1].p; + opc->v[4].p = bop->v[2].p; + return_true(sc, car_x); + } + if ((bop->v[0].fb == opt_b_ii_ss) || (bop->v[0].fb == opt_b_ii_ss_eq) || + (bop->v[0].fb == opt_b_ii_ss_lt) || (bop->v[0].fb == opt_b_ii_ss_gt) || + (bop->v[0].fb == opt_b_ii_ss_leq) || (bop->v[0].fb == opt_b_ii_ss_geq)) + { + opc->v[3].b_ii_f = bop->v[3].b_ii_f; + opc->v[2].p = bop->v[1].p; + opc->v[4].p = bop->v[2].p; + opc->v[0].fp = (opc->v[3].b_ii_f == num_eq_b_ii) ? opt_if_num_eq_ii_ss : opt_if_nbp_ss; + return_true(sc, car_x); + } + opc->v[4].o1 = bop; + opc->v[5].fb = bop->v[0].fb; + opc->v[0].fp = opt_if_nbp; + return_true(sc, car_x); + }}} + else + if (bool_optimize(sc, cdr(car_x))) + { + opt_info *top = sc->opts[sc->pc]; + if (cell_optimize(sc, cddr(car_x))) + { + opc->v[2].o1 = bop; + opc->v[4].o1 = top; + opc->v[5].fp = top->v[0].fp; + if (bop->v[0].fb == p_to_b) + { + opc->v[0].fp = opt_if_bp_pb; + opc->v[3].fp = bop->v[O_WRAP].fp; + return_true(sc, car_x); + } + if (bop->v[0].fb == opt_b_ii_fc) + { + opc->v[2].i = bop->v[2].i; + opc->v[3].b_ii_f = bop->v[3].b_ii_f; + opc->v[11].fi = bop->v[11].fi; + opc->v[10].o1 = bop->v[10].o1; + opc->v[0].fp = opt_if_bp_ii_fc; + return_true(sc, car_x); + } + opc->v[0].fp = (bop->v[0].fb == opt_b_7p_f) ? opt_if_b7p : ((bop->v[0].fb == opt_and_bb) ? opt_if_bp_and : opt_if_bp); + opc->v[3].fb = bop->v[0].fb; + return_true(sc, car_x); + }} + return_false(sc, car_x); + } + if (len == 4) + { + if (bool_optimize(sc, cdr(car_x))) + { + opt_info *top = sc->opts[sc->pc]; + if (cell_optimize(sc, cddr(car_x))) + { + opt_info *o3 = sc->opts[sc->pc]; + opc->v[0].fp = (bop->v[0].fb == opt_b_7ii_sc_bit) ? opt_if_bpp_bit : opt_if_bpp; + if (cell_optimize(sc, cdddr(car_x))) + { + opc->v[4].o1 = bop; + opc->v[5].fb = bop->v[0].fb; + opc->v[8].o1 = top; + opc->v[9].fp = top->v[0].fp; + opc->v[10].o1 = o3; + opc->v[11].fp = o3->v[0].fp; + return_true(sc, car_x); + }}}} + return_false(sc, car_x); +} + +/* -------- cell_case -------- */ +#define CASE_O1 3 +#define CASE_SEL 2 +#define CASE_CLAUSE_O1 4 +#define CASE_CLAUSE_KEYS 2 + +static s7_pointer case_value(opt_info *o) +{ + opt_info *o1; + int32_t i, len = o->v[1].i - 1; /* int32_t here and below seems to be faster than s7_int (tleft.scm) */ + for (i = 0; i < len; i++) + { + o1 = o->v[i + CASE_CLAUSE_O1].o1; + o1->v[0].fp(o1); + } + o1 = o->v[i + CASE_CLAUSE_O1].o1; + return(o1->v[0].fp(o1)); +} + +static s7_pointer opt_case(opt_info *o) +{ + opt_info *o1 = o->v[CASE_SEL].o1; + int32_t lim = o->v[1].i; + s7_scheme *sc = o->sc; + s7_pointer selector = o1->v[0].fp(o1); + + if (is_simple(selector)) + { + for (int32_t ctr = CASE_O1; ctr < lim; ctr++) + { + s7_pointer z; + o1 = o->v[ctr].o1; + for (z = o1->v[CASE_CLAUSE_KEYS].p; is_pair(z); z = cdr(z)) + if (selector == car(z)) + return(case_value(o1)); + if (z == sc->else_symbol) + return(case_value(o1)); + }} + else + for (int32_t ctr = CASE_O1; ctr < lim; ctr++) + { + s7_pointer z; + o1 = o->v[ctr].o1; + for (z = o1->v[CASE_CLAUSE_KEYS].p; is_pair(z); z = cdr(z)) + if (s7_is_eqv(sc, selector, car(z))) + return(case_value(o1)); + if (z == sc->else_symbol) + return(case_value(o1)); + } + return(sc->unspecified); +} + +static bool opt_cell_case(s7_scheme *sc, s7_pointer car_x) +{ + /* top->v[1].i is end index, clause->v[3].i is end of current clause, clause->v[1].i = clause result len */ + s7_pointer p; + int32_t ctr; + opt_info *top = alloc_opt_info(sc); + top->v[CASE_SEL].o1 = sc->opts[sc->pc]; + if (!cell_optimize(sc, cdr(car_x))) /* selector */ + return_false(sc, car_x); + for (ctr = CASE_O1, p = cddr(car_x); (is_pair(p)) && (ctr < NUM_VUNIONS); ctr++, p = cdr(p)) + { + opt_info *opc; + s7_pointer clause = car(p), cp; + int32_t blen; + if ((!is_pair(clause)) || + ((!is_pair(car(clause))) && (car(clause) != sc->else_symbol)) || + (!is_pair(cdr(clause))) || + (cadr(clause) == sc->feed_to_symbol)) + return_false(sc, clause); + + opc = alloc_opt_info(sc); + top->v[ctr].o1 = opc; + if (car(clause) == sc->else_symbol) + { + if (!is_null(cdr(p))) + return_false(sc, clause); + opc->v[CASE_CLAUSE_KEYS].p = sc->else_symbol; + } + else + { + if (!s7_is_proper_list(sc, car(clause))) + return_false(sc, clause); + opc->v[CASE_CLAUSE_KEYS].p = car(clause); + } + + for (blen = 0, cp = cdr(clause); (is_pair(cp)) && (blen < (NUM_VUNIONS - CASE_CLAUSE_O1)); blen++, cp = cdr(cp)) + { + opc->v[blen + CASE_CLAUSE_O1].o1 = sc->opts[sc->pc]; + if (!cell_optimize(sc, cp)) + return_false(sc, cp); + } + if (!is_null(cp)) + return_false(sc, cp); + opc->v[1].i = blen; + opc->v[0].fp = opt_case; /* just a placeholder I hope */ + } + if (!is_null(p)) + return_false(sc, p); + top->v[1].i = ctr; + top->v[0].fp = opt_case; + return_true(sc, car_x); +} + +/* -------- cell_let_temporarily -------- */ + +#define LET_TEMP_O1 5 + +static s7_pointer opt_let_temporarily(opt_info *o) +{ + opt_info *o1 = o->v[4].o1; + s7_int i, len; + s7_pointer result; + s7_scheme *sc = o->sc; + + if (is_immutable_slot(o->v[1].p)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->let_temporarily_symbol, slot_symbol(o->v[1].p))); + + o->v[3].p = slot_value(o->v[1].p); /* save and protect old value */ + gc_protect_via_stack(sc, o->v[3].p); + slot_set_value(o->v[1].p, o1->v[0].fp(o1)); /* set new value */ + len = o->v[2].i - 1; + for (i = 0; i < len; i++) + { + o1 = o->v[i + LET_TEMP_O1].o1; + o1->v[0].fp(o1); + } + o1 = o->v[i + LET_TEMP_O1].o1; + result = o1->v[0].fp(o1); + slot_set_value(o->v[1].p, o->v[3].p); /* restore old */ + unstack(sc); + return(result); +} + +static bool opt_cell_let_temporarily(s7_scheme *sc, s7_pointer car_x, int32_t len) +{ + s7_pointer vars; + if (len <= 2) + return_false(sc, car_x); + + vars = cadr(car_x); + if ((len < (NUM_VUNIONS - LET_TEMP_O1)) && + (is_proper_list_1(sc, vars)) && /* just one var for now */ + (is_proper_list_2(sc, car(vars))) && /* and var is (sym val) */ + (is_symbol(caar(vars))) && + (!is_immutable_symbol(caar(vars))) && + (!is_syntactic_symbol(caar(vars)))) + { + int32_t i; + s7_pointer p; + opt_info *opc = alloc_opt_info(sc); + opc->v[1].p = s7_slot(sc, caaadr(car_x)); + if (!is_slot(opc->v[1].p)) + return_false(sc, car_x); + + opc->v[4].o1 = sc->opts[sc->pc]; + if (!cell_optimize(sc, cdaadr(car_x))) + return_false(sc, car_x); + + for (i = LET_TEMP_O1, p = cddr(car_x); is_pair(p); i++, p = cdr(p)) + { + opc->v[i].o1 = sc->opts[sc->pc]; + if (!cell_optimize(sc, p)) + return_false(sc, car_x); + } + opc->v[2].i = len - 2; + opc->v[0].fp = opt_let_temporarily; + return_true(sc, car_x); + } + return_false(sc, car_x); +} + +/* -------- cell_do -------- */ + +#define do_curlet(o) o->v[2].p +#define do_body_length(o) o->v[3].i +#define do_result_length(o) o->v[4].i +#define do_any_inits(o) o->v[7].o1 +#define do_any_body(o) o->v[10].o1 +#define do_any_results(o) o->v[11].o1 +#define do_any_test(o) o->v[12].o1 +#define do_any_steps(o) o->v[13].o1 + +static void let_set_has_pending_value(s7_pointer lt) +{ + for (s7_pointer vp = let_slots(lt); tis_slot(vp); vp = next_slot(vp)) + slot_set_pending_value(vp, eof_object); /* gc needs a legit value here */ +} + +static void let_clear_has_pending_value(s7_pointer lt) +{ + for (s7_pointer vp = let_slots(lt); tis_slot(vp); vp = next_slot(vp)) + slot_clear_has_pending_value(vp); +} + +typedef s7_pointer (*opt_info_fp)(opt_info *o); + +static s7_pointer opt_do_any(opt_info *o) +{ + opt_info *o1; + opt_info *ostart = do_any_test(o); + opt_info *body = do_any_body(o); + opt_info *inits = do_any_inits(o); + opt_info *steps = do_any_steps(o); + opt_info *results = do_any_results(o); + int32_t i, k, len = do_body_length(o); /* len=6 tlist, 6|7 tbig, 0 tvect */ + s7_pointer vp, result; + s7_scheme *sc = o->sc; + opt_info *os[NUM_VUNIONS]; + opt_info_fp fp[NUM_VUNIONS]; + s7_pointer old_e = sc->curlet; + s7_gc_protect_via_stack(sc, old_e); + sc->curlet = T_Let(do_curlet(o)); + /* init */ + for (k = 0, vp = let_slots(sc->curlet); tis_slot(vp); k++, vp = next_slot(vp)) + { + o1 = inits->v[k].o1; + slot_set_value(vp, o1->v[0].fp(o1)); + } + let_set_has_pending_value(sc->curlet); + for (i = 0; i < len; i++) + { + os[i] = body->v[i].o1; + fp[i] = os[i]->v[0].fp; + } + while (true) + { + /* end */ + if (ostart->v[0].fb(ostart)) + break; + /* body */ + if (len == 6) /* here and in opt_do_n we need a better way to unroll these loops */ + {fp[0](os[0]); fp[1](os[1]); fp[2](os[2]); fp[3](os[3]); fp[4](os[4]); fp[5](os[5]);} + else + if (len == 7) + {fp[0](os[0]); fp[1](os[1]); fp[2](os[2]); fp[3](os[3]); fp[4](os[4]); fp[5](os[5]); fp[6](os[6]);} + else for (i = 0; i < len; i++) fp[i](os[i]); + /* step (let not let*) */ + for (k = 0, vp = let_slots(sc->curlet); tis_slot(vp); k++, vp = next_slot(vp)) + if (has_stepper(vp)) + { + o1 = steps->v[k].o1; + slot_simply_set_pending_value(vp, o1->v[0].fp(o1)); + } + for (vp = let_slots(sc->curlet); tis_slot(vp); vp = next_slot(vp)) + if (has_stepper(vp)) + slot_set_value(vp, slot_pending_value(vp)); + } + /* result */ + result = sc->T; + for (i = 0; i < do_result_length(o); i++) + { + o1 = results->v[i].o1; + result = o1->v[0].fp(o1); + } + let_clear_has_pending_value(sc->curlet); + unstack(sc); + set_curlet(sc, old_e); + return(result); +} + +static s7_pointer opt_do_step_1(opt_info *o) +{ + /* 1 stepper (multi inits perhaps), 1 body, 1 rtn */ + opt_info *o1; + opt_info *ostart = do_any_test(o); + opt_info *ostep = o->v[9].o1; + opt_info *inits = do_any_inits(o); + opt_info *body = do_any_body(o); + int32_t k; + s7_pointer vp, result, stepper = NULL; + s7_scheme *sc = o->sc; + s7_pointer old_e = sc->curlet; + s7_gc_protect_via_stack(sc, old_e); + sc->curlet = T_Let(do_curlet(o)); + for (k = 0, vp = let_slots(sc->curlet); tis_slot(vp); k++, vp = next_slot(vp)) + { + o1 = inits->v[k].o1; + slot_set_value(vp, o1->v[0].fp(o1)); + if (has_stepper(vp)) stepper = vp; + } + while (!(ostart->v[0].fb(ostart))) + { + body->v[0].fp(body); + slot_set_value(stepper, ostep->v[0].fp(ostep)); + } + o1 = do_any_results(o); + result = o1->v[0].fp(o1); + unstack(sc); + set_curlet(sc, old_e); + return(result); +} + +static s7_pointer opt_do_step_i(opt_info *o) +{ + /* 1 stepper (multi inits perhaps), 1 body, 1 rtn */ + opt_info *o1; + opt_info *ostart = do_any_test(o); + opt_info *ostep = o->v[9].o1; + opt_info *inits = do_any_inits(o); + opt_info *body = do_any_body(o); + int32_t k; + s7_pointer vp, result, stepper = NULL, si; + s7_scheme *sc = o->sc; + s7_int end, incr; + s7_pointer old_e = sc->curlet; + s7_gc_protect_via_stack(sc, old_e); + sc->curlet = T_Let(do_curlet(o)); + for (k = 0, vp = let_slots(sc->curlet); tis_slot(vp); k++, vp = next_slot(vp)) + { + o1 = inits->v[k].o1; + slot_set_value(vp, o1->v[0].fp(o1)); + if (has_stepper(vp)) stepper = vp; + } + end = integer(slot_value(ostart->v[2].p)); + incr = ostep->v[2].i; + si = make_mutable_integer(sc, integer(slot_value(ostart->v[1].p))); + if (stepper) slot_set_value(stepper, si); + while (integer(si) != end) + { + body->v[0].fp(body); + integer(si) += incr; + } + clear_mutable_integer(si); + o1 = do_any_results(o); + result = o1->v[0].fp(o1); + unstack(sc); + set_curlet(sc, old_e); + return(result); +} + +#define do_no_vars_test(o) o->v[6].o1 +#define do_no_vars_body(o) o->v[7].o1 + +static s7_pointer opt_do_no_vars(opt_info *o) +{ + /* no vars, no return, o->v[2].p=let, o->v[3].i=body length, o->v[4].i=return length=0, o->v[6]=end test */ + opt_info *ostart = do_no_vars_test(o); + int32_t len = do_body_length(o); + s7_scheme *sc = o->sc; + bool (*fb)(opt_info *o) = ostart->v[0].fb; + s7_pointer old_e = sc->curlet; + s7_gc_protect_via_stack(sc, old_e); + set_curlet(sc, do_curlet(o)); + if (len == 0) /* titer */ + while (!(fb(ostart))); + else + { + opt_info *body = do_no_vars_body(o); + while (!(fb(ostart))) /* tshoot, tfft */ + for (int32_t i = 0; i < len; i++) + { + opt_info *o1 = body->v[i].o1; + o1->v[0].fp(o1); + }} + unstack(sc); + set_curlet(sc, old_e); + return(sc->T); +} + +#define do_stepper_init(o) o->v[11].o1 + +static s7_pointer opt_do_1(opt_info *o) +{ + /* 1 var, 1 expr, no return */ + opt_info *o1 = do_stepper_init(o); + opt_info *ostart = do_any_test(o); + opt_info *ostep = o->v[9].o1; + opt_info *body = do_any_body(o); + s7_pointer vp = let_slots(do_curlet(o)); + s7_scheme *sc = o->sc; + s7_pointer old_e = sc->curlet; + s7_gc_protect_via_stack(sc, old_e); + set_curlet(sc, do_curlet(o)); + slot_set_value(vp, o1->v[0].fp(o1)); + if ((o->v[8].i == 1) && + (is_t_integer(slot_value(vp)))) + { + if ((ostep->v[0].fp == opt_p_ii_ss_add) || /* tmap */ + (ostep->v[0].fp == i_to_p)) + { + s7_pointer step_val = make_mutable_integer(sc, integer(slot_value(vp))); + slot_set_value(vp, step_val); + if (ostep->v[0].fp == opt_p_ii_ss_add) + while (!ostart->v[0].fb(ostart)) + { + body->v[0].fp(body); + integer(step_val) = opt_i_ii_ss_add(ostep); + } + else + while (!ostart->v[0].fb(ostart)) + { + body->v[0].fp(body); + integer(step_val) = ostep->v[O_WRAP].fi(ostep); + } + unstack(sc); + set_curlet(sc, old_e); + return(sc->T); + } + o->v[8].i = 2; + } + while (!(ostart->v[0].fb(ostart))) /* s7test tref */ + { + body->v[0].fp(body); + slot_set_value(vp, ostep->v[0].fp(ostep)); + } + unstack(sc); + set_curlet(sc, old_e); + return(sc->T); +} + +#define do_n_body(o) o->v[7].o1 + +static s7_pointer opt_do_n(opt_info *o) +{ + /* 1 var, no return */ + opt_info *o1 = do_stepper_init(o); + opt_info *ostart = do_any_test(o); + opt_info *ostep = o->v[9].o1; + opt_info *body = do_n_body(o); + int32_t len = do_body_length(o); + s7_pointer vp = let_slots(do_curlet(o)); + s7_scheme *sc = o->sc; + s7_pointer old_e = sc->curlet; + s7_gc_protect_via_stack(sc, old_e); + set_curlet(sc, do_curlet(o)); + slot_set_value(vp, o1->v[0].fp(o1)); + if (len == 2) /* tmac tshoot */ + { + opt_info *e1 = body->v[0].o1, *e2 = body->v[1].o1; + while (!(ostart->v[0].fb(ostart))) + { + e1->v[0].fp(e1); + e2->v[0].fp(e2); + slot_set_value(vp, ostep->v[0].fp(ostep)); + }} + else + { + opt_info *os[NUM_VUNIONS]; + opt_info_fp fp[NUM_VUNIONS]; + for (int32_t i = 0; i < len; i++) + { + os[i] = body->v[i].o1; + fp[i] = os[i]->v[0].fp; + } + if (len == 7) + while (!ostart->v[0].fb(ostart)) /* tfft teq */ + { + fp[0](os[0]); fp[1](os[1]); fp[2](os[2]); fp[3](os[3]); fp[4](os[4]); fp[5](os[5]); fp[6](os[6]); + slot_set_value(vp, ostep->v[0].fp(ostep)); + } + else + while (!ostart->v[0].fb(ostart)) /* tfft teq */ + { + for (int32_t i = 0; i < len; i++) fp[i](os[i]); + slot_set_value(vp, ostep->v[0].fp(ostep)); + }} + unstack(sc); + set_curlet(sc, old_e); + return(sc->T); +} + +static s7_pointer opt_do_times(opt_info *o) +{ + /* 1 var, no return */ + opt_info *o1 = do_stepper_init(o); + opt_info *body = do_n_body(o); + int32_t len = do_body_length(o); + s7_int end = (is_slot(let_dox_slot2_unchecked(do_curlet(o)))) ? integer(slot_value(let_dox_slot2(do_curlet(o)))) : o->v[6].i; + s7_pointer vp = let_dox1_value(do_curlet(o)); + s7_scheme *sc = o->sc; + s7_pointer old_e = sc->curlet; + s7_gc_protect_via_stack(sc, old_e); + set_curlet(sc, do_curlet(o)); + integer(vp) = integer(o1->v[0].fp(o1)); + if (len == 2) /* tmac tmisc */ + { + opt_info *e1 = body->v[0].o1, *e2 = body->v[1].o1; + while (integer(vp) < end) + { + e1->v[0].fp(e1); + e2->v[0].fp(e2); + integer(vp)++; + }} + else + while (integer(vp) < end) /* tbig sg */ + { + for (int32_t i = 0; i < len; i++) + { + o1 = body->v[i].o1; + o1->v[0].fp(o1); + } + integer(vp)++; + } + unstack(sc); + set_curlet(sc, old_e); + return(sc->T); +} + +static s7_pointer opt_do_list_simple(opt_info *o) +{ + opt_info *o1 = do_stepper_init(o); + s7_pointer vp = let_slots(do_curlet(o)); + s7_scheme *sc = o->sc; + s7_pointer (*fp)(opt_info *o); + s7_pointer old_e = sc->curlet; + s7_gc_protect_via_stack(sc, old_e); + set_curlet(sc, do_curlet(o)); + slot_set_value(vp, o1->v[0].fp(o1)); + o1 = do_any_body(o); + fp = o1->v[0].fp; + if (fp == opt_if_bp) + while (is_pair(slot_value(vp))) + { + if (o1->v[3].fb(o1->v[2].o1)) + o1->v[5].fp(o1->v[4].o1); + slot_set_value(vp, cdr(slot_value(vp))); + } + else + while (!is_null(slot_value(vp))) + { + fp(o1); + slot_set_value(vp, cdr(slot_value(vp))); + } + unstack(sc); + set_curlet(sc, old_e); + return(sc->T); +} + +static s7_pointer opt_do_very_simple(opt_info *o) +{ + /* like simple but step can be direct, v[2].p is a let, v[3].i=end? */ + opt_info *o1 = do_stepper_init(o); + s7_int end = (is_slot(let_dox_slot2_unchecked(do_curlet(o)))) ? integer(slot_value(let_dox_slot2(do_curlet(o)))) : o->v[3].i; + s7_pointer vp = let_dox1_value(do_curlet(o)); + s7_pointer (*f)(opt_info *o); + s7_scheme *sc = o->sc; + s7_pointer old_e = sc->curlet; + s7_gc_protect_via_stack(sc, old_e); + set_curlet(sc, do_curlet(o)); + integer(vp) = integer(o1->v[0].fp(o1)); + o1 = do_any_body(o); + f = o1->v[0].fp; + if (f == opt_p_pip_ssf) /* tref.scm */ + { + opt_info *o2 = o1; + o1 = o2->v[4].o1; + if (o2->v[3].p_pip_f == normal_vector_set_p_pip_direct) + { + s7_pointer v = slot_value(o2->v[1].p); + while (integer(vp) < end) + { + normal_vector_set_p_pip_direct(o2->sc, v, integer(slot_value(o2->v[2].p)), o1->v[0].fp(o1)); + integer(vp)++; + }} + else + while (integer(vp) < end) + { + o2->v[3].p_pip_f(o2->sc, slot_value(o2->v[1].p), integer(slot_value(o2->v[2].p)), o1->v[0].fp(o1)); + integer(vp)++; + }} + else + { + if (f == opt_p_pip_sso) + { /* vector-set from vector-ref (i.e. copy), but treating vector-* as generic */ + if (((let_dox_slot1(do_curlet(o)) == o1->v[2].p) && (o1->v[2].p == o1->v[4].p)) && + (((o1->v[5].p_pip_f == float_vector_set_p_pip_direct) && (o1->v[6].p_pi_f == float_vector_ref_p_pi_direct)) || + ((o1->v[5].p_pip_f == int_vector_set_p_pip_direct) && (o1->v[6].p_pi_f == int_vector_ref_p_pi_direct)) || + ((o1->v[5].p_pip_f == string_set_p_pip_direct) && (o1->v[6].p_pi_f == string_ref_p_pi_direct)) || + ((o1->v[5].p_pip_f == byte_vector_set_p_pip_direct) && (o1->v[6].p_pi_f == byte_vector_ref_p_pi_direct)))) + { + copy_to_same_type(sc, slot_value(o1->v[1].p), slot_value(o1->v[3].p), integer(vp), end, integer(vp)); + unstack(sc); + set_curlet(sc, old_e); + return(sc->T); + } + while (integer(vp) < end) + { + o1->v[5].p_pip_f(o1->sc, slot_value(o1->v[1].p), integer(slot_value(o1->v[2].p)), + o1->v[6].p_pi_f(o1->sc, slot_value(o1->v[3].p), integer(slot_value(o1->v[4].p)))); + integer(vp)++; + }} + else + if ((f == opt_set_p_i_f) && /* tvect.scm */ + (is_t_integer(slot_value(o1->v[1].p))) && + (o1->v[1].p != let_dox_slot1(do_curlet(o)))) + { + opt_info *o2 = o1->v[5].o1; /* set_p_i_f: x = make_integer(o->sc, o->v[6].fi(o->v[5].o1)); */ + s7_int (*fi)(opt_info *o) = o2->v[0].fi; + s7_pointer ival = make_mutable_integer(sc, integer(slot_value(o1->v[1].p))); + slot_set_value(o1->v[1].p, ival); + while (integer(vp) < end) + { + integer(ival) = fi(o2); + integer(vp)++; + } + slot_set_value(o1->v[1].p, make_integer(sc, integer(slot_value(o1->v[1].p)))); + } + else + if ((f == opt_d_7pid_ssf_nr) && /* tref.scm */ + (o1->v[4].d_7pid_f == float_vector_set_d_7pid_direct)) + { + s7_pointer ind = o1->v[2].p; + opt_info *o2 = do_any_body(o1); + s7_double (*fd)(opt_info *o) = o2->v[0].fd; + s7_pointer fv = slot_value(o1->v[1].p); + while (integer(vp) < end) + { + float_vector_set_d_7pid_direct(sc, fv, integer(slot_value(ind)), fd(o2)); + /* weird! els[integer(slot_value(ind))] = fd(o2) is much slower according to callgrind? */ + integer(vp)++; + }} + else + while (integer(vp) < end) {f(o1); integer(vp)++;}} + /* splitting out opt_set_p_d_f_sf_add here (for tgsl.scm) is marginal (time is in opt_d_dd_ff_mul -> opt_d_id_sf -> bessel funcs) */ + unstack(sc); + set_curlet(sc, old_e); + return(sc->T); +} + +#define do_prepack_end(o) o->v[1].i +#define do_prepack_stepper(o) o->v[6].p + +static s7_pointer opt_do_prepackaged(opt_info *o) +{ + opt_info *o1 = do_stepper_init(o); + s7_int end = (is_slot(let_dox_slot2_unchecked(do_curlet(o)))) ? integer(slot_value(let_dox_slot2(do_curlet(o)))) : o->v[3].i; + s7_pointer vp = let_dox1_value(do_curlet(o)); + s7_scheme *sc = o->sc; + s7_pointer old_e = sc->curlet; + s7_gc_protect_via_stack(sc, old_e); + set_curlet(sc, do_curlet(o)); + integer(vp) = integer(o1->v[0].fp(o1)); + + do_prepack_stepper(o) = vp; + do_prepack_end(o) = end; + o->v[7].fp(o); /* call opt_do_i|dpnr below */ + + unstack(sc); + set_curlet(sc, old_e); + return(sc->T); +} + +static s7_pointer opt_do_dpnr(opt_info *o) +{ + opt_info *o1 = do_any_body(o); + s7_pointer vp = do_prepack_stepper(o); + s7_int end = do_prepack_end(o); + s7_double (*f)(opt_info *o) = o1->v[O_WRAP].fd; + while (integer(vp) < end) {f(o1); integer(vp)++;} + return(NULL); +} + +static s7_pointer opt_do_ipnr(opt_info *o) +{ + opt_info *o1 = do_any_body(o); + s7_pointer vp = do_prepack_stepper(o); + s7_int end = do_prepack_end(o); + s7_int (*f)(opt_info *o) = o1->v[O_WRAP].fi; + while (integer(vp) < end) {f(o1); integer(vp)++;} + return(NULL); +} + +static bool stop_is_safe(s7_scheme *sc, s7_pointer stop, s7_pointer body) +{ + /* this could be folded into the cell_optimize traversal */ + for (s7_pointer p = body; is_pair(p); p = cdr(p)) + if ((is_pair(car(p))) && + (caar(p) == sc->set_symbol) && + (is_pair(cdar(p))) && + (cadar(p) == stop)) + return(!s7_tree_memq(sc, stop, cdr(p))); + return(true); +} + +static bool tree_has_setters(s7_scheme *sc, s7_pointer tree) +{ + clear_symbol_list(sc); + add_symbol_to_list(sc, sc->set_symbol); + add_symbol_to_list(sc, sc->vector_set_symbol); + add_symbol_to_list(sc, sc->list_set_symbol); + add_symbol_to_list(sc, sc->let_set_symbol); + add_symbol_to_list(sc, sc->hash_table_set_symbol); + add_symbol_to_list(sc, sc->set_car_symbol); + add_symbol_to_list(sc, sc->set_cdr_symbol); + return(tree_set_memq(sc, tree)); +} + +static bool do_is_safe(s7_scheme *sc, s7_pointer body, s7_pointer stepper, s7_pointer var_list, bool *has_set); + +static bool do_passes_safety_check(s7_scheme *sc, s7_pointer body, s7_pointer stepper, bool *has_set) +{ + if (!is_pair(body)) return(true); + if (!is_safety_checked(body)) + { + set_safety_checked(body); + if (!(do_is_safe(sc, body, stepper, sc->nil, has_set))) + set_unsafe_do(body); + } + return(!is_unsafe_do(body)); +} + +#define SIZE_O NUM_VUNIONS + +static bool all_integers(s7_scheme *sc, s7_pointer expr) +{ + if ((is_symbol(car(expr))) && (is_all_integer(car(expr)))) + { + s7_pointer p; + for (p = cdr(expr); is_pair(p); p = cdr(p)) + if (!((is_t_integer(car(p))) || + ((is_symbol(car(p))) && (is_t_integer(slot_value(s7_slot(sc, car(p)))))) || + ((is_pair(car(p))) && (all_integers(sc, car(p)))))) + break; + return(is_null(p)); + } + return(false); +} + +static bool all_floats(s7_scheme *sc, s7_pointer expr) +{ + if ((is_symbol(car(expr))) && (is_all_float(car(expr)))) + { + s7_pointer p; + for (p = cdr(expr); is_pair(p); p = cdr(p)) + if (!((is_t_real(car(p))) || + ((is_symbol(car(p))) && (is_t_real(slot_value(s7_slot(sc, car(p)))))) || + ((is_pair(car(p))) && (all_floats(sc, car(p)))))) + break; + return(is_null(p)); + } + return(false); +} + +static bool opt_cell_do(s7_scheme *sc, s7_pointer car_x, int32_t len) +{ + opt_info *opc; + s7_pointer p, end, let = NULL, old_e = sc->curlet, stop, ind, ind_step; + int32_t i, k, var_len, body_len = len - 3, body_index, step_len, rtn_len, step_pc, init_pc, end_test_pc; + bool has_set = false; + opt_info *init_o[SIZE_O], *step_o[SIZE_O], *body_o[SIZE_O], *return_o[SIZE_O]; + + if (len < 3) + return_false(sc, car_x); + + if (!s7_is_proper_list(sc, cadr(car_x))) + return_false(sc, car_x); + var_len = proper_list_length(cadr(car_x)); + step_len = var_len; + if (body_len > SIZE_O) + return_false(sc, car_x); + end = caddr(car_x); + if (!is_pair(end)) + return_false(sc, car_x); + + opc = alloc_opt_info(sc); + let = inline_make_let(sc, sc->curlet); + push_stack(sc, OP_GC_PROTECT, old_e, let); + + /* the vars have to be added to the let before evaluating the inits + * else symbol_id can be > let_id (see "(test (do ((i (do ((i (do ((i 0 (+ i 1)))...") + */ + clear_symbol_list(sc); + for (p = cadr(car_x); is_pair(p); p = cdr(p)) + { + s7_pointer var = car(p); + if ((is_pair(var)) && + (is_symbol(car(var))) && + (is_pair(cdr(var)))) + { + s7_pointer sym = car(var); + if (is_constant_symbol(sc, sym)) + return_false(sc, car_x); + if (symbol_is_in_list(sc, sym)) + syntax_error_nr(sc, "duplicate identifier in do: ~A", 30, var); + add_symbol_to_list(sc, sym); + add_slot(sc, let, sym, sc->undefined); + } + else return_false(sc, car_x); + } + if (tis_slot(let_slots(let))) + let_set_slots(let, reverse_slots(let_slots(let))); + + /* inits */ + { + s7_pointer slot; + init_pc = sc->pc; + for (k = 0, p = cadr(car_x), slot = let_slots(let); (is_pair(p)) && (k < SIZE_O); k++, p = cdr(p), slot = next_slot(slot)) + { + s7_pointer var = car(p); + init_o[k] = sc->opts[sc->pc]; + if (!cell_optimize(sc, cdr(var))) /* opt init in outer let */ + return_false(sc, car_x); + if (is_pair(cddr(var))) + { + set_has_stepper(slot); + if (!is_null(cdddr(var))) + return_false(sc, car_x); + } + else + { + step_len--; + if (!is_null(cddr(var))) + return_false(sc, car_x); + } + /* we can't use slot_set_value(slot, init_o[k]->v[0].fp(init_o[k])) to get the init value here: it might involve side-effects, + * and in some contexts might access variables that aren't set up yet. So, we kludge around... + */ + if (is_symbol(cadr(var))) + slot_set_value(slot, slot_value(s7_slot(sc, cadr(var)))); + else + if (!is_pair(cadr(var))) + slot_set_value(slot, cadr(var)); + else + if (is_proper_quote(sc, cadr(var))) + slot_set_value(slot, cadadr(var)); + else + { + s7_pointer sf = lookup_checked(sc, caadr(var)); + if (is_c_function(sf)) + { + s7_pointer sig = c_function_signature(sf); + if (is_pair(sig)) + { + if ((car(sig) == sc->is_integer_symbol) || + ((is_pair(car(sig))) && + (direct_memq(sc->is_integer_symbol, car(sig)))) || + (all_integers(sc, cadr(var)))) + slot_set_value(slot, int_zero); + else + if ((car(sig) == sc->is_float_symbol) || + ((is_pair(car(sig))) && + (direct_memq(sc->is_float_symbol, car(sig)))) || + (all_floats(sc, cadr(var)))) + slot_set_value(slot, real_zero); + /* need for stepper too -- how does it know (+ x 0.1) is float? try (i 0 (floor (+ i 1))) etc */ + }}}} + set_curlet(sc, let); + for (p = cadr(car_x); is_pair(p); p = cdr(p)) + { + s7_pointer var = car(p); + if (is_pair(cddr(var))) + { + s7_pointer init_type = opt_arg_type(sc, cdr(var)); + if (((init_type == sc->is_integer_symbol) || + (init_type == sc->is_float_symbol)) && + (opt_arg_type(sc, cddr(var)) != init_type)) + { + unstack(sc); /* not pop_stack! */ + set_curlet(sc, old_e); + return_false(sc, car_x); + }}}} + + /* end test */ + end_test_pc = sc->pc; + if (!bool_optimize_nw(sc, end)) + { + unstack(sc); /* not pop_stack! */ + set_curlet(sc, old_e); + return_false(sc, car_x); + } + + stop = car(end); + if ((is_proper_list_3(sc, stop)) && + ((car(stop) == sc->num_eq_symbol) || (car(stop) == sc->geq_symbol) || (car(stop) == sc->gt_symbol)) && + (is_symbol(cadr(stop))) && + ((is_t_integer(caddr(stop))) || (is_symbol(caddr(stop))))) + { + s7_pointer stop_slot = (is_symbol(caddr(stop))) ? opt_integer_symbol(sc, caddr(stop)) : sc->nil; + if (stop_slot) + { + s7_int lim = (is_slot(stop_slot)) ? integer(slot_value(stop_slot)) : integer(caddr(stop)); + bool set_stop = false; + s7_pointer slot; + + if (car(stop) == sc->gt_symbol) lim++; + for (p = cadr(car_x), slot = let_slots(let); is_pair(p); p = cdr(p), slot = next_slot(slot)) + { + /* this could be put off until it is needed (ref/set), but this code is not called much + * another choice: go from init downto 0: init is lim + */ + if (slot_symbol(slot) == cadr(stop)) + set_stop = true; /* don't overrule this decision below */ + if (has_stepper(slot)) + { + s7_pointer var = car(p), step = caddr(var); + if ((is_t_integer(slot_value(slot))) && + (is_pair(step)) && + (is_pair(cdr(step))) && + (car(var) == cadr(stop)) && + (car(var) == cadr(step)) && + ((car(stop) != sc->num_eq_symbol) || /* else > protects at least the top */ + ((caddr(step) == int_one) && (car(step) == sc->add_symbol)))) + { + set_has_loop_end(slot); + slot_set_value(slot, make_mutable_integer(sc, integer(slot_value(slot)))); + set_loop_end(slot, lim); + }}} + + if (!set_stop) + { + s7_pointer slot2 = opt_integer_symbol(sc, cadr(stop)); + if ((slot2) && + (stop_is_safe(sc, cadr(stop), cddr(car_x)))) /* b_fft in tfft.scm */ + { + set_has_loop_end(slot2); + set_loop_end(slot2, lim); + }}}} + + /* body */ + body_index = sc->pc; + for (k = 0, i = 3, p = cdddr(car_x); i < len; k++, i++, p = cdr(p)) + { + opt_info *start = sc->opts[sc->pc]; + body_o[k] = start; + if (i < 5) opc->v[i + 7].o1 = start; + if (!cell_optimize(sc, p)) + break; + oo_idp_nr_fixup(start); + } + if (!is_null(p)) + { + unstack(sc); + set_curlet(sc, old_e); + return_false(sc, car_x); + } + + /* we faked up sc->curlet above, so s7_optimize_1 (float_optimize) isn't safe here + * this means if clm nested loops get here, they aren't fully optimized -- fallback into dox would be better + */ + /* steps */ + step_pc = sc->pc; + for (k = 0, p = cadr(car_x); is_pair(p); k++, p = cdr(p)) + { + s7_pointer var = car(p); + step_o[k] = sc->opts[sc->pc]; + if ((is_pair(cddr(var))) && + (!cell_optimize(sc, cddr(var)))) + break; + } + if (!is_null(p)) + { + unstack(sc); + set_curlet(sc, old_e); + return_false(sc, car_x); + } + + /* result */ + if (!is_list(cdr(end))) + { + unstack(sc); + set_curlet(sc, old_e); + return_false(sc, car_x); + } + for (rtn_len = 0, p = cdr(end); (is_pair(p)) && (rtn_len < SIZE_O); p = cdr(p), rtn_len++) + { + return_o[rtn_len] = sc->opts[sc->pc]; + if (!cell_optimize(sc, p)) + break; + } + if (!is_null(p)) + { + unstack(sc); + set_curlet(sc, old_e); + return_false(sc, car_x); + } + + do_curlet(opc) = let; + do_body_length(opc) = len - 3; + do_result_length(opc) = rtn_len; + opc->v[9].o1 = sc->opts[step_pc]; + set_curlet(sc, old_e); + + if ((var_len == 0) && (rtn_len == 0)) + { + opt_info *body; + do_no_vars_test(opc) = sc->opts[end_test_pc]; + opc->v[0].fp = opt_do_no_vars; + if (body_len > 0) + { + body = alloc_opt_info(sc); + for (k = 0; k < body_len; k++) + body->v[k].o1 = body_o[k]; + do_no_vars_body(opc) = body; + } + return(true); + } + opc->v[8].i = 0; + if (body_len == 1) + { + s7_pointer expr = cadddr(car_x); + if ((is_pair(expr)) && + ((is_c_function(car(expr))) || + (is_safe_setter(car(expr))) || + ((car(expr) == sc->set_symbol) && + (cadr(expr) != caaadr(car_x))) || /* caadr: (stepper init ...) */ + ((car(expr) == sc->vector_set_symbol) && + (is_null(cddddr(expr))) && + (is_code_constant(sc, cadddr(expr)))))) + opc->v[8].i = 1; + } + if ((var_len != 1) || (step_len != 1) || (rtn_len != 0)) + { + opt_info *inits; + + opc->v[0].fp = ((step_len == 1) && (body_len == 1) && (rtn_len == 1)) ? opt_do_step_1 : opt_do_any; + /* (do ((sum 0.0) (k 0 (+ k 1))) ((= k size) (set! (C i j) sum)) (set! sum (+ sum (* (A i k) (B k j))))) tmat */ + + do_any_test(opc) = sc->opts[end_test_pc]; + + if ((opc->v[0].fp == opt_do_step_1) && + (opc->v[9].o1->v[0].fp == i_to_p) && + (opc->v[9].o1->v[O_WRAP].fi == opt_i_ii_sc_add) && + (do_any_test(opc)->v[0].fb == opt_b_ii_ss_eq)) + opc->v[0].fp = opt_do_step_i; + + inits = alloc_opt_info(sc); + for (k = 0; k < var_len; k++) + inits->v[k].o1 = init_o[k]; + do_any_inits(opc) = inits; + + if (opc->v[0].fp == opt_do_any) + { + opt_info *result, *step; + opt_info *body = alloc_opt_info(sc); + + for (k = 0; k < body_len; k++) + body->v[k].o1 = body_o[k]; + do_any_body(opc) = body; + + result = alloc_opt_info(sc); + for (k = 0; k < rtn_len; k++) + result->v[k].o1 = return_o[k]; + do_any_results(opc) = result; + + step = alloc_opt_info(sc); + for (k = 0; k < var_len; k++) + step->v[k].o1 = step_o[k]; + do_any_steps(opc) = step; + } + else + { + do_any_body(opc) = sc->opts[body_index]; + do_any_results(opc) = return_o[0]; + } + return(true); + } + + opc->v[0].fp = (body_len == 1) ? opt_do_1 : opt_do_n; + p = caadr(car_x); + ind = car(p); + ind_step = caddr(p); + end = caaddr(car_x); + + if (body_len == 1) /* opt_do_1 */ + do_any_body(opc) = sc->opts[body_index]; + else + { + opt_info *body = alloc_opt_info(sc); + for (k = 0; k < body_len; k++) + body->v[k].o1 = body_o[k]; + do_n_body(opc) = body; + } + do_stepper_init(opc) = sc->opts[init_pc]; + do_any_test(opc) = sc->opts[end_test_pc]; + do_any_steps(opc) = sc->opts[step_pc]; + + if ((is_pair(end)) && /* (= i len|100) */ + (cadr(end) == ind) && + (is_pair(ind_step))) /* (+ i 1) */ + { + /* we can't use loop_end_possible here yet (not set except for op_dox?) */ + + if (((car(end) == sc->num_eq_symbol) || (car(end) == sc->geq_symbol)) && + ((is_symbol(caddr(end))) || (is_t_integer(caddr(end)))) && + (is_null(cdddr(end))) && + (car(ind_step) == sc->add_symbol) && + (cadr(ind_step) == ind) && + (caddr(ind_step) == int_one) && + (is_null(cdddr(ind_step))) && + (do_passes_safety_check(sc, cdddr(car_x), ind, &has_set))) + { + s7_pointer slot = let_slots(let); + let_set_dox_slot1(let, slot); + let_set_dox_slot2_unchecked(let, (is_symbol(caddr(end))) ? s7_slot(sc, caddr(end)) : sc->undefined); + slot_set_value(slot, make_mutable_integer(sc, integer(slot_value(slot)))); + opc->v[4].i = body_index; + if (body_len == 1) /* opt_do_1 */ + { + opt_info *o1 = sc->opts[body_index]; + opc->v[0].fp = opt_do_very_simple; + if (is_t_integer(caddr(end))) + opc->v[3].i = integer(caddr(end)); + if (o1->v[0].fp == d_to_p_nr) /* snd-test: (do ((k 0 (+ k 1))) ((= k N)) (float-vector-set! rl k (read-sample rd))) */ + { + opc->v[0].fp = opt_do_prepackaged; + opc->v[7].fp = opt_do_dpnr; + } + else + if (o1->v[0].fp == i_to_p_nr) + { + opc->v[0].fp = opt_do_prepackaged; + opc->v[7].fp = opt_do_ipnr; + }} + else + { + opc->v[0].fp = opt_do_times; + if (is_t_integer(caddr(end))) + opc->v[6].i = integer(caddr(end)); + }} + else + if ((car(end) == sc->is_null_symbol) && + (is_null(cddr(end))) && + (car(ind_step) == sc->cdr_symbol) && + (cadr(ind_step) == ind) && + (is_null(cddr(ind_step))) && + (body_len == 1) && + (do_passes_safety_check(sc, cdddr(car_x), ind, &has_set))) + opc->v[0].fp = opt_do_list_simple; + } + return(true); +} + +static bool p_syntax(s7_scheme *sc, s7_pointer car_x, int32_t len) +{ + s7_pointer func = lookup_global(sc, car(car_x)); + opcode_t op; + if (!is_syntax(func)) {clear_syntactic(car_x); return(false);} + /* I think this is the only case where we don't precede syntax_opcode with syntactic_symbol checks */ + op = syntax_opcode(func); + switch (op) + { + case OP_QUOTE: if ((is_pair(cdr(car_x))) && (is_null(cddr(car_x)))) return(opt_cell_quote(sc, car_x)); break; + case OP_SET: if (len == 3) return(opt_cell_set(sc, car_x)); break; + case OP_BEGIN: if (len > 1) return(opt_cell_begin(sc, car_x, len)); break; + case OP_WHEN: + case OP_UNLESS: if (len > 2) return(opt_cell_when(sc, car_x, len)); break; + case OP_COND: if (len > 1) return(opt_cell_cond(sc, car_x)); break; + case OP_CASE: if (len > 2) return(opt_cell_case(sc, car_x)); break; + case OP_AND: + case OP_OR: return(opt_cell_and(sc, car_x, len)); + case OP_IF: return(opt_cell_if(sc, car_x, len)); + case OP_DO: return(opt_cell_do(sc, car_x, len)); + case OP_LET_TEMPORARILY: return(opt_cell_let_temporarily(sc, car_x, len)); + default: + /* for lambda et al we'd return the new closure, but if unsafe? + * let(*) -> make the let -> body (let=99% of cases), could we use do (i.e. do+no steppers+no end!) or let-temp? + * with-let -> establish car(args)=let, then body + * macroexpand -> return the expansion + * define et al -> define + return value + * map and for-each are not syntax, also call-with*(=exit) + * also let-temp for vars>1 + */ + break; + } + return_false(sc, car_x); +} + + +/* -------------------------------------------------------------------------------- */ +static bool float_optimize_1(s7_scheme *sc, s7_pointer expr) +{ + s7_pointer car_x = car(expr), head, s_func, s_slot = NULL; + s7_int len; + if (WITH_GMP) return(false); + if (!is_pair(car_x)) /* wrap constants/symbols */ + return(opt_float_not_pair(sc, car_x)); + + head = car(car_x); + len = s7_list_length(sc, car_x); + if (is_symbol(head)) + { + if ((is_syntactic_symbol(head)) || + (is_syntactic_pair(car_x))) + return(d_syntax_ok(sc, car_x, len)); + + s_slot = s7_slot(sc, head); + if (!is_slot(s_slot)) return_false(sc, car_x); + s_func = slot_value(s_slot); + } + else + if (is_c_function(head)) + s_func = head; + else return_false(sc, car_x); + + if (is_c_function(s_func)) + { + opt_info *opc = alloc_opt_info(sc); + switch (len) + { + case 1: + return(d_ok(sc, opc, s_func)); + + case 2: /* (f v) or (f d): (env e) or (abs x) */ + return((d_d_ok(sc, opc, s_func, car_x)) || + (d_v_ok(sc, opc, s_func, car_x)) || + (d_p_ok(sc, opc, s_func, car_x))); + + case 3: + return((d_dd_ok(sc, opc, s_func, car_x)) || + (d_id_ok(sc, opc, s_func, car_x)) || + (d_vd_ok(sc, opc, s_func, car_x)) || + (d_pd_ok(sc, opc, s_func, car_x)) || + (d_ip_ok(sc, opc, s_func, car_x)) || + (d_7pi_ok(sc, opc, s_func, car_x))); + + case 4: + return((d_ddd_ok(sc, opc, s_func, car_x)) || + (d_7pid_ok(sc, opc, s_func, car_x)) || + (d_vid_ok(sc, opc, s_func, car_x)) || + (d_vdd_ok(sc, opc, s_func, car_x)) || + (d_7pii_ok(sc, opc, s_func, car_x))); + + case 5: + return((d_dddd_ok(sc, opc, s_func, car_x)) || + (d_7piid_ok(sc, opc, s_func, car_x)) || + (d_7piii_ok(sc, opc, s_func, car_x))); + + case 6: + if (d_7piiid_ok(sc, opc, s_func, car_x)) + return(true); + /* fall through */ + + default: + return(d_add_any_ok(sc, opc, car_x)); + }} + else + { + if ((is_macro(s_func)) && (!no_cell_opt(expr))) + { + s7_pointer body = closure_body(s_func); + if ((is_null(cdr(body))) && (is_pair(car(body))) && + ((caar(body) == sc->list_symbol) || (caar(body) == sc->list_values_symbol))) + { + s7_pointer result = s7_macroexpand(sc, s_func, cdar(expr)); + if (result == sc->F) return_false(sc, car_x); + return(float_optimize(sc, set_plist_1(sc, result))); + }} + if (!s_slot) return_false(sc, car_x); + return(d_implicit_ok(sc, s_slot, car_x, len)); + } + return_false(sc, car_x); +} + +static bool float_optimize(s7_scheme *sc, s7_pointer expr) {return((float_optimize_1(sc, expr)) && (sc->pc < OPTS_SIZE));} +/* combining the sc->pc check into float_optimize_1 (and similarly for the other 3 cases) does not given any speedup */ + +static bool int_optimize_1(s7_scheme *sc, s7_pointer expr) +{ + s7_pointer car_x = car(expr), head, s_func, s_slot = NULL; + s7_int len; + if (WITH_GMP) return(false); + if (!is_pair(car_x)) /* wrap constants/symbols */ + return(opt_int_not_pair(sc, car_x)); + + head = car(car_x); + len = s7_list_length(sc, car_x); + if (is_symbol(head)) + { + if ((is_syntactic_symbol(head)) || + (is_syntactic_pair(car_x))) + return(i_syntax_ok(sc, car_x, len)); + s_slot = s7_slot(sc, head); + if (!is_slot(s_slot)) return_false(sc, car_x); + s_func = slot_value(s_slot); + } + else + if (is_c_function(head)) + s_func = head; + else return_false(sc, car_x); + + if (is_c_function(s_func)) + { + opt_info *opc = alloc_opt_info(sc); + switch (len) + { + case 2: + return(i_idp_ok(sc, opc, s_func, car_x)); + + case 3: + return((i_ii_ok(sc, opc, s_func, car_x)) || + (i_7pi_ok(sc, opc, s_func, car_x))); + + case 4: + return((i_iii_ok(sc, opc, s_func, car_x)) || + (i_7pii_ok(sc, opc, s_func, car_x))); + + case 5: + { + int32_t pstart = sc->pc; + if (i_7piii_ok(sc, opc, s_func, car_x)) + return(true); + sc->pc = pstart; + } + /* fall through */ + + default: + return(((head == sc->add_symbol) || + (head == sc->multiply_symbol)) && + (i_add_any_ok(sc, opc, car_x))); + }} + else + { + if ((is_macro(s_func)) && (!no_cell_opt(expr))) + { + s7_pointer body = closure_body(s_func); + if ((is_null(cdr(body))) && (is_pair(car(body))) && + ((caar(body) == sc->list_symbol) || (caar(body) == sc->list_values_symbol))) + { + s7_pointer result = s7_macroexpand(sc, s_func, cdar(expr)); + if (result == sc->F) return_false(sc, car_x); + return(int_optimize(sc, set_plist_1(sc, result))); + }} + if (!s_slot) return_false(sc, car_x); + return(i_implicit_ok(sc, s_slot, car_x, len)); + } + return_false(sc, car_x); +} + +static bool int_optimize(s7_scheme *sc, s7_pointer expr) {return((int_optimize_1(sc, expr)) && (sc->pc < OPTS_SIZE));} + +/* cell_optimize... */ +static bool p_2x_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x, int32_t pstart, s7_pointer expr) +{ + s7_pointer sig = c_function_signature(s_func); + if (is_symbol(cadr(car_x))) + { + if ((is_pair(sig)) && (is_pair(cdr(sig))) && (is_pair(cddr(sig))) && (caddr(sig) == sc->is_integer_symbol)) + { + if (p_pi_ok(sc, opc, s_func, sig, car_x)) + return(true); + + if ((car(sig) == sc->is_float_symbol) || + (car(sig) == sc->is_real_symbol)) + { + s7_d_7pi_t f = s7_d_7pi_function(s_func); + if (f) + { + sc->pc = pstart - 1; + if (float_optimize(sc, expr)) + { + opc->v[O_WRAP].fd = opc->v[0].fd; + opc->v[0].fp = d_to_p; + return(true); + }}} + sc->pc = pstart; + }} + { + s7_i_ii_t ifunc = s7_i_ii_function(s_func); + sc->pc = pstart - 1; + if ((ifunc) && + (int_optimize(sc, expr))) + { + opc->v[O_WRAP].fi = opc->v[0].fi; + opc->v[0].fp = i_to_p; + if (opc->v[O_WRAP].fi == opt_i_ii_ss_add) + opc->v[0].fp = opt_p_ii_ss_add; + return(true); + }} + sc->pc = pstart; + return((p_ii_ok(sc, opc, s_func, car_x, pstart)) || + (p_dd_ok(sc, opc, s_func, car_x, pstart)) || + (p_pp_ok(sc, opc, s_func, car_x, pstart)) || + (p_call_pp_ok(sc, opc, s_func, car_x, pstart))); +} + +static bool p_3x_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x, int32_t pstart, s7_pointer expr) +{ + s7_pointer sig = c_function_signature(s_func); + if (is_symbol(cadr(car_x))) + { + if ((is_pair(sig)) && (is_pair(cdr(sig))) && (is_pair(cddr(sig))) && + (caddr(sig) == sc->is_integer_symbol)) + { + if (p_pii_ok(sc, opc, s_func, car_x)) + return(true); + if (p_pip_ok(sc, opc, s_func, car_x)) + return(true); + + if (((car(sig) == sc->is_float_symbol) || + (car(sig) == sc->is_real_symbol)) && + (s7_d_7pid_function(s_func)) && + (d_7pid_ok(sc, opc, s_func, car_x))) + { + /* if d_7pid is ok, we need d_to_p for cell_optimize */ + opc->v[O_WRAP].fd = opc->v[0].fd; + opc->v[0].fp = d_to_p; + return(true); + } + + sc->pc = pstart - 1; + if ((car(sig) == sc->is_integer_symbol) && + (s7_i_7pii_function(s_func)) && + (i_7pii_ok(sc, alloc_opt_info(sc), s_func, car_x))) + { + opc->v[O_WRAP].fi = opc->v[0].fi; + opc->v[0].fp = i_to_p; + return(true); + } + sc->pc = pstart; + }} + return((p_ppi_ok(sc, opc, s_func, car_x)) || + (p_ppp_ok(sc, opc, s_func, car_x)) || + (p_call_ppp_ok(sc, opc, s_func, car_x))); +} + +static bool p_4x_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x, int32_t pstart, s7_pointer expr) +{ + s7_pointer head = car(car_x); + s7_int len = s7_list_length(sc, car_x); + + if ((is_target_or_its_alias(head, s_func, sc->float_vector_set_symbol)) && + (d_7piid_ok(sc, opc, s_func, car_x))) + { + opc->v[O_WRAP].fd = opc->v[0].fd; + opc->v[0].fp = d_to_p; /* as above, if d_7piid is ok, we need d_to_p for cell_optimize */ + return(true); + } + if ((is_target_or_its_alias(head, s_func, sc->float_vector_ref_symbol)) && + (d_7piii_ok(sc, opc, s_func, car_x))) + { + opc->v[O_WRAP].fd = opc->v[0].fd; + opc->v[0].fp = d_to_p; + return(true); + } + if (i_7piii_ok(sc, opc, s_func, car_x)) + { + opc->v[O_WRAP].fi = opc->v[0].fi; + opc->v[0].fp = i_to_p; + return(true); + } + if (is_target_or_its_alias(head, s_func, sc->int_vector_set_symbol)) + return_false(sc, car_x); + if (p_piip_ok(sc, opc, s_func, car_x)) + return(true); + sc->pc = pstart; + if (s_func == global_value(sc->vector_ref_symbol)) + { + s7_pointer obj; + if (!is_symbol(cadr(car_x))) return_false(sc, car_x); + obj = lookup_unexamined(sc, cadr(car_x)); /* was lookup_from (to avoid the unbound variable check) */ + if ((!obj) || (!is_any_vector(obj)) || (vector_rank(obj) != 3)) + return_false(sc, car_x); + } + return(p_call_any_ok(sc, opc, s_func, car_x, len)); +} + +static bool p_5x_ok(s7_scheme *sc, opt_info *opc, s7_pointer s_func, s7_pointer car_x, int32_t pstart, s7_pointer expr) +{ + s7_pointer head = car(car_x); + if ((is_target_or_its_alias(head, s_func, sc->float_vector_set_symbol)) && + (d_7piiid_ok(sc, opc, s_func, car_x))) + { + opc->v[O_WRAP].fd = opc->v[0].fd; + opc->v[0].fp = d_to_p; + return(true); + } + return(false); +} + +static bool cell_optimize_1(s7_scheme *sc, s7_pointer expr) +{ + s7_pointer car_x = car(expr), head, s_func, s_slot = NULL; + s7_int len; + if (WITH_GMP) return(false); + if (!is_pair(car_x)) /* wrap constants/symbols */ + return(opt_cell_not_pair(sc, car_x)); + + head = car(car_x); + len = s7_list_length(sc, car_x); + if (is_symbol(head)) + { + if ((is_syntactic_symbol(head)) || + (is_syntactic_pair(car_x))) /* this can be wrong! */ + return(p_syntax(sc, car_x, len)); + + s_slot = s7_slot(sc, head); + if (!is_slot(s_slot)) return_false(sc, car_x); + s_func = slot_value(s_slot); + } + else + if (is_c_function(head)) + s_func = head; + else return_false(sc, car_x); + + if (is_c_function(s_func)) + { + opt_info *opc = alloc_opt_info(sc); + switch (len) + { + case 1: return(p_ok(sc, opc, s_func, car_x)); + + case 2: return((p_i_ok(sc, opc, s_func, car_x, sc->pc)) || + (p_d_ok(sc, opc, s_func, car_x, sc->pc)) || + (p_p_ok(sc, opc, s_func, car_x))); + + case 3: return(p_2x_ok(sc, opc, s_func, car_x, sc->pc, expr)); + case 4: return(p_3x_ok(sc, opc, s_func, car_x, sc->pc, expr)); + case 5: return(p_4x_ok(sc, opc, s_func, car_x, sc->pc, expr)); + + case 6: if (p_5x_ok(sc, opc, s_func, car_x, sc->pc, expr)) return(true); + /* fall through */ + + default: return(p_call_any_ok(sc, opc, s_func, car_x, len)); /* >3D vector-set etc */ + }} + else + { + if (is_closure(s_func)) + { + opt_info *opc = alloc_opt_info(sc); + if (p_fx_any_ok(sc, opc, expr)) + return(true); + } + if (is_macro(s_func)) + return_false(sc, car_x); /* macroexpand+cell_optimize here restarts the optimize process */ + if (!s_slot) return_false(sc, car_x); +#if OPT_PRINT + { + bool res = p_implicit_ok(sc, s_slot, car_x, len); + if (!res) fprintf(stderr, " %sno p_implicit for %s%s\n", bold_text red_text, display(car_x), unbold_text uncolor_text); + return(res); + } +#else + return(p_implicit_ok(sc, s_slot, car_x, len)); +#endif + } + return_false(sc, car_x); +} + +static bool cell_optimize(s7_scheme *sc, s7_pointer expr) {return((cell_optimize_1(sc, expr)) && (sc->pc < OPTS_SIZE));} + +static bool bool_optimize_nw_1(s7_scheme *sc, s7_pointer expr) +{ + s7_pointer car_x = car(expr), head, s_func = NULL; + s7_int len; + if (!is_pair(car_x)) /* wrap constants/symbols */ + return(opt_bool_not_pair(sc, car_x)); + + head = car(car_x); + len = s7_list_length(sc, car_x); + if (is_symbol(head)) + { + if ((is_syntactic_symbol(head)) || + (is_syntactic_pair(car_x))) + { + if (head == sc->and_symbol) + return(opt_b_and(sc, car_x, len)); + if (head == sc->or_symbol) + return(opt_b_or(sc, car_x, len)); + return_false(sc, car_x); + } + s_func = lookup_unexamined(sc, head); + } + else + if (is_c_function(head)) + s_func = head; + else return_false(sc, car_x); + + if (!s_func) return_false(sc, car_x); + if (is_c_function(s_func)) + { + if ((is_symbol(head)) && (symbol_id(head) != 0)) /* (float-vector? (block)) -- both safe c_funcs, but this is a method invocation */ + return_false(sc, car_x); + switch (len) + { + case 2: + return(b_idp_ok(sc, s_func, car_x, opt_arg_type(sc, cdr(car_x)))); + + case 3: + { + s7_pointer arg1 = cadr(car_x), arg2 = caddr(car_x); + s7_pointer sig1 = opt_arg_type(sc, cdr(car_x)); + s7_pointer sig2 = opt_arg_type(sc, cddr(car_x)); + opt_info *opc = alloc_opt_info(sc); + int32_t cur_index = sc->pc; + s7_b_7pp_t bpf7 = NULL; + s7_b_pp_t bpf; + + if (sig2 == sc->is_integer_symbol) + { + if ((sig1 == sc->is_integer_symbol) && + (b_ii_ok(sc, opc, s_func, car_x, arg1, arg2))) + return(true); + sc->pc = cur_index; + if (b_pi_ok(sc, opc, s_func, car_x, arg2)) + return(true); + sc->pc = cur_index; + } + + if ((sig1 == sc->is_float_symbol) && + (sig2 == sc->is_float_symbol) && + (b_dd_ok(sc, opc, s_func, car_x, arg1, arg2))) + return(true); + sc->pc = cur_index; + + bpf = s7_b_pp_function(s_func); + if (!bpf) bpf7 = s7_b_7pp_function(s_func); + if ((bpf) || (bpf7)) + { + if (bpf) + opc->v[3].b_pp_f = bpf; + else opc->v[3].b_7pp_f = bpf7; + return(b_pp_ok(sc, opc, s_func, car_x, arg1, arg2, bpf)); + }} + break; + + default: break; + }} + return_false(sc, car_x); +} + +static bool bool_optimize_nw(s7_scheme *sc, s7_pointer expr) {return((bool_optimize_nw_1(sc, expr)) && (sc->pc < OPTS_SIZE));} + +static bool bool_optimize(s7_scheme *sc, s7_pointer expr) +{ + int32_t start = sc->pc; + opt_info *wrapper; + if (WITH_GMP) return(false); + if (bool_optimize_nw(sc, expr)) + return(true); + sc->pc = start; + wrapper = sc->opts[start]; + if (!cell_optimize(sc, expr)) + return_false(sc, NULL); + if (wrapper->v[O_WRAP].fp) /* (when (+ i 1) ...) */ + return_false(sc, NULL); + wrapper->v[O_WRAP].fp = wrapper->v[0].fp; + wrapper->v[0].fb = p_to_b; + return(true); +} + +static s7_pfunc s7_bool_optimize(s7_scheme *sc, s7_pointer expr) +{ + sc->pc = 0; + if ((bool_optimize(sc, expr)) && (sc->pc < OPTS_SIZE)) + return_success(sc, opt_bool_any, expr); + return_null(sc, expr); +} + +/* snd-sig.c experiment */ +static s7_double opt_float_any(s7_scheme *sc) {return(sc->opts[0]->v[0].fd(sc->opts[0]));} + +s7_float_function s7_float_optimize(s7_scheme *sc, s7_pointer expr) +{ + sc->pc = 0; + if ((float_optimize(sc, expr)) && (sc->pc < OPTS_SIZE)) + return(opt_float_any); + return(NULL); +} + +static s7_pfunc s7_optimize_1(s7_scheme *sc, s7_pointer expr, bool nv) +{ + if (WITH_GMP) return(NULL); + if ((!is_pair(expr)) || (no_cell_opt(expr)) || (sc->debug != 0)) + return(NULL); + sc->pc = 0; + if (!no_int_opt(expr)) + { + if (int_optimize(sc, expr)) + return((nv) ? opt_int_any_nv : opt_wrap_int); + sc->pc = 0; + set_no_int_opt(expr); + } + if (!no_float_opt(expr)) + { + if (float_optimize(sc, expr)) + return_success(sc, (nv) ? opt_float_any_nv : opt_wrap_float, expr); + sc->pc = 0; + set_no_float_opt(expr); + } + if (!no_bool_opt(expr)) + { + if (bool_optimize_nw(sc, expr)) + return_success(sc, (nv) ? opt_bool_any_nv : opt_wrap_bool, expr); + sc->pc = 0; + set_no_bool_opt(expr); + } + if (cell_optimize(sc, expr)) + return_success(sc, (nv) ? opt_cell_any_nv : opt_wrap_cell, expr); + set_no_cell_opt(expr); /* checked above */ + return_null(sc, expr); +} + +s7_pfunc s7_optimize(s7_scheme *sc, s7_pointer expr) {return(s7_optimize_1(sc, expr, false));} +static s7_pfunc s7_optimize_nv(s7_scheme *sc, s7_pointer expr) {return(s7_optimize_1(sc, expr, true));} + +static s7_pointer g_optimize(s7_scheme *sc, s7_pointer args) +{ + s7_pfunc f; + s7_pointer code = car(args), result = sc->undefined; + gc_protect_via_stack(sc, code); + f = s7_optimize(sc, code); + if (f) result = f(sc); + if (stack_top_op(sc) == OP_GC_PROTECT) unstack(sc); + return(result); +} + +static s7_pfunc s7_cell_optimize(s7_scheme *sc, s7_pointer expr, bool nv) +{ + sc->pc = 0; + if ((cell_optimize(sc, expr)) && (sc->pc < OPTS_SIZE)) + return((nv) ? opt_cell_any_nv : opt_wrap_cell); + return_null(sc, expr); +} + + +/* ---------------- bool funcs (an experiment) ---------------- */ +static void fx_curlet_tree(s7_scheme *sc, s7_pointer code) +{ + s7_pointer slot1 = let_slots(sc->curlet), slot3 = NULL, outer_e; + bool more_vars; + s7_pointer slot2 = next_slot(slot1); + if (tis_slot(slot2)) slot3 = next_slot(slot2); + + more_vars = (tis_slot(slot3)) && (tis_slot(next_slot(slot3))); + fx_tree(sc, code, + slot_symbol(slot1), + (tis_slot(slot2)) ? slot_symbol(slot2) : NULL, + (tis_slot(slot3)) ? slot_symbol(slot3) : NULL, + more_vars); + + outer_e = let_outlet(sc->curlet); + if ((!more_vars) && + (is_let(outer_e)) && + (!is_funclet(outer_e)) && + (tis_slot(let_slots(outer_e))) && + (slot_symbol(let_slots(outer_e)) != slot_symbol(slot1))) + { + slot1 = let_slots(outer_e); + slot2 = next_slot(slot1); + slot3 = (tis_slot(slot2)) ? next_slot(slot2) : NULL; + fx_tree_outer(sc, code, + slot_symbol(slot1), + (tis_slot(slot2)) ? slot_symbol(slot2) : NULL, + (tis_slot(slot3)) ? slot_symbol(slot3) : NULL, + (tis_slot(slot3)) && (tis_slot(next_slot(slot3)))); + } +} + +static void fx_curlet_tree_in(s7_scheme *sc, s7_pointer code) +{ + s7_pointer slot1 = let_slots(sc->curlet), slot3 = NULL; + s7_pointer slot2 = next_slot(slot1); + if (tis_slot(slot2)) slot3 = next_slot(slot2); + fx_tree_in(sc, code, + slot_symbol(slot1), + (tis_slot(slot2)) ? slot_symbol(slot2) : NULL, + (tis_slot(slot3)) ? slot_symbol(slot3) : NULL, + (tis_slot(slot3)) && (tis_slot(next_slot(slot3)))); +} + +typedef bool (*s7_bfunc)(s7_scheme *sc, s7_pointer expr); /* used in eval */ + +static bool fb_lt_ss(s7_scheme *sc, s7_pointer expr) +{ + s7_pointer x = lookup(sc, cadr(expr)); + s7_pointer y = lookup(sc, opt2_sym(cdr(expr))); + return(((is_t_integer(x)) && (is_t_integer(y))) ? (integer(x) < integer(y)) : lt_b_7pp(sc, x, y)); +} + +static bool fb_lt_ts(s7_scheme *sc, s7_pointer expr) +{ + s7_pointer x = t_lookup(sc, cadr(expr), expr); + s7_pointer y = lookup(sc, opt2_sym(cdr(expr))); + return(((is_t_integer(x)) && (is_t_integer(y))) ? (integer(x) < integer(y)) : lt_b_7pp(sc, x, y)); +} + +static bool fb_num_eq_ss(s7_scheme *sc, s7_pointer expr) +{ + s7_pointer x = lookup(sc, cadr(expr)); + s7_pointer y = lookup(sc, opt2_sym(cdr(expr))); + return(((is_t_integer(x)) && (is_t_integer(y))) ? (integer(x) == integer(y)) : num_eq_b_7pp(sc, x, y)); +} + +static bool fb_num_eq_s0(s7_scheme *sc, s7_pointer expr) +{ + s7_pointer x = lookup(sc, cadr(expr)); + return((is_t_integer(x)) ? (integer(x) == 0) : num_eq_b_7pp(sc, x, int_zero)); +} + +static bool fb_num_eq_s0f(s7_scheme *sc, s7_pointer expr) +{ + s7_pointer x = lookup(sc, cadr(expr)); + return((is_t_real(x)) ? (real(x) == 0.0) : num_eq_b_7pp(sc, x, real_zero)); +} + +static bool fb_gt_tu(s7_scheme *sc, s7_pointer expr) +{ + s7_pointer x = t_lookup(sc, cadr(expr), expr), y = u_lookup(sc, opt2_sym(cdr(expr)), expr); + return(((is_t_integer(x)) && (is_t_integer(y))) ? (integer(x) > integer(y)) : gt_b_7pp(sc, x, y)); +} + +static bool fb_gt_ss(s7_scheme *sc, s7_pointer expr) +{ + s7_pointer x = s_lookup(sc, cadr(expr), expr); + s7_pointer y = s_lookup(sc, opt2_sym(cdr(expr)), expr); + return(((is_t_integer(x)) && (is_t_integer(y))) ? (integer(x) > integer(y)) : gt_b_7pp(sc, x, y)); +} + +static bool fb_geq_ss(s7_scheme *sc, s7_pointer expr) +{ + s7_pointer x = s_lookup(sc, cadr(expr), expr); + s7_pointer y = s_lookup(sc, opt2_sym(cdr(expr)), expr); + return(((is_t_integer(x)) && (is_t_integer(y))) ? (integer(x) >= integer(y)) : geq_b_7pp(sc, x, y)); +} + +static bool fb_leq_ss(s7_scheme *sc, s7_pointer expr) +{ + s7_pointer x = s_lookup(sc, cadr(expr), expr); + s7_pointer y = s_lookup(sc, opt2_sym(cdr(expr)), expr); + return(((is_t_integer(x)) && (is_t_integer(y))) ? (integer(x) <= integer(y)) : leq_b_7pp(sc, x, y)); +} + +static bool fb_leq_ti(s7_scheme *sc, s7_pointer expr) +{ + s7_pointer x = t_lookup(sc, cadr(expr), expr); + if (is_t_integer(x)) return(integer(x) <= integer(opt2_con(cdr(expr)))); + return(g_leq_xi(sc, set_plist_2(sc, x, opt2_con(cdr(expr))))); +} + +static bool fb_leq_ui(s7_scheme *sc, s7_pointer expr) +{ + s7_pointer x = u_lookup(sc, cadr(expr), expr); + if (is_t_integer(x)) return(integer(x) <= integer(opt2_con(cdr(expr)))); + return(g_leq_xi(sc, set_plist_2(sc, x, opt2_con(cdr(expr))))); +} + +static s7_pointer fx_to_fb(s7_scheme *sc, s7_function fx) /* eventually parallel arrays? */ +{ + if (fx == fx_num_eq_ss) return((s7_pointer)fb_num_eq_ss); + if (fx == fx_lt_ss) return((s7_pointer)fb_lt_ss); + if (fx == fx_lt_ts) return((s7_pointer)fb_lt_ts); + if (fx == fx_gt_ss) return((s7_pointer)fb_gt_ss); + if (fx == fx_leq_ss) return((s7_pointer)fb_leq_ss); + if (fx == fx_leq_ti) return((s7_pointer)fb_leq_ti); + if (fx == fx_leq_ui) return((s7_pointer)fb_leq_ui); + if (fx == fx_geq_ss) return((s7_pointer)fb_geq_ss); + if (fx == fx_gt_tu) return((s7_pointer)fb_gt_tu); + if (fx == fx_num_eq_s0) return((s7_pointer)fb_num_eq_s0); + if (fx == fx_num_eq_s0f) return((s7_pointer)fb_num_eq_s0f); + return(NULL); +} + +static void fb_annotate(s7_scheme *sc, s7_pointer form, s7_pointer fx_expr, opcode_t op) +{ + s7_pointer bfunc; + if ((is_fx_treeable(cdr(form))) && (tis_slot(let_slots(sc->curlet)))) fx_curlet_tree(sc, cdr(form)); /* and not already treed? just the one expr? */ + bfunc = fx_to_fb(sc, fx_proc(fx_expr)); + if (bfunc) + { + set_opt3_any(cdr(form), bfunc); + pair_set_syntax_op(form, op); + } +#if 0 + /* fb_annotate additions? [these currently require new "B" ops] */ + else + { + fprintf(stderr, "fx: %s %s\n", ((is_pair(fx_expr)) && (is_pair(car(fx_expr)))) ? op_names[optimize_op(car(fx_expr))] : "", display_80(fx_expr)); + if (caar(fx_expr) == sc->num_eq_symbol) abort(); + /* [fx_leq_ti] fx_lt_t0 fx_gt_ti fx_num_eq_u0 */ + } +#endif +} + +/* when_b cond? do end-test? num_eq_vs|us */ + + +/* ---------------------------------------- for-each ---------------------------------------- */ +static Inline s7_pointer inline_make_counter(s7_scheme *sc, s7_pointer iter) /* all calls are hit about the same: lg/sg */ +{ + s7_pointer x; + new_cell(sc, x, T_COUNTER); + counter_set_result(x, sc->nil); + counter_set_list(x, iter); /* iterator -- here it's always either an iterator or a pair */ + counter_set_capture(x, 0); /* will be capture_let_counter */ + counter_set_let(x, sc->nil); /* will be the saved let */ + counter_set_slots(x, sc->nil); /* local let slots before body is evalled */ + stack_set_has_counters(sc->stack); + return(x); +} + +static s7_pointer make_iterators(s7_scheme *sc, s7_pointer caller, s7_pointer args) +{ + s7_pointer p = cdr(args); + sc->temp3 = args; + sc->z = sc->nil; /* don't use sc->args here -- it needs GC protection until we get the iterators */ + for (s7_int i = 2; is_pair(p); p = cdr(p), i++) + { + s7_pointer iter = car(p); + if (!is_iterator(iter)) + { + if (!is_mappable(iter)) + wrong_type_error_nr(sc, caller, i, iter, a_sequence_string); + iter = s7_make_iterator(sc, iter); + } + sc->z = cons(sc, iter, sc->z); + } + sc->temp3 = sc->unused; + return(proper_list_reverse_in_place(sc, sc->z)); +} + +static s7_pointer seq_init(s7_scheme *sc, s7_pointer seq) +{ + if (is_float_vector(seq)) return(real_zero); + if (is_string(seq)) return(chars[65]); + if ((is_int_vector(seq)) || (is_byte_vector(seq))) return(int_zero); + return(sc->F); +} + +#define MUTLIM 32 /* was 1000, sets when (in vector-length) to start using a mutated real, rather than make_real during the loop through the vector */ + +static s7_pointer clear_for_each(s7_scheme *sc) +{ + sc->map_call_ctr--; + unstack_with(sc, OP_MAP_UNWIND); + return(sc->unspecified); +} + +static s7_pointer g_for_each_closure(s7_scheme *sc, s7_pointer f, s7_pointer seq) /* one sequence arg */ +{ + s7_pointer body = closure_body(f); + if (!no_cell_opt(body)) /* if at top level we often get an unoptimized (not safe) function here that can be cell_optimized below */ + { + s7_pfunc func = NULL; + s7_pointer old_e = sc->curlet, pars = closure_args(f), val, slot, res = NULL; + + val = seq_init(sc, seq); + sc->curlet = inline_make_let_with_slot(sc, closure_let(f), (is_pair(car(pars))) ? caar(pars) : car(pars), val); + slot = let_slots(sc->curlet); + + if (sc->map_call_ctr == 0) + { + if (is_null(cdr(body))) + func = s7_optimize_nv(sc, body); + else + if (is_null(cddr(body))) /* 3 sometimes works */ + { + set_ulist_1(sc, sc->begin_symbol, body); + func = s7_cell_optimize(sc, set_clist_1(sc, sc->u1_1), true); /* was list_1 via cons 8-Apr-21, true=nr */ + }} + + if (func) + { + push_stack_no_let(sc, OP_MAP_UNWIND, f, seq); + sc->map_call_ctr++; + if (is_pair(seq)) + { + for (s7_pointer x = seq, y = x; is_pair(x); ) + { + slot_set_value(slot, car(x)); + func(sc); + x = cdr(x); + if (is_pair(x)) + { + slot_set_value(slot, car(x)); + func(sc); + x = cdr(x); + y = cdr(y); + if (x == y) break; + }} + res = sc->unspecified; + } + else + if (is_float_vector(seq)) + { + s7_double *vals = float_vector_floats(seq); + s7_int i, len = vector_length(seq); + if ((len > MUTLIM) && + (!tree_has_setters(sc, body))) + { + s7_pointer sv = s7_make_mutable_real(sc, 0.0); + slot_set_value(slot, sv); + if (func == opt_float_any_nv) + { + opt_info *o = sc->opts[0]; + s7_double (*fd)(opt_info *o) = o->v[0].fd; + for (i = 0; i < len; i++) {real(sv) = vals[i]; fd(o);}} + else + if (func == opt_cell_any_nv) + { + opt_info *o = sc->opts[0]; + s7_pointer (*fp)(opt_info *o) = o->v[0].fp; + if (fp == opt_unless_p_1) + for (i = 0; i < len; i++) {real(sv) = vals[i]; if (!(o->v[4].fb(o->v[3].o1))) o->v[5].o1->v[0].fp(o->v[5].o1);} + else for (i = 0; i < len; i++) {real(sv) = vals[i]; fp(o);} + } + else for (i = 0; i < len; i++) {real(sv) = vals[i]; func(sc);}} + else for (i = 0; i < len; i++) {slot_set_value(slot, make_real(sc, vals[i])); func(sc);} + res = sc->unspecified; + } + else + if (is_int_vector(seq)) + { + s7_int *vals = int_vector_ints(seq); + s7_int i, len = vector_length(seq); + if ((len > MUTLIM) && + (!tree_has_setters(sc, body))) + { + s7_pointer sv = make_mutable_integer(sc, 0); + slot_set_value(slot, sv); + /* since there are no setters, the inner step is also mutable if there is one. + * func=opt_cell_any_nv, sc->opts[0]->v[0].fp(sc->opts[0]) fp=opt_do_1 -> mutable version + */ + if (func == opt_int_any_nv) + { + opt_info *o = sc->opts[0]; + s7_int (*fi)(opt_info *o) = o->v[0].fi; + for (i = 0; i < len; i++) {integer(sv) = vals[i]; fi(o);}} + else for (i = 0; i < len; i++) {integer(sv) = vals[i]; func(sc);}} + else for (i = 0; i < len; i++) {slot_set_value(slot, make_integer(sc, vals[i])); func(sc);} + res = sc->unspecified; + } + else + if (is_normal_vector(seq)) + { + s7_pointer *vals = vector_elements(seq); + s7_int i, len = vector_length(seq); + if (func == opt_cell_any_nv) + { + opt_info *o = sc->opts[0]; + s7_pointer (*fp)(opt_info *o) = o->v[0].fp; + for (i = 0; i < len; i++) {slot_set_value(slot, vals[i]); fp(o);}} + else for (i = 0; i < len; i++) {slot_set_value(slot, vals[i]); func(sc);} + res = sc->unspecified; + } + else + if (is_string(seq)) + { + const char *str = string_value(seq); + s7_int len = string_length(seq); + for (s7_int i = 0; i < len; i++) {slot_set_value(slot, chars[(uint8_t)(str[i])]); func(sc);} + res = sc->unspecified; + } + else + if (is_byte_vector(seq)) + { + const uint8_t *vals = (const uint8_t *)byte_vector_bytes(seq); + s7_int i, len = vector_length(seq); + if (func == opt_int_any_nv) + { + opt_info *o = sc->opts[0]; + s7_int (*fi)(opt_info *o) = o->v[0].fi; + for (i = 0; i < len; i++) {slot_set_value(slot, small_int(vals[i])); fi(o);}} + else for (i = 0; i < len; i++) {slot_set_value(slot, small_int(vals[i])); func(sc);} + res = sc->unspecified; + } + if (res) + return(clear_for_each(sc)); + if (!is_iterator(seq)) + { + if (!is_mappable(seq)) + wrong_type_error_nr(sc, sc->for_each_symbol, 2, seq, a_sequence_string); + seq = s7_make_iterator(sc, seq); + set_stack_protected2_with(sc, seq, OP_MAP_UNWIND); /* GC protect new iterator */ + } + /* push_stack_no_let(sc, OP_GC_PROTECT, seq, f); */ + if (func == opt_cell_any_nv) + { + opt_info *o = sc->opts[0]; + s7_pointer (*fp)(opt_info *o) = o->v[0].fp; + while (true) + { + slot_set_value(slot, s7_iterate(sc, seq)); + if (iterator_is_at_end(seq)) return(clear_for_each(sc)); + fp(o); + }} + if (func == opt_int_any_nv) + { + opt_info *o = sc->opts[0]; + s7_int (*fi)(opt_info *o) = o->v[0].fi; + while (true) + { + slot_set_value(slot, s7_iterate(sc, seq)); + if (iterator_is_at_end(seq)) return(clear_for_each(sc)); + fi(o); + }} + while (true) + { + slot_set_value(slot, s7_iterate(sc, seq)); + if (iterator_is_at_end(seq)) return(clear_for_each(sc)); + func(sc); + }} /* we never get here -- the while loops above exit via return # */ + else /* not func -- unneeded "else" but otherwise confusing code */ + { + set_no_cell_opt(body); + set_curlet(sc, old_e); + }} + if ((!is_closure_star(f)) && /* for simplicity in op_for_each_2 (otherwise we need to check for default arg) */ + (is_null(cdr(body))) && + (is_pair(seq))) + { + s7_pointer c = inline_make_counter(sc, seq); + counter_set_result(c, seq); + push_stack(sc, OP_FOR_EACH_2, c, f); + return(sc->unspecified); + } + + if (!is_iterator(seq)) + { + if (!is_mappable(seq)) + wrong_type_error_nr(sc, sc->for_each_symbol, 2, seq, a_sequence_string); + sc->z = s7_make_iterator(sc, seq); + } + else sc->z = seq; + push_stack(sc, OP_FOR_EACH_1, inline_make_counter(sc, sc->z), f); + sc->z = sc->unused; + return(sc->unspecified); +} + +static void map_or_for_each_closure_pair_2(s7_scheme *sc, s7_pfunc func, s7_pointer seq1, s7_pointer seq2, s7_pointer slot1, s7_pointer slot2, bool for_each_case) +{ + for (s7_pointer fast1 = seq1, slow1 = seq1, fast2 = seq2, slow2 = seq2; (is_pair(fast1)) && (is_pair(fast2)); + fast1 = cdr(fast1), slow1 = cdr(slow1), fast2 = cdr(fast2), slow2 = cdr(slow2)) + { + slot_set_value(slot1, car(fast1)); + slot_set_value(slot2, car(fast2)); + if (for_each_case) + func(sc); + else + { + s7_pointer val = func(sc); + if (val != sc->no_value) + set_stack_protected3_with(sc, cons(sc, val, stack_protected3(sc)), OP_MAP_UNWIND); /* see map_closure_2 below -- stack_protected3 is our temp */ + } + if ((is_pair(cdr(fast1))) && (is_pair(cdr(fast2)))) + { + fast1 = cdr(fast1); + if (fast1 == slow1) break; + fast2 = cdr(fast2); + if (fast2 == slow2) break; + slot_set_value(slot1, car(fast1)); + slot_set_value(slot2, car(fast2)); + if (for_each_case) + func(sc); + else + { + s7_pointer val = func(sc); + if (val != sc->no_value) + set_stack_protected3_with(sc, cons(sc, val, stack_protected3(sc)), OP_MAP_UNWIND); + }}} +} + +static void map_or_for_each_closure_vector_2(s7_scheme *sc, s7_pfunc func, s7_pointer seq1, s7_pointer seq2, s7_pointer slot1, s7_pointer slot2, bool for_each_case) +{ + s7_int len = vector_length(seq1); + if (len > vector_length(seq2)) len = vector_length(seq2); + for (s7_int i = 0; i < len; i++) + { + slot_set_value(slot1, vector_getter(seq1)(sc, seq1, i)); + slot_set_value(slot2, vector_getter(seq2)(sc, seq2, i)); + if (for_each_case) + func(sc); + else + { + s7_pointer val = func(sc); + if (val != sc->no_value) + set_stack_protected3_with(sc, cons(sc, val, stack_protected3(sc)), OP_MAP_UNWIND); + }} +} + +static void map_or_for_each_closure_string_2(s7_scheme *sc, s7_pfunc func, s7_pointer seq1, s7_pointer seq2, s7_pointer slot1, s7_pointer slot2, bool for_each_case) +{ + s7_int len = string_length(seq1); + const char *s1 = string_value(seq1), *s2 = string_value(seq2); + if (len > string_length(seq2)) len = string_length(seq2); + for (s7_int i = 0; i < len; i++) + { + slot_set_value(slot1, chars[(uint8_t)(s1[i])]); + slot_set_value(slot2, chars[(uint8_t)(s2[i])]); + if (for_each_case) + func(sc); + else + { + s7_pointer val = func(sc); + if (val != sc->no_value) + set_stack_protected3_with(sc, cons(sc, val, stack_protected3(sc)), OP_MAP_UNWIND); + }} +} + +static s7_pointer g_for_each_closure_2(s7_scheme *sc, s7_pointer f, s7_pointer seq1, s7_pointer seq2) +{ + s7_pointer body = closure_body(f); + if (!no_cell_opt(body)) + { + s7_pfunc func = NULL; + s7_pointer olde = sc->curlet, pars = closure_args(f), slot1, slot2; + s7_pointer val1 = seq_init(sc, seq1); + s7_pointer val2 = seq_init(sc, seq2); + sc->curlet = make_let_with_two_slots(sc, closure_let(f), + (is_pair(car(pars))) ? caar(pars) : car(pars), val1, + (is_pair(cadr(pars))) ? cadar(pars) : cadr(pars), val2); + slot1 = let_slots(sc->curlet); + slot2 = next_slot(slot1); + + if (sc->map_call_ctr == 0) + { + if (is_null(cdr(body))) + func = s7_optimize_nv(sc, body); + else + if (is_null(cddr(body))) + { + set_ulist_1(sc, sc->begin_symbol, body); + func = s7_cell_optimize(sc, set_clist_1(sc, sc->u1_1), true); + }} + + if (func) + { + s7_pointer res = NULL; + push_stack_no_let(sc, OP_MAP_UNWIND, f, seq1); + sc->map_call_ctr++; + if ((is_pair(seq1)) && (is_pair(seq2))) + { + map_or_for_each_closure_pair_2(sc, func, seq1, seq2, slot1, slot2, true); + res = sc->unspecified; + } + else + if ((is_any_vector(seq1)) && (is_any_vector(seq2))) + { + map_or_for_each_closure_vector_2(sc, func, seq1, seq2, slot1, slot2, true); + res = sc->unspecified; + } + else + if ((is_string(seq1)) && (is_string(seq2))) + { + map_or_for_each_closure_string_2(sc, func, seq1, seq2, slot1, slot2, true); + res = sc->unspecified; + } + sc->map_call_ctr--; + unstack_with(sc, OP_MAP_UNWIND); + set_curlet(sc, olde); + if (res) return(res); + set_no_cell_opt(body); + } + else /* not func */ + { + set_no_cell_opt(body); + set_curlet(sc, olde); + }} + + if (!is_iterator(seq1)) + { + if (!is_mappable(seq1)) + wrong_type_error_nr(sc, sc->for_each_symbol, 2, seq1, a_sequence_string); + sc->z = s7_make_iterator(sc, seq1); + } + else sc->z = seq1; + if (!is_iterator(seq2)) + { + if (!is_mappable(seq2)) + wrong_type_error_nr(sc, sc->for_each_symbol, 3, seq2, a_sequence_string); + sc->z = list_2(sc, sc->z, s7_make_iterator(sc, seq2)); + } + else sc->z = list_2(sc, sc->z, seq2); + push_stack(sc, OP_FOR_EACH, cons_unchecked(sc, sc->z, make_list(sc, 2, sc->nil)), f); + sc->z = sc->unused; + return(sc->unspecified); +} + +static inline bool for_each_arg_is_null(s7_scheme *sc, s7_pointer args) +{ + s7_pointer p = args; + bool got_nil = false; + for (s7_int i = 2; is_pair(p); p = cdr(p), i++) + { + s7_pointer obj = car(p); + if (!is_mappable(obj)) + { + if (is_null(obj)) + got_nil = true; + else wrong_type_error_nr(sc, sc->for_each_symbol, i, obj, a_sequence_string); + }} + return(got_nil); +} + +static s7_pointer g_for_each(s7_scheme *sc, s7_pointer args) +{ + #define H_for_each "(for-each proc object . objects) applies proc to each element of the objects traversed in parallel. \ +Each object can be a list, string, vector, hash-table, or any other sequence." + #define Q_for_each s7_make_circular_signature(sc, 2, 3, sc->is_unspecified_symbol, sc->is_procedure_symbol, sc->is_sequence_symbol) + + s7_pointer f = car(args); + s7_int len = proper_list_length(cdr(args)); + bool arity_ok = false; + + /* try the normal case first */ + sc->value = f; + if (is_closure(f)) /* not lambda* that might get confused about arg names */ + { + if ((len == 1) && + (is_pair(closure_args(f))) && + (is_null(cdr(closure_args(f))))) + arity_ok = true; + } + else + if (is_c_object(f)) /* see note in g_map; s7_is_aritable can clobber sc->args=plist=args */ + args = copy_proper_list(sc, args); + else + if (!is_applicable(f)) + return(method_or_bust(sc, f, sc->for_each_symbol, args, something_applicable_string, 1)); + + if ((!arity_ok) && + (!s7_is_aritable(sc, f, len))) + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_4(sc, wrap_string(sc, "for-each first argument ~A called with ~A argument~P?", 53), f, wrap_integer(sc, len), wrap_integer(sc, len))); + + if (for_each_arg_is_null(sc, cdr(args))) return(sc->unspecified); + + /* if function is safe c func, do the for-each locally */ + if (is_safe_c_function(f)) + { + s7_function func; + s7_pointer iters; + + s7_p_p_t fp = s7_p_p_function(f); + if ((fp) && (len == 1)) + { + if (is_pair(cadr(args))) + { + for (s7_pointer fast = cadr(args), slow = cadr(args); is_pair(fast); fast = cdr(fast), slow = cdr(slow)) + { + fp(sc, car(fast)); + if (is_pair(cdr(fast))) + { + fast = cdr(fast); + if (fast == slow) break; + fp(sc, car(fast)); + }} + return(sc->unspecified); + } + if (is_any_vector(cadr(args))) + { + s7_pointer v = cadr(args); + s7_int vlen = vector_length(v); + if (is_float_vector(v)) + { + s7_pointer rl = s7_make_mutable_real(sc, 0.0); + sc->temp7 = rl; + for (s7_int i = 0; i < vlen; i++) + { + real(rl) = float_vector(v, i); + fp(sc, rl); + }} + else + if (is_int_vector(v)) + { + s7_pointer iv = make_mutable_integer(sc, 0); + sc->temp7 = iv; + for (s7_int i = 0; i < vlen; i++) + { + integer(iv) = int_vector(v, i); + fp(sc, iv); + }} + else + for (s7_int i = 0; i < vlen; i++) + fp(sc, vector_getter(v)(sc, v, i)); /* LOOP_4 here gains almost nothing */ + return(sc->unspecified); + } + if (is_string(cadr(args))) + { + s7_pointer str = cadr(args); + const char *s = string_value(str); + s7_int slen = string_length(str); + for (s7_int i = 0; i < slen; i++) fp(sc, chars[(uint8_t)(s[i])]); + return(sc->unspecified); + }} + func = c_function_call(f); /* presumably this is either display/write, or method call? */ + sc->z = make_iterators(sc, sc->for_each_symbol, args); + sc->z = cons_unchecked(sc, sc->z, make_list(sc, len, sc->nil)); + push_stack_no_let(sc, OP_GC_PROTECT, sc->args, sc->z); /* temporary GC protection */ + if (len == 1) + { + s7_pointer x = caar(sc->z), y = cdr(sc->z); + sc->z = sc->unused; + while (true) + { + set_car(y, s7_iterate(sc, x)); + if (iterator_is_at_end(x)) + { + /* not pop_stack here since that can clobber sc->code et al, and if this for-each call is + * being treated as safe, fn_proc(for-each) assumes everywhere that sc->code is left alone. + */ + unstack(sc); /* free_cell(sc, x); */ /* 16-Jan-19 */ + return(sc->unspecified); + } + func(sc, y); + }} + iters = sc->z; + sc->z = sc->unused; + while (true) + { + for (s7_pointer x = car(iters), y = cdr(iters); is_pair(x); x = cdr(x), y = cdr(y)) + { + set_car(y, s7_iterate(sc, car(x))); + if (iterator_is_at_end(car(x))) + { + unstack(sc); + return(sc->unspecified); + }} + func(sc, cdr(iters)); + }} + + /* if closure call is straightforward, use OP_FOR_EACH_1 */ + if ((len == 1) && + (((is_closure(f)) && + (closure_arity_to_int(sc, f) == 1) && + (!is_constant_symbol(sc, car(closure_args(f))))) || + ((is_closure_star(f)) && + (closure_star_arity_to_int(sc, f) == 1) && + (!is_constant_symbol(sc, (is_pair(car(closure_args(f)))) ? caar(closure_args(f)) : car(closure_args(f))))))) + return(g_for_each_closure(sc, f, cadr(args))); + + push_stack(sc, OP_FOR_EACH, cons_unchecked(sc, make_iterators(sc, sc->for_each_symbol, args), make_list(sc, len, sc->nil)), f); + sc->z = sc->unused; + return(sc->unspecified); +} + +static bool op_for_each(s7_scheme *sc) +{ + s7_pointer iterators = car(sc->args); + s7_pointer saved_args = cdr(sc->args); + for (s7_pointer x = saved_args, y = iterators; is_pair(x); x = cdr(x), y = cdr(y)) + { + set_car(x, s7_iterate(sc, car(y))); + if (iterator_is_at_end(car(y))) + { + sc->value = sc->unspecified; + free_cell(sc, sc->args); + return(true); + }} + push_stack_direct(sc, OP_FOR_EACH); + sc->args = (needs_copied_args(sc->code)) ? copy_proper_list(sc, saved_args) : saved_args; + return(false); +} + +/* for-each et al remake the local let, but that's only needed if the local let is exported, + * and that can only happen through make-closure in various guises and curlet. + * owlet captures, but it would require a deliberate error to use it in this context. + * c_objects call object_set_let but that requires a prior curlet or sublet. So we have + * sc->capture_let_counter that is incremented every time an environment is captured, then + * here we save that ctr, call body, on rerun check ctr, if it has not changed we are safe and + * can reuse let. But that reuse assumes no new slots were added (by define etc), because + * update_let* only update the symbol_id's they expect, and that can happen even in op_for_each_2. + */ + +static Inline bool inline_op_for_each_1(s7_scheme *sc) /* called once in eval, case fb gc iter */ +{ + s7_pointer counter = sc->args, code; + s7_pointer p = counter_list(counter); + s7_pointer arg = s7_iterate(sc, p); + if (iterator_is_at_end(p)) + { + sc->value = sc->unspecified; + free_cell(sc, counter); + return(true); + } + code = T_Clo(sc->code); + if (counter_capture(counter) != sc->capture_let_counter) + { + s7_pointer sym = car(closure_args(code)); + sc->curlet = inline_make_let_with_slot(sc, closure_let(code), (is_symbol(sym)) ? sym : car(sym), arg); + counter_set_let(counter, sc->curlet); + counter_set_slots(counter, let_slots(sc->curlet)); + counter_set_capture(counter, sc->capture_let_counter); + } + else + { + let_set_slots(counter_let(counter), counter_slots(counter)); /* this is needed (unless safe_closure but that costs more to check than this set) */ + sc->curlet = update_let_with_slot(sc, counter_let(counter), arg); + } + push_stack(sc, OP_FOR_EACH_1, counter, code); + sc->code = T_Pair(closure_body(code)); + return(false); +} + +static Inline bool inline_op_for_each_2(s7_scheme *sc) /* called once in eval, lg set */ +{ + s7_pointer c = sc->args; + s7_pointer lst = counter_list(c); + if (!is_pair(lst)) /* '(1 2 . 3) as arg? -- counter_list can be anything here */ + { + sc->value = sc->unspecified; + free_cell(sc, c); /* not sc->args = sc->nil; */ + return(true); + } + counter_set_list(c, cdr(lst)); + if (sc->cur_op == OP_FOR_EACH_3) + { + counter_set_result(c, cdr(counter_result(c))); + if (counter_result(c) == counter_list(c)) + { + sc->value = sc->unspecified; + free_cell(sc, c); /* not sc->args = sc->nil; */ + return(true); + } + push_stack_direct(sc, OP_FOR_EACH_2); + } + else push_stack_direct(sc, OP_FOR_EACH_3); + if (counter_capture(c) != sc->capture_let_counter) + { + sc->curlet = inline_make_let_with_slot(sc, closure_let(sc->code), car(closure_args(sc->code)), car(lst)); + counter_set_let(c, sc->curlet); + counter_set_slots(c, let_slots(sc->curlet)); + counter_set_capture(c, sc->capture_let_counter); + } + else + { + let_set_slots(counter_let(c), counter_slots(c)); + sc->curlet = update_let_with_slot(sc, counter_let(c), car(lst)); + } + sc->code = car(closure_body(sc->code)); + return(false); +} + + +/* ---------------------------------------- map ---------------------------------------- */ + +static s7_pointer g_map_closure(s7_scheme *sc, s7_pointer f, s7_pointer seq) /* one sequence argument */ +{ + s7_pointer body = closure_body(f); + sc->value = f; + + if (!no_cell_opt(body)) + { + s7_pfunc func = NULL; + s7_pointer old_e = sc->curlet, pars = closure_args(f), slot; + s7_pointer val = seq_init(sc, seq); + sc->curlet = inline_make_let_with_slot(sc, closure_let(f), (is_pair(car(pars))) ? caar(pars) : car(pars), val); + slot = let_slots(sc->curlet); + + if (sc->map_call_ctr == 0) + { + if (is_null(cdr(body))) + func = s7_cell_optimize(sc, body, false); + else + if (is_null(cddr(body))) + { + set_ulist_1(sc, sc->begin_symbol, body); + func = s7_cell_optimize(sc, set_clist_1(sc, sc->u1_1), false); /* list_1 8-Apr-21 */ + }} + if (func) + { + s7_pointer z, res = NULL; + push_stack_no_let(sc, OP_MAP_UNWIND, f, seq); + sc->map_call_ctr++; + if (is_pair(seq)) + { + set_stack_protected3_with(sc, sc->nil, OP_MAP_UNWIND); + for (s7_pointer fast = seq, slow = seq; is_pair(fast); fast = cdr(fast), slow = cdr(slow)) + { + slot_set_value(slot, car(fast)); + z = func(sc); + if (z != sc->no_value) set_stack_protected3_with(sc, cons(sc, z, stack_protected3(sc)), OP_MAP_UNWIND); + if (is_pair(cdr(fast))) + { + fast = cdr(fast); + if (fast == slow) break; + slot_set_value(slot, car(fast)); + z = func(sc); + if (z != sc->no_value) set_stack_protected3_with(sc, cons(sc, z, stack_protected3(sc)), OP_MAP_UNWIND); + }} + res = proper_list_reverse_in_place(sc, stack_protected3(sc)); + } + else + if (is_float_vector(seq)) + { + s7_double *vals = float_vector_floats(seq); + s7_int len = vector_length(seq); + set_stack_protected3_with(sc, sc->nil, OP_MAP_UNWIND); + for (s7_int i = 0; i < len; i++) + { + slot_set_value(slot, make_real(sc, vals[i])); + z = func(sc); + if (z != sc->no_value) set_stack_protected3_with(sc, cons(sc, z, stack_protected3(sc)), OP_MAP_UNWIND); + } + res = proper_list_reverse_in_place(sc, stack_protected3(sc)); + } + else + if (is_int_vector(seq)) + { + s7_int *vals = int_vector_ints(seq); + s7_int len = vector_length(seq); + set_stack_protected3_with(sc, sc->nil, OP_MAP_UNWIND); + for (s7_int i = 0; i < len; i++) + { + slot_set_value(slot, make_integer(sc, vals[i])); + z = func(sc); + if (z != sc->no_value) set_stack_protected3_with(sc, cons(sc, z, stack_protected3(sc)), OP_MAP_UNWIND); + } + res = proper_list_reverse_in_place(sc, stack_protected3(sc)); + } + else + if (is_normal_vector(seq)) + { + s7_pointer *vals = vector_elements(seq); + s7_int len = vector_length(seq); + set_stack_protected3_with(sc, sc->nil, OP_MAP_UNWIND); + for (s7_int i = 0; i < len; i++) + { + slot_set_value(slot, vals[i]); + z = func(sc); + if (z != sc->no_value) set_stack_protected3_with(sc, cons(sc, z, stack_protected3(sc)), OP_MAP_UNWIND); + } + res = proper_list_reverse_in_place(sc, stack_protected3(sc)); + } + else + if (is_string(seq)) + { + s7_int len = string_length(seq); + const char *str = string_value(seq); + set_stack_protected3_with(sc, sc->nil, OP_MAP_UNWIND); + for (s7_int i = 0; i < len; i++) + { + slot_set_value(slot, chars[(uint8_t)(str[i])]); + z = func(sc); + if (z != sc->no_value) set_stack_protected3_with(sc, cons(sc, z, stack_protected3(sc)), OP_MAP_UNWIND); + } + res = proper_list_reverse_in_place(sc, stack_protected3(sc)); + } + sc->map_call_ctr--; + unstack_with(sc, OP_MAP_UNWIND); + if ((S7_DEBUGGING) && (sc->map_call_ctr < 0)) {fprintf(stderr, "%s[%d]: map ctr: %" ld64 "\n", __func__, __LINE__, sc->map_call_ctr); sc->map_call_ctr = 0;} + if (res) return(res); + } + set_no_cell_opt(body); + set_curlet(sc, old_e); + } + if (is_closure_star(f)) + { + sc->z = make_iterators(sc, sc->map_symbol, set_plist_2(sc, sc->nil, seq)); + push_stack(sc, OP_MAP, inline_make_counter(sc, sc->z), f); + sc->z = sc->unused; + return(sc->nil); + } + if ((is_null(cdr(body))) && + (is_pair(seq))) + { + closure_set_map_list(f, seq); + push_stack(sc, OP_MAP_2, inline_make_counter(sc, seq), f); + return(sc->unspecified); + } + if (!is_iterator(seq)) + { + if (!is_mappable(seq)) + wrong_type_error_nr(sc, sc->map_symbol, 2, seq, a_sequence_string); + sc->z = s7_make_iterator(sc, seq); + } + else sc->z = seq; + push_stack(sc, OP_MAP_1, inline_make_counter(sc, sc->z), f); + sc->z = sc->unused; + return(sc->nil); +} + +static s7_pointer g_map_closure_2(s7_scheme *sc, s7_pointer f, s7_pointer seq1, s7_pointer seq2) /* two sequences */ +{ + s7_pointer body = closure_body(f); + if (!no_cell_opt(body)) + { + s7_pfunc func = NULL; + s7_pointer old_e = sc->curlet, pars = closure_args(f), slot1, slot2; + s7_pointer val1 = seq_init(sc, seq1); + s7_pointer val2 = seq_init(sc, seq2); + sc->curlet = make_let_with_two_slots(sc, closure_let(f), + (is_pair(car(pars))) ? caar(pars) : car(pars), val1, + (is_pair(cadr(pars))) ? cadar(pars) : cadr(pars), val2); + slot1 = let_slots(sc->curlet); + slot2 = next_slot(slot1); + + if (sc->map_call_ctr == 0) + { + if (is_null(cdr(body))) + func = s7_cell_optimize(sc, body, false); + else + if (is_null(cddr(body))) + { + set_ulist_1(sc, sc->begin_symbol, body); + func = s7_cell_optimize(sc, set_clist_1(sc, sc->u1_1), false); + }} + if (func) + { + s7_pointer res = NULL; + push_stack_no_let(sc, OP_MAP_UNWIND, f, seq1); + sc->map_call_ctr++; + if ((is_pair(seq1)) && (is_pair(seq2))) + { + set_stack_protected3_with(sc, sc->nil, OP_MAP_UNWIND); + map_or_for_each_closure_pair_2(sc, func, seq1, seq2, slot1, slot2, false); /* builds result on stack_protected3 */ + res = proper_list_reverse_in_place(sc, stack_protected3(sc)); + } + else + if ((is_any_vector(seq1)) && (is_any_vector(seq2))) + { + set_stack_protected3_with(sc, sc->nil, OP_MAP_UNWIND); + map_or_for_each_closure_vector_2(sc, func, seq1, seq2, slot1, slot2, false); + res = proper_list_reverse_in_place(sc, stack_protected3(sc)); + } + else + if ((is_string(seq1)) && (is_string(seq2))) + { + set_stack_protected3_with(sc, sc->nil, OP_MAP_UNWIND); + map_or_for_each_closure_string_2(sc, func, seq1, seq2, slot1, slot2, false); + res = proper_list_reverse_in_place(sc, stack_protected3(sc)); + } + sc->map_call_ctr--; + unstack_with(sc, OP_MAP_UNWIND); + set_curlet(sc, old_e); + if (res) return(res); + set_no_cell_opt(body); + } + else /* not func */ + { + set_no_cell_opt(body); + set_curlet(sc, old_e); + }} + + if (!is_iterator(seq1)) + { + if (!is_mappable(seq1)) + wrong_type_error_nr(sc, sc->map_symbol, 2, seq1, a_sequence_string); + sc->z = s7_make_iterator(sc, seq1); + } + else sc->z = seq1; + if (!is_iterator(seq2)) + { + if (!is_mappable(seq2)) + wrong_type_error_nr(sc, sc->map_symbol, 3, seq2, a_sequence_string); + sc->z = list_2(sc, sc->z, s7_make_iterator(sc, seq2)); + } + else sc->z = list_2(sc, sc->z, seq2); + + push_stack(sc, OP_MAP, inline_make_counter(sc, sc->z), f); + sc->z = sc->unused; + return(sc->unspecified); +} + +static s7_pointer g_map(s7_scheme *sc, s7_pointer args) +{ + #define H_map "(map proc object . objects) applies proc to a list made up of the next element of each of its arguments, returning \ +a list of the results. Its arguments can be lists, vectors, strings, hash-tables, or any applicable objects." + #define Q_map s7_make_circular_signature(sc, 2, 3, sc->is_proper_list_symbol, sc->is_procedure_symbol, sc->is_sequence_symbol) + + /* (apply f (map ...)) e.g. f=append -> use safe_list for map output list here? also for ( (map...)) + * but less savings if mapped func would have used the same safe_list? + */ + s7_pointer p, f = car(args); + s7_int len; + bool got_nil = false; + + for (len = 0, p = cdr(args); is_not_null(p); p = cdr(p), len++) + if (!is_mappable(car(p))) + { + if (is_null(car(p))) + got_nil = true; + else wrong_type_error_nr(sc, sc->map_symbol, len + 2, car(p), a_sequence_string); + } + + switch (type(f)) + { + case T_C_FUNCTION: + if (!(c_function_is_aritable(f, len))) + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_4(sc, wrap_string(sc, "map ~A: ~A argument~P?", 22), f, wrap_integer(sc, len), wrap_integer(sc, len))); + case T_C_RST_NO_REQ_FUNCTION: + /* if function is safe c func, do the map locally */ + if (got_nil) return(sc->nil); + if (is_safe_procedure(f)) + { + s7_pointer val, val1, old_args, iter_list; + s7_function func = c_function_call(f); + if (is_pair(cadr(args))) + { + if (len == 1) + { + s7_p_p_t fp = s7_p_p_function(f); + if (fp) + { + val = list_1_unchecked(sc, sc->nil); + push_stack_no_let_no_code(sc, OP_GC_PROTECT, val); + for (s7_pointer fast = cadr(args), slow = cadr(args); is_pair(fast); fast = cdr(fast), slow = cdr(slow)) + { + s7_pointer z = fp(sc, car(fast)); + if (z != sc->no_value) set_car(val, cons(sc, z, car(val))); + if (is_pair(cdr(fast))) + { + fast = cdr(fast); + if (fast == slow) break; + z = fp(sc, car(fast)); + if (z != sc->no_value) set_car(val, cons(sc, z, car(val))); + }} + unstack(sc); + return(proper_list_reverse_in_place(sc, car(val))); + }} + if ((len == 2) && (is_pair(caddr(args)))) + { + s7_p_pp_t fp = s7_p_pp_function(f); + if (fp) + { + val = list_1_unchecked(sc, sc->nil); + push_stack_no_let_no_code(sc, OP_GC_PROTECT, val); + for (s7_pointer fast1 = cadr(args), slow1 = cadr(args), fast2 = caddr(args), slow2 = caddr(args); + (is_pair(fast1)) && (is_pair(fast2)); + fast1 = cdr(fast1), slow1 = cdr(slow1), fast2 = cdr(fast2), slow2 = cdr(slow2)) + { + s7_pointer z = fp(sc, car(fast1), car(fast2)); + if (z != sc->no_value) set_car(val, cons(sc, z, car(val))); + if ((is_pair(cdr(fast1))) && (is_pair(cdr(fast2)))) + { + fast1 = cdr(fast1); + if (fast1 == slow1) break; + fast2 = cdr(fast2); + if (fast2 == slow2) break; + z = fp(sc, car(fast1), car(fast2)); + if (z != sc->no_value) set_car(val, cons(sc, z, car(val))); + }} + unstack(sc); + return(proper_list_reverse_in_place(sc, car(val))); + }}} + if ((is_string(cadr(args))) && (len == 1)) + { + s7_p_p_t fp = s7_p_p_function(f); + if (fp) + { + s7_pointer str = cadr(args); + const char *s = string_value(str); + val = list_1_unchecked(sc, sc->nil); + push_stack_no_let_no_code(sc, OP_GC_PROTECT, val); + len = string_length(str); + for (s7_int i = 0; i < len; i++) + { + s7_pointer z = fp(sc, chars[(uint8_t)(s[i])]); + if (z != sc->no_value) set_car(val, cons(sc, z, car(val))); + } + unstack(sc); + return(proper_list_reverse_in_place(sc, car(val))); + }} + if ((is_any_vector(cadr(args))) && (len == 1)) + { + s7_p_p_t fp = s7_p_p_function(f); + if (fp) + { + s7_pointer vec = cadr(args); + val = list_1_unchecked(sc, sc->nil); + push_stack_no_let_no_code(sc, OP_GC_PROTECT, val); + len = vector_length(vec); + for (s7_int i = 0; i < len; i++) + { + s7_pointer z = fp(sc, vector_getter(vec)(sc, vec, i)); + if (z != sc->no_value) set_car(val, cons(sc, z, car(val))); + } + unstack(sc); + return(proper_list_reverse_in_place(sc, car(val))); + }} + + sc->z = make_iterators(sc, sc->map_symbol, args); + val1 = cons_unchecked(sc, sc->z, make_list(sc, len, sc->nil)); + iter_list = sc->z; + old_args = sc->args; + push_stack_no_let(sc, OP_GC_PROTECT, val1, val = cons(sc, sc->nil, sc->code)); /* temporary GC protection: need to protect val1, iter_list, val */ + sc->z = sc->unused; + while (true) + { + s7_pointer z; + for (s7_pointer x = iter_list, y = cdr(val1); is_pair(x); x = cdr(x), y = cdr(y)) + { + set_car(y, s7_iterate(sc, car(x))); + if (iterator_is_at_end(car(x))) + { + unstack(sc); /* free_cell(sc, car(x)); */ /* 16-Jan-19 iterator in circular list -- see s7test */ + sc->args = T_Pos(old_args); /* can be # */ + return(proper_list_reverse_in_place(sc, car(val))); + }} + z = func(sc, cdr(val1)); /* multiple-values? values is unsafe, but s7_values used externally and claims to be safe? */ /* func = c_function_call(f) */ + if (z != sc->no_value) + set_car(val, cons(sc, z, car(val))); + }} + + else /* not safe procedure */ + if ((f == global_value(sc->values_symbol)) && + (len == 1) && + (!has_methods(cadr(args)))) /* iterator should be ok here -- object_to_list can handle it */ + { + p = object_to_list(sc, cadr(args)); + if (p != cadr(args)) + return(p); + } + break; + + case T_CLOSURE: + case T_CLOSURE_STAR: + { + int32_t fargs = (is_closure(f)) ? closure_arity_to_int(sc, f) : closure_star_arity_to_int(sc, f); + if ((len == 1) && + (fargs == 1) && + (!is_constant_symbol(sc, (is_pair(car(closure_args(f)))) ? caar(closure_args(f)) : car(closure_args(f))))) + { + if (got_nil) return(sc->nil); + if (is_closure_star(f)) + return(g_map_closure(sc, f, cadr(args))); + + /* don't go to OP_MAP_2 here! It assumes no recursion */ + sc->z = (!is_iterator(cadr(args))) ? s7_make_iterator(sc, cadr(args)) : cadr(args); + push_stack(sc, OP_MAP_1, inline_make_counter(sc, sc->z), f); + sc->z = sc->unused; + symbol_increment_ctr(car(closure_args(f))); + return(sc->nil); + } + if (((fargs >= 0) && (fargs < len)) || + ((is_closure(f)) && (abs(fargs) > len))) + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_4(sc, wrap_string(sc, "map ~A: ~A argument~P?", 22), f, wrap_integer(sc, len), wrap_integer(sc, len))); + if (got_nil) return(sc->nil); + } + break; + + case T_C_OBJECT: + /* args if sc->args (plist + c_object) can be clobbered here by s7_is_aritable, so we need to protect it */ + args = copy_proper_list(sc, args); + sc->temp10 = args; + + default: + if (!is_applicable(f)) + return(method_or_bust(sc, f, sc->map_symbol, args, something_applicable_string, 1)); + if ((!is_pair(f)) && + (!s7_is_aritable(sc, f, len))) + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_4(sc, wrap_string(sc, "map ~A: ~A argument~P?", 22), f, wrap_integer(sc, len), wrap_integer(sc, len))); + if (got_nil) return(sc->nil); + break; + } + + sc->z = make_iterators(sc, sc->map_symbol, args); + push_stack(sc, OP_MAP, inline_make_counter(sc, sc->z), f); + sc->z = sc->unused; + return(sc->nil); +} + +static bool op_map(s7_scheme *sc) +{ + s7_pointer iterators = counter_list(sc->args); + sc->x = sc->nil; /* can't use preset args list here (as in for-each): (map list '(a b c)) */ + for (s7_pointer y = iterators; is_pair(y); y = cdr(y)) + { + s7_pointer x = s7_iterate(sc, car(y)); + if (iterator_is_at_end(car(y))) + { + sc->value = proper_list_reverse_in_place(sc, counter_result(sc->args)); + free_cell(sc, sc->args); /* not sc->args = sc->nil; */ + return(true); + } + sc->x = cons(sc, x, sc->x); + } + sc->x = proper_list_reverse_in_place(sc, sc->x); + push_stack_direct(sc, OP_MAP_GATHER); + sc->args = sc->x; + sc->x = sc->unused; + if (needs_copied_args(sc->code)) + sc->args = copy_proper_list(sc, sc->args); + return(false); +} + +static bool op_map_1(s7_scheme *sc) +{ + s7_pointer args = sc->args, code = sc->code; + s7_pointer p = counter_list(args); + s7_pointer x = s7_iterate(sc, p); + + if (iterator_is_at_end(p)) + { + sc->value = proper_list_reverse_in_place(sc, counter_result(args)); + free_cell(sc, sc->args); /* not sc->args = sc->nil; */ + return(true); + } + push_stack_direct(sc, OP_MAP_GATHER_1); + if (counter_capture(args) != sc->capture_let_counter) + { + sc->curlet = inline_make_let_with_slot(sc, closure_let(code), car(closure_args(code)), x); + counter_set_let(args, sc->curlet); + counter_set_slots(args, let_slots(sc->curlet)); + counter_set_capture(args, sc->capture_let_counter); + } + else + { + /* the counter_slots field saves the original local let slot(s) representing the function + * argument. If the function has internal defines, they get added to the front of the + * slots list, but update_let_with_slot (maybe stupidly) assumes only the one original + * slot exists when it updates its symbol_id from the (possibly changed) let_id. So, + * a subsequent reference to the parameter name causes "unbound variable", or a segfault + * if the check has been optimized away. I think each function call should start with + * the original let slots, so counter_slots saves that pointer, and resets it here. + */ + let_set_slots(counter_let(args), counter_slots(args)); + sc->curlet = update_let_with_slot(sc, counter_let(args), x); + } + sc->code = T_Pair(closure_body(code)); + return(false); +} + +static bool op_map_2(s7_scheme *sc) /* possibly inline lg */ +{ + s7_pointer x, c = sc->args, code = sc->code; + s7_pointer p = counter_list(c); + if (!is_pair(p)) + { + sc->value = proper_list_reverse_in_place(sc, counter_result(c)); + free_cell(sc, sc->args); /* not sc->args = sc->nil; */ + return(true); + } + x = car(p); + counter_set_list(c, cdr(p)); + + if (sc->cur_op == OP_MAP_GATHER_3) + { + closure_set_map_list(code, cdr(closure_map_list(code))); + /* this depends on code (the function) being non-recursive, else closure_setter gets stepped on */ + if (closure_map_list(code) == counter_list(c)) + { + sc->value = proper_list_reverse_in_place(sc, counter_result(c)); + free_cell(sc, c); /* not sc->args = sc->nil; */ + return(true); + } + push_stack_direct(sc, OP_MAP_GATHER_2); + } + else push_stack_direct(sc, OP_MAP_GATHER_3); + + if (counter_capture(c) != sc->capture_let_counter) + { + sc->curlet = inline_make_let_with_slot(sc, closure_let(code), car(closure_args(code)), x); + counter_set_let(c, sc->curlet); + counter_set_slots(c, let_slots(sc->curlet)); + counter_set_capture(c, sc->capture_let_counter); + } + else + { + let_set_slots(counter_let(c), counter_slots(c)); /* needed -- see comment under for-each above */ + sc->curlet = update_let_with_slot(sc, counter_let(c), x); + } + sc->code = car(closure_body(code)); + return(false); +} + +static s7_pointer revappend(s7_scheme *sc, s7_pointer a, s7_pointer b) +{ + /* (map (lambda (x) (if (odd? x) (apply values '(1 2 3)) (values))) (list 1 2 3 4)) is a bad case -- we have to copy the incoming list (in op_map_gather) */ + s7_pointer p = b; + if (is_not_null(a)) + { + a = copy_proper_list(sc, a); + do { + s7_pointer q = cdr(a); + set_cdr(a, p); + p = a; + a = q; + } while (is_pair(a)); + } + return(p); +} + +static Inline void inline_op_map_gather(s7_scheme *sc) /* called thrice in eval, cb lg map */ +{ + if (sc->value != sc->no_value) + { + if (is_multiple_value(sc->value)) + counter_set_result(sc->args, revappend(sc, multiple_value(sc->value), counter_result(sc->args))); + else counter_set_result(sc->args, cons(sc, sc->value, counter_result(sc->args))); + } +} + + +/* -------------------------------- multiple-values -------------------------------- */ +#define stack_top4_op(Sc) ((opcode_t)T_Op(Sc->stack_end[-5])) /* top4 == top - 4 */ +#define stack_top4_args(Sc) (Sc->stack_end[-6]) +#define stack_top4_let(Sc) (Sc->stack_end[-7]) +#define stack_top4_code(Sc) (Sc->stack_end[-8]) + +static s7_pointer splice_in_values(s7_scheme *sc, s7_pointer args) +{ + s7_pointer x; + if (SHOW_EVAL_OPS) + safe_print(fprintf(stderr, "%s[%d]: splice %s %s\n", __func__, __LINE__, + (sc->stack_end > sc->stack_start) ? op_names[stack_top_op(sc)] : "no stack!", display_80(args))); + if ((S7_DEBUGGING) && ((is_null(args)) || (is_null(cdr(args))))) fprintf(stderr, "%s: %s\n", __func__, display(args)); + + switch (unchecked_stack_top_op(sc)) /* unchecked for C s7_values call at top-level -- see ffitest.c */ + { + /* the normal case -- splice values into caller's args */ + case OP_EVAL_ARGS1: case OP_EVAL_ARGS2: case OP_EVAL_ARGS3: case OP_EVAL_ARGS4: + /* code = args yet to eval in order, args = evalled args reversed. + * it is not safe to simply reverse args and tack the current stacked args onto its (new) end, + * setting stacked args to cdr of reversed-args and returning car because the list (args) + * can be some variable's value in a macro expansion via ,@ and reversing it in place + * (all this to avoid consing), clobbers the variable's value. + */ + sc->w = args; + for (x = args; is_not_null(cdr(x)); x = cdr(x)) + stack_top_args(sc) = cons(sc, car(x), stack_top_args(sc)); + sc->w = sc->unused; + return(car(x)); + + /* in the next set, the main evaluator branches blithely assume no multiple-values, and if it happens anyway, we go to a different branch here */ + case OP_ANY_CLOSURE_NP_2: + set_stack_top_op(sc, OP_ANY_CLOSURE_NP_MV); + goto FP_MV; + + case OP_ANY_C_NP_2: + set_stack_top_op(sc, OP_ANY_C_NP_MV); + goto FP_MV; + + case OP_ANY_C_NP_1: case OP_ANY_CLOSURE_NP_1: + set_stack_top_op(sc, stack_top_op(sc) + 1); /* replace with mv version */ + + case OP_ANY_C_NP_MV: case OP_ANY_CLOSURE_NP_MV: + FP_MV: + if ((is_immutable(args)) || /* (let () (define (func) (with-output-to-string (lambda () (apply-values (write '(1 2)))))) (func) (func)) */ + (needs_copied_args(args))) + { + clear_needs_copied_args(args); + args = copy_proper_list(sc, args); + } + set_multiple_value(args); + return(args); + + case OP_SAFE_C_SSP_1: + set_stack_top_op(sc, OP_SAFE_C_SSP_MV); + return(args); + + case OP_SAFE_C_SP_1: case OP_SAFE_CONS_SP_1: case OP_SAFE_ADD_SP_1: case OP_SAFE_MULTIPLY_SP_1: + set_stack_top_op(sc, OP_SAFE_C_SP_MV); + return(args); + + case OP_SAFE_C_PS_1: + set_stack_top_op(sc, OP_SAFE_C_PS_MV); + return(args); + + case OP_SAFE_C_PC_1: + set_stack_top_op(sc, OP_SAFE_C_PC_MV); + return(args); + + case OP_SAFE_C_PA_1: + set_stack_top_op(sc, OP_SAFE_C_PA_MV); + return(args); + + case OP_C_P_1: case OP_SAFE_C_P_1: + set_stack_top_op(sc, OP_C_P_MV); + return(args); + + case OP_C_AP_1: + set_stack_top_op(sc, OP_C_AP_MV); + /* sc->value = args; */ /* removed 29-Mar-22 -- seems redundant */ + return(args); + + case OP_SAFE_CLOSURE_P_1: case OP_CLOSURE_P_1: case OP_SAFE_CLOSURE_P_A_1: + case OP_SAFE_CLOSURE_AP_1: case OP_CLOSURE_AP_1: + case OP_SAFE_CLOSURE_PP_1: case OP_CLOSURE_PP_1: + case OP_SAFE_CLOSURE_PA_1: case OP_CLOSURE_PA_1: /* arity is 2, we have 2 args, this has to be an error (see optimize_closure_sym) */ + case OP_ANY_CLOSURE_3P_1: case OP_ANY_CLOSURE_3P_2: case OP_ANY_CLOSURE_3P_3: + case OP_ANY_CLOSURE_4P_1: case OP_ANY_CLOSURE_4P_2: case OP_ANY_CLOSURE_4P_3: case OP_ANY_CLOSURE_4P_4: + if (is_multiple_value(sc->value)) clear_multiple_value(sc->value); + error_nr(sc, sc->wrong_number_of_args_symbol, set_elist_3(sc, too_many_arguments_string, stack_top_code(sc), sc->value)); + + case OP_SAFE_C_PP_1: + set_stack_top_op(sc, OP_SAFE_C_PP_3_MV); + return(args); + + case OP_SAFE_C_PP_5: + set_stack_top_op(sc, OP_SAFE_C_PP_6_MV); + return(args); + + case OP_SAFE_C_3P_1: case OP_SAFE_C_3P_2: case OP_SAFE_C_3P_3: + set_stack_top_op(sc, stack_top_op(sc) + 3); + case OP_SAFE_C_3P_1_MV: case OP_SAFE_C_3P_2_MV: case OP_SAFE_C_3P_3_MV: + return(cons(sc, sc->unused, copy_proper_list(sc, args))); + + case OP_EVAL_ARGS5: + /* code = previous arg saved, args = ante-previous args reversed, we'll take value->code->args and reverse in args5 */ + if (is_null(args)) + return(sc->unspecified); + if (is_null(cdr(args))) + return(car(args)); + + stack_top_args(sc) = cons(sc, stack_top_code(sc), stack_top_args(sc)); + for (x = args; is_not_null(cddr(x)); x = cdr(x)) + stack_top_args(sc) = cons(sc, car(x), stack_top_args(sc)); + stack_top_code(sc) = car(x); + return(cadr(x)); + + /* look for errors here rather than glomming up the set! and let code */ + case OP_SET_SAFE: /* symbol is sc->code after pop */ + case OP_SET1: + case OP_SET_FROM_LET_TEMP: /* (set! var (values 1 2 3)) */ + case OP_SET_FROM_SETTER: + syntax_error_with_caller_nr(sc, "set!: can't set ~A to ~S", 24, stack_top_code(sc), set_ulist_1(sc, sc->values_symbol, args)); + + case OP_SET_opSAq_P_1: case OP_SET_opSAAq_P_1: + syntax_error_nr(sc, "too many arguments to set! ~S", 29, set_ulist_1(sc, sc->values_symbol, args)); + + case OP_LET1: /* (let ((var (values 1 2 3))) ...) */ + { + s7_pointer let_code, vars, sym, p = stack_top_args(sc); + for (let_code = p; is_pair(cdr(let_code)); let_code = cdr(let_code)); + for (vars = caar(let_code); is_pair(cdr(p)); p = cdr(p), vars = cdr(vars)); + sym = caar(vars); + syntax_error_with_caller2_nr(sc, "~A: can't bind ~A to ~S", 23, sc->let_symbol, sym, set_ulist_1(sc, sc->values_symbol, args)); + /* stack_args: ((((x (values 1 2))) x)) in (let ((x (values 1 2))) x) + * (1 (((x 1) (y (values 1 2))) x)) in (let ((x 1) (y (values 1 2))) x) + */ + } + + case OP_LET_ONE_NEW_1: case OP_LET_ONE_P_NEW_1: + syntax_error_with_caller2_nr(sc, "~A: can't bind ~A to ~S", 23, sc->let_symbol, + opt2_sym(stack_top_code(sc)), set_ulist_1(sc, sc->values_symbol, args)); + + case OP_LET_ONE_OLD_1: case OP_LET_ONE_P_OLD_1: + syntax_error_with_caller2_nr(sc, "~A: can't bind ~A to ~S", 23, sc->let_symbol, + slot_symbol(let_slots(opt3_let(stack_top_code(sc)))), set_ulist_1(sc, sc->values_symbol, args)); + + case OP_LET_STAR1: /* here caar(sc->code) is bound to sc->value */ + syntax_error_with_caller2_nr(sc, "~A: can't bind ~A to ~S", 23, sc->let_star_symbol, + caar(stack_top_code(sc)), set_ulist_1(sc, sc->values_symbol, args)); + + case OP_LETREC1: /* here sc->args is the slot about to receive a value */ + syntax_error_with_caller2_nr(sc, "~A: can't bind ~A to ~S", 23, sc->letrec_symbol, + slot_symbol(stack_top_args(sc)), set_ulist_1(sc, sc->values_symbol, args)); + + case OP_LETREC_STAR1: + syntax_error_with_caller2_nr(sc, "~A: can't bind ~A to ~S", 23, sc->letrec_star_symbol, + slot_symbol(stack_top_args(sc)), set_ulist_1(sc, sc->values_symbol, args)); + + case OP_AND_P1: + case OP_AND_SAFE_P_REST: /* from OP_AND_SAFE_P1 or P2 */ + for (x = args; is_not_null(cdr(x)); x = cdr(x)) + if (car(x) == sc->F) + return(sc->F); + return(car(x)); + + case OP_OR_P1: + for (x = args; is_not_null(cdr(x)); x = cdr(x)) + if (car(x) != sc->F) + return(car(x)); + return(car(x)); + + case OP_IF1: /* (if (values ...) ...) -- see s7.html at the end of the values writeup for explanation (we're following CL here) */ + case OP_IF_PP: case OP_IF_PPP: case OP_IF_PR: case OP_IF_PRR: + case OP_WHEN_PP: case OP_UNLESS_PP: case OP_WITH_LET1: + case OP_CASE_G_G: case OP_CASE_G_S: case OP_CASE_E_G: case OP_CASE_E_S: case OP_CASE_I_S: + case OP_COND1: case OP_COND1_SIMPLE: + return(car(args)); + + case OP_IF_PN: /* (if|when (not (values...)) ...) as opposed to (if|unless (values...)...) which follows CL and drops trailing values */ + syntax_error_nr(sc, "too many arguments to not: ~S", 29, set_ulist_1(sc, sc->values_symbol, args)); + + case OP_DYNAMIC_UNWIND: case OP_DYNAMIC_UNWIND_PROFILE: + { + s7_pointer old_value = sc->value; + bool mv = is_multiple_value(args); + if (mv) clear_multiple_value(args); + sc->value = cons(sc, sc->values_symbol, args); + dynamic_unwind(sc, stack_top_code(sc), stack_top_args(sc)); /* position (curlet), this applies code to sc->value */ + sc->value = old_value; + if (mv) set_multiple_value(args); + sc->stack_end -= 4; /* either op is possible I think */ + return(splice_in_values(sc, args)); + } + + case OP_BARRIER: + pop_stack(sc); + return(splice_in_values(sc, args)); + + case OP_GC_PROTECT: + sc->stack_end -= 4; + return(splice_in_values(sc, args)); + + case OP_BEGIN_HOOK: case OP_BEGIN_NO_HOOK: case OP_BEGIN_2_UNCHECKED: + case OP_SIMPLE_DO_STEP: case OP_DOX_STEP_O: case OP_DOX_STEP: + /* here we have a values call with nothing to splice into. So flush it... + * otherwise the multiple-values bit gets set in some innocent list and never unset: + * (let ((x '((1 2)))) (eval `(apply apply values x)) x) -> ((values 1 2)) + * other cases: (+ 1 (begin (values 5 6) (values 2 3)) 4) -> 10 -- the (5 6) is dropped + * (let () (values 1 2 3) 4) but (+ (let () (values 1 2))) -> 3 + */ + return(args); + + case OP_DEACTIVATE_GOTO: /* (+ (call-with-exit (lambda (ret) (values 1 2 3)))) */ + call_exit_active(stack_top_args(sc)) = false; /* stack_top_args(sc) is the goto */ + + case OP_CATCH: case OP_CATCH_1: case OP_CATCH_2: case OP_CATCH_ALL: /* (+ (catch #t (lambda () (values 3 4)) (lambda args args))) */ + pop_stack(sc); + return(splice_in_values(sc, args)); + + case OP_EVAL_MACRO_MV: /* perhaps reader-cond expansion at eval-time (not at run-time) via ((let () reader-cond) ...)? */ + { + opcode_t s_op = stack_top4_op(sc); + if (S7_DEBUGGING) + if (SHOW_EVAL_OPS) + fprintf(stderr, " eval_macro_mv splice %s with %s, code: %s, args: %s, value: %s\n", + display(args), op_names[s_op], display(sc->code), display(sc->args), display(sc->value)); + if ((s_op == OP_DO_STEP) || (s_op == OP_DEACTIVATE_GOTO) || (s_op == OP_LET1)) + return(args); /* tricky reader-cond as macro in do body returning values... or call-with-exit */ + + /* if eval_args2 here, how to maintain the current evaluation? + * (+ (reader-cond (#t 1 (values 2 3) 4))) -> 10 + * (+ (((vector reader-cond) 0) (#t 1 (values 2 3) 4))) -> 5 [10 if this block of code is included, s7test is ok with this code] + */ + if (s_op == OP_EVAL_ARGS2) + { + sc->w = args; + for (x = args; is_not_null(cdr(x)); x = cdr(x)) + stack_top4_args(sc) = cons(sc, car(x), stack_top4_args(sc)); + sc->w = sc->unused; + if (SHOW_EVAL_OPS) + fprintf(stderr, " eval_macro splice %s with %s, code: %s, args: %s, value: %s -> %s %s\n", + display(args), op_names[s_op], display(sc->code), display(sc->args), display(sc->value), display(stack_top4_args(sc)), display(car(x))); + return(car(x)); + } + /* fall through */ + /* safe_c_p_1 also happens and currently drops trailing arg: ((let () reader-cond) (#t (values 1 2) (iv))) + * op_eval_macro (not op_expansion) is called and can be included below (except it segfaults in s7test...), but trailing arg + * is still dropped because optimizer sees (reader-cond ...) -- one arg! + * (define iv (int-vector 1 2)) (define (func) (eof-object? ((let () reader-cond) (#t (values 1 2) (iv))))) (func) + */ + } + + case OP_EXPANSION: + /* we get here if a reader-macro (define-expansion) returns multiple values. + * these need to be read in order into the current reader lists (we'll assume OP_READ_LIST is next in the stack. + * and that it will be expecting the next arg entry in sc->value; but it could be OP_LOAD_RETURN_IF_EOF if the expansion is at top level). + */ + if (SHOW_EVAL_OPS) + fprintf(stderr, " %s[%d]: %s stack top: %" ld64 ", op: %s, args: %s\n", __func__, __LINE__, + op_names[stack_top_op(sc)], (s7_int)(intptr_t)stack_top(sc), op_names[stack_top4_op(sc)], display(args)); + if (stack_top4_op(sc) == OP_LOAD_RETURN_IF_EOF) + { + /* expansion at top-level returned values, eval args in order */ + sc->code = args; + push_stack_no_args_direct(sc, sc->begin_op); + return(sc->code); + } + for (x = args; is_not_null(cdr(x)); x = cdr(x)) + stack_top4_args(sc) = cons(sc, car(x), stack_top4_args(sc)); + pop_stack(sc); /* need GC protection in loop above, so do this afterwards */ + return(car(x)); /* sc->value from OP_READ_LIST point of view */ + + case OP_EVAL_DONE: + if (stack_top4_op(sc) == OP_NO_VALUES) + error_nr(sc, sc->error_symbol, + set_elist_1(sc, wrap_string(sc, "function-port should not return multiple-values", 47))); + set_stack_top_op(sc, OP_SPLICE_VALUES); /* tricky -- continue from eval_done with the current splice */ + stack_top_args(sc) = args; + push_stack_op(sc, OP_EVAL_DONE); + return(args); + + default: + /* if (SHOW_EVAL_OPS) fprintf(stderr, " %s[%d]: splice punts: %s\n", __func__, __LINE__, op_names[stack_top_op(sc)]); */ + break; + } + + /* let it meander back up the call chain until someone knows where to splice it + * the is_immutable check protects against setting the multiple value bit on (say) sc->hash_table_signature + */ + if (is_immutable(args)) + args = copy_proper_list(sc, args); /* copy needed else (apply values x) where x is a list can leave the mv bit on for x's value */ + if (needs_copied_args(args)) + { + clear_needs_copied_args(args); + args = copy_proper_list(sc, args); + } + set_multiple_value(args); + return(args); +} + + +/* -------------------------------- values -------------------------------- */ +s7_pointer s7_values(s7_scheme *sc, s7_pointer args) +{ + #define H_values "(values obj ...) splices its arguments into whatever list holds it (its 'continuation')" + #define Q_values s7_make_circular_signature(sc, 1, 2, sc->values_symbol, sc->T) + + if (is_null(args)) /* ((lambda () (let ((x 1)) (set! x (boolean? (values)))))) */ + return(sc->no_value); + if (is_null(cdr(args))) + return(car(args)); + set_needs_copied_args(args); + /* copy needed: see s7test (test `(,x ,@y ,x) '(3 a b c 3)) -> (append (list-values x (apply-values y)) x), and apply_values calls s7_values directly */ + return(splice_in_values(sc, args)); +} + +#define g_values s7_values + +static s7_pointer values_p(s7_scheme *sc) {return(sc->no_value);} +static s7_pointer values_p_p(s7_scheme *unused_sc, s7_pointer p) {return(p);} + +static s7_pointer values_chooser(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer unused_expr, bool unused_ops) +{ + if (args > 1) return(sc->values_uncopied); /* splice_in_values */ + return(f); +} + +bool s7_is_multiple_value(s7_pointer obj) {return(is_multiple_value(obj));} + + +/* -------------------------------- list-values -------------------------------- */ +static s7_pointer splice_out_values(s7_scheme *sc, s7_pointer args) +{ + s7_pointer tp; + while (car(args) == sc->no_value) {args = cdr(args); if (is_null(args)) return(sc->nil);} + tp = list_1(sc, car(args)); + sc->temp8 = tp; + for (s7_pointer p = cdr(args), np = tp; is_pair(p); p = cdr(p)) + if (car(p) != sc->no_value) + { + set_cdr(np, list_1(sc, car(p))); + np = cdr(np); + } + sc->temp8 = sc->unused; + return(tp); +} + +static s7_pointer g_list_values(s7_scheme *sc, s7_pointer args) +{ + #define H_list_values "(list-values ...) returns its arguments in a list (internal to quasiquote)" + #define Q_list_values s7_make_circular_signature(sc, 1, 2, sc->is_list_symbol, sc->T) + + /* list-values can't be replaced by list(-n) because (list-values (values)) -> () and anything can be # (see s7test) */ + /* but (list-values ) will complain or get into an infinite recursion in copy_tree, so it should not use copy_tree */ + + s7_pointer x; + bool checked = false; + for (x = args; is_pair(x); x = cdr(x)) + if (is_pair(car(x))) + { + if (is_checked(car(x))) + checked = true; + } + else + if (car(x) == sc->no_value) /* unchecked_car|cdr unrolled here is not faster */ + break; + if (is_null(x)) + { + if (!checked) /* (!tree_has_definers(sc, args)) seems to work, reduces copy_tree calls slightly, but costs more than it saves in tgen */ + { + for (s7_pointer p = args; is_pair(p); p = cdr(p)) /* embedded list can be immutable, so we need to copy (sigh) */ + if (is_immutable_pair(p)) + return(copy_proper_list(sc, args)); + return(args); + } + sc->temp5 = args; + check_free_heap_size(sc, 8192); + if (sc->safety > NO_SAFETY) + { + if (!tree_is_cyclic(sc, args)) /* we're copying to clear optimizations I think, and a cyclic list here can't be optimized */ + args = cons_unchecked(sc, /* since list-values is a safe function, args can be immutable, which should not be passed through the copy */ + (is_unquoted_pair(car(args))) ? copy_tree_with_type(sc, car(args)) : car(args), + (is_unquoted_pair(cdr(args))) ? copy_tree_with_type(sc, cdr(args)) : cdr(args)); + } + else args = copy_tree(sc, args); /* not copy_any_list here -- see comment below */ + sc->temp5 = sc->unused; + return(args); + } + /* if a macro expands into a recursive function with a macro argument as its body (or reasonable facsimile thereof), + * and the safety (as in safe_closure) of the body changes from safe to unsafe, then (due to the checked bits + * protecting against cycles in optimize_expression|syntax), the possible safe_closure call will not be fixed, + * the safe_closure's assumption about the saved local let will be violated, and we'll get " unbound" (see tgen.scm). + * clear_all_optimizations assumes its argument has no cycles, and automatically calling copy_tree slows + * everything down intolerably, so if the checked bit is on in a macro expansion, that means we're re-expanding this macro, + * and therefore have to copy the tree. But isn't that only the case if the macro expands into closures? + */ + return(splice_out_values(sc, args)); +} + +static s7_pointer g_simple_list_values(s7_scheme *sc, s7_pointer args) +{ + /* if just (code-)constant/symbol, symbol->pair won't be checked (not optimized/re-expanded code), but might be no-values */ + for (s7_pointer p = args; is_pair(p); p = cdr(p)) + if (car(p) == sc->no_value) + return(splice_out_values(sc, args)); + if (is_immutable(args)) + return(copy_proper_list(sc, args)); + return(args); +} + +static s7_pointer list_values_chooser(s7_scheme *sc, s7_pointer f, int32_t unused_args, s7_pointer expr, bool unused_ops) +{ + for (s7_pointer p = cdr(expr); is_pair(p); p = cdr(p)) + if ((is_pair(car(p))) && (caar(p) != sc->quote_symbol)) + return(f); + return(sc->simple_list_values); +} + + +/* -------------------------------- apply-values -------------------------------- */ +static s7_pointer g_apply_values(s7_scheme *sc, s7_pointer args) +{ + #define H_apply_values "(apply-values var) applies values to var. This is an internal function." + #define Q_apply_values s7_make_signature(sc, 2, sc->T, sc->is_list_symbol) + s7_pointer x; + /* apply-values takes 1 arg: ,@a -> (apply-values a) */ + if (is_null(args)) + return(sc->no_value); + x = car(args); + if (is_null(x)) + return(sc->no_value); + if (!s7_is_proper_list(sc, x)) + apply_list_error_nr(sc, args); + return(s7_values(sc, x)); /* g_values == s7_values */ +} + +/* (apply values ...) replaces (unquote_splicing ...) + * (define-macro (hi a) `(+ 1 ,a) == (list '+ 1 a) + * (define-macro (hi a) `(+ 1 ,@a) == (list '+ 1 (apply values a)) + * + * this is not the same as CL's quasiquote; for example: + * [1]> (let ((a 1) (b 2)) `(,a ,@b)) -> '(1 . 2) but in s7 this is an error. + * also in CL the target of ,@ can apparently be a circular list + * one surprising twist: write/display return their first argument directly, so (apply-values (write `(+ x 1))) is the same as (apply-values `(+ x 1)) + */ + + +/* -------------------------------- quasiquote -------------------------------- */ +static bool is_simple_code(s7_scheme *sc, s7_pointer form) +{ + /* if nested with quasiquotes say 20 levels, this is really slow, but to tag intermediate results burns up 2 type bits */ + s7_pointer tmp, slow; + for (tmp = form, slow = form; is_pair(tmp); tmp = cdr(tmp), slow = cdr(slow)) + { + if (is_pair(car(tmp))) + { + if (!is_simple_code(sc, car(tmp))) + return(false); + } + else + if (car(tmp) == sc->unquote_symbol) + return(false); + tmp = cdr(tmp); + if (!is_pair(tmp)) return(is_null(tmp)); + if (tmp == slow) return(false); + if (is_pair(car(tmp))) + { + if (!is_simple_code(sc, car(tmp))) + return(false); + } + else + if (car(tmp) == sc->unquote_symbol) + return(false); + } + return(is_null(tmp)); +} + +/* since the reader expands unquote et al, and the printer does not unexpand them, the standard scheme quine in s7 is: + * ((lambda (x) (list-values x (list-values 'quote x))) '(lambda (x) (list-values x (list-values 'quote x)))) + * but that depends on the "p" in repl... + */ + +static s7_pointer g_quasiquote_1(s7_scheme *sc, s7_pointer form, bool check_cycles) +{ + #define H_quasiquote "(quasiquote arg) is the same as `arg. If arg is a list, it can contain \ +comma (\"unquote\") and comma-atsign (\"apply values\") to pre-evaluate portions of the list. \ +unquoted expressions are evaluated and plugged into the list, apply-values evaluates the expression \ +and splices the resultant list into the outer list. `(1 ,(+ 1 1) ,@(list 3 4)) -> (1 2 3 4)." + + if (!is_pair(form)) + { + if (is_normal_symbol(form)) + return(list_2(sc, (is_global(sc->quote_symbol)) ? sc->quote_symbol : initial_value(sc->quote_symbol), form)); + /* things that evaluate to themselves don't need to be quoted */ + return(form); + } + if (car(form) == sc->unquote_symbol) + { + if (!is_pair(cdr(form))) /* (unquote) or (unquote . 1) */ + { + if (is_null(cdr(form))) + syntax_error_nr(sc, "unquote: no argument, ~S", 24, form); + syntax_error_nr(sc, "unquote: stray dot, ~S", 22, form); + } + if (is_not_null(cddr(form))) + syntax_error_nr(sc, "unquote: too many arguments, ~S", 31, form); + return(cadr(form)); + } + + /* it's a list, so return the list with each element handled as above. + * we try to support dotted lists which makes the code much messier. + * if no element of the list is a list or unquote, just return the original quoted + */ + if (((check_cycles) && (tree_is_cyclic(sc, form))) || + (is_simple_code(sc, form))) + /* we can't lookup sc->quote_symbol because this gets called in op_read_quasiquote (at read-time), and sc->curlet can be junk in that context */ + return(list_2(sc, (is_global(sc->quote_symbol)) ? sc->quote_symbol : initial_value(sc->quote_symbol), form)); + + { + s7_int i; + s7_pointer orig, bq, old_scw = sc->w; /* very often, sc->w is in use here */ + bool dotted = false; + s7_int len = s7_list_length(sc, form); + if (len < 0) + { + len = -len; + dotted = true; + } + s7_gc_protect_via_stack(sc, sc->w); + + check_free_heap_size(sc, len + 1); + sc->w = sc->nil; + for (i = 0; i <= len; i++) + sc->w = cons_unchecked(sc, sc->nil, sc->w); + + set_car(sc->w, sc->list_values_symbol); + if (!dotted) + { + for (orig = form, bq = cdr(sc->w), i = 0; i < len; i++, orig = cdr(orig), bq = cdr(bq)) + if ((is_pair(cdr(orig))) && /* this was is_pair(orig) which seems to be always the case */ + (cadr(orig) == sc->unquote_symbol)) /* `(1 . ,(+ 1 1)) -> '(1 unquote (+ 1 1)) -> '(1 . 2) etc */ + { + if (!is_pair(cddr(orig))) + { + sc->w = old_scw; + unstack(sc); + syntax_error_nr(sc, "unquote: no argument, ~S", 24, form); + } + set_car(bq, g_quasiquote_1(sc, car(orig), false)); + set_cdr(bq, sc->nil); + sc->w = list_3(sc, sc->qq_append_symbol, sc->w, caddr(orig)); /* `(f . ,(string-append "h" "i")) */ + break; + } + else set_car(bq, g_quasiquote_1(sc, car(orig), false)); + } + else /* `(1 2 . 3) */ + { + len--; + for (orig = form, bq = cdr(sc->w), i = 0; i < len; i++, orig = cdr(orig), bq = cdr(bq)) + set_car(bq, g_quasiquote_1(sc, car(orig), false)); + set_car(bq, g_quasiquote_1(sc, car(orig), false)); + sc->w = list_3(sc, sc->qq_append_symbol, sc->w, g_quasiquote_1(sc, cdr(orig), false)); + /* quasiquote might quote a symbol in cdr(orig), so it's not completely pointless */ + } + bq = sc->w; + sc->w = old_scw; + unstack(sc); + return(bq); + } +} + +static s7_pointer g_quasiquote(s7_scheme *sc, s7_pointer args) +{ + /* this is for explicit quasiquote support, not the backquote stuff in macros + * but it is problematic. g_quasiquote_1 above expands (for example) `(+ ,x) into (list (quote +) x), + * so (multiple-value-bind (quote) quasiquote `(+ ,x)) expands to ((lambda (quote) (list '+ x)) quasiquote) + * which is an infinite loop. Guile says syntax error (because it thinks "quote" can't be a parameter name, I think). + */ + return(g_quasiquote_1(sc, car(args), true)); +} + +static s7_pointer g_qq_append(s7_scheme *sc, s7_pointer args) +{ + #define H_qq_append "[list*]: CL list* (I think) for quasiquote's internal use" + #define Q_qq_append s7_make_circular_signature(sc, 0, 1, sc->T) + s7_pointer a = car(args), b = cadr(args); + s7_pointer p, tp, np; + if (is_null(a)) return(b); + p = cdr(a); + if (is_null(p)) return(cons(sc, car(a), b)); + tp = list_1(sc, car(a)); + s7_gc_protect_via_stack(sc, tp); + for (np = tp; is_pair(p); p = cdr(p), np = cdr(np)) + set_cdr(np, list_1(sc, car(p))); + set_cdr(np, b); + unstack(sc); + return(tp); +} + + +/* -------------------------------- choosers -------------------------------- */ +static s7_pointer make_function_with_class(s7_scheme *sc, s7_pointer cls, const char *name, s7_function f, + int32_t required_args, int32_t optional_args, bool rest_arg) +{ + s7_pointer uf = s7_make_safe_function(sc, name, f, required_args, optional_args, rest_arg, NULL); + s7_function_set_class(sc, uf, cls); + c_function_signature(uf) = c_function_signature(cls); + return(uf); +} + +static s7_pointer make_unsafe_function_with_class(s7_scheme *sc, s7_pointer cls, const char *name, s7_function f, + int32_t required_args, int32_t optional_args, bool rest_arg) +{ + s7_pointer uf = s7_make_function(sc, name, f, required_args, optional_args, rest_arg, NULL); /* was s7_make_safe_function! 14-Dec-20 */ + s7_function_set_class(sc, uf, cls); + c_function_signature(uf) = c_function_signature(cls); + return(uf); +} + +static s7_pointer set_function_chooser(s7_pointer sym, s7_pointer (*chooser)(s7_scheme *sc, s7_pointer f, int32_t args, s7_pointer expr, bool ops)) +{ + s7_pointer f = global_value(sym); + c_function_chooser(f) = chooser; + return(f); +} + +static void init_choosers(s7_scheme *sc) +{ + s7_pointer f; + + /* + */ + f = set_function_chooser(sc->add_symbol, add_chooser); + sc->add_class = c_function_class(f); + sc->add_2 = make_function_with_class(sc, f, "+", g_add_2, 2, 0, false); + sc->add_3 = make_function_with_class(sc, f, "+", g_add_3, 3, 0, false); + sc->add_1x = make_function_with_class(sc, f, "+", g_add_1x, 2, 0, false); + sc->add_x1 = make_function_with_class(sc, f, "+", g_add_x1, 2, 0, false); + sc->add_i_random = make_function_with_class(sc, f, "+", g_add_i_random, 2, 0, false); + sc->add_2_ff = make_function_with_class(sc, f, "+", g_add_2_ff, 2, 0, false); + sc->add_2_ii = make_function_with_class(sc, f, "+", g_add_2_ii, 2, 0, false); + sc->add_2_if = make_function_with_class(sc, f, "+", g_add_2_if, 2, 0, false); + sc->add_2_fi = make_function_with_class(sc, f, "+", g_add_2_fi, 2, 0, false); + sc->add_2_xi = make_function_with_class(sc, f, "+", g_add_2_xi, 2, 0, false); + sc->add_2_ix = make_function_with_class(sc, f, "+", g_add_2_ix, 2, 0, false); + sc->add_2_fx = make_function_with_class(sc, f, "+", g_add_2_fx, 2, 0, false); + sc->add_2_xf = make_function_with_class(sc, f, "+", g_add_2_xf, 2, 0, false); + + /* - */ + f = set_function_chooser(sc->subtract_symbol, subtract_chooser); + sc->subtract_class = c_function_class(f); + sc->subtract_1 = make_function_with_class(sc, f, "-", g_subtract_1, 1, 0, false); + sc->subtract_2 = make_function_with_class(sc, f, "-", g_subtract_2, 2, 0, false); + sc->subtract_3 = make_function_with_class(sc, f, "-", g_subtract_3, 3, 0, false); + sc->subtract_x1 = make_function_with_class(sc, f, "-", g_subtract_x1, 2, 0, false); + sc->subtract_2f = make_function_with_class(sc, f, "-", g_subtract_2f, 2, 0, false); + sc->subtract_f2 = make_function_with_class(sc, f, "-", g_subtract_f2, 2, 0, false); + + /* * */ + f = set_function_chooser(sc->multiply_symbol, multiply_chooser); + sc->multiply_class = c_function_class(f); + sc->multiply_2 = make_function_with_class(sc, f, "*", g_multiply_2, 2, 0, false); + sc->mul_2_ff = make_function_with_class(sc, f, "*", g_mul_2_ff, 2, 0, false); + sc->mul_2_ii = make_function_with_class(sc, f, "*", g_mul_2_ii, 2, 0, false); + sc->mul_2_if = make_function_with_class(sc, f, "*", g_mul_2_if, 2, 0, false); + sc->mul_2_fi = make_function_with_class(sc, f, "*", g_mul_2_fi, 2, 0, false); + sc->mul_2_xi = make_function_with_class(sc, f, "*", g_mul_2_xi, 2, 0, false); + sc->mul_2_ix = make_function_with_class(sc, f, "*", g_mul_2_ix, 2, 0, false); + sc->mul_2_fx = make_function_with_class(sc, f, "*", g_mul_2_fx, 2, 0, false); + sc->mul_2_xf = make_function_with_class(sc, f, "*", g_mul_2_xf, 2, 0, false); + + /* / */ + f = set_function_chooser(sc->divide_symbol, divide_chooser); + sc->invert_1 = make_function_with_class(sc, f, "/", g_invert_1, 1, 0, false); + sc->divide_2 = make_function_with_class(sc, f, "/", g_divide_2, 2, 0, false); + sc->invert_x = make_function_with_class(sc, f, "/", g_invert_x, 2, 0, false); + sc->divide_by_2 = make_function_with_class(sc, f, "/", g_divide_by_2, 2, 0, false); + + /* = */ + f = set_function_chooser(sc->num_eq_symbol, num_eq_chooser); + sc->num_eq_class = c_function_class(f); + sc->num_eq_2 = make_function_with_class(sc, f, "=", g_num_eq_2, 2, 0, false); + sc->num_eq_xi = make_function_with_class(sc, f, "=", g_num_eq_xi, 2, 0, false); + sc->num_eq_ix = make_function_with_class(sc, f, "=", g_num_eq_ix, 2, 0, false); + + /* min */ + f = set_function_chooser(sc->min_symbol, min_chooser); + sc->min_2 = make_function_with_class(sc, f, "min", g_min_2, 2, 0, false); + sc->min_3 = make_function_with_class(sc, f, "min", g_min_3, 3, 0, false); + + /* max */ + f = set_function_chooser(sc->max_symbol, max_chooser); + sc->max_2 = make_function_with_class(sc, f, "max", g_max_2, 2, 0, false); + sc->max_3 = make_function_with_class(sc, f, "max", g_max_3, 3, 0, false); + + /* < */ + f = set_function_chooser(sc->lt_symbol, less_chooser); + sc->less_xi = make_function_with_class(sc, f, "<", g_less_xi, 2, 0, false); + sc->less_x0 = make_function_with_class(sc, f, "<", g_less_x0, 2, 0, false); + sc->less_xf = make_function_with_class(sc, f, "<", g_less_xf, 2, 0, false); + sc->less_2 = make_function_with_class(sc, f, "<", g_less_2, 2, 0, false); + + /* > */ + f = set_function_chooser(sc->gt_symbol, greater_chooser); + sc->greater_xi = make_function_with_class(sc, f, ">", g_greater_xi, 2, 0, false); + sc->greater_xf = make_function_with_class(sc, f, ">", g_greater_xf, 2, 0, false); + sc->greater_2 = make_function_with_class(sc, f, ">", g_greater_2, 2, 0, false); + + /* <= */ + f = set_function_chooser(sc->leq_symbol, leq_chooser); + sc->leq_xi = make_function_with_class(sc, f, "<=", g_leq_xi, 2, 0, false); + sc->leq_2 = make_function_with_class(sc, f, "<=", g_leq_2, 2, 0, false); + sc->leq_ixx = make_function_with_class(sc, f, "<=", g_leq_ixx, 3, 0, false); + + /* >= */ + f = set_function_chooser(sc->geq_symbol, geq_chooser); + sc->geq_xi = make_function_with_class(sc, f, ">=", g_geq_xi, 2, 0, false); + sc->geq_xf = make_function_with_class(sc, f, ">=", g_geq_xf, 2, 0, false); + sc->geq_2 = make_function_with_class(sc, f, ">=", g_geq_2, 2, 0, false); + + /* log */ + f = set_function_chooser(sc->log_symbol, log_chooser); + sc->int_log2 = make_function_with_class(sc, f, "log", g_int_log2, 2, 0, false); + + /* random */ + f = set_function_chooser(sc->random_symbol, random_chooser); + sc->random_1 = make_function_with_class(sc, f, "random", g_random_1, 1, 0, false); + sc->random_i = make_function_with_class(sc, f, "random", g_random_i, 1, 0, false); + sc->random_f = make_function_with_class(sc, f, "random", g_random_f, 1, 0, false); + + /* defined? */ + f = set_function_chooser(sc->is_defined_symbol, is_defined_chooser); + sc->is_defined_in_rootlet = make_function_with_class(sc, f, "defined?", g_is_defined_in_rootlet, 2, 0, false); + + /* char=? */ + f = set_function_chooser(sc->char_eq_symbol, char_equal_chooser); + sc->simple_char_eq = make_function_with_class(sc, f, "char=?", g_simple_char_eq, 2, 0, false); + sc->char_equal_2 = make_function_with_class(sc, f, "char=?", g_char_equal_2, 2, 0, false); + + /* char>? */ + f = set_function_chooser(sc->char_gt_symbol, char_greater_chooser); + sc->char_greater_2 = make_function_with_class(sc, f, "char>?", g_char_greater_2, 2, 0, false); + + /* charchar_lt_symbol, char_less_chooser); + sc->char_less_2 = make_function_with_class(sc, f, "charread_char_symbol, read_char_chooser); + sc->read_char_1 = make_function_with_class(sc, f, "read-char", g_read_char_1, 1, 0, false); + + /* char-position */ + f = set_function_chooser(sc->char_position_symbol, char_position_chooser); + sc->char_position_csi = make_function_with_class(sc, f, "char-position", g_char_position_csi, 2, 1, false); + + /* string=? */ + f = set_function_chooser(sc->string_eq_symbol, string_equal_chooser); + sc->string_equal_2 = make_function_with_class(sc, f, "string=?", g_string_equal_2, 2, 0, false); + sc->string_equal_2c = make_function_with_class(sc, f, "string=?", g_string_equal_2c, 2, 0, false); + + /* substring */ + sc->substring_uncopied = s7_make_safe_function(sc, "substring", g_substring_uncopied, 2, 1, false, NULL); + s7_function_set_class(sc, sc->substring_uncopied, global_value(sc->substring_symbol)); + + /* string>? */ + f = set_function_chooser(sc->string_gt_symbol, string_greater_chooser); + sc->string_greater_2 = make_function_with_class(sc, f, "string>?", g_string_greater_2, 2, 0, false); + + /* stringstring_lt_symbol, string_less_chooser); + sc->string_less_2 = make_function_with_class(sc, f, "stringstring_symbol, string_chooser); + sc->string_c1 = make_function_with_class(sc, f, "string", g_string_c1, 1, 0, false); + + /* string-append */ + f = set_function_chooser(sc->string_append_symbol, string_append_chooser); + sc->string_append_2 = make_function_with_class(sc, f, "string-append", g_string_append_2, 2, 0, false); + + /* string-ref et al */ + set_function_chooser(sc->string_ref_symbol, string_substring_chooser); + set_function_chooser(sc->string_to_symbol_symbol, string_substring_chooser); /* not string_to_number here */ + set_function_chooser(sc->string_to_keyword_symbol, string_substring_chooser); + set_function_chooser(sc->string_downcase_symbol, string_substring_chooser); + set_function_chooser(sc->string_upcase_symbol, string_substring_chooser); + /* if the function assumes a null-terminated string, substring needs to return a copy */ +#if (!WITH_PURE_S7) + set_function_chooser(sc->string_length_symbol, string_substring_chooser); + set_function_chooser(sc->string_to_list_symbol, string_substring_chooser); +#endif + set_function_chooser(sc->string_copy_symbol, string_copy_chooser); + + /* symbol->string */ + f = global_value(sc->symbol_to_string_symbol); + sc->symbol_to_string_uncopied = s7_make_safe_function(sc, "symbol->string", g_symbol_to_string_uncopied, 1, 0, false, NULL); + s7_function_set_class(sc, sc->symbol_to_string_uncopied, f); + + /* display */ + f = set_function_chooser(sc->display_symbol, display_chooser); + sc->display_f = make_function_with_class(sc, f, "display", g_display_f, 2, 0, false); + sc->display_2 = make_function_with_class(sc, f, "display", g_display_2, 2, 0, false); + + /* vector */ + f = set_function_chooser(sc->vector_symbol, vector_chooser); + sc->vector_2 = make_function_with_class(sc, f, "vector", g_vector_2, 2, 0, false); + sc->vector_3 = make_function_with_class(sc, f, "vector", g_vector_3, 3, 0, false); + + /* vector-ref */ + f = set_function_chooser(sc->vector_ref_symbol, vector_ref_chooser); + sc->vector_ref_2 = make_function_with_class(sc, f, "vector-ref", g_vector_ref_2, 2, 0, false); + sc->vector_ref_3 = make_function_with_class(sc, f, "vector-ref", g_vector_ref_3, 3, 0, false); + + /* vector-set! */ + f = set_function_chooser(sc->vector_set_symbol, vector_set_chooser); + sc->vector_set_3 = make_function_with_class(sc, f, "vector-set!", g_vector_set_3, 3, 0, false); + sc->vector_set_4 = make_function_with_class(sc, f, "vector-set!", g_vector_set_4, 4, 0, false); + + /* float-vector-ref */ + f = set_function_chooser(sc->float_vector_ref_symbol, float_vector_ref_chooser); + sc->fv_ref_2 = make_function_with_class(sc, f, "float-vector-ref", g_fv_ref_2, 2, 0, false); + sc->fv_ref_3 = make_function_with_class(sc, f, "float-vector-ref", g_fv_ref_3, 3, 0, false); + + /* float-vector-set */ + f = set_function_chooser(sc->float_vector_set_symbol, float_vector_set_chooser); + sc->fv_set_3 = make_function_with_class(sc, f, "float-vector-set!", g_fv_set_3, 3, 0, false); + sc->fv_set_unchecked = make_function_with_class(sc, f, "float-vector-set!", g_fv_set_unchecked, 3, 0, false); + + /* int-vector-ref */ + f = set_function_chooser(sc->int_vector_ref_symbol, int_vector_ref_chooser); + sc->iv_ref_2 = make_function_with_class(sc, f, "int-vector-ref", g_iv_ref_2, 2, 0, false); + sc->iv_ref_3 = make_function_with_class(sc, f, "int-vector-ref", g_iv_ref_3, 3, 0, false); + + /* int-vector-set */ + f = set_function_chooser(sc->int_vector_set_symbol, int_vector_set_chooser); + sc->iv_set_3 = make_function_with_class(sc, f, "int-vector-set!", g_iv_set_3, 3, 0, false); + + /* byte-vector-ref */ + f = set_function_chooser(sc->byte_vector_ref_symbol, byte_vector_ref_chooser); + sc->bv_ref_2 = make_function_with_class(sc, f, "byte-vector-ref", g_bv_ref_2, 2, 0, false); + sc->bv_ref_3 = make_function_with_class(sc, f, "byte-vector-ref", g_bv_ref_3, 3, 0, false); + + /* byte-vector-set */ + f = set_function_chooser(sc->byte_vector_set_symbol, byte_vector_set_chooser); + sc->bv_set_3 = make_function_with_class(sc, f, "byte-vector-set!", g_bv_set_3, 3, 0, false); + + /* list-set! */ + f = set_function_chooser(sc->list_set_symbol, list_set_chooser); + sc->list_set_i = make_function_with_class(sc, f, "list-set!", g_list_set_i, 3, 0, false); + + /* hash-table-ref */ + f = set_function_chooser(sc->hash_table_ref_symbol, hash_table_ref_chooser); + sc->hash_table_ref_2 = make_function_with_class(sc, f, "hash-table-ref", g_hash_table_ref_2, 2, 0, false); + + /* hash-table-set! */ + set_function_chooser(sc->hash_table_set_symbol, hash_table_set_chooser); + + /* hash-table */ + f = set_function_chooser(sc->hash_table_symbol, hash_table_chooser); + sc->hash_table_2 = make_function_with_class(sc, f, "hash-table", g_hash_table_2, 2, 0, false); + + /* format */ + f = set_function_chooser(sc->format_symbol, format_chooser); + sc->format_f = make_function_with_class(sc, f, "format", g_format_f, 1, 0, true); + sc->format_no_column = make_function_with_class(sc, f, "format", g_format_no_column, 1, 0, true); + sc->format_just_control_string = make_function_with_class(sc, f, "format", g_format_just_control_string, 2, 0, false); + sc->format_as_objstr = make_function_with_class(sc, f, "format", g_format_as_objstr, 3, 0, true); + + /* list */ + f = set_function_chooser(sc->list_symbol, list_chooser); + sc->list_0 = make_function_with_class(sc, f, "list", g_list_0, 0, 0, false); + sc->list_1 = make_function_with_class(sc, f, "list", g_list_1, 1, 0, false); + sc->list_2 = make_function_with_class(sc, f, "list", g_list_2, 2, 0, false); + sc->list_3 = make_function_with_class(sc, f, "list", g_list_3, 3, 0, false); + sc->list_4 = make_function_with_class(sc, f, "list", g_list_4, 4, 0, false); + + /* append */ + f = set_function_chooser(sc->append_symbol, append_chooser); + sc->append_2 = make_function_with_class(sc, f, "append", g_append_2, 2, 0, false); + + /* list-ref */ + f = set_function_chooser(sc->list_ref_symbol, list_ref_chooser); + sc->list_ref_at_0 = make_function_with_class(sc, f, "list", g_list_ref_at_0, 2, 0, false); + sc->list_ref_at_1 = make_function_with_class(sc, f, "list", g_list_ref_at_1, 2, 0, false); + sc->list_ref_at_2 = make_function_with_class(sc, f, "list", g_list_ref_at_2, 2, 0, false); + + /* assoc */ + set_function_chooser(sc->assoc_symbol, assoc_chooser); + + /* member */ + set_function_chooser(sc->member_symbol, member_chooser); + + /* memq */ + f = set_function_chooser(sc->memq_symbol, memq_chooser); /* is pure-s7, use member here */ + sc->memq_2 = make_function_with_class(sc, f, "memq", g_memq_2, 2, 0, false); + sc->memq_3 = make_function_with_class(sc, f, "memq", g_memq_3, 2, 0, false); + sc->memq_4 = make_function_with_class(sc, f, "memq", g_memq_4, 2, 0, false); + sc->memq_any = make_function_with_class(sc, f, "memq", g_memq_any, 2, 0, false); + + /* tree-set-memq */ + f = set_function_chooser(sc->tree_set_memq_symbol, tree_set_memq_chooser); + sc->tree_set_memq_syms = make_function_with_class(sc, f, "tree-set-memq", g_tree_set_memq_1, 2, 0, false); + + /* eval-string */ + set_function_chooser(sc->eval_string_symbol, eval_string_chooser); + + /* dynamic-wind */ + f = set_function_chooser(sc->dynamic_wind_symbol, dynamic_wind_chooser); + sc->dynamic_wind_unchecked = make_unsafe_function_with_class(sc, f, "dynamic-wind", g_dynamic_wind_unchecked, 3, 0, false); + sc->dynamic_wind_body = make_unsafe_function_with_class(sc, f, "dynamic-wind", g_dynamic_wind_body, 3, 0, false); + sc->dynamic_wind_init = make_unsafe_function_with_class(sc, f, "dynamic-wind", g_dynamic_wind_init, 3, 0, false); + + /* inlet */ + f = set_function_chooser(sc->inlet_symbol, inlet_chooser); + sc->simple_inlet = make_function_with_class(sc, f, "inlet", g_simple_inlet, 0, 0, true); + + /* sublet */ + f = set_function_chooser(sc->sublet_symbol, sublet_chooser); + sc->sublet_curlet = make_function_with_class(sc, f, "sublet", g_sublet_curlet, 3, 0, false); + + /* let-ref */ + f = set_function_chooser(sc->let_ref_symbol, let_ref_chooser); + sc->lint_let_ref = make_function_with_class(sc, f, "let-ref", g_lint_let_ref, 2, 0, false); + + /* let-set */ + f = set_function_chooser(sc->let_set_symbol, let_set_chooser); + sc->lint_let_set = make_function_with_class(sc, f, "let-set!", g_lint_let_set, 3, 0, false); + + /* values */ + f = set_function_chooser(sc->values_symbol, values_chooser); + sc->values_uncopied = make_unsafe_function_with_class(sc, f, "values", splice_in_values, 0, 0, true); + + /* list-values */ + f = set_function_chooser(sc->list_values_symbol, list_values_chooser); + sc->simple_list_values = make_function_with_class(sc, f, "list-values", g_simple_list_values, 0, 0, true); +} + + +/* ---------------- *unbound-variable-hook* ---------------- */ +static s7_pointer loaded_library(s7_scheme *sc, const char *file) +{ + for (s7_pointer p = global_value(sc->libraries_symbol); is_pair(p); p = cdr(p)) + if (local_strcmp(file, string_value(caar(p)))) + return(cdar(p)); + return(sc->nil); +} + +static void pair_set_current_input_location(s7_scheme *sc, s7_pointer p) +{ + if (current_input_port(sc) != sc->standard_input) /* (port_file_number(current_input_port(sc)) > 1) -- maybe 0 is legit? */ + { + pair_set_location(p, port_location(current_input_port(sc))); + set_has_location(p); /* current_input_port(sc) above can't be nil(?) -- it falls back on stdin now */ + } +} + +static noreturn void unbound_variable_error_nr(s7_scheme *sc, s7_pointer sym) +{ + s7_pointer err_code = NULL; + + if ((is_pair(current_code(sc))) && (s7_tree_memq(sc, sym, current_code(sc)))) err_code = current_code(sc); + if ((is_pair(sc->code)) && (s7_tree_memq(sc, sym, sc->code))) err_code = sc->code; +#if WITH_HISTORY + { + s7_pointer p; + for (p = cdr(sc->cur_code); cdr(p) != sc->cur_code; p = cdr(p)); + if ((is_pair(car(p))) && (s7_tree_memq(sc, sym, car(p)))) err_code = car(p); + } +#endif + if (err_code) + error_nr(sc, sc->unbound_variable_symbol, + set_elist_3(sc, wrap_string(sc, "unbound variable ~S in ~S", 25), sym, err_code)); + + if ((symbol_name(sym)[symbol_name_length(sym) - 1] == ',') && + (lookup_unexamined(sc, make_symbol(sc, symbol_name(sym), symbol_name_length(sym) - 1)))) + error_nr(sc, sc->unbound_variable_symbol, + set_elist_2(sc, wrap_string(sc, "unbound variable ~S (perhaps a stray comma?)", 44), sym)); + + error_nr(sc, sc->unbound_variable_symbol, + set_elist_2(sc, wrap_string(sc, "unbound variable ~S", 19), sym)); +} + +static s7_pointer unbound_variable(s7_scheme *sc, s7_pointer sym) +{ + /* this always occurs in a context where we're trying to find anything, so I'll move a couple of those checks here */ + if (has_let_ref_fallback(sc->curlet)) /* an experiment -- see s7test (with-let *db* (+ int32_t (length str))) */ + return(call_let_ref_fallback(sc, sc->curlet, sym)); + /* but if the thing we want to hit this fallback happens to exist at a higher level, oops... */ + + if (sym == sc->unquote_symbol) + syntax_error_nr(sc, "unquote (',') occurred outside quasiquote: ~S", 45, current_code(sc)); + + if (safe_strcmp(symbol_name(sym), "|#")) + read_error_nr(sc, "unmatched |#"); + + /* check *autoload*, autoload_names, then *unbound-variable-hook* */ + if ((sc->autoload_names) || + (is_hash_table(sc->autoload_table)) || + ((is_procedure(sc->unbound_variable_hook)) && + (hook_has_functions(sc->unbound_variable_hook)))) + { + s7_pointer cur_code = current_code(sc); + s7_pointer value = sc->value; + s7_pointer code = sc->code; + s7_pointer current_let = sc->curlet; + s7_pointer x = sc->x; + s7_pointer z = sc->z; + /* sc->args and sc->code are pushed on the stack by s7_call, then + * restored by eval, so they are normally protected, but sc->value and current_code(sc) are + * not protected. We need current_code(sc) so that the possible eventual error + * call can tell where the error occurred, and we need sc->value because it might + * be awaiting addition to sc->args in e.g. OP_EVAL_ARGS5, and then be clobbered + * by the hook function. (+ 1 asdf) will end up evaluating (+ asdf asdf) if sc->value + * is not protected. We also need to save/restore sc->curlet in case s7_load is called. + */ + s7_pointer args = (sc->args) ? sc->args : sc->nil; + s7_pointer result = sc->undefined; + sc->temp7 = cons_unchecked(sc, current_let, cons_unchecked(sc, code, /* perhaps elist_7 except we use elist_3 above? */ + cons_unchecked(sc, args, list_4(sc, value, cur_code, x, z)))); /* not s7_list (debugger checks) */ + if (!is_pair(cur_code)) + { + /* isolated typo perhaps -- no pair to hold the position info, so make one. current_code(sc) is GC-protected, so this should be safe */ + cur_code = list_1(sc, sym); /* the error will say "(sym)" which is not too misleading */ + pair_set_current_input_location(sc, cur_code); + } + +#if (!DISABLE_AUTOLOAD) + if ((sc->is_autoloading) && + (sc->autoload_names)) /* created by s7_autoload_set_names which requires alphabetization by the caller (e.g. snd-xref.c) */ + { + bool loaded = false; + const char *file = find_autoload_name(sc, sym, &loaded, true); + if ((file) && (!loaded)) + { + /* if we've already loaded this file, we can get the library (e) from a table [(file lib) ...] + * here it was possible to get caught in a loop: + * change file, reload, unbound var seen, check autoload, it says "load file"... (where file does not get added to *libraries*) + * so the "loaded" arg tries to catch such cases + */ + s7_pointer e = loaded_library(sc, file); + if ((!e) || (!is_let(e))) + { + if (hook_has_functions(sc->autoload_hook)) + s7_apply_function(sc, sc->autoload_hook, set_plist_2(sc, sym, s7_make_string(sc, file))); + e = s7_load(sc, file); /* s7_load can return NULL */ + } + result = s7_symbol_value(sc, sym); /* calls lookup, does not trigger unbound_variable search */ + if ((result == sc->undefined) && (e) && (is_let(e))) + { + /* the current_let refs here are trying to handle local autoloads, but that is problematic -- we'd need to + * save the autoload curlet when autoload is called, and hope the current reference can still access that let? + * but if the same symbol is autloaded in several lets, we are in trouble, and how to handle a function that + * has an autoload? I think I'll just assume rootlet, even though that is not very elegant. Actually in the + * libgsl case, we're trying to export a name from *libgsl* -- should that be done with define rather than autoload? + */ + result = let_ref(sc, e, sym); /* add '(sym . result) to current_let (was sc->nil, s7_load can set sc->curlet to sc->nil) */ + if (result != sc->undefined) + s7_define(sc, sc->nil /* current_let */, sym, result); + }}} +#endif + if (result == sc->undefined) + { +#if (!DISABLE_AUTOLOAD) + /* check the *autoload* hash table */ + if ((sc->is_autoloading) && + (is_hash_table(sc->autoload_table))) + { + /* it was possible to get in a loop here: missing paren in x.scm, checks last symbol, sees + * autoload sym -> x.scm, loads x.scm, missing paren... + */ + s7_pointer val = s7_hash_table_ref(sc, sc->autoload_table, sym); + s7_pointer e = NULL; + if (is_string(val)) /* val should be a filename. *load-path* is searched if necessary */ + { + if (hook_has_functions(sc->autoload_hook)) + s7_apply_function(sc, sc->autoload_hook, set_plist_2(sc, sym, val)); + e = s7_load(sc, string_value(val)); + } + else + if (is_closure(val)) /* val should be a function of one argument, the current (calling) environment */ + { + if (hook_has_functions(sc->autoload_hook)) + s7_apply_function(sc, sc->autoload_hook, set_plist_2(sc, sym, val)); + e = s7_call(sc, val, set_ulist_1(sc, sc->curlet, sc->nil)); + } + result = s7_symbol_value(sc, sym); /* calls lookup, does not trigger unbound_variable search */ + if ((result == sc->undefined) && (e) && (is_let(e))) /* added 31-Mar-23 to match sc->autoload_names case above */ + { + result = let_ref(sc, e, sym); + if (result != sc->undefined) + s7_define(sc, sc->nil /* current_let */, sym, result); /* as above, was sc->nil -- s7_load above can set sc->curlet to sc->nil */ + }} +#endif + /* check *unbound-variable-hook* */ + if ((result == sc->undefined) && + (is_procedure(sc->unbound_variable_hook)) && + (hook_has_functions(sc->unbound_variable_hook))) + { + /* (let () (set! (hook-functions *unbound-variable-hook*) (list (lambda (v) _asdf_))) _asdf_) */ + s7_pointer old_hook = sc->unbound_variable_hook; + bool old_history_enabled = s7_set_history_enabled(sc, false); + gc_protect_via_stack(sc, old_hook); + sc->unbound_variable_hook = sc->nil; + result = s7_call(sc, old_hook, set_plist_1(sc, sym)); /* not s7_apply_function */ + sc->unbound_variable_hook = old_hook; + s7_set_history_enabled(sc, old_history_enabled); + unstack(sc); + }} + sc->value = T_Ext(value); + sc->args = T_Pos(args); /* can be # */ + sc->code = code; + set_curlet(sc, current_let); + sc->x = x; + sc->z = z; + sc->temp7 = sc->unused; + if ((result != sc->undefined) && + (result != sc->unspecified)) + return(result); + } + unbound_variable_error_nr(sc, sym); + return(sc->unbound_variable_symbol); +} + +static bool gx_annotate_arg(s7_scheme *sc, s7_pointer p, s7_pointer e) +{ + if (is_gxable(car(p))) + { + opcode_t old_op = optimize_op(car(p)); + s7_pointer fxf; + set_optimize_op(car(p), old_op + 1); + fxf = (s7_pointer)fx_choose(sc, p, e, (is_list(e)) ? pair_symbol_is_safe : let_symbol_is_safe); + if (fxf) + { + set_has_gx(p); + set_opt2(p, fxf, OPT2_FX); + } + set_optimize_op(car(p), old_op); + return(fxf); + } + return(false); +} + +static void gx_annotate_args(s7_scheme *sc, s7_pointer args, s7_pointer e) +{ + for (s7_pointer p = args; is_pair(p); p = cdr(p)) + gx_annotate_arg(sc, p, e); +} + +#define choose_c_function(Sc, Expr, Func, Args) set_c_function(Expr, c_function_chooser(Func)(Sc, Func, Args, Expr, true)) + +static void fx_annotate_arg(s7_scheme *sc, s7_pointer arg, s7_pointer e) +{ +#if S7_DEBUGGING + s7_function fx; + if (has_fx(arg)) return; + fx = fx_choose(sc, arg, e, (is_list(e)) ? pair_symbol_is_safe : let_symbol_is_safe); + if (fx) set_fx_direct(arg, fx); +#else + if (has_fx(arg)) return; + set_fx(arg, fx_choose(sc, arg, e, (is_list(e)) ? pair_symbol_is_safe : let_symbol_is_safe)); +#endif +} + +static void fx_annotate_args(s7_scheme *sc, s7_pointer args, s7_pointer e) +{ + for (s7_pointer p = args; is_pair(p); p = cdr(p)) +#if S7_DEBUGGING + fx_annotate_arg(sc, p, e); /* checks has_fx */ +#else + if (!has_fx(p)) + set_fx(p, fx_choose(sc, p, e, (is_list(e)) ? pair_symbol_is_safe : let_symbol_is_safe)); +#endif +} + +static opt_t optimize_thunk(s7_scheme *sc, s7_pointer expr, s7_pointer func, int32_t hop, s7_pointer e) +{ + if ((hop != 1) && (is_constant_symbol(sc, car(expr)))) hop = 1; + + if ((is_closure(func)) || (is_closure_star(func))) + { + bool safe_case = is_safe_closure(func); + s7_pointer body = closure_body(func); + bool one_form = is_null(cdr(body)); + + if (is_immutable(func)) hop = 1; + if (is_null(closure_args(func))) /* no rest arg funny business */ + { + set_optimized(expr); + if ((one_form) && (safe_case) && (is_fxable(sc, car(body)))) /* fx stuff is not set yet */ + { + fx_annotate_arg(sc, body, e); + set_optimize_op(expr, hop + OP_SAFE_THUNK_A); + set_closure_one_form_fx_arg(func); + set_opt1_lambda_add(expr, func); + return(OPT_T); + } + /* thunks with fully fxable bodies are rare apparently, and the time spent here overwhelms run time gains */ + set_optimize_op(expr, hop + ((safe_case) ? OP_SAFE_THUNK : ((one_form) ? OP_THUNK_O : OP_THUNK))); + set_opt1_lambda_add(expr, func); + return((safe_case) ? OPT_T : OPT_F); + } + + if (is_symbol(closure_args(func))) /* (define* (f1 . a) ...) called (f1) -- called a closure (not closure*) in define_unchecked */ + { + set_opt1_lambda_add(expr, func); + if (safe_case) + { + if (!has_fx(body)) + { + fx_annotate_args(sc, body, e); + fx_tree(sc, body, closure_args(func), NULL, NULL, false); + } + set_safe_optimize_op(expr, hop + OP_SAFE_THUNK_ANY); + return(OPT_T); + } + set_unsafe_optimize_op(expr, hop + OP_THUNK_ANY); /* "thunk" because here it is called with no args, I guess */ + return(OPT_F); + } + if (is_closure_star(func)) + { + set_opt1_lambda_add(expr, func); + set_safe_optimize_op(expr, hop + ((safe_case) ? OP_SAFE_CLOSURE_STAR_NA_0 : OP_CLOSURE_STAR_NA)); + } + return(OPT_F); + } + if (is_c_function(func)) + { + if (c_function_min_args(func) != 0) + return(OPT_F); + if ((hop == 0) && (symbol_id(car(expr)) == 0)) hop = 1; + if (is_safe_procedure(func)) + { + set_safe_optimize_op(expr, hop + OP_SAFE_C_NC); + choose_c_function(sc, expr, func, 0); + return(OPT_T); + } + set_unsafe_optimize_op(expr, hop + OP_C); + choose_c_function(sc, expr, func, 0); + return(OPT_F); + } + if (is_c_function_star(func)) + { + set_safe_optimize_op(expr, hop + OP_SAFE_C_STAR); + set_c_function(expr, func); + return(OPT_T); + } + return(OPT_F); +} + +static int32_t combine_ops(s7_scheme *sc, s7_pointer expr, combine_op_t cop, s7_pointer e1, s7_pointer e2) /* sc needed for debugger stuff */ +{ /* sc arg is used if debugging (hidden in set_op2_con for example) */ + switch (cop) + { + case E_C_P: + switch (op_no_hop(e1)) + { + case OP_SAFE_C_S: return(OP_SAFE_C_opSq); + case OP_SAFE_C_NC: return(OP_SAFE_C_opNCq); + case OP_SAFE_C_SC: return(OP_SAFE_C_opSCq); + case OP_SAFE_C_CS: return(OP_SAFE_C_opCSq); + case OP_SAFE_C_A: return(OP_SAFE_C_opAq); + case OP_SAFE_C_AA: return(OP_SAFE_C_opAAq); + case OP_SAFE_C_AAA: return(OP_SAFE_C_opAAAq); + case OP_SAFE_C_SS: + set_opt3_sym(expr, cadr(e1)); + set_opt1_sym(cdr(expr), caddr(e1)); + return(OP_SAFE_C_opSSq); + case OP_SAFE_C_opSq: + set_opt3_pair(expr, cadr(e1)); + set_opt3_sym(cdr(expr), cadadr(e1)); + return(OP_SAFE_C_op_opSqq); + case OP_SAFE_C_S_opSq: + set_opt3_pair(expr, caddr(e1)); + return(OP_SAFE_C_op_S_opSqq); + case OP_SAFE_C_opSq_S: + set_opt3_pair(expr, cadr(e1)); + return(OP_SAFE_C_op_opSq_Sq); + } + return(OP_SAFE_C_P); /* this splits out to A in optimize_func one_arg */ + + case E_C_SP: + switch (op_no_hop(e2)) + { + case OP_SAFE_C_S: return(OP_SAFE_C_S_opSq); + case OP_SAFE_C_AA: return(OP_SAFE_C_S_opAAq); + case OP_SAFE_C_SC: + set_opt2_con(cdr(expr), caddr(e2)); + return(OP_SAFE_C_S_opSCq); + case OP_SAFE_C_CS: /* expr is (* a (- 1 b)), e2 is (- 1 b) */ + set_opt2_sym(cdr(expr), caddr(e2)); + return(OP_SAFE_C_S_opCSq); + case OP_SAFE_C_SS: /* (* a (- b c)) */ + set_opt2_sym(cdr(expr), caddr(e2)); + return(OP_SAFE_C_S_opSSq); + case OP_SAFE_C_A: + set_opt3_pair(expr, cdaddr(expr)); + return(OP_SAFE_C_S_opAq); + } + return(OP_SAFE_C_SP); /* if fxable -> AA later */ + + case E_C_PS: + switch (op_no_hop(e1)) + { + case OP_SAFE_C_S: + set_opt1_sym(cdr(expr), cadr(e1)); + set_opt3_sym(expr, e2); + return(OP_SAFE_C_opSq_S); + case OP_SAFE_C_SS: return(OP_SAFE_C_opSSq_S); + case OP_SAFE_C_CS: return(OP_SAFE_C_opCSq_S); + case OP_SAFE_C_A: return(OP_SAFE_C_opAq_S); + case OP_SAFE_C_opSSq: + set_opt1_pair(cdr(expr), cadadr(expr)); + set_opt3_pair(expr, cadr(e1)); + return(OP_SAFE_C_op_opSSqq_S); + } + return(OP_SAFE_C_PS); + + case E_C_PC: + switch (op_no_hop(e1)) + { + case OP_SAFE_C_S: + set_opt1_sym(cdr(expr), cadr(e1)); + set_opt2_con(cdr(expr), e2); + return(OP_SAFE_C_opSq_C); + case OP_SAFE_C_CS: return(OP_SAFE_C_opCSq_C); + case OP_SAFE_C_SC: return(OP_SAFE_C_opSCq_C); + case OP_SAFE_C_SS: + set_opt3_con(cdr(expr), caddr(expr)); + return(OP_SAFE_C_opSSq_C); + } + set_opt3_con(cdr(expr), caddr(expr)); + return(OP_SAFE_C_PC); + + case E_C_CP: + switch (op_no_hop(e2)) + { + case OP_SAFE_C_S: + set_opt3_pair(expr, e2); + return(OP_SAFE_C_C_opSq); + case OP_SAFE_C_SC: + set_opt1_sym(cdr(expr), cadr(e2)); + set_opt2_con(cdr(expr), caddr(e2)); + return(OP_SAFE_C_C_opSCq); + case OP_SAFE_C_SS: + set_opt1_sym(cdr(expr), cadr(e2)); + return(OP_SAFE_C_C_opSSq); + } + return(OP_SAFE_C_CP); + + case E_C_PP: + switch (op_no_hop(e2)) + { + case OP_SAFE_C_S: + if (is_safe_c_s(e1)) + return(OP_SAFE_C_opSq_opSq); + if (optimize_op_match(e1, OP_SAFE_C_SS)) + return(OP_SAFE_C_opSSq_opSq); + break; + case OP_SAFE_C_SS: + if (optimize_op_match(e1, OP_SAFE_C_SS)) + return(OP_SAFE_C_opSSq_opSSq); + if (is_safe_c_s(e1)) + return(OP_SAFE_C_opSq_opSSq); + break; + } + return(OP_SAFE_C_PP); + + default: break; + } + return(OP_UNOPT); +} + +static bool arg_findable(s7_scheme *sc, s7_pointer arg1, s7_pointer e) +{ + if (pair_symbol_is_safe(sc, arg1, e)) return(true); /* includes global_slot check */ + return((!sc->in_with_let) && + (is_slot(s7_slot(sc, arg1)))); +} + +static bool safe_c_aa_to_ag_ga(s7_scheme *sc, s7_pointer arg, int32_t hop) +{ + if (fx_proc(cddr(arg)) == fx_s) {set_opt3_sym(arg, caddr(arg)); set_safe_optimize_op(arg, hop + OP_SAFE_C_AS); return(true);} + if (fx_proc(cdr(arg)) == fx_s) {set_opt3_sym(arg, cadr(arg)); set_safe_optimize_op(arg, hop + OP_SAFE_C_SA); return(true);} + if (fx_proc(cddr(arg)) == fx_c) {set_opt3_con(arg, caddr(arg)); set_safe_optimize_op(arg, hop + OP_SAFE_C_AC); return(true);} + if (fx_proc(cdr(arg)) == fx_c) {set_opt3_con(arg, cadr(arg)); set_safe_optimize_op(arg, hop + OP_SAFE_C_CA); return(true);} + if (fx_proc(cddr(arg)) == fx_q) {set_opt3_con(arg, cadaddr(arg)); set_safe_optimize_op(arg, hop + OP_SAFE_C_AC); return(true);} + if (fx_proc(cdr(arg)) == fx_q) {set_opt3_con(arg, cadadr(arg)); set_safe_optimize_op(arg, hop + OP_SAFE_C_CA); return(true);} + return(false); +} + +static opt_t check_c_aa(s7_scheme *sc, s7_pointer expr, s7_pointer func, int32_t hop, s7_pointer e) +{ + fx_annotate_args(sc, cdr(expr), e); + if (!safe_c_aa_to_ag_ga(sc, expr, hop)) + { + set_optimize_op(expr, hop + OP_SAFE_C_AA); + set_opt3_pair(expr, cddr(expr)); + } + choose_c_function(sc, expr, func, 2); + return(OPT_T); +} + +static opt_t wrap_bad_args(s7_scheme *sc, s7_pointer func, s7_pointer expr, int32_t n_args, int32_t hop, s7_pointer e) +{ + set_opt3_arglen(cdr(expr), n_args); + if (is_c_function(func)) + { + set_safe_optimize_op(expr, hop + ((is_safe_procedure(func)) ? + ((n_args == 1) ? OP_SAFE_C_A : OP_SAFE_C_AA) : + ((n_args == 1) ? ((is_semisafe(func)) ? OP_CL_A : OP_C_A) : + ((is_semisafe(func)) ? OP_CL_AA : OP_C_AA)))); + if (op_no_hop(expr) == OP_SAFE_C_AA) + { + set_opt3_pair(expr, cddr(expr)); + if (optimize_op(expr) == HOP_SAFE_C_AA) return(check_c_aa(sc, expr, func, hop, e)); + } + set_c_function(expr, func); + return(OPT_T); + } + if ((is_closure(func)) && + (!arglist_has_rest(sc, closure_args(func)))) + { + s7_pointer body = closure_body(func); + bool one_form = is_null(cdr(body)), safe_case = is_safe_closure(func); + set_unsafely_optimized(expr); + set_opt1_lambda_add(expr, func); + if (one_form) + set_optimize_op(expr, hop + ((safe_case) ? + ((n_args == 1) ? OP_SAFE_CLOSURE_A_O : OP_SAFE_CLOSURE_AA_O) : + ((n_args == 1) ? OP_CLOSURE_A_O : OP_CLOSURE_AA_O))); + else + set_optimize_op(expr, hop + ((safe_case) ? + ((n_args == 1) ? OP_SAFE_CLOSURE_A : OP_SAFE_CLOSURE_AA) : + ((n_args == 1) ? OP_CLOSURE_A : OP_CLOSURE_AA))); + return(OPT_F); + } + if ((is_closure_star(func)) && + (lambda_has_simple_defaults(func)) && + (closure_star_arity_to_int(sc, func) >= n_args) && + (!arglist_has_rest(sc, closure_args(func)))) + { + set_unsafely_optimized(expr); + if (n_args == 1) + set_optimize_op(expr, ((is_safe_closure(func)) ? OP_SAFE_CLOSURE_STAR_A : OP_CLOSURE_STAR_A)); + else + if (closure_star_arity_to_int(sc, func) == 2) + set_optimize_op(expr, ((is_safe_closure(func)) ? ((is_null(cdr(closure_body(func)))) ? OP_SAFE_CLOSURE_STAR_AA_O : + OP_SAFE_CLOSURE_STAR_AA) : OP_CLOSURE_STAR_NA)); + else set_optimize_op(expr, ((is_safe_closure(func)) ? OP_SAFE_CLOSURE_STAR_NA : OP_CLOSURE_STAR_NA)); + set_opt1_lambda_add(expr, func); + } + return(OPT_F); +} + +static inline s7_pointer find_uncomplicated_symbol(s7_scheme *sc, s7_pointer symbol, s7_pointer e) +{ + s7_pointer x; + int64_t id; + + if ((symbol_is_in_list(sc, symbol)) && + (direct_memq(symbol, e))) /* it's probably a local variable reference */ + return(sc->nil); + /* ((!symbol_is_in_list(sc, symbol)) && (direct_memq(symbol, e))) can happen if there's an intervening lambda: + * (let loop () (with-let (for-each (lambda (a) a) (list))) (loop)) + * misses 'loop (it's not in symbol_list when recursive call is encountered) -- tricky to fix + */ + + if (is_global(symbol)) + return(global_slot(symbol)); + + /* see 59108 (OP_DEFINE_* in optimize_syntax) -- keyword version of name is used if a definition is + * contingent on some run-time decision, so we're looking here for local defines that might not happen. + * s7test.scm has a test case using acos. + */ + if ((has_keyword(symbol)) && + (symbol_is_in_list(sc, symbol_to_keyword(sc, symbol)))) + return(sc->nil); + + for (x = sc->curlet, id = symbol_id(symbol); id < let_id(x); x = let_outlet(x)); + for (; is_let(x); x = let_outlet(x)) + { + if (let_id(x) == id) + return(local_slot(symbol)); + for (s7_pointer y = let_slots(x); tis_slot(y); y = next_slot(y)) + if (slot_symbol(y) == symbol) + return(y); + } + return(global_slot(symbol)); /* it's no longer global perhaps (local definition now inaccessible) */ +} + +static bool is_ok_lambda(s7_scheme *sc, s7_pointer arg2) +{ + return((is_pair(arg2)) && + (is_lambda(sc, car(arg2))) && /* must start (lambda ...) */ + (is_pair(cdr(arg2))) && /* must have arg(s) */ + (is_pair(cddr(arg2))) && /* must have body */ + (s7_is_proper_list(sc, cdddr(arg2)))); +} + +static bool hop_if_constant(s7_scheme *sc, s7_pointer sym) +{ + return(((!sc->in_with_let) && (symbol_id(sym) == 0)) ? 1 : 0); /* for with-let, see s7test atanh (77261) */ +} + +static opt_t optimize_c_function_one_arg(s7_scheme *sc, s7_pointer expr, s7_pointer func, + int32_t hop, int32_t pairs, int32_t symbols, int32_t quotes, int32_t bad_pairs, s7_pointer e) +{ + s7_pointer arg1 = cadr(expr); + bool func_is_safe = is_safe_procedure(func); + if (hop == 0) hop = hop_if_constant(sc, car(expr)); + if (pairs == 0) + { + if (func_is_safe) /* safe c function */ + { + set_safe_optimize_op(expr, hop + ((symbols == 0) ? OP_SAFE_C_NC : OP_SAFE_C_S)); + choose_c_function(sc, expr, func, 1); + return(OPT_T); + } + /* c function is not safe */ + if (symbols == 0) + { + set_unsafe_optimize_op(expr, hop + ((is_semisafe(func)) ? OP_CL_A : OP_C_A)); /* OP_C_C never happens */ + fx_annotate_arg(sc, cdr(expr), e); + set_opt3_arglen(cdr(expr), 1); + } + else + { + set_unsafely_optimized(expr); + if (c_function_call(func) == g_read) + set_optimize_op(expr, hop + OP_READ_S); + else set_optimize_op(expr, hop + ((is_semisafe(func)) ? OP_CL_S : OP_C_S)); + } + choose_c_function(sc, expr, func, 1); + return(OPT_F); + } + /* pairs == 1 */ + if (bad_pairs == 0) + { + if (func_is_safe) + { + int32_t op = combine_ops(sc, expr, E_C_P, arg1, NULL); + /* if ((hop == 1) && (!op_has_hop(arg1))) hop = 0; *//* probably not the right way to fix this (s7test tc_or_a_and_a_a_la) */ + set_safe_optimize_op(expr, hop + op); + + if ((op == OP_SAFE_C_P) && + (is_fxable(sc, arg1))) + { + set_optimize_op(expr, hop + OP_SAFE_C_A); + fx_annotate_arg(sc, cdr(expr), e); + } + choose_c_function(sc, expr, func, 1); + return(OPT_T); + } + if (is_fxable(sc, arg1)) + { + set_unsafe_optimize_op(expr, hop + ((is_semisafe(func)) ? OP_CL_A : OP_C_A)); + fx_annotate_arg(sc, cdr(expr), e); + set_opt3_arglen(cdr(expr), 1); + choose_c_function(sc, expr, func, 1); + return(OPT_F); + }} + else /* bad_pairs == 1 */ + { + if (quotes == 1) + { + fx_annotate_arg(sc, cdr(expr), e); + set_opt3_arglen(cdr(expr), 1); + if (func_is_safe) + { + set_safe_optimize_op(expr, hop + OP_SAFE_C_A); + choose_c_function(sc, expr, func, 1); + return(OPT_T); + } + set_unsafe_optimize_op(expr, hop + ((is_semisafe(func)) ? OP_CL_A : OP_C_A)); + choose_c_function(sc, expr, func, 1); + return(OPT_F); + } + /* quotes == 0 */ + if (!func_is_safe) + { + s7_pointer lambda_expr = arg1; + if ((is_ok_lambda(sc, lambda_expr)) && + (!direct_memq(car(lambda_expr), e))) /* (let ((lambda #f)) (call-with-exit (lambda ...))) */ + { + if (((c_function_call(func) == g_call_with_exit) || + (c_function_call(func) == g_call_cc) || + (c_function_call(func) == g_call_with_output_string)) && + (is_proper_list_1(sc, cadr(lambda_expr))) && + (is_symbol(caadr(lambda_expr))) && + (!is_probably_constant(caadr(lambda_expr)))) /* (call-with-exit (lambda (pi) ...) */ + { + if (c_function_call(func) == g_call_cc) + set_unsafe_optimize_op(expr, OP_CALL_CC); + else + if (c_function_call(func) == g_call_with_exit) + set_unsafe_optimize_op(expr, (is_null(cdddr(lambda_expr))) ? OP_CALL_WITH_EXIT_O : OP_CALL_WITH_EXIT); + else + { + set_unsafe_optimize_op(expr, OP_CALL_WITH_OUTPUT_STRING); + set_opt2_pair(expr, cddr(lambda_expr)); + set_opt3_sym(expr, caadr(lambda_expr)); + set_local(caadr(lambda_expr)); + return(OPT_F); + } + choose_c_function(sc, expr, func, 1); + set_opt2_pair(expr, cdr(lambda_expr)); + set_local(caadr(lambda_expr)); /* check_lambda_args normally handles this, but if hop==1, we'll skip that step */ + return(OPT_F); + } + if ((c_function_call(func) == g_with_output_to_string) && + (is_null(cadr(lambda_expr)))) + { + set_unsafe_optimize_op(expr, OP_WITH_OUTPUT_TO_STRING); + set_opt2_pair(expr, cddr(lambda_expr)); + return(OPT_F); + }}}} + set_unsafe_optimize_op(expr, hop + ((func_is_safe) ? OP_SAFE_C_P : OP_C_P)); + choose_c_function(sc, expr, func, 1); + return(OPT_F); +} + +static bool walk_fxable(s7_scheme *sc, s7_pointer tree) +{ + for (s7_pointer p = cdr(tree); is_pair(p); p = cdr(p)) + { + s7_pointer q = car(p); + if ((is_pair(q)) && + (is_optimized(q))) + { + opcode_t op = optimize_op(q); + if (is_safe_c_op(op)) return(true); + if ((op >= OP_TC_AND_A_OR_A_LA) || + ((op >= OP_THUNK) && (op < OP_BEGIN)) || + (!walk_fxable(sc, q))) + return(false); + }} + return(true); +} + +static bool is_safe_fxable(s7_scheme *sc, s7_pointer p) +{ + if (!is_pair(p)) return(true); + if (is_optimized(p)) + { + if ((fx_function[optimize_op(p)]) && + (walk_fxable(sc, (p)))) + return(true); + } + if (is_proper_quote(sc, p)) return(true); + if ((S7_DEBUGGING) && (is_optimized(p)) && (fx_function[optimize_op(p)])) fprintf(stderr, "omit %s: %s\n", op_names[optimize_op(p)], display(p)); + return(false); +} + +static opt_t fxify_closure_s(s7_scheme *sc, s7_pointer func, s7_pointer expr, s7_pointer e, int32_t hop) +{ + s7_pointer body = closure_body(func); + fx_annotate_arg(sc, body, e); + /* we can't currently fx_annotate_arg(sc, cdr(expr), e) here because that opt2 field is in use elsewhere (opt2_sym, not sure where it's set) */ + set_safe_optimize_op(expr, hop + OP_SAFE_CLOSURE_S_A); + if ((is_pair(car(body))) && (is_pair(cdar(body))) && (car(closure_args(func)) == cadar(body))) + { + if (optimize_op(car(body)) == HOP_SAFE_C_S) + set_safe_optimize_op(expr, hop + OP_SAFE_CLOSURE_S_TO_S); + else + if (optimize_op(car(body)) == HOP_SAFE_C_SC) + { + s7_pointer body_arg2 = caddar(body); + set_opt3_con(cdr(expr), (is_pair(body_arg2)) ? cadr(body_arg2) : body_arg2); + set_safe_optimize_op(expr, hop + OP_SAFE_CLOSURE_S_TO_SC); + if ((caar(body) == sc->vector_ref_symbol) && (is_global(sc->vector_ref_symbol))) + set_fx_direct(cdr(expr), fx_safe_closure_s_to_vref); + else + { + set_fx_direct(cdr(expr), fx_safe_closure_s_to_sc); + if ((is_t_integer(body_arg2)) && (integer(body_arg2) == 1)) + { + if (caar(body) == sc->subtract_symbol) set_fx_direct(cdr(expr), fx_safe_closure_s_to_sub1); + if (caar(body) == sc->add_symbol) set_fx_direct(cdr(expr), fx_safe_closure_s_to_add1); + }}}} + set_closure_one_form_fx_arg(func); + fx_tree(sc, body, car(closure_args(func)), NULL, NULL, false); + return(OPT_T); +} + +static bool fxify_closure_a(s7_scheme *sc, s7_pointer func, bool one_form, bool safe_case, int32_t hop, s7_pointer expr, s7_pointer e) +{ + if (!one_form) + set_optimize_op(expr, hop + ((safe_case) ? OP_SAFE_CLOSURE_A : OP_CLOSURE_A)); + else + if (!safe_case) + set_optimize_op(expr, hop + OP_CLOSURE_A_O); + else + { + s7_pointer body = closure_body(func); + if (!is_fxable(sc, car(body))) + set_optimize_op(expr, hop + OP_SAFE_CLOSURE_A_O); + else + { + fx_annotate_arg(sc, body, e); + set_safe_optimize_op(expr, hop + OP_SAFE_CLOSURE_A_A); + if ((is_pair(car(body))) && + (optimize_op(car(body)) == HOP_SAFE_C_SC) && + (car(closure_args(func)) == cadar(body))) + { + s7_pointer body_arg2 = caddar(body); + set_opt3_con(cdr(expr), (is_pair(body_arg2)) ? cadr(body_arg2) : body_arg2); + set_safe_optimize_op(expr, hop + OP_SAFE_CLOSURE_A_TO_SC); + /* why is this setting expr whereas _s case above sets cdr(expr)? */ + if ((caar(body) == sc->vector_ref_symbol) && (is_global(sc->vector_ref_symbol))) + set_fx_direct(expr, fx_safe_closure_a_to_vref); + else set_fx_direct(expr, fx_safe_closure_a_to_sc); + } + set_closure_one_form_fx_arg(func); + fx_tree(sc, body, car(closure_args(func)), NULL, NULL, false); + return(true); + }} + return(false); +} + +static opt_t optimize_closure_sym(s7_scheme *sc, s7_pointer expr, s7_pointer func, int32_t hop, int32_t args, s7_pointer e) +{ + if (fx_count(sc, expr) != args) /* fx_count starts at cdr, args here is the number of exprs in cdr(expr) -- so this means "are all args fxable" */ + return(OPT_F); + set_opt3_arglen(cdr(expr), args); + set_opt1_lambda_add(expr, func); + fx_annotate_args(sc, cdr(expr), e); + if (is_safe_closure(func)) + { + s7_pointer body = closure_body(func); + if (!has_fx(body)) /* does this have any effect? */ + { + fx_annotate_args(sc, body, e); + fx_tree(sc, body, closure_args(func), NULL, NULL, false); + } + set_safe_optimize_op(expr, hop + OP_ANY_CLOSURE_SYM); + return(OPT_T); + } + set_unsafe_optimize_op(expr, hop + OP_ANY_CLOSURE_SYM); + return(OPT_F); +} + +static opt_t optimize_closure_a_sym(s7_scheme *sc, s7_pointer expr, s7_pointer func, int32_t hop, int32_t args, s7_pointer e) +{ + if (fx_count(sc, expr) != args) return(OPT_F); + set_opt3_arglen(cdr(expr), args); + set_opt1_lambda_add(expr, func); + fx_annotate_args(sc, cdr(expr), e); + if (is_safe_closure(func)) + { + s7_pointer body = closure_body(func); + if (!has_fx(body)) /* does this have any effect? */ + { + fx_annotate_args(sc, body, e); + fx_tree(sc, body, car(closure_args(func)), cdr(closure_args(func)), NULL, false); + } + set_safe_optimize_op(expr, hop + OP_ANY_CLOSURE_A_SYM); + return(OPT_T); + } + set_unsafe_optimize_op(expr, hop + OP_ANY_CLOSURE_A_SYM); + return(OPT_F); +} + +static opt_t optimize_closure_one_arg(s7_scheme *sc, s7_pointer expr, s7_pointer func, int32_t hop, int32_t symbols, s7_pointer e) +{ + bool one_form, safe_case; + s7_pointer body, arg1 = cadr(expr); + int32_t arit = closure_arity_to_int(sc, func); + if (arit != 1) + { + if (is_symbol(closure_args(func))) /* (arit == -1) is ambiguous: (define (f . a)...) and (define (f a . b)...) both are -1 here */ + return(optimize_closure_sym(sc, expr, func, hop, 1, e)); + if ((arit == -1) && (is_symbol(cdr(closure_args(func))))) + return(optimize_closure_a_sym(sc, expr, func, hop, 1, e)); + return(OPT_F); + } + safe_case = is_safe_closure(func); + body = closure_body(func); + one_form = is_null(cdr(body)); + if (is_immutable(func)) hop = 1; + + if (symbols == 1) + { + set_opt2_sym(expr, arg1); + set_opt1_lambda_add(expr, func); + if (one_form) + { + if (safe_case) + { + if (is_fxable(sc, car(body))) + return(fxify_closure_s(sc, func, expr, e, hop)); + set_optimize_op(expr, hop + OP_SAFE_CLOSURE_S_O); /* tleft 7638 if _O here, 7692 if not (and claims 80 in the begin setup) */ + } + else set_optimize_op(expr, hop + OP_CLOSURE_S_O); + } + else set_optimize_op(expr, hop + ((safe_case) ? OP_SAFE_CLOSURE_S : OP_CLOSURE_S)); + set_unsafely_optimized(expr); + return(OPT_F); + } + if (fx_count(sc, expr) == 1) + { + set_unsafely_optimized(expr); + set_opt1_lambda_add(expr, func); + fx_annotate_arg(sc, cdr(expr), e); + set_opt3_arglen(cdr(expr), 1); + if (fxify_closure_a(sc, func, one_form, safe_case, hop, expr, e)) return(OPT_T); + set_unsafely_optimized(expr); + return(OPT_F); + } + set_optimize_op(expr, hop + ((safe_case) ? OP_SAFE_CLOSURE_P : OP_CLOSURE_P)); + set_opt1_lambda_add(expr, func); + set_opt3_arglen(cdr(expr), 1); + set_unsafely_optimized(expr); + if ((safe_case) && (one_form) && (is_fxable(sc, car(closure_body(func))))) + { + set_optimize_op(expr, hop + OP_SAFE_CLOSURE_P_A); /* other possibilities: 3p fp (ap|pa only get a few hits), but none of these matter much */ + fx_annotate_arg(sc, closure_body(func), e); + } + return(OPT_F); /* don't check is_optimized here for OPT_T */ +} + +static opt_t optimize_func_one_arg(s7_scheme *sc, s7_pointer expr, s7_pointer func, + int32_t hop, int32_t pairs, int32_t symbols, int32_t quotes, int32_t bad_pairs, s7_pointer e) +{ + s7_pointer arg1; + /* very often, expr is already optimized, quoted stuff is counted under "bad_pairs"! as well as quotes */ + if (quotes > 0) + { + if (direct_memq(sc->quote_symbol, e)) + return(OPT_OOPS); + if ((bad_pairs == quotes) && + (is_symbol(car(expr))) && + (is_constant_symbol(sc, car(expr)))) + hop = 1; + } + arg1 = cadr(expr); + /* need in_with_let -> search only rootlet not lookup */ + if ((symbols == 1) && + (!arg_findable(sc, arg1, e))) + { + /* wrap the bad arg in a check symbol lookup */ + if (s7_is_aritable(sc, func, 1)) + { + set_fx_direct(cdr(expr), fx_unsafe_s); + return(wrap_bad_args(sc, func, expr, 1, hop, e)); + } + return(OPT_F); + } + if ((is_c_function(func)) && (c_function_is_aritable(func, 1))) + return(optimize_c_function_one_arg(sc, expr, func, hop, pairs, symbols, quotes, bad_pairs, e)); + + if (is_closure(func)) + return(optimize_closure_one_arg(sc, expr, func, hop, symbols, e)); + + if (is_closure_star(func)) + { + if (is_null(closure_args(func))) + return(OPT_F); + if (fx_count(sc, expr) == 1) + { + bool safe_case = is_safe_closure(func); + if (is_immutable(func)) hop = 1; + fx_annotate_arg(sc, cdr(expr), e); + set_opt1_lambda_add(expr, func); + set_opt3_arglen(cdr(expr), 1); + set_unsafely_optimized(expr); + + if ((safe_case) && (is_null(cdr(closure_args(func))))) + set_optimize_op(expr, hop + OP_SAFE_CLOSURE_STAR_A1); + else + if (lambda_has_simple_defaults(func)) + { + if (arglist_has_rest(sc, closure_args(func))) + set_optimize_op(expr, hop + ((safe_case) ? OP_SAFE_CLOSURE_STAR_NA_1 : OP_CLOSURE_STAR_NA)); + else set_optimize_op(expr, hop + ((safe_case) ? OP_SAFE_CLOSURE_STAR_A : OP_CLOSURE_STAR_A)); + } + else set_optimize_op(expr, hop + ((safe_case) ? OP_SAFE_CLOSURE_STAR_NA_1 : OP_CLOSURE_STAR_NA)); + } + return(OPT_F); + } + + if ((is_c_function_star(func)) && + (fx_count(sc, expr) == 1) && + (c_function_max_args(func) >= 1) && + (!is_symbol_and_keyword(arg1))) /* the only arg should not be a keyword (needs error checks later) */ + { + if ((hop == 0) && ((is_immutable(func)) || ((!sc->in_with_let) && (symbol_id(car(expr)) == 0)))) hop = 1; + set_safe_optimize_op(expr, hop + OP_SAFE_C_STAR_A); + fx_annotate_arg(sc, cdr(expr), e); + set_opt3_arglen(cdr(expr), 1); + set_c_function(expr, func); + return(OPT_T); + } + + if (((is_any_vector(func)) || (is_pair(func))) && + (is_fxable(sc, arg1))) + { + set_unsafe_optimize_op(expr, (is_pair(func) ? OP_IMPLICIT_PAIR_REF_A : OP_IMPLICIT_VECTOR_REF_A)); + fx_annotate_arg(sc, cdr(expr), e); + set_opt3_arglen(cdr(expr), 1); + return(OPT_T); + } + + if ((func == sc->s7_starlet) && /* (*s7* ...) */ + (((quotes == 1) && (is_symbol(cadr(arg1)))) || + (is_symbol_and_keyword(arg1)))) + { + s7_pointer sym = (quotes == 1) ? cadr(arg1) : arg1; + if (is_keyword(sym)) sym = keyword_symbol(sym); /* might even be ':print-length */ + set_safe_optimize_op(expr, OP_IMPLICIT_S7_STARLET_REF_S); + set_opt3_int(expr, s7_starlet_symbol(sym)); + return(OPT_T); + } + + if (is_let(func)) + { + if ((is_pair(arg1)) && (car(arg1) == sc->quote_symbol)) + { + set_opt3_con(expr, cadr(arg1)); + set_unsafe_optimize_op(expr, OP_IMPLICIT_LET_REF_C); + return(OPT_T); + } + if (is_fxable(sc, arg1)) + { + set_unsafe_optimize_op(expr, OP_IMPLICIT_LET_REF_A); + set_opt3_any(expr, arg1); + fx_annotate_arg(sc, cdr(expr), e); + set_opt3_arglen(cdr(expr), 1); + return(OPT_T); + }} + + /* unknown_* for other cases is set later(? -- we're getting eval-args...) */ + /* op_safe_c_p for (< (values 1 2 3)) op_s_s for (op arg) + * but is it better to wait for unknown* ? These are not hit often at this point (except in s7test). + * do they end up in op_s_a or whatever after unknown*? + */ + return((is_optimized(expr)) ? OPT_T : OPT_F); +} + +static bool unsafe_is_safe(s7_scheme *sc, s7_pointer f, s7_pointer e) +{ + if (!is_symbol(f)) return(false); + f = find_uncomplicated_symbol(sc, f, e); /* how to catch local c-funcs here? */ + if (!is_slot(f)) return(false); + return(is_safe_c_function(slot_value(f))); +} + +static opt_t set_any_closure_np(s7_scheme *sc, s7_pointer func, s7_pointer expr, s7_pointer e, int32_t num_args, opcode_t op) +{ + for (s7_pointer p = cdr(expr); is_pair(p); p = cdr(p)) + set_fx(p, fx_choose(sc, p, e, (is_list(e)) ? pair_symbol_is_safe : let_symbol_is_safe)); + set_opt3_arglen(cdr(expr), num_args); + set_unsafe_optimize_op(expr, op); + set_opt1_lambda_add(expr, func); + return(OPT_F); +} + +static bool two_args_ok(s7_scheme *sc, s7_pointer expr, s7_pointer e) +{ + if ((is_symbol(car(expr))) && ((car(expr) == sc->member_symbol) || (car(expr) == sc->assoc_symbol))) return(true); + return(unsafe_is_safe(sc, cadr(expr), e)); +} + +static void opt_sp_1(s7_scheme *sc, s7_function g, s7_pointer expr) +{ + set_opt1_any(cdr(expr), + (s7_pointer)((intptr_t)((g == g_cons) ? OP_SAFE_CONS_SP_1 : + (((g == g_multiply) || (g == g_multiply_2)) ? OP_SAFE_MULTIPLY_SP_1 : + (((g == g_add) || (g == g_add_2)) ? OP_SAFE_ADD_SP_1 : OP_SAFE_C_SP_1))))); +} + +static opt_t set_any_c_np(s7_scheme *sc, s7_pointer func, s7_pointer expr, s7_pointer e, int32_t num_args, opcode_t op) +{ + /* we get safe/semisafe funcs here of 2 args and up! very few more than 5 */ + /* would safe_c_pp work for cl? or should unknown_* deal with op_cl_*? why aren't unknown* used in op_safe_c and op_c? + * 2 | 3 args store on stack rather than consing? then use sc->t2|3 to pass to fn_proc (unless unsafe) + * or use op_stack? error clears this? op-any-c-fp: op_any_c_2p|3p|fp? -- mimic clo_3p|4p? + * all: 3 1 0 any_c_np (* 0.5 (- n 1) y)?? + */ + for (s7_pointer p = cdr(expr); is_pair(p); p = cdr(p)) + { + set_fx(p, fx_choose(sc, p, e, (is_list(e)) ? pair_symbol_is_safe : let_symbol_is_safe)); + if (!has_fx(p)) + gx_annotate_arg(sc, p, e); + } + set_opt3_arglen(cdr(expr), num_args); /* for op_unknown_np */ + set_unsafe_optimize_op(expr, op); + choose_c_function(sc, expr, func, num_args); /* we can use num_args -- mv will redirect to generic call */ + return(OPT_F); +} + +static s7_function io_function(s7_function func) +{ + if (func == g_with_input_from_string) return(with_string_in); + if (func == g_with_input_from_file) return(with_file_in); + if (func == g_with_output_to_file) return(with_file_out); + if (func == g_call_with_input_string) return(call_string_in); + if (func == g_call_with_input_file) return(call_file_in); + return(call_file_out); /* call_with_output_to_file */ +} + +static void fixup_closure_star_aa(s7_scheme *sc, s7_pointer f, s7_pointer code, int32_t hop) +{ + int32_t arity = closure_star_arity_to_int(sc, f); + bool safe_case = is_safe_closure(f); + s7_pointer arg1 = cadr(code), par1 = car(closure_args(f)); + + if (is_pair(par1)) par1 = car(par1); + set_opt3_arglen(cdr(code), 2); + set_unsafely_optimized(code); + + if ((arity == 1) && (is_symbol_and_keyword(arg1)) && (keyword_symbol(arg1) == par1)) + set_optimize_op(code, hop + ((safe_case) ? OP_SAFE_CLOSURE_STAR_KA : OP_CLOSURE_STAR_KA)); + else + if ((lambda_has_simple_defaults(f)) && (arity == 2)) + set_optimize_op(code, hop + ((is_safe_closure(f)) ? ((is_null(cdr(closure_body(f)))) ? OP_SAFE_CLOSURE_STAR_AA_O : + OP_SAFE_CLOSURE_STAR_AA) : OP_CLOSURE_STAR_NA)); + else set_optimize_op(code, hop + ((is_safe_closure(f)) ? OP_SAFE_CLOSURE_STAR_NA_2 : OP_CLOSURE_STAR_NA)); +} + +static int32_t check_lambda(s7_scheme *sc, s7_pointer form, bool optl); + +static opt_t optimize_func_two_args(s7_scheme *sc, s7_pointer expr, s7_pointer func, int32_t hop, + int32_t pairs, int32_t symbols, int32_t quotes, int32_t bad_pairs, s7_pointer e) +{ + s7_pointer arg1 = cadr(expr), arg2 = caddr(expr); + if (quotes > 0) + { + if (direct_memq(sc->quote_symbol, e)) + return(OPT_OOPS); + if ((bad_pairs == quotes) && + (is_symbol(car(expr))) && + (is_constant_symbol(sc, car(expr)))) + hop = 1; + } + if (((is_symbol(arg1)) && + (!arg_findable(sc, arg1, e))) || + ((is_symbol(arg2)) && + (!arg_findable(sc, arg2, e)))) + { + /* wrap bad args */ + if ((is_fxable(sc, arg1)) && + (is_fxable(sc, arg2)) && + (s7_is_aritable(sc, func, 2))) /* arg_findable key -> #t(?) so clo* ok */ + { + fx_annotate_args(sc, cdr(expr), e); + return(wrap_bad_args(sc, func, expr, 2, hop, e)); + } + return(OPT_F); + } + /* end of bad symbol wrappers */ + + if (is_c_function(func) && (c_function_is_aritable(func, 2))) + { + /* this is a mess */ + bool func_is_safe = is_safe_procedure(func); + if (hop == 0) hop = hop_if_constant(sc, car(expr)); + + if (pairs == 0) + { + if ((func_is_safe) || + ((is_maybe_safe(func)) && (two_args_ok(sc, expr, e)))) + { + /* another case here: set-car! and set-cdr! are safe if symbols==1 and arg1 is the symbol (i.e. arg2 is a constant) */ + if (symbols == 0) + set_optimize_op(expr, hop + OP_SAFE_C_NC); + else + if (symbols == 2) /* these two symbols are almost never the same, (sqrt (+ (* x x) (* y y))) */ + { + set_optimize_op(expr, hop + OP_SAFE_C_SS); + set_opt2_sym(cdr(expr), arg2); + } + else + if (is_normal_symbol(arg1)) + { + set_opt2_con(cdr(expr), arg2); + set_optimize_op(expr, hop + OP_SAFE_C_SC); + } + else + { + set_opt1_con(cdr(expr), arg1); + set_opt2_sym(cdr(expr), arg2); + set_optimize_op(expr, hop + OP_SAFE_C_CS); + } + set_optimized(expr); + choose_c_function(sc, expr, func, 2); + return(OPT_T); + } + + set_unsafely_optimized(expr); + if (symbols == 2) + { + if (c_function_call(func) == g_apply) + { + set_optimize_op(expr, OP_APPLY_SS); + set_opt1_cfunc(expr, func); /* not quite set_c_function */ + set_opt2_sym(expr, arg2); + } + else + { + if (is_semisafe(func)) + { + set_opt2_sym(cdr(expr), arg2); + set_optimize_op(expr, hop + OP_CL_SS); + } + else set_optimize_op(expr, hop + OP_C_SS); + choose_c_function(sc, expr, func, 2); + }} + else + { + set_optimize_op(expr, hop + ((is_semisafe(func)) ? OP_CL_AA : OP_C_AA)); + fx_annotate_args(sc, cdr(expr), e); + set_opt3_arglen(cdr(expr), 2); + choose_c_function(sc, expr, func, 2); + if (is_safe_procedure(opt1_cfunc(expr))) + { + clear_unsafe(expr); + /* symbols can be 0..2 here, no pairs */ + set_optimized(expr); + if (symbols == 1) + { + if (is_normal_symbol(arg1)) + { + set_optimize_op(expr, hop + OP_SAFE_C_SC); + set_opt2_con(cdr(expr), arg2); + } + else + { + set_opt1_con(cdr(expr), arg1); + set_opt2_sym(cdr(expr), arg2); + set_optimize_op(expr, hop + OP_SAFE_C_CS); + }} + return(OPT_T); + }} + return(OPT_F); + } + + /* pairs != 0 */ + if ((bad_pairs == 0) && + (pairs == 2)) + { + if ((func_is_safe) || + ((is_maybe_safe(func)) && (two_args_ok(sc, expr, e)))) + { + int32_t op = combine_ops(sc, expr, E_C_PP, arg1, arg2); + set_safe_optimize_op(expr, hop + op); + if (op == OP_SAFE_C_PP) + { + if (((op_no_hop(cadr(expr))) == OP_SAFE_CLOSURE_S_TO_SC) && + ((op_no_hop(caddr(expr))) == OP_SAFE_CLOSURE_S_TO_SC) && + (is_global(caadr(expr))) && (is_global(caaddr(expr)))) + { + /* ideally this would be OP not HOP, but safe_closure_s_to_sc is too picky */ + /* set_opt3_pair(expr, caddr(expr)); */ /* set_opt3_arglen(cdr(expr), 2); */ + set_safe_optimize_op(expr, HOP_SAFE_C_FF); + } + + opt_sp_1(sc, c_function_call(func), expr); /* calls set_opt1_any, sets opt1(cdr(expr)) to OP_SAFE_CONS_SP_1 and friends */ + if (is_fxable(sc, arg1)) + { + if (is_fxable(sc, arg2)) + return(check_c_aa(sc, expr, func, hop, e)); /* AA case */ + set_optimize_op(expr, hop + OP_SAFE_C_AP); + fx_annotate_arg(sc, cdr(expr), e); + gx_annotate_arg(sc, cddr(expr), e); + set_opt3_arglen(cdr(expr), 2); + } + else + if (is_fxable(sc, arg2)) + { + set_optimize_op(expr, hop + OP_SAFE_C_PA); + fx_annotate_arg(sc, cddr(expr), e); + gx_annotate_arg(sc, cdr(expr), e); + set_opt3_arglen(cdr(expr), 2); + } + else gx_annotate_args(sc, cdr(expr), e); + } + choose_c_function(sc, expr, func, 2); /* this might change the op to safe_c_c, so it has to be last */ + return(OPT_T); + }} + + if ((bad_pairs == 0) && + (pairs == 1)) + { + if ((func_is_safe) || + ((is_maybe_safe(func)) && (two_args_ok(sc, expr, e)))) + { + combine_op_t orig_op; + int32_t op; + + if (is_pair(arg1)) + { + orig_op = (is_normal_symbol(arg2)) ? E_C_PS : E_C_PC; + op = combine_ops(sc, expr, orig_op, arg1, arg2); + } + else + { + orig_op = (is_normal_symbol(arg1)) ? E_C_SP : E_C_CP; + op = combine_ops(sc, expr, orig_op, arg1, arg2); + } + if ((((op == OP_SAFE_C_SP) || (op == OP_SAFE_C_CP)) && + (is_fxable(sc, arg2))) || + (((op == OP_SAFE_C_PS) || (op == OP_SAFE_C_PC)) && + (is_fxable(sc, arg1)))) + { + fx_annotate_args(sc, cdr(expr), e); + if (!safe_c_aa_to_ag_ga(sc, expr, hop)) + { + set_safe_optimize_op(expr, hop + OP_SAFE_C_AA); + set_opt3_pair(expr, cddr(expr)); + }} + else + { + set_safe_optimize_op(expr, hop + op); + if ((op == OP_SAFE_C_SP) || (op == OP_SAFE_C_CP)) + { + opt_sp_1(sc, c_function_call(func), expr); + set_opt3_any(cdr(expr), arg1); + } + else + if (op == OP_SAFE_C_PC) + set_opt3_con(cdr(expr), arg2); + } + choose_c_function(sc, expr, func, 2); + return(OPT_T); + }} + + if ((bad_pairs == 1) && (quotes == 1)) + { + if ((func_is_safe) || + ((is_maybe_safe(func)) && (two_args_ok(sc, expr, e)))) + { + if (symbols == 1) + { + set_optimized(expr); + if (is_normal_symbol(arg1)) + { + set_opt2_con(cdr(expr), cadr(arg2)); + set_optimize_op(expr, hop + OP_SAFE_C_SC); + } + else + { + set_opt1_con(cdr(expr), cadr(arg1)); + set_opt2_sym(cdr(expr), arg2); + set_optimize_op(expr, hop + OP_SAFE_C_CS); + } + choose_c_function(sc, expr, func, 2); + return(OPT_T); + } + if ((pairs == 1) && (is_pair(arg2))) /* QC never happens */ + { + set_safe_optimize_op(expr, hop + OP_SAFE_C_CQ); + set_opt2_con(cdr(expr), cadr(arg2)); + choose_c_function(sc, expr, func, 2); + return(OPT_T); + } + if (!is_safe_c_s(arg1)) + { + if ((is_fxable(sc, arg1)) && (is_fxable(sc, arg2))) + return(check_c_aa(sc, expr, func, hop, e)); + }} + else + if (pairs == 1) + { + set_unsafe_optimize_op(expr, hop + ((is_semisafe(func)) ? OP_CL_AA : OP_C_AA)); + fx_annotate_args(sc, cdr(expr), e); + set_opt3_arglen(cdr(expr), 2); + choose_c_function(sc, expr, func, 2); + return(OPT_F); + }} + + if (quotes == 2) + { + if (func_is_safe) + { + set_safe_optimize_op(expr, hop + OP_SAFE_C_AA); /* op_safe_c_nc -> fx_c_nc appears to leave quoted pairs quoted? */ + set_opt3_pair(expr, cddr(expr)); + } + else + { + set_unsafe_optimize_op(expr, hop + ((is_semisafe(func)) ? OP_CL_AA : OP_C_AA)); + set_opt3_arglen(cdr(expr), 2); + } + fx_annotate_args(sc, cdr(expr), e); + choose_c_function(sc, expr, func, 2); + return((func_is_safe) ? OPT_T : OPT_F); + } + + if ((pairs == 1) && + (quotes == 0) && + ((func_is_safe) || + ((is_maybe_safe(func)) && (two_args_ok(sc, expr, e))))) + { + if (symbols == 1) + { + set_optimized(expr); + if (is_normal_symbol(arg1)) /* this is what optimize_expression uses to count symbols */ + { + set_optimize_op(expr, hop + OP_SAFE_C_SP); + opt_sp_1(sc, c_function_call(func), expr); + } + else set_optimize_op(expr, hop + OP_SAFE_C_PS); + choose_c_function(sc, expr, func, 2); + if (bad_pairs == 0) + return(OPT_T); + set_unsafe(expr); + return(OPT_F); + } + if (symbols == 0) + { + set_optimized(expr); + if ((is_fxable(sc, arg1)) && (is_fxable(sc, arg2))) + return(check_c_aa(sc, expr, func, hop, e)); + if (is_pair(arg1)) + { + set_optimize_op(expr, hop + OP_SAFE_C_PC); + set_opt3_con(cdr(expr), arg2); + } + else + { + set_optimize_op(expr, hop + OP_SAFE_C_CP); + opt_sp_1(sc, c_function_call(func), expr); + set_opt3_any(cdr(expr), arg1); + } + choose_c_function(sc, expr, func, 2); + if (bad_pairs == 0) + return(OPT_T); + set_unsafe(expr); + return(OPT_F); + }} + + if ((pairs == 2) && + ((func_is_safe) || + ((is_maybe_safe(func)) && (two_args_ok(sc, expr, e))))) + { + if ((bad_pairs == 1) && + (is_safe_c_s(arg1))) + { + /* unsafe func here won't work unless we check that later and make the new arg list (for list-values etc) + * (and it has to be the last pair else the unknown_g stuff can mess up) + */ + if ((car(arg2) == sc->quote_symbol) && + (is_global(sc->quote_symbol))) + { + if (!is_proper_list_1(sc, cdr(arg2))) + return(OPT_OOPS); + set_safe_optimize_op(expr, hop + OP_SAFE_C_opSq_C); + set_opt1_sym(cdr(expr), cadr(arg1)); + set_opt2_con(cdr(expr), cadr(arg2)); + choose_c_function(sc, expr, func, 2); + return(OPT_T); + }} + if (quotes == 0) + { + set_unsafely_optimized(expr); + if (is_fxable(sc, arg1)) + { + if (is_fxable(sc, arg2)) + return(check_c_aa(sc, expr, func, hop, e)); + set_optimize_op(expr, hop + OP_SAFE_C_AP); + opt_sp_1(sc, c_function_call(func), expr); + fx_annotate_arg(sc, cdr(expr), e); + gx_annotate_arg(sc, cddr(expr), e); + } + else + if (is_fxable(sc, arg2)) + { + set_optimize_op(expr, hop + OP_SAFE_C_PA); + fx_annotate_arg(sc, cddr(expr), e); + gx_annotate_arg(sc, cdr(expr), e); + } + else + { + set_optimize_op(expr, hop + OP_SAFE_C_PP); + opt_sp_1(sc, c_function_call(func), expr); + gx_annotate_args(sc, cdr(expr), e); + } + choose_c_function(sc, expr, func, 2); + return(OPT_F); + } + if (quotes == 1) + { + if ((car(arg1) == sc->quote_symbol) && + (is_global(sc->quote_symbol))) + { + if (!is_proper_list_1(sc, cdr(arg1))) + return(OPT_OOPS); + set_optimize_op(expr, hop + OP_SAFE_C_CP); + opt_sp_1(sc, c_function_call(func), expr); + set_opt3_any(cdr(expr), cadr(arg1)); + } + else + { + set_optimize_op(expr, hop + OP_SAFE_C_PC); + set_opt3_con(cdr(expr), cadr(arg2)); + } + set_unsafely_optimized(expr); + choose_c_function(sc, expr, func, 2); + return(OPT_F); + }} + + if (func_is_safe) + { + if (fx_count(sc, expr) == 2) + return(check_c_aa(sc, expr, func, hop, e)); + } + else + { + if (is_fxable(sc, arg1)) + { + if (is_fxable(sc, arg2)) + { + if ((c_function_call(func) == g_apply) && + (is_normal_symbol(arg1))) + { + set_optimize_op(expr, OP_APPLY_SA); + if ((is_pair(arg2)) && + (is_normal_symbol(car(arg2)))) /* arg2 might be ((if expr op1 op2) ...) */ + { + s7_pointer lister = lookup(sc, car(arg2)); + if ((is_c_function(lister)) && + (is_pair(c_function_signature(lister))) && + (car(c_function_signature(lister)) == sc->is_proper_list_symbol)) + set_optimize_op(expr, OP_APPLY_SL); + } + set_opt1_cfunc(expr, func); /* not quite set_c_function */ + } + else set_unsafe_optimize_op(expr, hop + ((is_semisafe(func)) ? OP_CL_AA : OP_C_AA)); + fx_annotate_args(sc, cdr(expr), e); + set_opt3_arglen(cdr(expr), 2); + } + else + { + if (((c_function_call(func) == g_with_input_from_string) || + (c_function_call(func) == g_with_input_from_file) || + (c_function_call(func) == g_with_output_to_file)) && + (is_ok_lambda(sc, arg2)) && + (is_null(cadr(arg2))) && + (!direct_memq(car(arg2), e))) /* lambda is redefined?? */ + { + set_unsafe_optimize_op(expr, (is_string(arg1)) ? OP_WITH_IO_C : OP_WITH_IO); + set_opt2_pair(expr, cddr(arg2)); + set_opt1_any(expr, (s7_pointer)io_function(c_function_call(func))); + return(OPT_F); + } + if (((c_function_call(func) == g_call_with_input_string) || + (c_function_call(func) == g_call_with_input_file) || + (c_function_call(func) == g_call_with_output_file)) && + (is_ok_lambda(sc, arg2)) && + (is_proper_list_1(sc, cadr(arg2))) && + (is_symbol(caadr(arg2))) && + (!is_probably_constant(caadr(arg2))) && + (!direct_memq(sc->lambda_symbol, e))) /* lambda is redefined?? */ + { + set_unsafe_optimize_op(expr, (is_string(arg1)) ? OP_WITH_IO_C : OP_WITH_IO); + set_opt2_pair(expr, cddr(arg2)); + set_opt3_sym(expr, caadr(arg2)); + set_opt1_any(expr, (s7_pointer)io_function(c_function_call(func))); + return(OPT_F); + } + set_unsafe_optimize_op(expr, hop + OP_C_AP); + fx_annotate_arg(sc, cdr(expr), e); + } + choose_c_function(sc, expr, func, 2); + return(OPT_F); + } + + if ((is_semisafe(func)) && + (is_symbol(car(expr))) && + (car(expr) != sc->values_symbol) && + (is_fxable(sc, arg2)) && + (is_pair(arg1)) && + (car(arg1) == sc->lambda_symbol)) + { + fx_annotate_arg(sc, cddr(expr), e); + set_unsafe_optimize_op(expr, hop + OP_CL_FA); + check_lambda(sc, arg1, true); /* this changes symbol_list */ + + clear_symbol_list(sc); /* so restore it */ + for (s7_pointer p = e; is_pair(p); p = cdr(p)) + if (is_normal_symbol(car(p))) + add_symbol_to_list(sc, car(p)); + + /* two seq args can't happen here (func_2_args = map + lambda + seq, arg1 is the lambda form, arg2 is fxable (see above) */ + choose_c_function(sc, expr, func, 2); + if (((fn_proc(expr) == g_for_each) || (fn_proc(expr) == g_map)) && + ((is_proper_list_1(sc, cadr(arg1))) && /* one parameter */ + (!is_possibly_constant(caadr(arg1))))) /* parameter name not trouble */ + { + /* built-in permanent closure here was not much faster */ + set_fn(expr, (fn_proc(expr) == g_for_each) ? g_for_each_closure : NULL); + set_opt3_pair(expr, cdr(arg1)); + set_unsafe_optimize_op(expr, OP_MAP_FOR_EACH_FA); + } + return(OPT_F); + }} + return(set_any_c_np(sc, func, expr, e, 2, hop + OP_ANY_C_NP)); /* OP_C_PP doesn't exist */ + } + + if (is_closure(func)) + { + bool one_form, safe_case; + s7_pointer body; + int32_t arit = closure_arity_to_int(sc, func); + + if (arit != 2) + { + if (is_symbol(closure_args(func))) + return(optimize_closure_sym(sc, expr, func, hop, 2, e)); + if ((arit == -1) && (is_symbol(cdr(closure_args(func))))) /* (define (f a . b) ...) */ + return(optimize_closure_a_sym(sc, expr, func, hop, 2, e)); + return(OPT_F); + } + if (is_immutable(func)) hop = 1; + + body = closure_body(func); + one_form = is_null(cdr(body)); + safe_case = is_safe_closure(func); + + if ((pairs == 0) && + (symbols >= 1)) + { + set_unsafely_optimized(expr); + set_opt1_lambda_add(expr, func); + if (symbols == 2) + { + set_opt2_sym(expr, arg2); + if (!one_form) + set_optimize_op(expr, hop + ((safe_case) ? OP_SAFE_CLOSURE_SS : OP_CLOSURE_SS)); + else + if (!safe_case) + set_optimize_op(expr, hop + OP_CLOSURE_SS_O); + else + if (!is_fxable(sc, car(body))) + set_optimize_op(expr, hop + OP_SAFE_CLOSURE_SS_O); + else + { + fx_annotate_arg(sc, body, e); + fx_tree(sc, body, car(closure_args(func)), cadr(closure_args(func)), NULL, false); + set_safe_optimize_op(expr, hop + OP_SAFE_CLOSURE_SS_A); + /* fx_annotate_args(sc, cdr(expr), e); */ + set_closure_one_form_fx_arg(func); + return(OPT_T); + } + return(OPT_F); + } + if (is_normal_symbol(arg1)) + { + if (one_form) + set_optimize_op(expr, hop + ((safe_case) ? OP_SAFE_CLOSURE_SC_O : OP_CLOSURE_SC_O)); /* _A case is very rare */ + else set_optimize_op(expr, hop + ((safe_case) ? OP_SAFE_CLOSURE_SC : OP_CLOSURE_SC)); + set_opt2_con(expr, arg2); + return(OPT_F); + }} + + if ((!arglist_has_rest(sc, closure_args(func))) && + (fx_count(sc, expr) == 2)) + { + if (!one_form) + set_safe_optimize_op(expr, hop + ((safe_case) ? OP_SAFE_CLOSURE_AA : OP_CLOSURE_AA)); + else + if (!safe_case) + set_optimize_op(expr, hop + OP_CLOSURE_AA_O); + else + if (!is_fxable(sc, car(body))) + set_optimize_op(expr, hop + OP_SAFE_CLOSURE_AA_O); + else + { + fx_annotate_arg(sc, body, e); + set_safe_optimize_op(expr, hop + OP_SAFE_CLOSURE_AA_A); /* safe_closure_as|sa_a? */ + set_closure_one_form_fx_arg(func); + fx_annotate_args(sc, cdr(expr), e); + set_opt1_lambda_add(expr, func); + set_opt3_arglen(cdr(expr), 2); + return(OPT_T); + } + fx_annotate_args(sc, cdr(expr), e); + set_opt1_lambda_add(expr, func); + set_opt3_arglen(cdr(expr), 2); + return(OPT_F); + } + + if (is_fxable(sc, arg1)) + { + set_unsafely_optimized(expr); + fx_annotate_arg(sc, cdr(expr), e); + set_safe_optimize_op(expr, hop + ((safe_case) ? OP_SAFE_CLOSURE_AP : OP_CLOSURE_AP)); + set_opt1_lambda_add(expr, func); + set_opt3_arglen(cdr(expr), 2); /* for op_unknown_np */ + return(OPT_F); + } + + if ((is_pair(arg1)) && + (car(arg1) == sc->lambda_symbol) && + (is_pair(cdr(arg1))) && /* not (lambda) */ + (is_fxable(sc, arg2)) && + (is_null(cdr(closure_body(func))))) + { + fx_annotate_arg(sc, cddr(expr), e); + set_opt2_pair(expr, cdr(arg1)); + set_unsafe_optimize_op(expr, hop + OP_CLOSURE_FA); + check_lambda(sc, arg1, false); + + clear_symbol_list(sc); /* clobbered in check_lambda so restore it? */ + for (s7_pointer p = e; is_pair(p); p = cdr(p)) + if (is_normal_symbol(car(p))) + add_symbol_to_list(sc, car(p)); + + /* check_lambda calls optimize_lambda if define in progress, else just optimize on the body */ + clear_safe_closure_body(cddr(arg1)); /* otherwise we need to fixup the local let for the optimizer -- see s7test intersection case 91492 */ + set_opt1_lambda_add(expr, func); + return(OPT_F); + } + + if (is_fxable(sc, arg2)) + { + set_unsafely_optimized(expr); + fx_annotate_arg(sc, cddr(expr), e); + set_optimize_op(expr, hop + ((safe_case) ? OP_SAFE_CLOSURE_PA : OP_CLOSURE_PA)); + set_opt1_lambda_add(expr, func); + set_opt3_arglen(cdr(expr), 2); /* for op_unknown_np */ + return(OPT_F); + } + + if (is_safe_closure(func)) /* clo* too */ + return(set_any_closure_np(sc, func, expr, e, 2, hop + OP_SAFE_CLOSURE_PP)); + + set_unsafely_optimized(expr); + set_optimize_op(expr, hop + OP_CLOSURE_PP); + set_opt1_lambda_add(expr, func); + set_opt3_arglen(cdr(expr), 2); /* for op_unknown_np */ + return(OPT_F); + } + + if (is_closure_star(func)) + { + if (!closure_star_is_aritable(sc, func, closure_args(func), 1)) /* not 2, cadr(expr) might be keyword or pair->keyword etc */ + return(OPT_OOPS); /* (let* cons () (lambda* (a . b) (cons a b))) so closure_args=(), arity=0 ?? */ + if (is_immutable(func)) hop = 1; + if (fx_count(sc, expr) == 2) + { + fixup_closure_star_aa(sc, func, expr, hop); + fx_annotate_args(sc, cdr(expr), e); + set_opt1_lambda_add(expr, func); + return(OPT_F); + }} + + if ((is_c_function_star(func)) && + (fx_count(sc, expr) == 2) && + (c_function_max_args(func) >= 1) && + (!is_symbol_and_keyword(arg2))) + { + if ((hop == 0) && ((is_immutable(func)) || ((!sc->in_with_let) && (symbol_id(car(expr)) == 0)))) hop = 1; + set_optimized(expr); + set_optimize_op(expr, hop + OP_SAFE_C_STAR_AA); /* k+c? = cc */ + fx_annotate_args(sc, cdr(expr), e); + set_opt3_arglen(cdr(expr), 2); + set_c_function(expr, func); + return(OPT_T); + } + + if ((((is_any_vector(func)) && (vector_rank(func) == 2)) || (is_pair(func))) && + (is_fxable(sc, arg1)) && (is_fxable(sc, arg2))) + { + set_unsafe_optimize_op(expr, ((is_pair(func)) ? OP_IMPLICIT_PAIR_REF_AA : OP_IMPLICIT_VECTOR_REF_AA)); + fx_annotate_args(sc, cdr(expr), e); + set_opt3_arglen(cdr(expr), 2); + return(OPT_T); + } + return((is_optimized(expr)) ? OPT_T : OPT_F); +} + +static opt_t optimize_safe_c_func_three_args(s7_scheme *sc, s7_pointer expr, s7_pointer func, + int32_t hop, int32_t pairs, int32_t symbols, int32_t quotes, s7_pointer e) +{ + s7_pointer arg1 = cadr(expr), arg2 = caddr(expr), arg3 = cadddr(expr); + if (pairs == 0) + { + set_optimized(expr); + if (symbols == 0) + set_optimize_op(expr, hop + OP_SAFE_C_NC); + else + { + clear_has_fx(cdr(expr)); + if (symbols == 3) + { + set_optimize_op(expr, hop + OP_SAFE_C_SSS); + set_opt1_sym(cdr(expr), arg2); + set_opt2_sym(cdr(expr), arg3); + } + else + if (symbols == 2) + if (!is_normal_symbol(arg1)) + { + set_optimize_op(expr, hop + OP_SAFE_C_CSS); + set_opt1_sym(cdr(expr), arg2); + set_opt2_sym(cdr(expr), arg3); + } + else + if (!is_normal_symbol(arg3)) + { + set_opt2_con(cdr(expr), arg3); + set_opt1_sym(cdr(expr), arg2); + set_optimize_op(expr, hop + OP_SAFE_C_SSC); + } + else + { + set_opt1_con(cdr(expr), arg2); + set_opt2_sym(cdr(expr), arg3); + set_optimize_op(expr, hop + OP_SAFE_C_SCS); + } + else + if (is_normal_symbol(arg1)) + { + set_opt1_con(cdr(expr), arg2); + set_opt2_con(cdr(expr), arg3); + set_optimize_op(expr, hop + OP_SAFE_C_SCC); + } + else + if (is_normal_symbol(arg2)) + { + set_opt1_sym(cdr(expr), arg2); + set_opt2_con(cdr(expr), arg3); + set_opt3_con(cdr(expr), arg1); + set_optimize_op(expr, hop + OP_SAFE_C_CSC); + } + else + { + set_opt1_sym(cdr(expr), arg3); + set_opt2_con(cdr(expr), arg2); + set_opt3_con(cdr(expr), arg1); + set_optimize_op(expr, hop + OP_SAFE_C_CCS); + }} + choose_c_function(sc, expr, func, 3); + return(OPT_T); + } + + /* pairs != 0 */ + if (fx_count(sc, expr) == 3) + { + set_optimized(expr); + if (quotes == 1) + { + if ((symbols == 2) && + (is_normal_symbol(arg1)) && + (is_normal_symbol(arg3))) + { + set_opt1_con(cdr(expr), cadr(arg2)); /* fx_c_scs uses opt1_con */ + set_opt2_sym(cdr(expr), arg3); + set_optimize_op(expr, hop + OP_SAFE_C_SCS); /* used to be SQS */ + choose_c_function(sc, expr, func, 3); + return(OPT_T); + } + if (symbols == 1) + { + if ((is_normal_symbol(arg3)) && + (is_proper_quote(sc, arg2)) && + (is_safe_c_s(arg1))) + { + set_safe_optimize_op(expr, hop + OP_SAFE_C_opSq_CS); /* lg */ + set_opt1_con(cdr(expr), cadr(arg2)); /* opt1_con is T_Pos|Ext (unchecked) */ + set_opt2_sym(cdr(expr), arg3); + set_opt3_sym(cdr(expr), cadr(arg1)); + choose_c_function(sc, expr, func, 3); + return(OPT_T); + } + if ((is_normal_symbol(arg2)) && + (is_proper_quote(sc, arg1)) && + (!is_pair(arg3))) + { + set_optimize_op(expr, hop + OP_SAFE_C_CSC); + set_opt1_sym(cdr(expr), arg2); + set_opt2_con(cdr(expr), arg3); + set_opt3_con(cdr(expr), cadr(arg1)); + choose_c_function(sc, expr, func, 3); + return(OPT_T); + }}} + + fx_annotate_args(sc, cdr(expr), e); + set_opt3_arglen(cdr(expr), 3); + set_opt3_pair(expr, cddr(expr)); + set_optimize_op(expr, hop + OP_SAFE_C_AAA); + + if (pairs == 1) + { + if (is_pair(arg1)) set_optimize_op(expr, hop + OP_SAFE_C_AGG); + + if ((symbols == 0) && (is_pair(arg2))) + set_optimize_op(expr, hop + OP_SAFE_C_CAC); + else + { + if ((symbols == 1) && (is_pair(arg3))) + set_optimize_op(expr, hop + ((is_normal_symbol(arg2)) ? OP_SAFE_C_CSA : OP_SAFE_C_SCA)); + else + { + if (symbols == 2) + { + if (is_normal_symbol(arg1)) + { + if (is_normal_symbol(arg2)) + { + set_optimize_op(expr, hop + OP_SAFE_C_SSA); + clear_has_fx(cdr(expr)); /* has_fx might have been on (see s7test) */ + } + else set_optimize_op(expr, hop + OP_SAFE_C_SAS); + } + else + if (is_pair(arg1)) + set_optimize_op(expr, hop + OP_SAFE_C_ASS); + }}}} + else + if ((is_normal_symbol(arg1)) && (pairs == 2)) + set_optimize_op(expr, hop + OP_SAFE_C_SAA); + + choose_c_function(sc, expr, func, 3); + return(OPT_T); + } + return(OPT_F); /* tell caller to try something else */ +} + +static opt_t optimize_func_three_args(s7_scheme *sc, s7_pointer expr, s7_pointer func, int32_t hop, + int32_t pairs, int32_t symbols, int32_t quotes, int32_t bad_pairs, s7_pointer e) +{ + s7_pointer arg1, arg2, arg3; + if ((quotes > 0) && + (direct_memq(sc->quote_symbol, e))) + return(OPT_OOPS); + + arg1 = cadr(expr); + arg2 = caddr(expr); + arg3 = cadddr(expr); + if (((is_symbol(arg1)) && + (!arg_findable(sc, arg1, e))) || + ((is_symbol(arg2)) && + (!arg_findable(sc, arg2, e))) || + ((is_symbol(arg3)) && + (!arg_findable(sc, arg3, e)))) + { + /* wrap bad args */ + if ((is_fxable(sc, arg1)) && + (is_fxable(sc, arg2)) && + (is_fxable(sc, arg3)) && + (s7_is_aritable(sc, func, 3))) + { + fx_annotate_args(sc, cdr(expr), e); + set_opt3_arglen(cdr(expr), 3); + if (is_c_function(func)) + { + if (is_safe_procedure(func)) + { + set_safe_optimize_op(expr, hop + OP_SAFE_C_AAA); + set_opt3_pair(cdr(expr), cdddr(expr)); + set_opt3_pair(expr, cddr(expr)); + } + else set_safe_optimize_op(expr, hop + ((is_semisafe(func)) ? OP_CL_NA : OP_C_NA)); + set_c_function(expr, func); + return(OPT_T); + } + if ((is_closure(func)) && + (closure_arity_to_int(sc, func) == 3) && + (!arglist_has_rest(sc, closure_args(func)))) + { + set_unsafely_optimized(expr); + set_optimize_op(expr, hop + ((is_safe_closure(func)) ? OP_SAFE_CLOSURE_3A : OP_CLOSURE_3A)); + set_opt1_lambda_add(expr, func); + return(OPT_F); + } + if ((is_closure_star(func)) && + (lambda_has_simple_defaults(func)) && + (closure_star_arity_to_int(sc, func) != 0) && + (closure_star_arity_to_int(sc, func) != 1)) + { + set_unsafely_optimized(expr); + if ((is_safe_closure(func)) && (closure_star_arity_to_int(sc, func) == 3)) + set_optimize_op(expr, OP_SAFE_CLOSURE_STAR_3A); + else set_optimize_op(expr, ((is_safe_closure(func)) ? OP_SAFE_CLOSURE_STAR_NA : OP_CLOSURE_STAR_NA)); + set_opt1_lambda_add(expr, func); + }} + return(OPT_F); + } /* end of bad symbol wrappers */ + + if ((bad_pairs == quotes) && + (is_symbol(car(expr))) && + (is_constant_symbol(sc, car(expr)))) + hop = 1; + + if (is_c_function(func) && (c_function_is_aritable(func, 3))) + { + if (hop == 0) hop = hop_if_constant(sc, car(expr)); + if ((is_safe_procedure(func)) || + ((is_maybe_safe(func)) && (unsafe_is_safe(sc, arg3, e)))) + { + if (optimize_safe_c_func_three_args(sc, expr, func, hop, pairs, symbols, quotes, e) == OPT_T) + return(OPT_T); + if ((is_normal_symbol(arg1)) && (is_normal_symbol(arg2))) + { + set_opt3_pair(expr, arg3); + set_unsafe_optimize_op(expr, hop + OP_SAFE_C_SSP); + choose_c_function(sc, expr, func, 3); + return(OPT_F); + } + return(set_any_c_np(sc, func, expr, e, 3, hop + OP_SAFE_C_3P)); + } + /* func is not safe */ + if (fx_count(sc, expr) == 3) + { + set_optimized(expr); + fx_annotate_args(sc, cdr(expr), e); + set_opt3_arglen(cdr(expr), 3); + if (is_semisafe(func)) + set_optimize_op(expr, hop + (((is_normal_symbol(arg1)) && (is_normal_symbol(arg3))) ? OP_CL_SAS : OP_CL_NA)); + else set_optimize_op(expr, hop + OP_C_NA); + choose_c_function(sc, expr, func, 3); + set_unsafe(expr); + return(OPT_F); + } + + /* (define (hi) (catch #t (lambda () 1) (lambda args 2))) + * first arg list must be (), second a symbol + */ + if (c_function_call(func) == g_catch) + { + if (((bad_pairs == 2) && (!is_pair(arg1))) || + ((bad_pairs == 3) && (car(arg1) == sc->quote_symbol))) + { + s7_pointer body_lambda = arg2, error_lambda = arg3; + if ((is_ok_lambda(sc, body_lambda)) && + (is_ok_lambda(sc, error_lambda)) && + (is_null(cadr(body_lambda))) && + (((is_symbol(cadr(error_lambda))) && /* (lambda args ... */ + (!is_probably_constant(cadr(error_lambda)))) || + ((is_pair(cadr(error_lambda))) && /* (lambda (type info) ... */ + (is_pair(cdadr(error_lambda))) && + (is_null(cddadr(error_lambda))) && + (!is_probably_constant(caadr(error_lambda))) && /* (lambda (pi ...) ...) */ + (!is_probably_constant(cadadr(error_lambda)))))) + { + s7_pointer error_result = caddr(error_lambda); + set_unsafely_optimized(expr); + if ((arg1 == sc->T) && /* tag is #t */ + (is_null(cdddr(error_lambda))) && /* error lambda body is one expr */ + ((!is_symbol(error_result)) || /* (lambda args #f) */ + ((is_pair(cadr(error_lambda))) && + (error_result == caadr(error_lambda)))) && /* (lambda (type info) type) */ + ((!is_pair(error_result)) || + (car(error_result) == sc->quote_symbol) || /* (lambda args 'a) */ + ((car(error_result) == sc->car_symbol) && + (is_pair(cdr(error_result))) && /* (lambda (type info) (car)) */ + (cadr(error_result) == cadr(error_lambda))))) /* (lambda args (car args) -> error-type */ + { + set_optimize_op(expr, OP_C_CATCH_ALL); /* catch_all* = #t tag, error handling can skip to the simple lambda body */ + set_c_function(expr, func); + + if (is_pair(error_result)) + error_result = (car(error_result) == sc->quote_symbol) ? cadr(error_result) : sc->unused; + else + if (is_symbol(error_result)) + error_result = sc->unused; + set_opt2_con(expr, error_result); /* for op_c_catch_all|_a -> stack */ + + set_opt1_pair(cdr(expr), cddr(body_lambda)); + if (is_null(cdddr(body_lambda))) + { + if (is_fxable(sc, caddr(body_lambda))) + { + set_optimize_op(expr, OP_C_CATCH_ALL_A); + set_fx_direct(cddr(body_lambda), fx_choose(sc, cddr(body_lambda), sc->curlet, let_symbol_is_safe)); + } + else + { + set_opt1_pair(cdr(expr), caddr(body_lambda)); + set_optimize_op(expr, OP_C_CATCH_ALL_O); + /* fn got no hits */ + }}} + else + { + set_optimize_op(expr, OP_C_CATCH); /* mainly c_catch_p, but this is not a common case */ + choose_c_function(sc, expr, func, 3); + } + return(OPT_F); + }}} + + if ((is_semisafe(func)) && + (is_symbol(car(expr))) && (car(expr) != sc->values_symbol) && + (is_fxable(sc, arg2)) && (is_fxable(sc, arg3)) && + (is_pair(arg1)) && (car(arg1) == sc->lambda_symbol)) + { + choose_c_function(sc, expr, func, 3); + if (((fn_proc(expr) == g_for_each) || (fn_proc(expr) == g_map)) && + (is_proper_list_2(sc, cadr(arg1))) && /* two parameters */ + (is_symbol(caadr(arg1))) && (!is_possibly_constant(caadr(arg1))) && /* parameter name not trouble */ + (is_symbol(cadadr(arg1))) && (!is_possibly_constant(cadadr(arg1)))) + { + fx_annotate_args(sc, cddr(expr), e); + check_lambda(sc, arg1, true); /* this changes symbol_list */ + + clear_symbol_list(sc); /* so restore it */ + for (s7_pointer p = e; is_pair(p); p = cdr(p)) + if (is_normal_symbol(car(p))) + add_symbol_to_list(sc, car(p)); + + set_fn(expr, (fn_proc(expr) == g_for_each) ? g_for_each_closure_2 : NULL); + set_opt3_pair(expr, cdr(arg1)); + set_unsafe_optimize_op(expr, OP_MAP_FOR_EACH_FAA); + return(OPT_F); + }} + + if ((is_safe_procedure(func)) || + ((is_semisafe(func)) && + (((car(expr) != sc->assoc_symbol) && (car(expr) != sc->member_symbol)) || + (unsafe_is_safe(sc, arg3, e))))) + return(set_any_c_np(sc, func, expr, e, 3, hop + OP_SAFE_C_3P)); + return(set_any_c_np(sc, func, expr, e, 3, hop + OP_ANY_C_NP)); + } + + /* not c func */ + if (is_closure(func)) + { + int32_t arit = closure_arity_to_int(sc, func); + if (arit != 3) + { + if (is_symbol(closure_args(func))) + return(optimize_closure_sym(sc, expr, func, hop, 3, e)); + return(OPT_F); + } + if (is_immutable(func)) hop = 1; + + if (symbols == 3) + { + s7_pointer body = closure_body(func); + bool one_form = is_null(cdr(body)); + set_opt1_lambda_add(expr, func); + set_opt3_arglen(cdr(expr), 3); + + if (is_safe_closure(func)) + { + if ((one_form) && + (is_fxable(sc, car(body)))) + { + set_opt2_sym(expr, arg2); + set_opt3_sym(expr, arg3); + fx_annotate_arg(sc, body, e); + fx_tree(sc, body, car(closure_args(func)), cadr(closure_args(func)), caddr(closure_args(func)), false); + set_safe_optimize_op(expr, hop + OP_SAFE_CLOSURE_3S_A); + set_closure_one_form_fx_arg(func); + } + else set_optimize_op(expr, hop + OP_SAFE_CLOSURE_3S); + return(OPT_T); + } + set_unsafe_optimize_op(expr, hop + ((one_form) ? OP_CLOSURE_3S_O : OP_CLOSURE_3S)); + return(OPT_F); + } + + if (fx_count(sc, expr) == 3) + { + if (is_safe_closure(func)) + { + if ((!is_pair(arg2)) && (!is_pair(arg3))) + set_optimize_op(expr, hop + OP_SAFE_CLOSURE_AGG); + else + if (is_normal_symbol(arg1)) + set_optimize_op(expr, hop + ((is_normal_symbol(arg2)) ? OP_SAFE_CLOSURE_SSA : OP_SAFE_CLOSURE_SAA)); + else set_optimize_op(expr, hop + OP_SAFE_CLOSURE_3A); + } + else + if ((is_normal_symbol(arg2)) && (is_normal_symbol(arg3))) + set_optimize_op(expr, hop + OP_CLOSURE_ASS); + else + if (is_normal_symbol(arg1)) + set_optimize_op(expr, hop + ((is_normal_symbol(arg3)) ? OP_CLOSURE_SAS : OP_CLOSURE_SAA)); + else + if (is_normal_symbol(arg3)) + set_optimize_op(expr, hop + OP_CLOSURE_AAS); + else set_optimize_op(expr, hop + ((is_normal_symbol(arg2)) ? OP_CLOSURE_ASA : OP_CLOSURE_3A)); + set_unsafely_optimized(expr); + fx_annotate_args(sc, cdr(expr), e); + + if (is_fx_treeable(cdr(expr))) + fx_tree(sc, closure_body(func), car(closure_args(func)), cadr(closure_args(func)), caddr(closure_args(func)), false); + + set_opt1_lambda_add(expr, func); + set_opt3_arglen(cdr(expr), 3); + return(OPT_F); + } + return(set_any_closure_np(sc, func, expr, e, 3, hop + OP_ANY_CLOSURE_3P)); + } + + if (is_closure_star(func)) + { + if ((!lambda_has_simple_defaults(func)) || + (closure_star_arity_to_int(sc, func) == 0) || + (closure_star_arity_to_int(sc, func) == 1)) + return(OPT_F); + if (fx_count(sc, expr) == 3) + { + if (is_immutable(func)) hop = 1; + if ((is_safe_closure(func)) && (closure_star_arity_to_int(sc, func) == 3)) + set_optimize_op(expr, OP_SAFE_CLOSURE_STAR_3A); + else set_unsafe_optimize_op(expr, hop + ((is_safe_closure(func) ? OP_SAFE_CLOSURE_STAR_NA : OP_CLOSURE_STAR_NA))); + fx_annotate_args(sc, cdr(expr), e); + set_opt1_lambda_add(expr, func); + set_opt3_arglen(cdr(expr), 3); + return(OPT_F); + }} + + if ((is_c_function_star(func)) && + (fx_count(sc, expr) == 3) && + (c_function_max_args(func) >= 2)) + { + set_safe_optimize_op(expr, hop + OP_SAFE_C_STAR_NA); + fx_annotate_args(sc, cdr(expr), e); + set_opt3_arglen(cdr(expr), 3); + set_c_function(expr, func); + return(OPT_T); + } + /* implicit_vector_3a doesn't happen */ + + if (bad_pairs > quotes) return(OPT_F); + return((is_optimized(expr)) ? OPT_T : OPT_F); +} + +static bool symbols_are_safe(s7_scheme *sc, s7_pointer args, s7_pointer e) +{ + for (s7_pointer p = args; is_pair(p); p = cdr(p)) + { + s7_pointer arg = car(p); + if ((is_normal_symbol(arg)) && + (!arg_findable(sc, arg, e))) + return(false); + } + return(true); +} + +static opt_t optimize_func_many_args(s7_scheme *sc, s7_pointer expr, s7_pointer func, int32_t hop, int32_t args, + int32_t pairs, int32_t symbols, int32_t quotes, int32_t bad_pairs, s7_pointer e) +{ + bool func_is_closure; + if (quotes > 0) + { + if (direct_memq(sc->quote_symbol, e)) + return(OPT_OOPS); + if ((bad_pairs == quotes) && + (is_symbol(car(expr))) && + (is_constant_symbol(sc, car(expr)))) + hop = 1; + } + if ((is_c_function(func)) && (c_function_is_aritable(func, args))) + { + if (hop == 0) hop = hop_if_constant(sc, car(expr)); + if (is_safe_procedure(func)) + { + if (pairs == 0) + { + if (symbols == 0) + { + set_safe_optimize_op(expr, hop + OP_SAFE_C_NC); + choose_c_function(sc, expr, func, args); + return(OPT_T); + } + if (symbols == args) + { + if (symbols_are_safe(sc, cdr(expr), e)) + set_safe_optimize_op(expr, hop + OP_SAFE_C_NS); + else + { + set_safe_optimize_op(expr, hop + ((args == 4) ? OP_SAFE_C_4A : OP_SAFE_C_NA)); + fx_annotate_args(sc, cdr(expr), e); + } + set_opt3_arglen(cdr(expr), args); + choose_c_function(sc, expr, func, args); + return(OPT_T); + }} + + if (fx_count(sc, expr) == args) + { + s7_pointer p; + set_optimized(expr); + set_optimize_op(expr, hop + ((args == 4) ? OP_SAFE_C_4A : OP_SAFE_C_NA)); + fx_annotate_args(sc, cdr(expr), e); + set_opt3_arglen(cdr(expr), args); + choose_c_function(sc, expr, func, args); + + for (p = cdr(expr); (is_pair(p)) && (is_pair(cdr(p))); p = cddr(p)) + { + if (is_normal_symbol(car(p))) + break; + if ((is_pair(car(p))) && + ((!is_pair(cdar(p))) || (caar(p) != sc->quote_symbol))) + break; + } + if (is_null(p)) + { + set_optimize_op(expr, hop + OP_SAFE_C_ALL_CA); + for (p = cdr(expr); is_pair(p); p = cddr(p)) + { + clear_has_fx(p); + set_opt2_con(p, (is_pair(car(p))) ? cadar(p) : car(p)); + }} + return(OPT_T); + } + return(set_any_c_np(sc, func, expr, e, args, hop + OP_ANY_C_NP)); + } + /* c_func is not safe */ + if (fx_count(sc, expr) == args) /* trigger_size doesn't matter for unsafe funcs */ + { + set_unsafe_optimize_op(expr, hop + ((is_semisafe(func)) ? OP_CL_NA : OP_C_NA)); + fx_annotate_args(sc, cdr(expr), e); + set_opt3_arglen(cdr(expr), args); + choose_c_function(sc, expr, func, args); + return(OPT_F); + } + return(set_any_c_np(sc, func, expr, e, args, hop + OP_ANY_C_NP)); /* was num_args=3! 2-Sep-20 */ + } + + func_is_closure = is_closure(func); + if (func_is_closure) + { + int32_t arit = closure_arity_to_int(sc, func); + if (arit != args) + { + if (is_symbol(closure_args(func))) + return(optimize_closure_sym(sc, expr, func, hop, args, e)); + return(OPT_F); + } + if (is_immutable(func)) hop = 1; + + if (fx_count(sc, expr) == args) + { + bool safe_case = is_safe_closure(func); + set_unsafely_optimized(expr); + set_unsafe_optimize_op(expr, hop + ((safe_case) ? OP_SAFE_CLOSURE_NA : ((args == 4) ? OP_CLOSURE_4A : OP_CLOSURE_NA))); + fx_annotate_args(sc, cdr(expr), e); + set_opt3_arglen(cdr(expr), args); + set_opt1_lambda_add(expr, func); + + if ((symbols == args) && + (symbols_are_safe(sc, cdr(expr), e))) + { + if (safe_case) + set_optimize_op(expr, hop + OP_SAFE_CLOSURE_NS); + else set_optimize_op(expr, hop + ((args == 4) ? ((is_null(cdr(closure_body(func)))) ? OP_CLOSURE_4S_O : OP_CLOSURE_4S) : ((args == 5) ? OP_CLOSURE_5S : OP_CLOSURE_NS))); + } + return(OPT_F); + } + if (args == 4) + return(set_any_closure_np(sc, func, expr, e, 4, hop + OP_ANY_CLOSURE_4P)); + return(set_any_closure_np(sc, func, expr, e, args, hop + OP_ANY_CLOSURE_NP)); + } + + if ((is_closure_star(func)) && + ((!lambda_has_simple_defaults(func)) || + (closure_star_arity_to_int(sc, func) == 0) || + (closure_star_arity_to_int(sc, func) == 1))) + return(OPT_F); + + if ((is_c_function_star(func)) && + (fx_count(sc, expr) == args) && + (c_function_max_args(func) >= (args / 2))) + { + if (is_immutable(func)) hop = 1; + set_safe_optimize_op(expr, hop + OP_SAFE_C_STAR_NA); + fx_annotate_args(sc, cdr(expr), e); + set_opt3_arglen(cdr(expr), args); + set_c_function(expr, func); + return(OPT_T); + } + if (((func_is_closure) || + (is_closure_star(func))) && + (fx_count(sc, expr) == args)) + { + set_unsafely_optimized(expr); + if (func_is_closure) + set_optimize_op(expr, hop + ((is_safe_closure(func)) ? OP_SAFE_CLOSURE_NA : ((args == 4) ? OP_CLOSURE_4A : OP_CLOSURE_NA))); + else set_optimize_op(expr, hop + ((is_safe_closure(func)) ? OP_SAFE_CLOSURE_STAR_NA : OP_CLOSURE_STAR_NA)); + fx_annotate_args(sc, cdr(expr), e); + set_opt3_arglen(cdr(expr), args); + set_opt1_lambda_add(expr, func); + return(OPT_F); + } + return((is_optimized(expr)) ? OPT_T : OPT_F); +} + +static bool vars_syntax_ok(s7_pointer vars) +{ + for (s7_pointer p = vars; is_pair(p); p = cdr(p)) + { + s7_pointer var = car(p); + if ((!is_pair(var)) || + (!is_symbol(car(var))) || + (!is_pair(cdr(var)))) + return(false); + } + return(true); +} + +static opt_t optimize_expression(s7_scheme *sc, s7_pointer expr, int32_t hop, s7_pointer e, bool export_ok); + +static bool vars_opt_ok(s7_scheme *sc, s7_pointer vars, int32_t hop, s7_pointer e) +{ + for (s7_pointer p = vars; is_pair(p); p = cdr(p)) + { + s7_pointer init = cadar(p); + /* if ((is_slot(global_slot(caar(p)))) && (is_c_function(global_value(caar(p))))) return(false); */ /* too draconian (see snd-test) */ + if ((is_pair(init)) && + (!is_checked(init)) && + (optimize_expression(sc, init, hop, e, false) == OPT_OOPS)) + return(false); + } + return(true); +} + +static opt_t optimize_syntax(s7_scheme *sc, s7_pointer expr, s7_pointer func, int32_t hop, s7_pointer e, bool export_ok) +{ + opcode_t op = syntax_opcode(func); + s7_pointer body = cdr(expr), vars; + bool body_export_ok = true; + + sc->w = e; + switch (op) + { + case OP_QUOTE: + case OP_MACROEXPAND: + return((is_proper_list_1(sc, body)) ? OPT_F : OPT_OOPS); + + case OP_LET: case OP_LETREC: + case OP_LET_STAR: case OP_LETREC_STAR: + if (is_symbol(cadr(expr))) + { + if (!is_pair(cddr(expr))) /* (let name . x) */ + return(OPT_F); + vars = caddr(expr); + if (!is_list(vars)) return(OPT_OOPS); + body = cdddr(expr); + } + else + { + vars = cadr(expr); + body = cddr(expr); + if (is_null(vars)) + e = cons(sc, sc->nil, e); /* () in e = empty let */ + else + if (!is_pair(vars)) + return(OPT_OOPS); + } + if (!is_pair(body)) return(OPT_OOPS); + + if (!vars_syntax_ok(vars)) + return(OPT_OOPS); + + if ((op == OP_LETREC) || (op == OP_LETREC_STAR)) + { + e = collect_variables(sc, vars, e); + if (!vars_opt_ok(sc, vars, hop, e)) + return(OPT_OOPS); + } + else + if (op == OP_LET) + { + if (!vars_opt_ok(sc, vars, hop, e)) + return(OPT_OOPS); + e = collect_variables(sc, vars, e); + } + else + for (s7_pointer p = vars; is_pair(p); p = cdr(p)) + { + s7_pointer var = car(p); + if ((is_pair(cadr(var))) && + (!is_checked(cadr(var))) && + (optimize_expression(sc, cadr(var), hop, e, false) == OPT_OOPS)) + return(OPT_OOPS); + e = cons(sc, add_symbol_to_list(sc, car(var)), e); + sc->w = e; + } + if (is_symbol(cadr(expr))) + { + e = cons(sc, add_symbol_to_list(sc, cadr(expr)), e); + sc->w = e; + } + break; + + case OP_LET_TEMPORARILY: + vars = cadr(expr); + if (!is_list(vars)) return(OPT_OOPS); + body = cddr(expr); + for (s7_pointer p = vars; is_pair(p); p = cdr(p)) + { + s7_pointer var = car(vars); + if ((is_pair(var)) && + (is_pair(cdr(var))) && + (is_pair(cadr(var))) && + (!is_checked(cadr(var))) && + (optimize_expression(sc, cadr(var), hop, e, false) == OPT_OOPS)) + return(OPT_OOPS); + } + /* e = cons(sc, sc->nil, e); */ /* !? currently let-temporarily does not make a new let, so it is like begin? */ + body_export_ok = export_ok; /* (list x (let-temporarily () (define x 0))) just as in begin */ + break; + + case OP_DO: + vars = cadr(expr); + if (is_null(vars)) + e = cons(sc, sc->nil, e); + else + if (!is_pair(vars)) + return(OPT_OOPS); + body = cddr(expr); + + for (s7_pointer p = vars; is_pair(p); p = cdr(p)) + { + s7_pointer var = car(p); + if ((!is_pair(var)) || + (!is_symbol(car(var))) || + (!is_pair(cdr(var)))) + return(OPT_OOPS); + if ((is_pair(cadr(var))) && + (!is_checked(cadr(var))) && + (optimize_expression(sc, cadr(var), hop, e, false) == OPT_OOPS)) /* the init field -- locals are not defined yet */ + return(OPT_OOPS); + } + e = collect_variables(sc, vars, e); + for (s7_pointer p = vars; is_pair(p); p = cdr(p)) + { + s7_pointer var = cddar(p); + if ((is_pair(var)) && + (is_pair(car(var))) && + (!is_checked(car(var))) && + (optimize_expression(sc, car(var), hop, e, false) == OPT_OOPS)) /* the step field -- locals are defined */ + return(OPT_OOPS); + } + break; + + case OP_BEGIN: + body_export_ok = export_ok; /* (list x (begin (define x 0))) */ + break; + + case OP_WITH_BAFFLE: + e = cons(sc, sc->nil, e); + break; + + case OP_DEFINE_BACRO: case OP_DEFINE_BACRO_STAR: + case OP_BACRO: case OP_BACRO_STAR: + return(OPT_F); + + case OP_DEFINE_MACRO: case OP_DEFINE_MACRO_STAR: + case OP_DEFINE_CONSTANT: case OP_DEFINE_EXPANSION: case OP_DEFINE_EXPANSION_STAR: + case OP_DEFINE: case OP_DEFINE_STAR: + /* define adds a name to the incoming let (e), the added name is inserted into e after the first, so the caller + * can flush added symbols by maintaining its own pointer into the list if blockers set the car. + * the list is used both to see local symbols and to catch "complicated" functions (find_uncomplicated_symbol). + * In cases like (if expr (define...)) we can't tell at this level whether the define takes place, so + * its name should not be in "e", but it needs to be marked for find_uncomplicated_symbol in a way + * that can be distinguished from members of "e". So in that (rare) case, we use the associated keyword. + * Then find_uncomplicated_symbol can use has_keyword to tell if the keyword search is needed. + * export_ok is trying to protect against optimizing (list x (define x 0)) as op_safe_c_sp and all related cases + */ + vars = cadr(expr); + body = cddr(expr); + if (is_pair(vars)) + { + if ((export_ok) && + (is_symbol(car(vars)))) + { + add_symbol_to_list(sc, car(vars)); + if (is_pair(e)) + { + if (car(e) != sc->if_keyword) + set_cdr(e, cons(sc, car(vars), cdr(e))); /* export it */ + else add_symbol_to_list(sc, symbol_to_keyword(sc, car(vars))); + } + else e = cons(sc, car(vars), e); + } + e = collect_parameters(sc, cdr(vars), e); + body_export_ok = export_ok; + } + else + { + if ((export_ok) && + (is_symbol(vars))) + { + /* actually if this is defining a function, the name should probably be included in the local let + * but that's next-to-impossible to guarantee unless it's (define x (lambda...)) of course. + */ + sc->temp9 = e; + for (s7_pointer p = body; is_pair(p); p = cdr(p)) + if ((is_pair(car(p))) && + (!is_checked(car(p))) && /* ((full_type(p) & (TYPE_MASK | T_CHECKED)) == T_PAIR) is not faster */ + (optimize_expression(sc, car(p), hop, e, false) == OPT_OOPS)) /* "body" here is not body in terms of export_ok */ + { + sc->temp9 = sc->unused; + return(OPT_OOPS); + } + sc->temp9 = sc->unused; + + add_symbol_to_list(sc, vars); + if (is_pair(e)) + { + if (car(e) != sc->if_keyword) + set_cdr(e, cons(sc, vars, cdr(e))); /* export it */ + else add_symbol_to_list(sc, symbol_to_keyword(sc, vars)); + } + /* else e = cons(sc, vars, e); */ /* ?? should this be set-cdr etc? */ + return(OPT_F); + } + body_export_ok = false; + } + break; + + case OP_LAMBDA: case OP_LAMBDA_STAR: + case OP_MACRO: case OP_MACRO_STAR: + vars = cadr(expr); + if (is_null(vars)) + e = cons(sc, sc->nil, e); + else + if ((!is_pair(vars)) && (!is_symbol(vars))) + return(OPT_OOPS); + e = collect_parameters(sc, vars, e); + body = cddr(expr); + break; + + case OP_SET: + if ((is_pair(cadr(expr))) && (caadr(expr) == sc->outlet_symbol)) + return(OPT_OOPS); + if (!is_pair(cddr(expr))) + return(OPT_OOPS); + if ((is_pair(cadr(expr))) && + (!is_checked(cadr(expr)))) + { + set_checked(cadr(expr)); + for (s7_pointer lp = cdadr(expr); is_pair(lp); lp = cdr(lp)) + if ((is_pair(car(lp))) && + (!is_checked(car(lp))) && + (optimize_expression(sc, car(lp), hop, e, body_export_ok) == OPT_OOPS)) + return(OPT_OOPS); + } + if ((is_pair(caddr(expr))) && + (!is_checked(caddr(expr))) && + (optimize_expression(sc, caddr(expr), hop, e, body_export_ok) == OPT_OOPS)) + return(OPT_OOPS); + + if ((is_pair(cadr(expr))) && (caadr(expr) == sc->s7_starlet_symbol)) + return(OPT_T); + return(OPT_F); + + case OP_WITH_LET: + /* we usually can't trust anything here, so hop ought to be off. For example, + * (define (hi) (let ((e (sublet (curlet) :abs (lambda (a) (- a 1))))) (with-let e (abs -1)))) + * returns 1 if hop is 1, but -2 otherwise. (with-let (unlet)...) is safe however. + */ + { + bool old_with_let = sc->in_with_let; + sc->in_with_let = (old_with_let) || (!is_pair(body)) || (!is_pair(car(body))) || + ((caar(body) != sc->unlet_symbol) && /* (caar(body) != sc->rootlet_symbol) && */ (caar(body) != sc->curlet_symbol)); + /* not rootlet here: (let ((i 0)) (_rd3_ (with-let (rootlet) ((null? i) i)))) */ + for (s7_pointer p = body; is_pair(p); p = cdr(p)) + if ((is_pair(car(p))) && + (!is_checked(car(p))) && + (optimize_expression(sc, car(p), 0, sc->nil, body_export_ok) == OPT_OOPS)) + { + sc->in_with_let = old_with_let; + return(OPT_OOPS); + } + sc->in_with_let = old_with_let; + return(OPT_F); + } + + case OP_CASE: + if ((is_pair(cadr(expr))) && + (!is_checked(cadr(expr))) && + (optimize_expression(sc, cadr(expr), hop, e, false) == OPT_OOPS)) + return(OPT_OOPS); + for (s7_pointer p = cddr(expr); is_pair(p); p = cdr(p)) + if ((is_pair(car(p))) && + (is_pair(cdar(p)))) + for (s7_pointer rst = cdar(p); is_pair(rst); rst = cdr(rst)) + if ((is_pair(car(rst))) && + (!is_checked(car(rst))) && + (optimize_expression(sc, car(rst), hop, e, false) == OPT_OOPS)) + return(OPT_OOPS); + return(OPT_F); + + case OP_COND: /* split opt is necessary: (cond (lambda (x) ...)) */ + for (s7_pointer p = cdr(expr); is_pair(p); p = cdr(p)) + if (is_pair(car(p))) + { + s7_pointer test = caar(p); + e = cons(sc, sc->if_keyword, e); /* I think this is a marker in case define is encountered? (see above) */ + if ((is_pair(test)) && + (!is_checked(test)) && + (optimize_expression(sc, test, hop, e, false) == OPT_OOPS)) + return(OPT_OOPS); + for (s7_pointer rst = cdar(p); is_pair(rst); rst = cdr(rst)) + if ((is_pair(car(rst))) && + (!is_checked(car(rst))) && + (optimize_expression(sc, car(rst), hop, e, false) == OPT_OOPS)) + return(OPT_OOPS); + } + { + s7_pointer p; + for (p = cdr(expr); is_pair(p); p = cdr(p)) + { + s7_pointer q; + if ((!is_pair(car(p))) || (!is_fxable(sc, caar(p)))) + break; + if (!is_pair(cdar(p))) + break; + for (q = cdar(p); is_pair(q); q = cdr(q)) + if ((car(q) == sc->feed_to_symbol) || (!is_fxable(sc, car(q)))) + break; + if (!is_null(q)) break; + } + if (!is_null(p)) return(OPT_F); + set_safe_optimize_op(expr, OP_COND_NA_NA); + } + for (s7_pointer p = cdr(expr); is_pair(p); p = cdr(p)) + { + set_fx_direct(car(p), fx_choose(sc, car(p), e, pair_symbol_is_safe)); + for (s7_pointer q = cdar(p); is_pair(q); q = cdr(q)) + set_fx_direct(q, fx_choose(sc, q, e, pair_symbol_is_safe)); + } + return(OPT_T); + + case OP_IF: case OP_WHEN: case OP_UNLESS: + if ((!is_pair(cdr(expr))) || (!is_pair(cddr(expr)))) + return(OPT_OOPS); + case OP_OR: case OP_AND: + e = cons(sc, sc->if_keyword, e); + break; + + default: break; + } + + sc->temp9 = e; + for (s7_pointer p = body; is_pair(p); p = cdr(p)) + if ((is_pair(car(p))) && + (!is_checked(car(p))) && /* ((full_type(p) & (TYPE_MASK | T_CHECKED)) == T_PAIR) is not faster */ + (optimize_expression(sc, car(p), hop, e, body_export_ok) == OPT_OOPS)) + { + sc->temp9 = sc->unused; + return(OPT_OOPS); + } + sc->temp9 = sc->unused; + + if ((hop == 1) && + ((is_syntax(car(expr))) || + (symbol_id(car(expr)) == 0))) + { + if (op == OP_IF) + { + s7_pointer test = cdr(expr), b1, b2, p; + for (p = cdr(expr); is_pair(p); p = cdr(p)) + if (!is_fxable(sc, car(p))) + return(OPT_F); + if (!is_null(p)) return(OPT_OOPS); + if ((is_pair(cdr(test))) && (is_pair(cddr(test))) && (!is_null(cdddr(test)))) + return(OPT_OOPS); + + for (p = cdr(expr); is_pair(p); p = cdr(p)) + set_fx_direct(p, fx_choose(sc, p, e, pair_symbol_is_safe)); + + b1 = cdr(test); + b2 = cdr(b1); + if ((fx_proc(b1) == fx_q) && + (is_pair(b2))) + { + set_opt3_con(test, cadar(b1)); + if (fx_proc(b2) == fx_q) + { + set_safe_optimize_op(expr, OP_IF_A_C_C); + set_opt1_con(expr, cadar(b1)); + set_opt2_con(expr, cadar(b2)); + return(OPT_T); + } + set_opt1_pair(expr, b1); + set_opt2_pair(expr, b2); + set_safe_optimize_op(expr, OP_IF_A_A_A); + } + else + { + if ((is_pair(car(test))) && + (caar(test) == sc->not_symbol) && + (is_fxable(sc, cadar(test)))) + { + set_fx_direct(cdar(test), fx_choose(sc, cdar(test), e, pair_symbol_is_safe)); + set_opt1_pair(expr, cdar(test)); + set_opt2_pair(expr, b1); + if (is_pair(b2)) set_opt3_pair(expr, b2); + set_safe_optimize_op(expr, (is_null(b2)) ? OP_IF_NOT_A_A : OP_IF_NOT_A_A_A); + } + else + { + if ((is_pair(b2)) && (fx_proc(b1) == fx_c) && (fx_proc(b2) == fx_c)) + { + set_safe_optimize_op(expr, OP_IF_A_C_C); + set_opt1_con(expr, car(b1)); + set_opt2_con(expr, car(b2)); + return(OPT_T); + } + if ((fx_proc(test) == fx_and_2a) && (fx_proc(b1) == fx_s)) + { + set_opt1_pair(expr, cdadr(expr)); + set_opt2_pair(expr, cddadr(expr)); + set_opt3_sym(expr, car(b1)); + set_safe_optimize_op(expr, OP_IF_AND2_S_A); + return(OPT_T); + } + set_opt1_pair(expr, b1); + if (is_pair(b2)) set_opt2_pair(expr, b2); + set_safe_optimize_op(expr, (is_null(b2)) ? OP_IF_A_A : ((fx_proc(test) == fx_s) ? OP_IF_S_A_A : OP_IF_A_A_A)); + }} + return(OPT_T); + } + else + { + if ((op == OP_OR) || (op == OP_AND)) + { + int32_t args, pairs = 0; + s7_pointer p, sym = NULL; + bool c_s_is_ok = true; + + for (p = cdr(expr); is_pair(p); p = cdr(p)) + if (!is_fxable(sc, car(p))) + return(OPT_F); + if (!is_null(p)) return(OPT_OOPS); + for (args = 0, p = cdr(expr); is_pair(p); p = cdr(p), args++) /* this only applies to or/and */ + if (is_pair(car(p))) + { + pairs++; + if ((c_s_is_ok) && + ((!is_h_safe_c_s(car(p))) || + ((sym) && (sym != cadar(p))))) + c_s_is_ok = false; + else sym = (is_pair(cdar(p))) ? cadar(p) : sc->unspecified; + } + + if ((c_s_is_ok) && (args == 2) && (pairs == 2)) + { + if (op == OP_OR) + { + set_opt3_sym(cdr(expr), cadadr(expr)); + if ((is_symbol(caadr(expr))) && (symbol_type(caadr(expr)) > 0) && (is_global(caadr(expr))) && + ((is_symbol(caaddr(expr))) && (symbol_type(caaddr(expr)) > 0) && (is_global(caaddr(expr))))) + { + set_opt3_int(expr, symbol_type(caadr(expr))); + set_opt2_int(cdr(expr), symbol_type(caaddr(expr))); + set_safe_optimize_op(expr, OP_OR_S_TYPE_2); + } + else set_safe_optimize_op(expr, OP_OR_S_2); + } + else + { + set_opt3_sym(cdr(expr), cadadr(expr)); + set_safe_optimize_op(expr, OP_AND_S_2); + } + return(OPT_T); + } + + for (p = cdr(expr); is_pair(p); p = cdr(p)) + set_fx_direct(p, fx_choose(sc, p, e, pair_symbol_is_safe)); + if (op == OP_OR) + { + if (args == 2) + set_safe_optimize_op(expr, OP_OR_2A); + else + { + if (args == 3) + set_safe_optimize_op(expr, OP_OR_3A); + else set_safe_optimize_op(expr, OP_OR_N); + } + return(OPT_T); + } + if (args == 2) + set_safe_optimize_op(expr, OP_AND_2A); + else set_safe_optimize_op(expr, (args == 3) ? OP_AND_3A : OP_AND_N); + return(OPT_T); + } + else + if (op == OP_BEGIN) + { + s7_pointer p; + if (!is_pair(cdr(expr))) return(OPT_F); + for (p = cdr(expr); is_pair(p); p = cdr(p)) + if (!is_fxable(sc, car(p))) + return(OPT_F); + if (!is_null(p)) return(OPT_OOPS); + for (p = cdr(expr); is_pair(p); p = cdr(p)) + set_fx_direct(p, fx_choose(sc, p, e, pair_symbol_is_safe)); + set_safe_optimize_op(expr, ((is_pair(cddr(expr))) && (is_null(cdddr(expr)))) ? OP_BEGIN_AA : OP_BEGIN_NA); + return(OPT_T); + }}} /* fully fxable lets don't happen much: even let-2a-a is scarcely used */ + return(OPT_F); +} + +static opt_t optimize_funcs(s7_scheme *sc, s7_pointer expr, s7_pointer func, int32_t hop, int32_t orig_hop, s7_pointer e) +{ + int32_t pairs = 0, symbols = 0, args = 0, bad_pairs = 0, quotes = 0; + s7_pointer p; + if (SHOW_EVAL_OPS) fprintf(stderr, "%s[%d]: %s\n", __func__, __LINE__, display_80(expr)); + for (p = cdr(expr); is_pair(p); p = cdr(p), args++) /* check the args (the calling expression) */ + { + s7_pointer car_p = car(p); + if (is_normal_symbol(car_p)) /* for opt func */ + symbols++; + else + if (is_pair(car_p)) + { + pairs++; + if (!is_checked(car_p)) + { + opt_t res; + if ((is_pair(car(car_p))) && (caar(car_p) == sc->let_symbol)) + res = OPT_F; + else res = optimize_expression(sc, car_p, orig_hop, e, false); + if (res == OPT_F) + { + bad_pairs++; + if (is_proper_quote(sc, car_p)) + quotes++; + } + else + if (res == OPT_OOPS) + return(OPT_OOPS); + } + else + if ((!is_optimized(car_p)) || + (is_unsafe(car_p))) + { + bad_pairs++; + if (is_proper_quote(sc, car_p)) + quotes++; + }}} + if (is_null(p)) /* if not null, dotted list of args, (cons 1 . 2) etc -- error perhaps? */ + { + switch (args) + { + case 0: return(optimize_thunk(sc, expr, func, hop, e)); + case 1: return(optimize_func_one_arg(sc, expr, func, hop, pairs, symbols, quotes, bad_pairs, e)); + case 2: return(optimize_func_two_args(sc, expr, func, hop, pairs, symbols, quotes, bad_pairs, e)); + case 3: return(optimize_func_three_args(sc, expr, func, hop, pairs, symbols, quotes, bad_pairs, e)); + default: return(optimize_func_many_args(sc, expr, func, hop, args, pairs, symbols, quotes, bad_pairs, e)); + }} + return(OPT_OOPS); /* was OPT_F, but this is always an error */ +} + +static opt_t optimize_expression(s7_scheme *sc, s7_pointer expr, int32_t hop, s7_pointer e, bool export_ok) +{ + s7_pointer car_expr = car(expr); + int32_t orig_hop = hop; + set_checked(expr); + + if (is_symbol(car_expr)) + { + s7_pointer slot; + if (is_syntactic_symbol(car_expr)) + { + if (!is_pair(cdr(expr))) + return(OPT_OOPS); + return(optimize_syntax(sc, expr, T_Syn(global_value(car_expr)), hop, e, export_ok)); + } + slot = find_uncomplicated_symbol(sc, car_expr, e); /* local vars (recursive calls too??) are considered complicated */ + if (is_slot(slot)) + { + s7_pointer func = slot_value(slot); + if (is_syntax(func)) /* not is_syntactic -- here we have the value */ + return((is_pair(cdr(expr))) ? optimize_syntax(sc, expr, func, hop, e, export_ok) : OPT_OOPS); /* e can be extended via set-cdr! here */ + + if (is_any_macro(func)) + return(OPT_F); + + /* we miss implicit indexing here because at this time, the data are not set */ + if ((is_t_procedure(func)) || /* t_procedure_p: c_funcs, closures, etc */ + ((is_applicable(func)) && + (is_safe_procedure(func)))) /* built-in applicable objects like vectors */ + { + if ((hop != 0) && + ((is_any_closure(func)) || /* see use-redef in s7test -- I'm not sure about this */ + ((!is_global(car_expr)) && + ((!is_slot(global_slot(car_expr))) || + (global_value(car_expr) != func)))) && + (!is_immutable(car_expr)) && /* list|apply-values -- can't depend on opt1 here because it might not be global, or might be redefined locally */ + (!is_immutable_slot(slot))) /* (define-constant...) */ + { + /* (let () (define (f2 a) (+ a 1)) (define (f1 a) (f2 a)) (define (f2 a) (- a)) (f1 12)) + * (let () (define (f2 a) (+ a 1)) (define (f1 a) (f2 a)) (define (f2 a) (- a 1)) (f1 12)) + * and similar define* cases + */ + hop = 0; + /* this is very tricky! See s7test for some cases. Basically, we need to protect a recursive call + * of the current function being optimized from being confused with some previous definition + * of the same name. But method lists have global names so the global bit is off even though the + * thing is actually a safe global. But no closure can be considered safe in the hop sense -- + * even a global function might be redefined at any time, and previous uses of it in other functions + * need to reflect its new value. + * So, closures are always checked, but built-in functions are used as if never redefined until that redefinition. + * Syntax handling is already impure in s7, so the special handling of built-in functions doesn't + * offend me much. Consider each a sort of reader macro until someone redefines it -- previous + * uses might not be affected because they might have been optimized away -- the result depends on the + * current optimizer. + * Another case (from K Matheussen): + * (define (call-func func arg1 arg2) (define (call) (func arg1 arg2)) (call)) (call-func + 1 2.5) (call-func - 5 2) + * when we get here originally "func" is +, hop=1, but just checking for !is_global(car_expr) is + * not good enough -- if we load mockery.scm, nothing is global! + * Yet another case (define (test-abs) (define (abs x) (+ x 1)) (format *stderr* "abs ~A~%" (abs -1))) + * when optimize_syntax sees the (define abs ...), it inserts abs into e so that the caller's e is extended (set-cdr!) + * so that find_uncomplicated_symbol above will be unhappy when we reach (abs -1) as the format arg. + * This can be confused if lambda is redefined at some point, but... + */ + } + return(optimize_funcs(sc, expr, func, hop, orig_hop, e)); + }} + else + if ((sc->undefined_identifier_warnings) && + (slot == sc->undefined) && /* car_expr is not in e or global */ + (symbol_tag(car_expr) == 0)) /* and we haven't looked it up earlier */ + { + s7_pointer p = current_input_port(sc); + if ((is_input_port(p)) && + (port_file(p) != stdin) && + (!port_is_closed(p)) && + (port_filename(p))) + s7_warn(sc, 1024, "%s might be undefined (%s %u)\n", display(car_expr), port_filename(p), port_line_number(p)); + else s7_warn(sc, 1024, "; %s might be undefined\n", display(car_expr)); + symbol_set_tag(car_expr, 1); /* one warning is enough */ + } + + /* car_expr is a symbol but it's not a built-in procedure or a safe case = vector etc */ + { + /* else maybe it's something like a let variable binding: (sqrtfreq (sqrt frequency)) */ + s7_pointer p; + int32_t len = 0, pairs = 0, symbols = 0; + + for (p = cdr(expr); is_pair(p); p = cdr(p), len++) + { + s7_pointer car_p = car(p); + if (is_pair(car_p)) + { + pairs++; + if ((!is_checked(car_p)) && + (optimize_expression(sc, car_p, hop, e, false) == OPT_OOPS)) + return(OPT_OOPS); + } + else + if (is_symbol(car_p)) + symbols++; + } + if ((is_null(p)) && /* (+ 1 . 2) */ + (!is_optimized(expr))) + { + /* len=0 case is almost entirely arglists */ + set_opt1_con(expr, sc->unused); + + if (pairs == 0) + { + if (len == 0) + { + /* hoping to catch object application here, as in readers in Snd */ + set_unsafe_optimize_op(expr, OP_UNKNOWN); + return(OPT_F); + } + if (len == 1) + { + if (car_expr != sc->quote_symbol) /* !! quote can be redefined locally, unsetting the T_SYNTACTIC flag -- can this happen elsewhere? */ + set_unsafe_optimize_op(expr, (is_normal_symbol(cadr(expr))) ? OP_UNKNOWN_S : OP_UNKNOWN_A); + fx_annotate_arg(sc, cdr(expr), e); /* g->a later if closure */ + return(OPT_F); + } + if (len == 2) + { + set_unsafely_optimized(expr); + set_optimize_op(expr, OP_UNKNOWN_GG); + return(OPT_F); + } + if (len >= 3) + { + if (len == symbols) + { + set_unsafe_optimize_op(expr, OP_UNKNOWN_NS); + set_opt3_arglen(cdr(expr), len); + return(OPT_F); + } + if (fx_count(sc, expr) == len) + { + set_unsafe_optimize_op(expr, OP_UNKNOWN_NA); + set_opt3_arglen(cdr(expr), len); + return(OPT_F); + }}} + else /* pairs != 0 */ + { + s7_pointer arg1 = cadr(expr); + if ((pairs == 1) && (len == 1)) + { + if ((car_expr == sc->quote_symbol) && + (direct_memq(sc->quote_symbol, e))) + return(OPT_OOPS); + + if (is_fxable(sc, arg1)) + { + set_opt3_arglen(cdr(expr), 1); + fx_annotate_arg(sc, cdr(expr), e); + set_unsafe_optimize_op(expr, OP_UNKNOWN_A); + return(OPT_F); + }} + if (fx_count(sc, expr) == len) + { + set_unsafe_optimize_op(expr, (len == 1) ? OP_UNKNOWN_A : ((len == 2) ? OP_UNKNOWN_AA : OP_UNKNOWN_NA)); + set_opt3_arglen(cdr(expr), len); + if (len <= 2) fx_annotate_args(sc, cdr(expr), e); + return(OPT_F); + } + set_unsafe_optimize_op(expr, OP_UNKNOWN_NP); + set_opt3_arglen(cdr(expr), len); + return(OPT_F); + }}}} + else + { + /* car(expr) is not a symbol, but there might be interesting stuff here */ + /* (define (hi a) (case 1 ((1) (if (> a 2) a 2)))) */ + s7_pointer p; + + if (is_c_function(car_expr)) /* (#_abs x) etc */ + return(optimize_funcs(sc, expr, car_expr, 1, orig_hop, e)); + + if (is_syntax(car_expr)) /* (#_cond ...) */ + { + if (!is_pair(cdr(expr))) + return(OPT_OOPS); + return(optimize_syntax(sc, expr, car_expr, orig_hop, e, export_ok)); + } + if (is_any_macro(car_expr)) + return(OPT_F); + + /* if car is a pair, we can't easily tell whether its value is (say) + or cond, so we need to catch this case and fixup fx settings */ + for (p = expr; is_pair(p); p = cdr(p)) + if (((is_symbol(car(p))) && (is_syntactic_symbol(car(p)))) || + ((is_pair(car(p))) && (!is_checked(car(p))) && + (optimize_expression(sc, car(p), hop, e, false) == OPT_OOPS))) + return(OPT_OOPS); + /* here we get for example: + * ((if (not (let? p)) write write-to-vector) obj p) ; not uncomplicated/c-function [((if 3d fourth third) p) in index] + * ((if (symbol? (cadr f)) cadr (if (pair? (cadr f)) caadr not)) f) ; fx not symbol -- opif_a_aaq_a + * ((if (input-port? port) call-with-input-file call-with-output-file) port proc) ; not safe I guess + */ + } + return(OPT_F); +} + +static opt_t optimize(s7_scheme *sc, s7_pointer code, int32_t hop, s7_pointer e) +{ + s7_pointer x; + for (x = code; (is_pair(x)) && (!is_checked(x)); x = cdr(x)) + { + s7_pointer obj = car(x); + set_checked(x); + if (is_pair(obj)) + { + if ((!is_checked(obj)) && + (optimize_expression(sc, obj, hop, e, true) == OPT_OOPS)) + { + s7_pointer p; + for (p = cdr(x); is_pair(p); p = cdr(p)); + if (!is_null(p)) + syntax_error_nr(sc, "stray dot in function body: ~S", 30, code); + return(OPT_OOPS); + }} + else /* new 22-Sep-19, but I don't think this saves anything over falling into trailers */ + if (is_symbol(obj)) + set_optimize_op(obj, (is_keyword(obj)) ? OP_CONSTANT : OP_SYMBOL); + else set_optimize_op(obj, OP_CONSTANT); + } + if (!is_list(x)) + syntax_error_nr(sc, "stray dot in function body: ~S", 30, code); + return(OPT_F); +} + + +static bool symbol_is_in_arg_list(const s7_pointer sym, s7_pointer lst) +{ + s7_pointer x; + for (x = lst; is_pair(x); x = cdr(x)) + if ((sym == car(x)) || + ((is_pair(car(x))) && (sym == caar(x)))) + return(true); + return(sym == x); +} + +static void check_lambda_args(s7_scheme *sc, s7_pointer args, int32_t *arity, s7_pointer form) +{ + s7_pointer x; + int32_t i; + + if (!is_list(args)) + { + if (is_constant(sc, args)) /* (lambda :a ...) or (define (f :a) ...) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "lambda parameter is a constant: (~S ~S ...)", 43), car(form), cadr(form))); + /* we currently accept (lambda i i . i) (lambda quote i) (lambda : : . #()) (lambda : 1 . "") + * at this level, but when the lambda form is evaluated, it will trigger an error. + */ + if (is_symbol(args)) set_local(args); + if (arity) (*arity) = -1; + return; + } + for (i = 0, x = args; is_pair(x); i++, x = cdr(x)) + { + s7_pointer car_x = car(x); + if (is_constant(sc, car_x)) /* (lambda (pi) pi), constant here means not a symbol */ + { + if (is_pair(car_x)) /* (lambda ((:hi . "hi") . "hi") 1) */ + error_nr(sc, sc->syntax_error_symbol, /* don't use ~A here or below, (lambda #\null do) for example */ + set_elist_4(sc, wrap_string(sc, "lambda parameter ~S is a pair (perhaps use lambda*?): (~S ~S ...)", 65), + car_x, car(form), cadr(form))); + if ((car_x == sc->rest_keyword) && + ((car(form) == sc->define_symbol) || (car(form) == sc->lambda_symbol))) + error_nr(sc, sc->syntax_error_symbol, + set_elist_5(sc, wrap_string(sc, "lambda parameter is ~S? (~S ~S ...), perhaps use ~S", 51), + car_x, car(form), cadr(form), + (car(form) == sc->define_symbol) ? sc->define_star_symbol : sc->lambda_star_symbol)); + error_nr(sc, sc->syntax_error_symbol, /* (lambda (a :b c) 1) */ + set_elist_4(sc, wrap_string(sc, "lambda parameter ~S is a constant: (~S ~S ...)", 46), + car_x, car(form), cadr(form))); + } + if (symbol_is_in_arg_list(car_x, cdr(x))) /* (lambda (a a) ...) or (lambda (a . a) ...) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_4(sc, wrap_string(sc, "lambda parameter ~S is used twice in the parameter list, (~S ~S ...)", 68), + car_x, car(form), cadr(form))); + set_local(car_x); + } + if (is_not_null(x)) + { + if (is_constant(sc, x)) /* (lambda (a . 0.0) a) or (lambda (a . :b) a) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_4(sc, wrap_string(sc, "lambda :rest parameter ~S is a constant in (~S ~S ...)", 54), + x, car(form), cadr(form))); + i = -i - 1; + } + if (arity) (*arity) = i; +} + +static s7_pointer check_lambda_star_args(s7_scheme *sc, s7_pointer args, s7_pointer body, s7_pointer form) /* checks closure*, macro*, and bacro* */ +{ + s7_pointer top, v, w; + bool has_defaults; + + if (!is_list(args)) + { + if (is_constant(sc, args)) /* (lambda* :a ...) or (define* (f . :a) ...) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "lambda* parameter is a constant: (~S ~S ...)", 44), car(form), cadr(form))); + if (is_symbol(args)) set_local(args); + return(args); + } + + has_defaults = false; + top = args; + for (v = args, w = args; is_pair(w); v = w, w = cdr(w)) + { + s7_pointer car_w = car(w); + if (is_pair(car_w)) + { + has_defaults = true; + if (is_constant(sc, car(car_w))) /* (lambda* ((:a 1)) ...) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_4(sc, wrap_string(sc, "lambda* parameter ~S is a constant: (~S ~S ...)", 47), + car(car_w), car(form), cadr(form))); + if (symbol_is_in_arg_list(caar(w), cdr(w))) /* (lambda* ((a 1) a) ...) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_4(sc, wrap_string(sc, "lambda* parameter ~S occurs twice in the argument list: (~S ~S ...)", 67), + car(car_w), car(form), cadr(form))); + if (!is_pair(cdr(car_w))) + { + if (is_null(cdr(car_w))) /* (lambda* ((a)) ...) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_4(sc, wrap_string(sc, "lambda* parameter ~S default value missing in (~S ~S ...)", 57), + car_w, car(form), cadr(form))); + error_nr(sc, sc->syntax_error_symbol, /* (lambda* ((a . 0.0)) a) */ + set_elist_4(sc, wrap_string(sc, "lambda* parameter ~S is a dotted pair in (~S ~S ...)", 52), + car_w, car(form), cadr(form))); + } + if ((is_pair(cadr(car_w))) && /* (lambda* ((a (quote . -1))) ...) */ + (s7_list_length(sc, cadr(car_w)) < 0)) + error_nr(sc, sc->syntax_error_symbol, + set_elist_4(sc, wrap_string(sc, "lambda* parameter ~S default value is not a proper list in (~S ~S ...)", 70), + car_w, car(form), cadr(form))); + if (is_not_null(cddr(car_w))) /* (lambda* ((a 0.0 'hi)) a) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_4(sc, wrap_string(sc, "lambda* parameter ~S has multiple default values in (~S ~S ...)", 63), + car_w, car(form), cadr(form))); + + set_local(car(car_w)); + } + else + if (car_w != sc->rest_keyword) + { + if (is_constant(sc, car_w)) + { + if (car_w != sc->allow_other_keys_keyword) + error_nr(sc, sc->syntax_error_symbol, /* (lambda* (pi) ...) */ + set_elist_4(sc, wrap_string(sc, "lambda* parameter ~S is a constant: (~S ~S ...)", 47), + car_w, car(form), cadr(form))); + if (is_not_null(cdr(w))) /* (lambda* (:allow-other-keys x) x) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, ":allow-other-keys should be the last parameter: (~S ~S ...)", 59), + car(form), cadr(form))); + if (w == top) /* (lambda* (:allow-other-keys) 1) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, ":allow-other-keys can't be the only parameter: (~S ~S ...)", 58), + car(form), cadr(form))); + set_allow_other_keys(top); + set_cdr(v, sc->nil); + } + if (symbol_is_in_arg_list(car_w, cdr(w))) /* (lambda* (a a) ...) or (lambda* (a . a) ...) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_4(sc, wrap_string(sc, "lambda* parameter ~S is used twice in the parameter list: (~S ~S ...)", 69), + car_w, car(form), cadr(form))); + + if (!is_keyword(car_w)) set_local(car_w); + } + else + { + has_defaults = true; + if (!is_pair(cdr(w))) /* (lambda* (:rest) ...) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "lambda* :rest parameter missing in (~S ~S ...)", 46), + car(form), cadr(form))); + if (!is_symbol(cadr(w))) /* (lambda* (:rest (a 1)) ...) */ + { + if (!is_pair(cadr(w))) /* (lambda* (:rest 1) ...) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_4(sc, wrap_string(sc, "lambda* :rest parameter is not a symbol: ~S in (~S ~S ...)", 58), + w, car(form), cadr(form))); + error_nr(sc, sc->syntax_error_symbol, /* (lambda* (:rest '(1 2)) 1) */ + set_elist_4(sc, wrap_string(sc, "lambda* :rest parameter can't have a default value: ~S in (~S ~S ...)", 69), + w, car(form), cadr(form))); + } + if (is_constant(sc, cadr(w))) /* (lambda* (a :rest x) ...) where x is locally a constant */ + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_4(sc, wrap_string(sc, "lambda*: ~S is immutable, so it can't be the :rest parameter name: (~S ~S ...)", 78), + cadr(w), car(form), cadr(form))); + set_local(cadr(w)); + }} + if (is_not_null(w)) + { + if (is_constant(sc, w)) /* (lambda* (a . 0.0) a) or (lambda* (a . :b) a) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_4(sc, wrap_string(sc, "lambda* :rest parameter ~S is a constant, (~S ~S ...)", 53), + w, car(form), cadr(form))); + if (is_symbol(w)) + set_local(w); + } + else + if ((body) && (!has_defaults) && (is_pair(args))) + set_has_no_defaults(body); + return(top); +} + +static void set_rec_tc_args(s7_scheme *sc, s7_int args) +{ + if (sc->rec_tc_args == -1) + sc->rec_tc_args = args; + else + if (sc->rec_tc_args != args) + sc->rec_tc_args = -2; +} + +typedef enum {UNSAFE_BODY=0, RECUR_BODY, SAFE_BODY, VERY_SAFE_BODY} body_t; +static body_t min_body(body_t b1, body_t b2) {return((b1 < b2) ? b1 : b2);} +static body_t body_is_safe(s7_scheme *sc, s7_pointer func, s7_pointer body, bool at_end); + +static body_t form_is_safe(s7_scheme *sc, s7_pointer func, s7_pointer x, bool at_end) /* called only from body_is_safe */ +{ + s7_pointer expr = car(x); + body_t result = VERY_SAFE_BODY; + + if (is_symbol_and_syntactic(expr)) + { + if (!is_pair(cdr(x))) return(UNSAFE_BODY); + switch (symbol_syntax_op_checked(x)) + /* symbol_syntax_op(expr) here gets tangled in fx_annotation order problems! -- fix this?!? + * it appears that safe bodies are marked unsafe because the opts are out-of-order? + */ + { + case OP_OR: case OP_AND: case OP_BEGIN: case OP_WITH_BAFFLE: + return(body_is_safe(sc, func, cdr(x), at_end)); + + case OP_MACROEXPAND: + return(UNSAFE_BODY); + + case OP_QUOTE: case OP_QUOTE_UNCHECKED: + return(((!is_pair(cdr(x))) || (!is_null(cddr(x)))) ? UNSAFE_BODY : VERY_SAFE_BODY); /* (quote . 1) or (quote 1 2) etc */ + + case OP_IF: + if (!is_pair(cddr(x))) return(UNSAFE_BODY); + if (is_pair(cadr(x))) + { + result = form_is_safe(sc, func, cadr(x), false); + if (result == UNSAFE_BODY) return(UNSAFE_BODY); + } + if (is_pair(caddr(x))) + { + result = min_body(result, form_is_safe(sc, func, caddr(x), at_end)); + if (result == UNSAFE_BODY) return(UNSAFE_BODY); + } + if ((is_pair(cdddr(x))) && + (is_pair(cadddr(x)))) + return(min_body(result, form_is_safe(sc, func, cadddr(x), at_end))); + return(result); + + case OP_WHEN: case OP_UNLESS: + if (!is_pair(cddr(x))) return(UNSAFE_BODY); + if (is_pair(cadr(x))) + { + result = form_is_safe(sc, func, cadr(x), false); + if (result == UNSAFE_BODY) return(UNSAFE_BODY); + } + return(min_body(result, body_is_safe(sc, func, cddr(x), at_end))); + + case OP_COND: + { + bool follow = false; + s7_pointer p = cdr(x); + for (s7_pointer sp = x; is_pair(p); p = cdr(p)) + { + s7_pointer ex = car(p); + if (!is_pair(ex)) return(UNSAFE_BODY); + if (is_pair(car(ex))) + { + result = min_body(result, form_is_safe(sc, func, car(ex), false)); + if (result == UNSAFE_BODY) return(UNSAFE_BODY); + } + if (is_pair(cdr(ex))) + { + result = min_body(result, body_is_safe(sc, func, cdr(ex), at_end)); + if (result == UNSAFE_BODY) return(UNSAFE_BODY); + } + if (follow) {sp = cdr(sp); if (p == sp) return(UNSAFE_BODY);} + follow = (!follow); + } + return((is_null(p)) ? result : UNSAFE_BODY); + } + + case OP_CASE: + { + bool follow = false; + s7_pointer sp, p; + if (!is_pair(cddr(x))) return(UNSAFE_BODY); + if (is_pair(cadr(x))) + { + result = form_is_safe(sc, func, cadr(x), false); + if (result == UNSAFE_BODY) return(UNSAFE_BODY); + } + sp = cdr(x); + for (p = cdr(sp); is_pair(p); p = cdr(p)) + { + if (!is_pair(car(p))) return(UNSAFE_BODY); + if (is_pair(cdar(p))) + { + result = min_body(result, body_is_safe(sc, func, cdar(p), at_end)); /* null cdar(p) ok here */ + if (result == UNSAFE_BODY) return(UNSAFE_BODY); + } + if (follow) {sp = cdr(sp); if (p == sp) return(UNSAFE_BODY);} + follow = (!follow); + } + return(result); + } + + case OP_SET: + /* if we set func, we have to abandon the tail call scan: (let () (define (hi a) (let ((v (vector 1 2 3))) (set! hi v) (hi a))) (hi 1)) */ + if (!is_pair(cddr(x))) return(UNSAFE_BODY); + if (cadr(x) == func) return(UNSAFE_BODY); + + /* car(x) is set!, cadr(x) is settee or obj, caddr(x) is val */ + if (is_pair(caddr(x))) + { + result = form_is_safe(sc, func, caddr(x), false); + if (result == UNSAFE_BODY) return(UNSAFE_BODY); + } + return((is_pair(cadr(x))) ? min_body(result, form_is_safe(sc, func, cadr(x), false)) : result); + /* not OP_DEFINE even in simple cases (safe_closure assumes constant funclet) */ + + case OP_WITH_LET: + if (!is_pair(cddr(x))) return(UNSAFE_BODY); + return((is_pair(cadr(x))) ? UNSAFE_BODY : min_body(body_is_safe(sc, sc->F, cddr(x), at_end), SAFE_BODY)); + /* shadowing can happen in with-let -- symbols are global so local_slots are shadowable */ + + case OP_LET_TEMPORARILY: + if (!is_pair(cadr(x))) return(UNSAFE_BODY); + for (s7_pointer p = cadr(x); is_pair(p); p = cdr(p)) + { + if ((!is_pair(car(p))) || + (!is_pair(cdar(p)))) + return(UNSAFE_BODY); + if (is_pair(cadar(p))) + { + result = min_body(result, form_is_safe(sc, sc->F, cadar(p), false)); + if (result == UNSAFE_BODY) return(UNSAFE_BODY); + }} + return(min_body(result, body_is_safe(sc, sc->F, cddr(x), at_end))); + + /* in the name binders, we first have to check that "func" actually is the same thing as the caller's func */ + case OP_LET: case OP_LET_STAR: case OP_LETREC: case OP_LETREC_STAR: + { + bool follow = false; + s7_pointer let_name, sp, vars = cadr(x), body = cddr(x); + if (is_symbol(vars)) + { + if (!is_pair(body)) return(UNSAFE_BODY); /* (let name . res) */ + if (vars == func) return(UNSAFE_BODY); /* named let shadows caller */ + let_name = vars; + vars = caddr(x); + body = cdddr(x); + if (is_symbol(func)) + add_symbol_to_list(sc, func); + } + else let_name = func; + + for (sp = NULL; is_pair(vars); vars = cdr(vars)) + { + s7_pointer let_var = car(vars), var_name; + + if ((!is_pair(let_var)) || + (!is_pair(cdr(let_var)))) + return(UNSAFE_BODY); + var_name = car(let_var); + if ((!is_symbol(var_name)) || + (var_name == let_name) || /* let var shadows caller */ + (var_name == func)) + return(UNSAFE_BODY); + add_symbol_to_list(sc, var_name); + + if (is_pair(cadr(let_var))) + { + result = min_body(result, form_is_safe(sc, let_name, cadr(let_var), false)); + if (result == UNSAFE_BODY) return(UNSAFE_BODY); + } + follow = (!follow); + if (follow) + { + if (!sp) + sp = vars; + else + { + sp = cdr(sp); + if (vars == sp) return(UNSAFE_BODY); + }}} + return(min_body(result, body_is_safe(sc, let_name, body, (let_name != func) || at_end))); + } + + case OP_DO: /* (do (...) (...) ...) */ + if (!is_pair(cddr(x))) return(UNSAFE_BODY); + if (is_pair(cadr(x))) + { + s7_pointer vars = cadr(x); + s7_pointer sp = vars; + for (bool follow = false; is_pair(vars); vars = cdr(vars)) + { + s7_pointer do_var = car(vars); + if ((!is_pair(do_var)) || + (!is_pair(cdr(do_var))) || /* (do ((a . 1) (b . 2)) ...) */ + (car(do_var) == func) || + (!is_symbol(car(do_var)))) + return(UNSAFE_BODY); + + add_symbol_to_list(sc, car(do_var)); + + if (is_pair(cadr(do_var))) + result = min_body(result, form_is_safe(sc, func, cadr(do_var), false)); + if ((is_pair(cddr(do_var))) && (is_pair(caddr(do_var)))) + result = min_body(result, form_is_safe(sc, func, caddr(do_var), false)); + if (result == UNSAFE_BODY) + return(UNSAFE_BODY); + if (sp != vars) + { + if (follow) {sp = cdr(sp); if (vars == sp) return(UNSAFE_BODY);} + follow = (!follow); + }}} + if (is_pair(caddr(x))) + result = min_body(result, body_is_safe(sc, func, caddr(x), at_end)); + return(min_body(result, body_is_safe(sc, func, cdddr(x), false))); + + /* define and friends are not safe: (define (a) (define b 3)...) tries to put b in the current let, + * but in a safe func, that's a constant. See s7test L 1865 for an example. + */ + default: + /* OP_LAMBDA is major case here */ + /* try to catch weird cases like: + * (let () (define (hi1 a) (define (hi1 b) (+ b 1)) (hi1 a)) (hi1 1)) + * (let () (define (hi1 a) (define (ho1 b) b) (define (hi1 b) (+ b 1)) (hi1 a)) (hi1 1)) + */ + return(UNSAFE_BODY); + }} + else /* car(x) is not syntactic */ + { + if (expr == func) /* try to catch tail call, expr is car(x) */ + { + bool follow = false; + s7_pointer sp = x, p; + sc->got_rec = true; /* (walk (car tree)) lint and almost all others in s7test */ + set_rec_tc_args(sc, proper_list_length(cdr(x))); + if (!at_end) {result = RECUR_BODY; sc->not_tc = true;} + for (p = cdr(x); is_pair(p); p = cdr(p)) + { + if (is_pair(car(p))) + { + if (caar(p) == func) /* func called as arg, so not tail call */ + { + sc->not_tc = true; + result = RECUR_BODY; + } + result = min_body(result, form_is_safe(sc, func, car(p), false)); + if (result == UNSAFE_BODY) return(UNSAFE_BODY); + } + else + if (car(p) == func) /* func itself as arg */ + return(UNSAFE_BODY); + + if (follow) {sp = cdr(sp); if (p == sp) return(UNSAFE_BODY);} + follow = (!follow); + } + if ((at_end) && (!sc->not_tc) && (is_null(p))) /* tail call, so safe */ + { + sc->got_tc = true; + set_rec_tc_args(sc, proper_list_length(cdr(x))); + return(result); + } + if (result != UNSAFE_BODY) result = RECUR_BODY; + return(result); + } + + if (is_symbol(expr)) /* expr=car(x) */ + { + s7_pointer f, f_slot; + bool c_safe; + + if (symbol_is_in_list(sc, expr)) return(UNSAFE_BODY); + if ((is_slot(global_slot(expr))) && (is_syntax(global_value(expr)))) + return(UNSAFE_BODY); /* syntax hidden behind some other name */ + + f_slot = s7_slot(sc, expr); + if (!is_slot(f_slot)) return(UNSAFE_BODY); + + f = slot_value(f_slot); + if (is_c_function(f)) + { + if ((expr == sc->apply_symbol) && (is_pair(cdr(x))) && (is_symbol(cadr(x)))) /* (apply ...) */ + { + s7_pointer cadr_f = lookup_unexamined(sc, cadr(x)); /* "unexamined" to skip unbound_variable */ + c_safe = ((cadr_f) && /* (cadr_f != sc->undefined) && */ + ((is_safe_c_function(cadr_f)) || + ((is_closure(cadr_f)) && (is_very_safe_closure(cadr_f))))); + } + else c_safe = (is_safe_or_scope_safe_procedure(f)); + } + else c_safe = false; + + result = ((is_sequence(f)) || + ((is_closure(f)) && (is_very_safe_closure(f))) || + ((c_safe) && ((is_immutable_slot(f_slot)) || (is_global(expr))))) ? VERY_SAFE_BODY : SAFE_BODY; + + if ((c_safe) || + ((is_any_closure(f)) && (is_safe_closure(f))) || + (is_sequence(f))) + { + bool follow = false; + s7_pointer sp = x, p = cdr(x); + + for (; is_pair(p); p = cdr(p)) + { + if (is_unquoted_pair(car(p))) + { + if (caar(p) == func) + { + sc->got_rec = true; /* (+ 1 (recur (- x 1))) t123 (and others) */ + set_rec_tc_args(sc, proper_list_length(cdar(p))); + return(RECUR_BODY); + } + if ((is_c_function(f)) && (is_scope_safe(f)) && + (caar(p) == sc->lambda_symbol)) + { + s7_pointer largs, lbody, q; + body_t lresult; + + if (!is_pair(cdar(p))) /* (lambda . /) */ + return(UNSAFE_BODY); + largs = cadar(p); + lbody = cddar(p); + for (q = largs; is_pair(q); q = cdr(q)) + { + if (!is_symbol(car(q))) + return(UNSAFE_BODY); + add_symbol_to_list(sc, car(q)); + } + lresult = body_is_safe(sc, func, lbody, false); + result = min_body(result, lresult); + } + else result = min_body(result, form_is_safe(sc, func, car(p), false)); + if (result == UNSAFE_BODY) return(UNSAFE_BODY); + } + else + if (car(p) == func) /* the current function passed as an argument to something */ + return(UNSAFE_BODY); + + if (follow) {sp = cdr(sp); if (p == sp) return(UNSAFE_BODY);} + follow = (!follow); + } + return((is_null(p)) ? result : UNSAFE_BODY); + } + if ((expr == sc->quote_symbol) && + (is_proper_list_1(sc, cdr(x))) && + (is_global(sc->quote_symbol))) + return(result); + + if (expr == sc->values_symbol) /* (values) is safe, as is (values x) if x is: (values (define...)) */ + { + if (is_null(cdr(x))) return(result); + if ((is_pair(cdr(x))) && (is_null(cddr(x)))) + return((is_pair(cadr(x))) ? min_body(result, form_is_safe(sc, func, cadr(x), false)) : result); + }} + return(UNSAFE_BODY); /* not recur_body here if at_end -- possible defines in body etc */ + } + return(result); +} + +static body_t body_is_safe(s7_scheme *sc, s7_pointer func, s7_pointer body, bool at_end) +{ + bool follow = false; + s7_pointer p = body; + body_t result = VERY_SAFE_BODY; + for (s7_pointer sp = body; is_pair(p); p = cdr(p)) + { + if (is_pair(car(p))) + { + result = min_body(result, form_is_safe(sc, func, car(p), (at_end) && (is_null(cdr(p))))); + if (result == UNSAFE_BODY) return(UNSAFE_BODY); + } + if (p != body) + { + if (follow) {sp = cdr(sp); if (p == sp) return(UNSAFE_BODY);} + follow = (!follow); + }} + return((is_null(p)) ? result : UNSAFE_BODY); +} + +static bool tree_has_definers_or_binders(s7_scheme *sc, s7_pointer tree) +{ + for (s7_pointer p = tree; is_pair(p); p = cdr(p)) + if (tree_has_definers_or_binders(sc, car(p))) + return(true); + return((is_symbol(tree)) && + (is_definer_or_binder(tree))); +} + +static bool check_recur_if(s7_scheme *sc, const s7_pointer name, int32_t vars, s7_pointer args, s7_pointer body) +{ + s7_pointer test = cadr(body); + if (is_fxable(sc, test)) /* if_(A)... */ + { + s7_pointer obody = cddr(body), orig = NULL; + s7_pointer true_p = car(obody); /* if_a_(A)... */ + s7_pointer false_p = cadr(obody); /* if_a_a_(if...) */ + + if ((vars <= 2) && + (is_fxable(sc, true_p)) && + (is_proper_list_4(sc, false_p))) + { + if (car(false_p) == sc->if_symbol) + { + s7_pointer test2 = cadr(false_p); + s7_pointer true2 = caddr(false_p); + s7_pointer false2 = cadddr(false_p); + if ((is_fxable(sc, test2)) && + (is_proper_list_3(sc, false2)) && /* opa_laaq or oplaa_laaq */ + (is_h_optimized(false2))) /* the c-op */ + { + s7_pointer la1 = cadr(false2); + s7_pointer la2 = caddr(false2); + if ((is_fxable(sc, true2)) && + (((vars == 1) && (is_proper_list_2(sc, la1)) && (is_proper_list_2(sc, la2))) || + (((vars == 2) && (is_proper_list_3(sc, la1)) && (is_proper_list_3(sc, la2))))) && + (car(la1) == name) && (car(la2) == name) && + (is_fxable(sc, cadr(la1))) && (is_fxable(sc, cadr(la2))) && + ((vars == 1) || ((is_fxable(sc, caddr(la1))) && (is_fxable(sc, caddr(la2)))))) + { + set_safe_optimize_op(body, (vars == 1) ? OP_RECUR_IF_A_A_IF_A_A_opLA_LAq : OP_RECUR_IF_A_A_IF_A_A_opLAA_LAAq); + fx_annotate_arg(sc, cdr(body), args); + fx_annotate_arg(sc, obody, args); + fx_annotate_args(sc, cdr(false_p), args); + fx_annotate_args(sc, cdr(la1), args); + fx_annotate_args(sc, cdr(la2), args); + fx_tree(sc, cdr(body), car(args), (vars == 2) ? cadr(args) : NULL, NULL, false); + set_opt1_pair(body, cdr(false_p)); + set_opt3_pair(body, false2); + set_opt3_pair(false2, cdr(la2)); + return(true); + } + if ((vars == 2) && (is_proper_list_3(sc, true2)) && + (car(true2) == name) && + (is_fxable(sc, cadr(true2))) && (is_fxable(sc, caddr(true2))) && + (is_fxable(sc, cadr(false2))) && + (is_proper_list_3(sc, la2)) && + (car(la2) == name) && /* actually, not needed because func is TC (not RECUR) if not == name */ + (is_fxable(sc, cadr(la2))) && + (is_fxable(sc, caddr(la2)))) + { + set_safe_optimize_op(body, OP_RECUR_IF_A_A_IF_A_LAA_opA_LAAq); + fx_annotate_arg(sc, cdr(body), args); /* if_(A)... */ + fx_annotate_arg(sc, obody, args); /* if_a_(A)... */ + fx_annotate_arg(sc, cdr(false_p), args); /* if_a_a_if_(A)... */ + fx_annotate_args(sc, cdr(true2), args); /* if_a_a_if_a_l(AA)... */ + fx_annotate_arg(sc, cdr(false2), args); /* if_a_a_if_a_laa_op(A).. */ + fx_annotate_args(sc, cdr(la2), args); /* if_a_a_if_a_laa_opa_l(AA)q */ + fx_tree(sc, cdr(body), car(args), cadr(args), NULL, false); + set_opt3_pair(body, false2); + set_opt3_pair(false2, la2); + return(true); + }}} + + if (car(false_p) == sc->and_symbol) + { + s7_pointer a1 = cadr(false_p); + s7_pointer a2 = caddr(false_p); + s7_pointer a3 = cadddr(false_p); + if ((is_fxable(sc, a1)) && + (is_proper_list_3(sc, a2)) && (is_proper_list_3(sc, a3)) && + (car(a2) == name) && (car(a3) == name) && + (is_fxable(sc, cadr(a2))) && (is_fxable(sc, cadr(a3))) && + (is_fxable(sc, caddr(a2))) && (is_fxable(sc, caddr(a3)))) + { + set_safe_optimize_op(body, OP_RECUR_IF_A_A_AND_A_LAA_LAA); + fx_annotate_arg(sc, cdr(body), args); /* if_(A)... */ + fx_annotate_arg(sc, cddr(body), args); /* if_a_(A)... */ + fx_annotate_arg(sc, cdr(false_p), args); /* if_a_a_and_(A)... */ + fx_annotate_args(sc, cdr(a2), args); /* if_a_a_and_a_l(AA)... */ + fx_annotate_args(sc, cdr(a3), args); /* if_a_a_and_a_laa_l(AA) */ + fx_tree(sc, cdr(body), car(args), cadr(args), NULL, false); + set_opt3_pair(body, false_p); + return(true); + }}} + + if ((is_fxable(sc, true_p)) && + (is_pair(false_p)) && + (is_h_optimized(false_p)) && + (is_pair(cdr(false_p))) && + (is_pair(cddr(false_p)))) + orig = false_p; + else + if ((is_fxable(sc, false_p)) && + (is_pair(true_p)) && + (is_h_optimized(true_p)) && + (is_pair(cdr(true_p))) && + (is_pair(cddr(true_p)))) + { + orig = true_p; + /* true_p = false_p; */ + false_p = orig; + obody = cdr(obody); + } + + if (orig) + { + if (is_null(cdddr(false_p))) /* 2 args to outer (c) func */ + { + if ((is_fxable(sc, cadr(false_p))) || (is_fxable(sc, caddr(false_p)))) + { + s7_pointer la = (is_fxable(sc, cadr(false_p))) ? caddr(false_p) : cadr(false_p); + if ((is_pair(la)) && + (car(la) == name) && + (is_pair(cdr(la))) && + (is_fxable(sc, cadr(la)))) + { + if ((vars == 1) && (is_null(cddr(la)))) + set_safe_optimize_op(body, (orig == cadddr(body)) ? + ((la == cadr(false_p)) ? OP_RECUR_IF_A_A_opLA_Aq : OP_RECUR_IF_A_A_opA_LAq) : + ((la == cadr(false_p)) ? OP_RECUR_IF_A_opLA_Aq_A : OP_RECUR_IF_A_opA_LAq_A)); + else + if ((vars == 2) && + (is_pair(cddr(la))) && (is_fxable(sc, caddr(la))) && + (is_null(cdddr(la)))) + set_safe_optimize_op(body, (orig == cadddr(body)) ? OP_RECUR_IF_A_A_opA_LAAq : OP_RECUR_IF_A_opA_LAAq_A); + else + { + if ((vars == 3) && + (is_pair(cddr(la))) && (is_fxable(sc, caddr(la))) && + (is_pair(cdddr(la))) && (is_fxable(sc, cadddr(la))) && + (is_null(cddddr(la))) && + (orig == cadddr(body))) + set_safe_optimize_op(body, OP_RECUR_IF_A_A_opA_L3Aq); + else return(false); + } + fx_annotate_arg(sc, cdr(body), args); + fx_annotate_arg(sc, obody, args); + fx_annotate_arg(sc, (la == cadr(false_p)) ? cddr(false_p) : cdr(false_p), args); + fx_annotate_args(sc, cdr(la), args); + fx_tree(sc, cdr(body), car(args), (vars > 1) ? cadr(args) : NULL, (vars > 2) ? caddr(args) : NULL, false); + set_opt3_pair(body, false_p); + set_opt3_pair(false_p, la); + return(true); + }} + else + { + s7_pointer la1 = cadr(false_p); + s7_pointer la2 = caddr(false_p); + if ((vars == 1) && + (is_proper_list_2(sc, la1)) && (is_proper_list_2(sc, la2)) && + (car(la1) == name) && (car(la2) == name) && + (is_fxable(sc, cadr(la1))) && (is_fxable(sc, cadr(la2)))) + { + set_safe_optimize_op(body, (orig == cadddr(body)) ? OP_RECUR_IF_A_A_opLA_LAq : OP_RECUR_IF_A_opLA_LAq_A); + fx_annotate_arg(sc, cdr(body), args); + fx_annotate_arg(sc, obody, args); + fx_annotate_arg(sc, cdr(la1), args); + fx_annotate_arg(sc, cdr(la2), args); + fx_tree(sc, cdr(body), car(args), NULL, NULL, false); + set_opt3_pair(body, false_p); + set_opt3_pair(false_p, la2); + return(true); + }}} + else /* 3 args to c func */ + { + if ((vars == 1) && + (is_pair(cdddr(false_p))) && + (is_null(cddddr(false_p)))) + { + s7_pointer la1 = cadr(false_p); + s7_pointer la2 = caddr(false_p); + s7_pointer la3 = cadddr(false_p); + if ((is_proper_list_2(sc, la2)) && (is_proper_list_2(sc, la3)) && + (car(la2) == name) && (car(la3) == name) && + (is_fxable(sc, cadr(la2))) && (is_fxable(sc, cadr(la3)))) + { + if ((is_proper_list_2(sc, la1)) && (car(la1) == name) && (is_fxable(sc, cadr(la1)))) + { + if (orig != cadddr(body)) + return(false); + set_safe_optimize_op(body, OP_RECUR_IF_A_A_opLA_LA_LAq); + fx_annotate_arg(sc, cdr(la1), args); + } + else + if (is_fxable(sc, la1)) + { + set_safe_optimize_op(body, (orig == cadddr(body)) ? OP_RECUR_IF_A_A_opA_LA_LAq : OP_RECUR_IF_A_opA_LA_LAq_A); + fx_annotate_arg(sc, cdr(false_p), args); + } + else return(false); + fx_annotate_arg(sc, cdr(body), args); + fx_annotate_arg(sc, obody, args); + fx_annotate_arg(sc, cdr(la2), args); + fx_annotate_arg(sc, cdr(la3), args); + fx_tree(sc, cdr(body), car(args), NULL, NULL, false); + set_opt3_pair(body, false_p); + set_opt3_pair(false_p, la3); + return(true); + }}}}} + + if ((vars == 3) && + (is_fxable(sc, test))) + { + s7_pointer true_p = caddr(body); + s7_pointer false_p = cadddr(body); + if ((is_fxable(sc, true_p)) && + (is_proper_list_4(sc, false_p)) && + (car(false_p) == name)) + { + s7_pointer l3a = cdr(false_p); + s7_pointer la1 = car(l3a); + s7_pointer la2 = cadr(l3a); + s7_pointer la3 = caddr(l3a); + if ((is_proper_list_4(sc, la1)) && (is_proper_list_4(sc, la2)) && (is_proper_list_4(sc, la3)) && + (car(la1) == name) && (car(la2) == name) && (car(la3) == name) && + (is_fxable(sc, cadr(la1))) && (is_fxable(sc, cadr(la2))) && (is_fxable(sc, cadr(la3))) && + (is_fxable(sc, caddr(la1))) && (is_fxable(sc, caddr(la2))) && (is_fxable(sc, caddr(la3))) && + (is_fxable(sc, cadddr(la1))) && (is_fxable(sc, cadddr(la2))) && (is_fxable(sc, cadddr(la3)))) + { + set_safe_optimize_op(body, OP_RECUR_IF_A_A_LopL3A_L3A_L3Aq); + fx_annotate_args(sc, cdr(la1), args); + fx_annotate_args(sc, cdr(la2), args); + fx_annotate_args(sc, cdr(la3), args); + fx_annotate_arg(sc, cdr(body), args); + fx_annotate_arg(sc, cddr(body), args); + fx_tree(sc, cdr(body), car(args), cadr(args), caddr(args), false); + set_opt3_pair(body, false_p); + set_opt3_pair(false_p, la3); + return(true); + }}} + return(false); +} + +static bool check_recur(s7_scheme *sc, s7_pointer name, int32_t vars, s7_pointer args, s7_pointer body) +{ + if ((car(body) == sc->if_symbol) && + (proper_list_length(body) == 4)) + return(check_recur_if(sc, name, vars, args, body)); + + if ((car(body) == sc->and_symbol) && + (vars == 2) && + (proper_list_length(body) == 3) && + (proper_list_length(caddr(body)) == 4) && + (caaddr(body) == sc->or_symbol) && + (is_fxable(sc, cadr(body)))) + { + s7_pointer or_p = caddr(body); + s7_pointer la1 = caddr(or_p); + s7_pointer la2 = cadddr(or_p); + if ((is_fxable(sc, cadr(or_p))) && + (proper_list_length(la1) == 3) && + (proper_list_length(la2) == 3) && + (car(la1) == name) && + (car(la2) == name) && + (is_fxable(sc, cadr(la1))) && + (is_fxable(sc, caddr(la1))) && + (is_fxable(sc, cadr(la2))) && + (is_fxable(sc, caddr(la2)))) + { + set_safe_optimize_op(body, OP_RECUR_AND_A_OR_A_LAA_LAA); + fx_annotate_args(sc, cdr(la1), args); + fx_annotate_args(sc, cdr(la2), args); + fx_annotate_arg(sc, cdr(body), args); + fx_annotate_arg(sc, cdr(or_p), args); + fx_tree(sc, cdr(body), car(args), cadr(args), NULL, false); + set_opt3_pair(body, or_p); + return(true); + }} + + if (car(body) == sc->cond_symbol) + { + s7_pointer clause = cadr(body), clause2 = NULL; + if ((is_proper_list_1(sc, (cdr(clause)))) && + (is_fxable(sc, car(clause))) && + (is_fxable(sc, cadr(clause)))) + { + s7_pointer la_clause = caddr(body); + s7_int len = proper_list_length(body); + if (len == 4) + { + if ((is_proper_list_2(sc, la_clause)) && + (is_fxable(sc, car(la_clause)))) + { + clause2 = la_clause; + la_clause = cadddr(body); + } + else return(false); + } + if ((is_proper_list_2(sc, la_clause)) && + ((car(la_clause) == sc->else_symbol) || (car(la_clause) == sc->T)) && + (is_pair(cadr(la_clause)))) + { + la_clause = cadr(la_clause); /* (c_op arg (recur par)) or (c_op (recur) (recur)) or (op|l a laa) */ + if (is_proper_list_2(sc, cdr(la_clause))) + { + if (is_h_optimized(la_clause)) + { + if ((is_fxable(sc, cadr(la_clause))) && + ((len == 3) || + ((len == 4) && (vars == 2) && + (is_proper_list_3(sc, cadr(clause2))) && + (caadr(clause2) == name)))) + { + s7_pointer la = caddr(la_clause); + if ((is_pair(la)) && + (car(la) == name) && + (is_pair(cdr(la))) && + (is_fxable(sc, cadr(la))) && + (((vars == 1) && (is_null(cddr(la)))) || + ((vars == 2) && + (is_pair(cddr(la))) && + (is_fxable(sc, caddr(la))) && + (is_null(cdddr(la)))))) + { + if (len == 3) + set_safe_optimize_op(body, (vars == 1) ? OP_RECUR_COND_A_A_opA_LAq : OP_RECUR_COND_A_A_opA_LAAq); + else + { + s7_pointer laa = cadr(clause2); + if ((is_fxable(sc, cadr(laa))) && /* args to first laa */ + (is_fxable(sc, caddr(laa)))) + { + set_safe_optimize_op(body, OP_RECUR_COND_A_A_A_LAA_opA_LAAq); + fx_annotate_arg(sc, clause2, args); + fx_annotate_args(sc, cdr(laa), args); + } + else return(false); + } + fx_annotate_args(sc, clause, args); + fx_annotate_arg(sc, cdr(la_clause), args); + fx_annotate_args(sc, cdr(la), args); + fx_tree(sc, cdr(body), car(args), (vars == 1) ? NULL : cadr(args), NULL, false); + set_opt3_pair(body, la_clause); + set_opt3_pair(la_clause, la); + return(true); + }} + else + { + if ((len == 4) && + (is_fxable(sc, cadr(clause2)))) + { + s7_pointer la1 = cadr(la_clause); + s7_pointer la2 = caddr(la_clause); + bool happy = false; + + if ((vars == 1) && + (is_proper_list_2(sc, la1)) && (is_proper_list_2(sc, la2)) && + (car(la1) == name) && (car(la2) == name) && (is_fxable(sc, cadr(la1))) && (is_fxable(sc, cadr(la2)))) + { + set_safe_optimize_op(body, OP_RECUR_COND_A_A_A_A_opLA_LAq); + fx_annotate_arg(sc, cdr(la1), args); + happy = true; + } + else + if ((vars == 2) && + /* (is_fxable(sc, cadr(clause2))) && */ + (is_proper_list_3(sc, la2)) && (car(la2) == name) && (is_fxable(sc, cadr(la2))) && (is_fxable(sc, caddr(la2)))) + { + if (is_fxable(sc, la1)) + { + set_safe_optimize_op(body, OP_RECUR_COND_A_A_A_A_opA_LAAq); + fx_annotate_arg(sc, cdr(la_clause), args); + happy = true; + } + else + if ((is_proper_list_3(sc, la1)) && + (car(la1) == name) && + (is_fxable(sc, cadr(la1))) && + (is_fxable(sc, caddr(la1)))) + { + set_safe_optimize_op(body, OP_RECUR_COND_A_A_A_A_opLAA_LAAq); + fx_annotate_args(sc, cdr(la1), args); + happy = true; + }} + if (happy) + { + set_opt3_pair(la_clause, cdr(la2)); + fx_annotate_args(sc, clause, args); + fx_annotate_args(sc, clause2, args); + fx_annotate_args(sc, cdr(la2), args); + fx_tree(sc, cdr(body), car(args), (vars == 1) ? NULL : cadr(args), NULL, false); + set_opt3_pair(body, la_clause); + return(true); + }}}} + else + { + if (clause2) + { + s7_pointer laa = cadr(clause2); + if ((vars == 2) && (len == 4) && + (is_proper_list_3(sc, laa)) && (car(laa) == name) && (is_fxable(sc, cadr(laa))) && (is_fxable(sc, caddr(laa)))) + { + s7_pointer la1 = cadr(la_clause); + s7_pointer la2 = caddr(la_clause); + if ((is_fxable(sc, la1)) && + (is_proper_list_3(sc, la2)) && (car(la2) == name) && (is_fxable(sc, cadr(la2))) && (is_fxable(sc, caddr(la2)))) + { + set_safe_optimize_op(body, OP_RECUR_COND_A_A_A_LAA_LopA_LAAq); + fx_annotate_args(sc, clause, args); + fx_annotate_arg(sc, clause2, args); + fx_annotate_args(sc, cdr(laa), args); + fx_annotate_arg(sc, cdr(la_clause), args); + fx_annotate_args(sc, cdr(la2), args); + fx_tree(sc, cdr(body), car(args), cadr(args), NULL, false); + set_opt3_pair(body, la_clause); + set_opt3_pair(la_clause, cdr(la2)); + return(true); + }}}}}}}} + return(false); +} + +static bool check_tc_when(s7_scheme *sc, const s7_pointer name, int32_t vars, s7_pointer args, s7_pointer body) +{ + s7_pointer test_expr = cadr(body); + if (is_fxable(sc, test_expr)) + { + s7_pointer p; + for (p = cddr(body); is_pair(cdr(p)); p = cdr(p)) + if (!is_fxable(sc, car(p))) + break; + if ((is_proper_list_1(sc, p)) && /* i.e. p is the last form in the when body */ + (is_pair(car(p))) && + (caar(p) == name)) + { + s7_pointer laa = car(p); + if ((is_pair(cdr(laa))) && (is_fxable(sc, cadr(laa)))) + { + if (is_null(cddr(laa))) + { + if (vars != 1) return(false); + set_safe_optimize_op(body, OP_TC_WHEN_LA); + } + else + if (is_fxable(sc, caddr(laa))) + { + if (is_null(cdddr(laa))) + { + if (vars != 2) return(false); + set_safe_optimize_op(body, OP_TC_WHEN_LAA); + } + else + if ((vars == 3) && (is_fxable(sc, cadddr(laa))) && (is_null(cddddr(laa)))) + set_safe_optimize_op(body, OP_TC_WHEN_L3A); + else return(false); + } + fx_annotate_arg(sc, cdr(body), args); + for (p = cddr(body); is_pair(cdr(p)); p = cdr(p)) + fx_annotate_arg(sc, p, args); + fx_annotate_args(sc, cdr(laa), args); + fx_tree(sc, cdr(body), car(args), (is_pair(cdr(args))) ? cadr(args) : NULL, ((is_pair(cdr(args))) && (is_pair(cddr(args)))) ? caddr(args) : NULL, false); + return(true); + }}} + return(false); +} + +static bool check_tc_case(s7_scheme *sc, s7_pointer name, s7_pointer args, s7_pointer body) +{ + /* vars == 1, opt1_any(clause) = key, has_tc(arg) = is tc call, opt2_any(clause) = result: has_tc(la arg) has_fx(val) or ((...)...) */ + s7_pointer clauses; + s7_int len; + bool got_else = false, results_fxable = true; + for (clauses = cddr(body), len = 0; is_pair(clauses); clauses = cdr(clauses), len++) + { + s7_pointer clause = car(clauses), result; + if (is_proper_list_1(sc, car(clause))) + { + if (!is_simple(caar(clause))) + return(false); /* even if key is a small int, selector might be a mutable alias of that, so = will fail */ + set_opt1_any(clauses, caar(clause)); + } + else + { + if ((car(clause) != sc->else_symbol) || + (!is_null(cdr(clauses)))) + return(false); + got_else = true; + } + set_opt2_any(clauses, NULL); + result = cdr(clause); + if (is_null(result)) + return(false); + if (is_proper_list_1(sc, result)) + { + if (is_fxable(sc, car(result))) + { + fx_annotate_arg(sc, result, args); + set_opt2_any(clauses, result); + } + else + if ((is_proper_list_2(sc, car(result))) && + (caar(result) == name) && + (is_fxable(sc, cadar(result)))) + { + set_has_tc(car(result)); + set_opt2_any(clauses, car(result)); + fx_annotate_arg(sc, cdar(result), args); + } + else results_fxable = false; + } + else results_fxable = false; + if (!opt2_any(clauses)) + { + if (car(result) == sc->feed_to_symbol) + return(false); + if (tree_count(sc, name, result, 0) != 0) + return(false); + set_opt2_any(clauses, result); + }} + if ((!got_else) || (!is_null(clauses))) + return(false); + set_optimize_op(body, OP_TC_CASE_LA); + set_opt3_arglen(cdr(body), (len < 6) ? len : 0); + fx_annotate_arg(sc, cdr(body), args); + fx_tree(sc, cdr(body), car(args), NULL, NULL, true); + if (results_fxable) set_optimized(body); + return(results_fxable); +} + +static bool check_tc_cond(s7_scheme *sc, s7_pointer name, int32_t vars, s7_pointer args, s7_pointer body) +{ + s7_pointer p = cdr(body), clause1 = car(p); + if ((is_proper_list_2(sc, clause1)) && (is_fxable(sc, car(clause1)))) /* cond_a... */ + { + p = cdr(p); + if ((is_pair(p)) && (is_null(cdr(p))) && ((caar(p) == sc->else_symbol) || (caar(p) == sc->T))) + { + s7_pointer else_clause; + if (((vars != 1) && (vars != 2)) || (tree_count(sc, name, body, 0) != 1)) return(false); + else_clause = cdar(p); + if (is_proper_list_1(sc, else_clause)) + { + s7_pointer la = car(else_clause); + fx_annotate_arg(sc, clause1, args); + if ((is_pair(la)) && (car(la) == name) && (is_pair(cdr(la)))) + { + if ((is_fxable(sc, cadr(la))) && + ((((vars == 1) && (is_null(cddr(la)))) || + ((vars == 2) && (is_pair(cddr(la))) && (is_null(cdddr(la))) && (is_fxable(sc, caddr(la))))))) + { + bool zs_fxable = is_fxable(sc, cadr(clause1)); + set_optimize_op(body, (vars == 1) ? OP_TC_COND_A_Z_LA : OP_TC_COND_A_Z_LAA); + if (zs_fxable) fx_annotate_arg(sc, cdr(clause1), args); + fx_annotate_args(sc, cdr(la), args); + fx_tree(sc, cdr(body), car(args), (vars == 1) ? NULL : cadr(args), NULL, false); + if (zs_fxable) set_optimized(body); + set_opt1_pair(cdr(body), cdadr(body)); + set_opt3_pair(cdr(body), cdadr(caddr(body))); + return(zs_fxable); + }} + else + { + la = cadr(clause1); + if ((is_pair(la)) && (car(la) == name) && (is_pair(cdr(la)))) + { + if ((is_fxable(sc, cadr(la))) && + (((vars == 1) && (is_null(cddr(la)))) || + ((vars == 2) && (is_pair(cddr(la))) && (is_null(cdddr(la))) && (is_fxable(sc, caddr(la)))))) + { + bool zs_fxable = is_fxable(sc, car(else_clause)); + set_optimize_op(body, (vars == 1) ? OP_TC_COND_A_LA_Z : OP_TC_COND_A_LAA_Z); + if (zs_fxable) fx_annotate_arg(sc, else_clause, args); + fx_annotate_args(sc, cdr(la), args); + fx_tree(sc, cdr(body), car(args), (vars == 1) ? NULL : cadr(args), NULL, false); + if (zs_fxable) set_optimized(body); + set_opt1_pair(cdr(body), cdaddr(body)); + set_opt3_pair(cdr(body), cdadr(cadr(body))); + return(zs_fxable); + }}}} + return(false); + } + if (is_proper_list_2(sc, p)) + { + s7_pointer clause2 = car(p); + if ((is_proper_list_2(sc, clause2)) && + (is_fxable(sc, car(clause2)))) + { + s7_pointer else_p = cdr(p); + s7_pointer else_clause = car(else_p); + + if ((is_proper_list_2(sc, else_clause)) && + ((car(else_clause) == sc->else_symbol) || (car(else_clause) == sc->T))) + { + bool zs_fxable = true; + if ((vars == 2) && /* ...laa_laa case */ + (is_proper_list_3(sc, cadr(clause2))) && (caadr(clause2) == name) && + (is_fxable(sc, cadadr(clause2))) && (is_safe_fxable(sc, caddadr(clause2))) && + (is_proper_list_3(sc, cadr(else_clause))) && (caadr(else_clause) == name) && + (is_fxable(sc, cadadr(else_clause))) && (is_safe_fxable(sc, caddadr(else_clause)))) + { + set_optimize_op(body, OP_TC_COND_A_Z_A_LAA_LAA); + if (is_fxable(sc, cadr(clause1))) + fx_annotate_args(sc, clause1, args); + else + { + fx_annotate_arg(sc, clause1, args); + zs_fxable = false; + } + fx_annotate_arg(sc, clause2, args); + fx_annotate_args(sc, cdadr(clause2), args); + fx_annotate_args(sc, cdadr(else_clause), args); + fx_tree(sc, cdr(body), car(args), cadr(args), NULL, false); + set_opt3_pair(body, cadr(else_clause)); + if (zs_fxable) set_optimized(body); + return(zs_fxable); + } + + if ((tree_count(sc, name, body, 0) == 1) && /* needed to filter out cond_a_a_a_laa_opa_laa */ + + (((is_pair(cadr(else_clause))) && (caadr(else_clause) == name) && + (is_pair(cdadr(else_clause))) && (is_fxable(sc, cadadr(else_clause))) && + (((vars == 1) && (is_null(cddadr(else_clause)))) || + ((vars == 2) && (is_proper_list_3(sc, cadr(else_clause))) && (is_fxable(sc, caddadr(else_clause)))))) || + + ((is_pair(cadr(clause2))) && (caadr(clause2) == name) && + (is_pair(cdadr(clause2))) && (is_fxable(sc, cadadr(clause2))) && + (((vars == 1) && (is_null(cddadr(clause2)))) || + ((vars == 2) && (is_pair(cddadr(clause2))) && (is_fxable(sc, caddadr(clause2))) && (is_null(cdddr(cadr(clause2))))))))) + { + s7_pointer test2 = clause2; + s7_pointer la_test = else_clause; + if (vars == 1) + { + if ((is_pair(cadr(else_clause))) && (caadr(else_clause) == name)) + set_optimize_op(body, OP_TC_COND_A_Z_A_Z_LA); + else + { + set_optimize_op(body, OP_TC_COND_A_Z_A_LA_Z); + test2 = else_clause; + la_test = clause2; + fx_annotate_arg(sc, clause2, args); + }} + else + if ((is_pair(cadr(else_clause))) && (caadr(else_clause) == name)) + { + set_opt3_pair(body, cdadr(else_clause)); + set_optimize_op(body, OP_TC_COND_A_Z_A_Z_LAA); + } + else + { + set_optimize_op(body, OP_TC_COND_A_Z_A_LAA_Z); + test2 = else_clause; + la_test = clause2; + set_opt3_pair(body, cdadr(la_test)); + fx_annotate_arg(sc, clause2, args); + } + if (is_fxable(sc, cadr(clause1))) + fx_annotate_args(sc, clause1, args); + else + { + fx_annotate_arg(sc, clause1, args); + zs_fxable = false; + } + if (is_fxable(sc, cadr(test2))) + fx_annotate_args(sc, test2, args); + else + { + fx_annotate_arg(sc, test2, args); + zs_fxable = false; + } + fx_annotate_args(sc, cdadr(la_test), args); + fx_tree(sc, cdr(body), car(args), (vars == 2) ? cadr(args) : NULL, NULL, false); + if (zs_fxable) set_optimized(body); + return(zs_fxable); + }}}}} + return(false); +} + +static bool check_tc_let(s7_scheme *sc, const s7_pointer name, int32_t vars, s7_pointer args, s7_pointer body) +{ + s7_pointer let_body = caddr(body); /* body: (let ((x (- y 1))) (if (<= x 0) 0 (f1 (- x 1)))) etc */ + if (((vars == 2) && ((car(let_body) == sc->if_symbol) || (car(let_body) == sc->when_symbol) || (car(let_body) == sc->unless_symbol))) || + ((vars == 1) && (car(let_body) == sc->if_symbol))) + { + s7_pointer test_expr = cadr(let_body); + if (is_fxable(sc, test_expr)) + { + if ((car(let_body) == sc->if_symbol) && (is_pair(cdddr(let_body)))) + { + s7_pointer laa = cadddr(let_body); + if ((is_pair(laa)) && /* else caddr is laa and cadddr is z */ + (car(laa) == name) && + (((vars == 1) && (is_proper_list_2(sc, laa))) || + ((vars == 2) && (is_proper_list_3(sc, laa)) && (is_safe_fxable(sc, caddr(laa))))) && + (is_fxable(sc, cadr(laa)))) + { + bool z_fxable; + set_optimize_op(body, (vars == 1) ? OP_TC_LET_IF_A_Z_LA : OP_TC_LET_IF_A_Z_LAA); + fx_annotate_arg(sc, cdaadr(body), args); /* let var binding, caadr: (x (- y 1)) etc */ + fx_tree(sc, cdaadr(body), car(args), (vars == 1) ? NULL : cadr(args), NULL, false); /* these are references to laa args, applied to the let var binding */ + fx_annotate_arg(sc, cdr(let_body), args); /* test_expr */ + fx_annotate_args(sc, cdr(laa), args); + z_fxable = is_fxable(sc, caddr(let_body)); + if (z_fxable) fx_annotate_arg(sc, cddr(let_body), args); + fx_tree(sc, cdr(let_body), car(caadr(body)), NULL, NULL, false); + fx_tree_outer(sc, cdr(let_body), car(args), (vars == 1) ? NULL : cadr(args), NULL, false); + if (z_fxable) set_optimized(body); + return(z_fxable); + }} + else + { + s7_pointer p; + for (p = cddr(let_body); is_pair(cdr(p)); p = cdr(p)) + if (!is_fxable(sc, car(p))) + break; + if ((is_proper_list_1(sc, p)) && + (is_proper_list_3(sc, car(p))) && + (caar(p) == name)) + { + s7_pointer laa = car(p); + if ((is_fxable(sc, cadr(laa))) && + (is_safe_fxable(sc, caddr(laa)))) + { + set_optimize_op(body, (car(let_body) == sc->when_symbol) ? OP_TC_LET_WHEN_LAA : OP_TC_LET_UNLESS_LAA); + fx_annotate_arg(sc, cdaadr(body), args); /* outer var */ + fx_annotate_arg(sc, cdr(let_body), args); /* test */ + for (p = cddr(let_body); is_pair(cdr(p)); p = cdr(p)) + fx_annotate_arg(sc, p, args); + fx_annotate_args(sc, cdr(laa), args); + fx_tree(sc, cdaadr(body), car(args), cadr(args), NULL, false); /* these are references to the outer let */ + fx_tree(sc, cdr(let_body), car(caadr(body)), NULL, NULL, false); + fx_tree_outer(sc, cdr(let_body), car(args), cadr(args), NULL, false); + set_optimized(body); + return(true); + }}}}} + else + { + if (car(let_body) == sc->cond_symbol) /* vars=#loop pars, args=names thereof (arglist) */ + { + s7_pointer var_name; + bool all_fxable = true; + for (s7_pointer p = cdr(let_body); is_pair(p); p = cdr(p)) + { + s7_pointer clause = car(p); + if ((is_proper_list_2(sc, clause)) && + (is_fxable(sc, car(clause)))) /* test is ok */ + { + s7_pointer result; + + if ((!is_pair(cdr(p))) && + (car(clause) != sc->else_symbol) && (car(clause) != sc->T)) + return(false); + + result = cadr(clause); + if ((is_pair(result)) && + (car(result) == name)) /* result is recursive call */ + { + s7_int i = 0; + for (s7_pointer arg = cdr(result); is_pair(arg); i++, arg = cdr(arg)) + if (!is_fxable(sc, car(arg))) + return(false); + if (i != vars) + return(false); + }} + else return(false); + } + /* cond form looks ok */ + set_optimize_op(body, OP_TC_LET_COND); + set_opt3_arglen(cdr(body), vars); + fx_annotate_arg(sc, cdaadr(body), args); /* let var */ + if (vars > 0) + fx_tree(sc, cdaadr(body), car(args), (vars > 1) ? cadr(args) : NULL, (vars > 2) ? caddr(args) : NULL, vars > 3); + var_name = caaadr(body); + for (s7_pointer p = cdr(let_body); is_pair(p); p = cdr(p)) + { + s7_pointer clause = car(p); + s7_pointer result = cadr(clause); + fx_annotate_arg(sc, clause, args); + if ((is_pair(result)) && (car(result) == name)) + { + set_has_tc(cdr(clause)); + fx_annotate_args(sc, cdr(result), args); + } + else + if (is_fxable(sc, result)) + fx_annotate_arg(sc, cdr(clause), args); + else all_fxable = false; + fx_tree(sc, clause, var_name, NULL, NULL, false); /* just 1 let var */ + if (vars > 0) + fx_tree_outer(sc, clause, car(args), (vars > 1) ? cadr(args) : NULL, (vars > 2) ? caddr(args) : NULL, vars > 3); + } + if (all_fxable) set_optimized(body); + return(all_fxable); + }} + return(false); +} + +/* tc lets can be let* or let+vars that don't refer to previous names, and there are more cond/if choices */ + +static bool check_tc(s7_scheme *sc, s7_pointer name, int32_t vars, s7_pointer args, s7_pointer body) +{ + if (!is_pair(body)) return(false); + + if (((vars == 1) || (vars == 2) || (vars == 3)) && + ((car(body) == sc->and_symbol) || (car(body) == sc->or_symbol)) && + (is_pair(cdr(body))) && + (is_fxable(sc, cadr(body))) && + (is_pair(cddr(body)))) + { + s7_pointer orx = caddr(body); + if (((car(orx) == sc->or_symbol) || (car(orx) == sc->and_symbol)) && + (car(body) != car(orx)) && + (is_fxable(sc, cadr(orx)))) + { + s7_int len = proper_list_length(orx); + if ((len == 3) || + ((vars == 1) && (len == 4) && (tree_count(sc, name, orx, 0) == 1) && (is_fxable(sc, caddr(orx))))) /* the ...or|and_a_a_la case below? */ + { + s7_pointer tc = (len == 3) ? caddr(orx) : cadddr(orx); + if ((is_pair(tc)) && + (car(tc) == name) && + (is_pair(cdr(tc))) && + (is_fxable(sc, cadr(tc))) && + (((vars == 1) && (is_null(cddr(tc)))) || + ((vars == 2) && (is_pair(cddr(tc))) && (is_null(cdddr(tc))) && (is_safe_fxable(sc, caddr(tc)))) || + ((vars == 3) && (is_pair(cddr(tc))) && (is_pair(cdddr(tc))) && (is_null(cddddr(tc))) && + (is_safe_fxable(sc, caddr(tc))) && (is_safe_fxable(sc, cadddr(tc)))))) + { + if (vars == 1) + set_safe_optimize_op(body, (car(body) == sc->and_symbol) ? + ((len == 3) ? OP_TC_AND_A_OR_A_LA : OP_TC_AND_A_OR_A_A_LA) : + ((len == 3) ? OP_TC_OR_A_AND_A_LA : OP_TC_OR_A_AND_A_A_LA)); + else + if (vars == 2) + set_safe_optimize_op(body, (car(body) == sc->and_symbol) ? OP_TC_AND_A_OR_A_LAA : OP_TC_OR_A_AND_A_LAA); + else set_safe_optimize_op(body, (car(body) == sc->and_symbol) ? OP_TC_AND_A_OR_A_L3A : OP_TC_OR_A_AND_A_L3A); + fx_annotate_arg(sc, cdr(body), args); + fx_annotate_arg(sc, cdr(orx), args); + if (len == 4) fx_annotate_arg(sc, cddr(orx), args); + fx_annotate_args(sc, cdr(tc), args); + /* if ((fx_proc(cdr(tc)) == fx_c_sca) && (fn_proc(cadr(tc)) == g_substring)) -> g_substring_uncopied); */ + /* for that to be safe we need to be sure nothing in the body looks for null-termination (e.g.. string->number) */ + fx_tree(sc, cdr(body), car(args), (vars == 1) ? NULL : cadr(args), (vars == 3) ? caddr(args) : NULL, false); + return(true); + }}} + else + { + if ((vars == 1) && + (car(body) == sc->or_symbol) && + (is_fxable(sc, orx)) && + (is_pair(cdddr(body))) && + (is_pair(cadddr(body)))) + { + s7_pointer and_p = cadddr(body); + if ((is_proper_list_4(sc, and_p)) && + (car(and_p) == sc->and_symbol) && + (is_fxable(sc, cadr(and_p))) && + (is_fxable(sc, caddr(and_p)))) + { + s7_pointer la = cadddr(and_p); + if ((is_proper_list_2(sc, la)) && + (car(la) == name) && + (is_fxable(sc, cadr(la)))) + { + set_safe_optimize_op(body, OP_TC_OR_A_A_AND_A_A_LA); + fx_annotate_arg(sc, cdr(body), args); + fx_annotate_arg(sc, cddr(body), args); + fx_annotate_arg(sc, cdr(and_p), args); + fx_annotate_arg(sc, cddr(and_p), args); + fx_annotate_args(sc, cdr(la), args); + fx_tree(sc, cdr(body), car(args), NULL, NULL, false); + return(true); + }}} + else + { + if ((vars == 1) && (car(body) == sc->and_symbol) && (car(orx) == sc->if_symbol) && + (is_proper_list_4(sc, orx)) && (is_fxable(sc, cadr(orx))) && (tree_count(sc, name, orx, 0) == 1)) + { + bool z_first = ((is_pair(cadddr(orx))) && (car(cadddr(orx)) == name)); + s7_pointer la = (z_first) ? cadddr(orx) : caddr(orx); + if ((car(la) == name) && (is_proper_list_2(sc, la)) && (is_fxable(sc, cadr(la)))) + { + bool z_fxable = true; + s7_pointer z = (z_first) ? cddr(orx) : cdddr(orx); + set_optimize_op(body, (z_first) ? OP_TC_AND_A_IF_A_Z_LA : OP_TC_AND_A_IF_A_LA_Z); + fx_annotate_arg(sc, cdr(body), args); + fx_annotate_arg(sc, cdr(orx), args); + fx_annotate_arg(sc, cdr(la), args); + if (is_fxable(sc, car(z))) fx_annotate_arg(sc, z, args); else z_fxable = false; + fx_tree(sc, cdr(body), car(args), NULL, NULL, false); + if (z_fxable) set_optimized(body); + return(z_fxable); + }}}}} + + if ((vars == 3) && + (((car(body) == sc->or_symbol) && (is_proper_list_2(sc, cdr(body)))) || + ((car(body) == sc->if_symbol) && (is_proper_list_3(sc, cdr(body))) && (caddr(body) == sc->T))) && + (is_fxable(sc, cadr(body)))) + { + s7_pointer and_p = (car(body) == sc->or_symbol) ? caddr(body) : cadddr(body); + if ((is_proper_list_4(sc, and_p)) && + (car(and_p) == sc->and_symbol) && + (is_fxable(sc, cadr(and_p))) && + (is_fxable(sc, caddr(and_p)))) + { + s7_pointer la = cadddr(and_p); + if ((is_proper_list_4(sc, la)) && + (car(la) == name) && + (is_fxable(sc, cadr(la))) && + (is_safe_fxable(sc, caddr(la))) && + (is_safe_fxable(sc, cadddr(la)))) + { + set_safe_optimize_op(body, OP_TC_OR_A_AND_A_A_L3A); + set_opt3_pair(cdr(body), (car(body) == sc->or_symbol) ? cdaddr(body) : cdr(cadddr(body))); + fx_annotate_arg(sc, cdr(body), args); + fx_annotate_arg(sc, cdr(and_p), args); + fx_annotate_arg(sc, cddr(and_p), args); + fx_annotate_args(sc, cdr(la), args); + fx_tree(sc, cdr(body), car(args), cadr(args), caddr(args), false); + return(true); + }}} + + if (((vars >= 1) && (vars <= 3)) && + (car(body) == sc->if_symbol) && + (proper_list_length(body) == 4)) + { + s7_pointer test = cadr(body); + if (is_fxable(sc, test)) + { + s7_pointer true_p = caddr(body); + s7_pointer false_p = cadddr(body); + s7_int true_len = proper_list_length(true_p); + s7_int false_len = proper_list_length(false_p); + + fx_annotate_arg(sc, cdr(body), args); + + if (vars == 1) + { + if ((false_len == 2) && + (car(false_p) == name) && + (is_fxable(sc, cadr(false_p)))) + { + set_optimize_op(body, OP_TC_IF_A_Z_LA); + fx_annotate_arg(sc, cdr(false_p), args); /* arg */ + set_opt1_pair(cdr(body), cddr(body)); + set_opt3_pair(cdr(body), cdar(cdddr(body))); + if (!is_fxable(sc, true_p)) return(false); + fx_annotate_arg(sc, cddr(body), args); /* result */ + fx_tree(sc, cdr(body), car(args), NULL, NULL, false); + set_optimized(body); /* split here and elsewhere from set_optimize_op is deliberate */ + return(true); + } + if ((true_len == 2) && + (car(true_p) == name) && + (is_fxable(sc, cadr(true_p)))) + { + set_optimize_op(body, OP_TC_IF_A_LA_Z); + fx_annotate_arg(sc, cdr(true_p), args); /* arg */ + set_opt1_pair(cdr(body), cdddr(body)); + set_opt3_pair(cdr(body), cdar(cddr(body))); + if (!is_fxable(sc, false_p)) return(false); + fx_annotate_arg(sc, cdddr(body), args); /* result */ + fx_tree(sc, cdr(body), car(args), NULL, NULL, false); + set_optimized(body); + return(true); + }} + + if (vars == 2) + { + if ((false_len == 3) && + (car(false_p) == name) && + (is_fxable(sc, cadr(false_p))) && + (is_safe_fxable(sc, caddr(false_p)))) + { + set_optimize_op(body, OP_TC_IF_A_Z_LAA); + fx_annotate_args(sc, cdr(false_p), args); + set_opt1_pair(cdr(body), cddr(body)); /* body == code in op, if_z */ + set_opt3_pair(cdr(body), cdar(cdddr(body))); /* la */ + if (!is_fxable(sc, true_p)) return(false); + fx_annotate_arg(sc, cddr(body), args); + fx_tree(sc, cdr(body), car(args), cadr(args), NULL, false); + set_optimized(body); + return(true); + } + if ((true_len == 3) && + (car(true_p) == name) && + (is_fxable(sc, cadr(true_p))) && + (is_safe_fxable(sc, caddr(true_p)))) + { + set_optimize_op(body, OP_TC_IF_A_LAA_Z); + fx_annotate_args(sc, cdr(true_p), args); + set_opt1_pair(cdr(body), cdddr(body)); + set_opt3_pair(cdr(body), cdar(cddr(body))); + if (!is_fxable(sc, false_p)) return(false); + fx_annotate_arg(sc, cdddr(body), args); + fx_tree(sc, cdr(body), car(args), cadr(args), NULL, false); + set_optimized(body); + return(true); + }} + + if (vars == 3) + { + if ((false_len == 4) && + (car(false_p) == name) && + (is_fxable(sc, cadr(false_p))) && (is_safe_fxable(sc, caddr(false_p))) && (is_safe_fxable(sc, cadddr(false_p)))) + { + set_optimize_op(body, OP_TC_IF_A_Z_L3A); + fx_annotate_args(sc, cdr(false_p), args); + set_opt1_pair(cdr(body), cddr(body)); + set_opt3_pair(cdr(body), cdar(cdddr(body))); + if (!is_fxable(sc, true_p)) return(false); + fx_annotate_arg(sc, cddr(body), args); + fx_tree(sc, cdr(body), car(args), cadr(args), caddr(args), false); + set_optimized(body); + return(true); + } + if ((true_len == 4) && + (car(true_p) == name) && + (is_fxable(sc, cadr(true_p))) && (is_safe_fxable(sc, caddr(true_p))) && (is_safe_fxable(sc, cadddr(true_p)))) + { + set_optimize_op(body, OP_TC_IF_A_L3A_Z); + fx_annotate_args(sc, cdr(true_p), args); + set_opt1_pair(cdr(body), cdddr(body)); + set_opt3_pair(cdr(body), cdar(cddr(body))); + if (!is_fxable(sc, false_p)) return(false); + fx_annotate_arg(sc, cdddr(body), args); + fx_tree(sc, cdr(body), car(args), cadr(args), caddr(args), false); + set_optimized(body); + return(true); + }} + + if ((false_len == 4) && + (car(false_p) == sc->if_symbol)) + { + s7_pointer in_test = cadr(false_p); + s7_pointer in_true = caddr(false_p); + s7_pointer in_false = cadddr(false_p); + if (is_fxable(sc, in_test)) + { + s7_pointer la = NULL, z; + if ((is_pair(in_false)) && + (car(in_false) == name) && + (is_pair(cdr(in_false))) && + (is_fxable(sc, cadr(in_false)))) + { + la = in_false; + z = cddr(false_p); + } + else + if ((is_pair(in_true)) && + (car(in_true) == name) && + (is_pair(cdr(in_true))) && + (is_fxable(sc, cadr(in_true)))) + { + la = in_true; + z = cdddr(false_p); + } + if ((la) && ((vars == 3) || (!s7_tree_memq(sc, name, car(z))))) + { + if (((vars == 1) && (is_null(cddr(la)))) || + ((vars == 2) && (is_pair(cddr(la))) && (is_null(cdddr(la))) && (is_safe_fxable(sc, caddr(la)))) || + ((vars == 3) && (is_proper_list_4(sc, in_true)) && (car(in_true) == name) && + (is_proper_list_4(sc, in_false)) && (is_safe_fxable(sc, caddr(la))) && (is_safe_fxable(sc, cadddr(la))) && + (is_fxable(sc, cadr(in_true))) && (is_safe_fxable(sc, caddr(in_true))) && (is_safe_fxable(sc, cadddr(in_true))))) + { + bool zs_fxable = true; + if (vars == 1) + set_optimize_op(body, (la == in_false) ? OP_TC_IF_A_Z_IF_A_Z_LA : OP_TC_IF_A_Z_IF_A_LA_Z); + else + if (vars == 2) + set_optimize_op(body, (la == in_false) ? OP_TC_IF_A_Z_IF_A_Z_LAA : OP_TC_IF_A_Z_IF_A_LAA_Z); + else set_optimize_op(body, OP_TC_IF_A_Z_IF_A_L3A_L3A); + if (is_fxable(sc, true_p)) /* outer (z) result */ + fx_annotate_arg(sc, cddr(body), args); + else zs_fxable = false; + fx_annotate_arg(sc, cdr(false_p), args); /* inner test */ + fx_annotate_args(sc, cdr(la), args); /* la arg(s) */ + if (vars == 3) + fx_annotate_args(sc, cdr(in_true), args); + else + if (is_fxable(sc, car(z))) + fx_annotate_arg(sc, z, args); /* inner (z) result */ + else zs_fxable = false; + if ((has_fx(cddr(body))) && (has_fx(z))) + fx_tree(sc, cdr(body), car(args), (vars > 1) ? cadr(args) : NULL, (vars > 2) ? caddr(args) : NULL, false); + if (zs_fxable) set_optimized(body); + return(zs_fxable); + }}}} + + if ((vars == 2) && + (false_len == 3) && + (car(false_p) == sc->let_star_symbol)) + { + s7_pointer letv = cadr(false_p), letb, v; + + if (!is_pair(letv)) return(false); + letb = caddr(false_p); + for (v = letv; is_pair(v); v = cdr(v)) + if (!is_fxable(sc, cadar(v))) + return(false); + if ((is_proper_list_4(sc, letb)) && + (car(letb) == sc->if_symbol) && + (is_fxable(sc, cadr(letb)))) + { + s7_pointer laa = cadddr(letb); + if ((car(laa) == name) && + (is_proper_list_3(sc, laa)) && + (is_fxable(sc, cadr(laa))) && + (is_safe_fxable(sc, caddr(laa)))) + { + bool zs_fxable; + set_safe_optimize_op(body, OP_TC_IF_A_Z_LET_IF_A_Z_LAA); + fx_annotate_args(sc, cdr(laa), args); + zs_fxable = is_fxable(sc, caddr(letb)); + fx_annotate_args(sc, cdr(letb), args); + for (v = letv; is_pair(v); v = cdr(v)) + fx_annotate_arg(sc, cdar(v), args); + fx_tree(sc, cdar(letv), car(args), cadr(args), NULL, true); /* first var of let*, second var of let* can't be fx_treed */ + fx_tree(sc, cdr(body), car(args), cadr(args), NULL, true); /* these are references to the outer let */ + fx_tree(sc, cdr(laa), caar(letv), (is_pair(cdr(letv))) ? caadr(letv) : NULL, NULL, true); + fx_tree(sc, cdr(letb), caar(letv), (is_pair(cdr(letv))) ? caadr(letv) : NULL, NULL, true); + fx_tree_outer(sc, cddr(letb), car(args), cadr(args), NULL, true); + if (!is_fxable(sc, caddr(body))) + return(false); + fx_annotate_arg(sc, cddr(body), args); + return(zs_fxable); + }}}}} + + /* let */ + if ((is_proper_list_3(sc, body)) && + (car(body) == sc->let_symbol) && + (is_proper_list_1(sc, cadr(body))) && + (is_fxable(sc, cadr(caadr(body)))) && /* let one var is fxable */ + (is_pair(caddr(body)))) + return(check_tc_let(sc, name, vars, args, body)); + + /* cond */ + if ((car(body) == sc->cond_symbol) && + (vars <= 2)) + return(check_tc_cond(sc, name, vars, args, body)); + + /* case */ + if ((vars == 1) && + (car(body) == sc->case_symbol) && + (is_pair(cdr(body))) && + (is_fxable(sc, cadr(body)))) + return(check_tc_case(sc, name, args, body)); + + /* when */ + if ((vars >= 1) && (vars <= 3) && + (car(body) == sc->when_symbol) && + (is_fxable(sc, cadr(body)))) + return(check_tc_when(sc, name, vars, args, body)); + return(false); +} + +static void mark_fx_treeable(s7_scheme *sc, s7_pointer body) +{ /* it is possible to encounter a cyclic body here -- should we protect against that if safety>0? */ + if (is_pair(body)) /* slightly faster than the other way of writing this */ + { + if (is_pair(car(body))) + { + set_is_fx_treeable(body); + mark_fx_treeable(sc, car(body)); + } + mark_fx_treeable(sc, cdr(body)); + } +} + +static void optimize_lambda(s7_scheme *sc, bool unstarred_lambda, s7_pointer func, s7_pointer args, s7_pointer body) +{ /* func is either sc->unused or a symbol */ + s7_int len = s7_list_length(sc, body); + if (SHOW_EVAL_OPS) fprintf(stderr, "%s[%d]: %s\n", __func__, __LINE__, display_80(body)); + if (len < 0) /* (define (hi) 1 . 2) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "~A: function body messed up, ~A", 31), + (unstarred_lambda) ? sc->lambda_symbol : sc->lambda_star_symbol, + sc->code)); + if (len > 0) /* i.e. not circular */ + { + body_t result; + s7_pointer p, lst, cleared_args; + + clear_symbol_list(sc); + for (p = args; is_pair(p); p = cdr(p)) + add_symbol_to_list(sc, (is_symbol(car(p))) ? car(p) : caar(p)); + if (!is_null(p)) + add_symbol_to_list(sc, p); + sc->got_tc = false; + sc->not_tc = false; + sc->got_rec = false; + sc->rec_tc_args = -1; + result = ((is_symbol(func)) && (symbol_is_in_list(sc, func))) ? UNSAFE_BODY : body_is_safe(sc, func, body, true); /* (define (f f)...) */ + clear_symbol_list(sc); + + /* if the body is safe, we can optimize the calling sequence */ + if (!unstarred_lambda) + { + bool happy = true; + /* check default vals -- if none is an expression or symbol, set simple args */ + for (p = args; is_pair(p); p = cdr(p)) + { + s7_pointer arg = car(p); + if ((is_pair(arg)) && /* has default value */ + (is_pair(cdr(arg))) && /* is not a ridiculous improper list */ + ((is_symbol(cadr(arg))) || /* if default value might involve eval in any way, it isn't simple */ + (is_unquoted_pair(cadr(arg))))) /* pair as default only ok if it is (quote ...) */ + { + happy = false; + if ((result > UNSAFE_BODY) && + (tree_has_definers_or_binders(sc, cadr(arg)))) /* if the default has a definer, body is not safe (funclet is not stable) */ + result = UNSAFE_BODY; + break; + }} + if (happy) + lambda_set_simple_defaults(body); + } + if (result >= SAFE_BODY) /* not RECUR_BODY here (need new let for cons-r in s7test) */ + { + set_safe_closure_body(body); + if (result == VERY_SAFE_BODY) + set_very_safe_closure_body(body); + } + if (is_symbol(func)) + { + lst = list_1(sc, add_symbol_to_list(sc, func)); + sc->temp1 = lst; + } + else lst = sc->nil; + + if (optimize(sc, body, 1, cleared_args = collect_parameters(sc, args, lst)) == OPT_OOPS) + clear_all_optimizations(sc, body); + else + if (result >= RECUR_BODY) + { + int32_t nvars; + mark_fx_treeable(sc, body); + + for (nvars = 0, p = args; (is_pair(p)) && (!is_symbol_and_keyword(car(p))); nvars++, p = cdr(p)); + if ((is_null(p)) && + (nvars > 0)) + { + fx_annotate_args(sc, body, cleared_args); /* almost useless -- we need a recursive traversal here but that collides with check_if et al */ + fx_tree(sc, body, /* this usually costs more than it saves! */ + (is_pair(car(args))) ? caar(args) : car(args), + (nvars > 1) ? ((is_pair(cadr(args))) ? caadr(args) : cadr(args)) : NULL, + (nvars > 2) ? ((is_pair(caddr(args))) ? caaddr(args) : caddr(args)) : NULL, + nvars > 3); + } + if (((unstarred_lambda) || ((is_null(p)) && (nvars == sc->rec_tc_args))) && + (is_null(cdr(body)))) + { /* (if #t|#f...) happens only rarely */ + if (sc->got_tc) + { + if (check_tc(sc, func, nvars, args, car(body))) + set_safe_closure_body(body); /* (very_)safe_closure set above if > RECUR_BODY */ + /* if not check_tc, car(body) is either not a tc op or it is not optimized so that is_fxable will return false */ + } + if ((sc->got_rec) && + (!is_tc_op(optimize_op(car(body)))) && + (check_recur(sc, func, nvars, args, car(body)))) + set_safe_closure_body(body); + }} + if (is_symbol(func)) + { + sc->temp1 = sc->unused; + free_cell(sc, lst); + } + sc->got_tc = false; + sc->not_tc = false; + sc->got_rec = false; + } +} + +static int32_t check_lambda(s7_scheme *sc, s7_pointer form, bool opt) +{ + /* code is a lambda form: (lambda (a b) (+ a b)) */ + /* this includes unevaluated symbols (direct symbol table refs) in macro arg list */ + s7_pointer code, body; + int32_t arity = 0; + + if ((sc->safety > NO_SAFETY) && + (tree_is_cyclic(sc, form))) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "lambda: body is cyclic: ~S", 26), form)); + + code = cdr(form); + if (!is_pair(code)) /* (lambda) or (lambda . 1) */ + syntax_error_nr(sc, "lambda: no arguments? ~A", 24, form); + + body = cdr(code); + if (!is_pair(body)) /* (lambda #f) */ + syntax_error_nr(sc, "lambda: no body? ~A", 19, form); + + /* in many cases, this is a no-op -- we already checked at define */ + check_lambda_args(sc, car(code), &arity, sc->code); + /* clear_symbol_list(sc); */ /* not used in check_lambda_args and clobbers optimize_expression find_uncomplicated_symbol check */ + + /* look for (define f (let (...) (lambda ...))) and treat as equivalent to (define (f ...)...) + * one problem the hop=0 fixes is that safe closures assume the old let exists, so we need to check for define below + * I wonder about apply define... + */ + /* OP_LET1 should work here also, (let ((f (lambda...)))), but subsequent calls assume a saved let if safe + * to mimic define, we need to parallel op_define_with_setter + make_funclet, I think + */ + if ((opt) || + (stack_top_op(sc) == OP_DEFINE1) || + (((sc->stack_end - sc->stack_start) > 4) && + (stack_top4_op(sc) == OP_DEFINE1) && /* surely if define is ok, so is define dilambda? 16-Apr-16 */ + (sc->op_stack_now > sc->op_stack) && + ((*(sc->op_stack_now - 1)) == (s7_pointer)global_value(sc->dilambda_symbol)))) + optimize_lambda(sc, true, sc->unused, car(code), body); + else + if (optimize(sc, body, 0, + /* ((sc->op_stack_now > sc->op_stack) && (is_c_function((*(sc->op_stack_now - 1)))) && (is_scope_safe((*(sc->op_stack_now - 1))))) ? 1 : 0, */ + /* this works except when someone resets outlet(curlet) after defining a local function! */ + collect_parameters(sc, car(code), sc->nil)) == OPT_OOPS) + clear_all_optimizations(sc, body); + pair_set_syntax_op(form, OP_LAMBDA_UNCHECKED); + if (arity < -1) arity++; /* confusing! at least 0 = (), but (lambda arg...) has same "arity" here as (lambda (a . b)...)? */ + set_opt3_any(code, (s7_pointer)((intptr_t)arity)); + return(arity); +} + +static s7_pointer op_lambda(s7_scheme *sc, s7_pointer code) +{ + int32_t arity = check_lambda(sc, code, false); + code = cdr(code); + set_opt3_any(code, (s7_pointer)((intptr_t)arity)); + return(make_closure(sc, car(code), cdr(code), T_CLOSURE | ((arity < 0) ? T_COPY_ARGS : 0), arity)); +} + +static inline s7_pointer op_lambda_unchecked(s7_scheme *sc, s7_pointer code) +{ + int32_t arity = (int32_t)((intptr_t)opt3_any(cdr(code))); + return(make_closure_gc_checked(sc, cadr(code), cddr(code), T_CLOSURE | ((arity < 0) ? T_COPY_ARGS : 0), arity)); +} + +static void check_lambda_star(s7_scheme *sc) +{ + s7_pointer code = cdr(sc->code); + if ((sc->safety > NO_SAFETY) && + (tree_is_cyclic(sc, sc->code))) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "lambda*: body is cyclic: ~S", 27), sc->code)); + + if ((!is_pair(code)) || + (!is_pair(cdr(code)))) /* (lambda*) or (lambda* #f) */ + syntax_error_nr(sc, "lambda*: no arguments or no body? ~A", 36, sc->code); + + set_car(code, check_lambda_star_args(sc, car(code), NULL, sc->code)); + if ((sc->safety > NO_SAFETY) || + (stack_top_op(sc) != OP_DEFINE1)) + { + if (optimize(sc, cdr(code), 0, collect_parameters(sc, car(code), sc->nil)) == OPT_OOPS) + clear_all_optimizations(sc, cdr(code)); + } + else optimize_lambda(sc, false, sc->unused, car(code), cdr(code)); + + pair_set_syntax_op(sc->code, OP_LAMBDA_STAR_UNCHECKED); + sc->code = code; +} + + +/* -------------------------------- case -------------------------------- */ +static inline bool is_undefined_feed_to(s7_scheme *sc, const s7_pointer sym) +{ + return((sym == sc->feed_to_symbol) && + ((symbol_ctr(sc->feed_to_symbol) == 0) || (s7_symbol_value(sc, sc->feed_to_symbol) == sc->undefined))); +} + +static bool is_all_fxable(s7_scheme *sc, s7_pointer x) +{ + for (s7_pointer p = x; is_pair(p); p = cdr(p)) + if (!is_fxable(sc, car(p))) + return(false); + return(true); +} + +static s7_pointer check_case(s7_scheme *sc) +{ + /* we're not checking repeated or ridiculous (non-eqv?) keys here because they aren't errors */ + bool keys_simple = true, has_feed_to = false, keys_single = true, bodies_simple = true, has_else = false, use_fx = true; + int32_t key_type = T_FREE; + s7_pointer x, carc, code = cdr(sc->code), form = sc->code; + + if (!is_pair(code)) /* (case) or (case . 1) */ + syntax_error_nr(sc, "case has no selector: ~S", 25, form); + if (!is_pair(cdr(code))) /* (case 1) or (case 1 . 1) */ + syntax_error_nr(sc, "case has no clauses?: ~S", 25, form); + if (!is_pair(cadr(code))) /* (case 1 1) */ + syntax_error_nr(sc, "case clause is not a pair? ~S", 29, form); + set_opt3_any(code, sc->unspecified); + + for (x = cdr(code); is_pair(x); x = cdr(x)) + { + s7_pointer y, car_x; + if (!is_pair(car(x))) + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "case clause ~S messed up in ~A", 30), + x, object_to_truncated_string(sc, form, 80))); + car_x = car(x); + + if (!is_list(cdr(car_x))) /* (case 1 ((1))) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "case clause result ~S is messed up in ~A", 40), + car_x, object_to_truncated_string(sc, form, 80))); + if ((bodies_simple) && + ((is_null(cdr(car_x))) || (!is_null(cddr(car_x))))) + bodies_simple = false; + + use_fx = ((use_fx) && (is_pair(cdr(car_x))) && (is_all_fxable(sc, cdr(car_x)))); + y = car(car_x); + if (!is_pair(y)) + { + if ((y != sc->else_symbol) && /* (case 1 (2 1)) */ + ((!is_symbol(y)) || + (s7_symbol_value(sc, y) != sc->else_symbol))) /* "proper list" below because: (case 1 (() 2) ... */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_4(sc, wrap_string(sc, "case clause key-list ~S in ~S is not a proper list or 'else', in ~A", 67), + y, car_x, object_to_truncated_string(sc, form, 80))); + has_else = true; + if (is_not_null(cdr(x))) /* (case 1 (else 1) ((2) 1)) */ + syntax_error_nr(sc, "case 'else' clause is not the last clause: ~S", 45, x); + if (!is_null(cdr(car_x))) /* else (else) so return selector */ + { + if (is_pair(cddr(car_x))) + { + set_opt3_any(code, cdr(car_x)); + bodies_simple = false; + } + else + { + set_opt3_any(code, ((bodies_simple) && (keys_single)) ? cadr(car_x) : cdr(car_x)); + set_opt1_clause(x, cadr(car_x)); + }}} + else + { + if (!is_simple(car(y))) keys_simple = false; + if (!is_null(cdr(y))) keys_single = false; + if (key_type == T_FREE) + key_type = type(car(y)); + else + if (key_type != type(car(y))) + key_type = NUM_TYPES; + if (key_type == T_SYMBOL) set_case_key(car(y)); + + for (y = cdr(y); is_pair(y); y = cdr(y)) + { + if (!is_simple(car(y))) + keys_simple = false; + if (key_type != type(car(y))) + key_type = NUM_TYPES; + if (key_type == T_SYMBOL) set_case_key(car(y)); + } + if (!is_null(y)) /* (case () ((1 . 2) . hi) . hi) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "case key list ~S is improper, in ~A", 35), + car_x, object_to_truncated_string(sc, form, 80))); + } + y = car_x; + if (!s7_is_proper_list(sc, cdr(y))) /* (case 2 ((1 2) 1 . 2)) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "case: stray dot? ~S in ~A", 25), + y, object_to_truncated_string(sc, form, 80))); + if ((is_pair(cdr(y))) && (is_undefined_feed_to(sc, cadr(y)))) + { + has_feed_to = true; + if (!is_pair(cddr(y))) /* (case 1 (else =>)) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "case: '=>' target missing: ~S in ~A", 35), + y, object_to_truncated_string(sc, form, 80))); + if (is_pair(cdddr(y))) /* (case 1 (else => + - *)) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "case: '=>' has too many targets: ~S in ~A", 41), + y, object_to_truncated_string(sc, form, 80))); + }} + if (is_not_null(x)) /* (case x ((1 2)) . 1) */ + syntax_error_nr(sc, "case: stray dot? ~S", 19, form); + + if ((keys_single) && + (bodies_simple)) + { + for (x = cdr(code); is_not_null(x); x = cdr(x)) + { + set_opt2_any(x, caar(x)); + if (is_pair(opt2_any(x))) + { + set_opt2_any(x, car(opt2_any(x))); + if (is_pair(cdar(x))) + set_opt1_clause(x, cadar(x)); + }}} + else + for (x = cdr(code); is_not_null(x); x = cdr(x)) + { + set_opt2_any(x, caar(x)); + if ((is_pair(opt2_any(x))) && + (is_pair(cdar(x)))) + set_opt1_clause(x, cadar(x)); + } + if (key_type == T_INTEGER) + set_has_integer_keys(form); + + /* X_Y_Z: X (selector): S=symbol, A=fxable, P=any, Y: E(keys simple) G(any keys) I(integer keys) , Z: S: no =>, bodies simple, keys single G: all else, -- ?? */ + pair_set_syntax_op(form, OP_CASE_P_G_G); /* fallback on this */ + if ((has_feed_to) || + (!bodies_simple) || /* x_x_g g=general keys or bodies */ + (!keys_single)) + { + if (!keys_simple) /* x_g_g */ + { + if (is_fxable(sc, car(code))) + { + pair_set_syntax_op(form, OP_CASE_A_G_G); + set_fx_direct(code, fx_choose(sc, code, sc->curlet, let_symbol_is_safe)); + if ((is_fx_treeable(cdr(code))) && (tis_slot(let_slots(sc->curlet)))) fx_curlet_tree_in(sc, code); + } + else pair_set_syntax_op(form, OP_CASE_P_G_G); + } + else /* x_e_g */ + { + if (!has_else) set_opt3_any(code, sc->unused); /* affects all that goto CASE_E_G */ + if (is_fxable(sc, car(code))) + { + pair_set_syntax_op(form, (key_type == T_SYMBOL) ? OP_CASE_A_S_G : OP_CASE_A_E_G); + set_fx_direct(code, fx_choose(sc, code, sc->curlet, let_symbol_is_safe)); + if ((is_fx_treeable(cdr(code))) && (tis_slot(let_slots(sc->curlet)))) fx_curlet_tree_in(sc, code); + } + else pair_set_syntax_op(form, OP_CASE_P_E_G); + }} + else /* x_x_s */ + if (!keys_simple) /* x_g|i_s */ + { + if (is_fxable(sc, car(code))) + { + pair_set_syntax_op(form, ((!WITH_GMP) && (key_type == T_INTEGER)) ? OP_CASE_A_I_S : OP_CASE_A_G_S); + set_fx_direct(code, fx_choose(sc, code, sc->curlet, let_symbol_is_safe)); + if ((is_fx_treeable(cdr(code))) && (tis_slot(let_slots(sc->curlet)))) fx_curlet_tree_in(sc, code); + } + else pair_set_syntax_op(form, ((!WITH_GMP) && (key_type == T_INTEGER)) ? OP_CASE_P_I_S : OP_CASE_P_G_S); + } + else /* x_e_s */ + if (is_fxable(sc, car(code))) + { + pair_set_syntax_op(form, OP_CASE_A_E_S); + set_fx_direct(code, fx_choose(sc, code, sc->curlet, let_symbol_is_safe)); + if ((is_fx_treeable(cdr(code))) && (tis_slot(let_slots(sc->curlet)))) fx_curlet_tree_in(sc, code); + } + else pair_set_syntax_op(form, OP_CASE_P_E_S); + + if ((use_fx) && (has_else) && (!has_feed_to)) + { + opcode_t op = optimize_op(form); + if ((op == OP_CASE_A_E_S) || (op == OP_CASE_A_G_S) || (op == OP_CASE_A_S_G) || ((!WITH_GMP) && (op == OP_CASE_A_I_S))) + { + pair_set_syntax_op(form, + (op == OP_CASE_A_I_S) ? OP_CASE_A_I_S_A : + ((op == OP_CASE_A_E_S) ? OP_CASE_A_E_S_A : + ((op == OP_CASE_A_S_G) ? OP_CASE_A_S_G_A : OP_CASE_A_G_S_A))); + for (x = cdr(code); is_pair(x); x = cdr(x)) + { + s7_pointer clause = cdar(x); + fx_annotate_args(sc, clause, sc->curlet); + if ((is_fx_treeable(cdr(code))) && (tis_slot(let_slots(sc->curlet)))) fx_curlet_tree(sc, clause); + if (is_null(cdr(x))) set_opt3_any(code, clause); + }}} + carc = cadr(form); + if (!is_pair(carc)) + { + sc->value = (is_symbol(carc)) ? lookup_checked(sc, carc) : carc; + return(NULL); + } + push_stack_no_args_direct(sc, OP_CASE_G_G); + sc->code = carc; + return(carc); +} + +#if (!WITH_GMP) +static bool op_case_i_s(s7_scheme *sc) +{ + s7_pointer selector = sc->value; + s7_pointer else_clause = opt3_any(cdr(sc->code)); + if (else_clause != sc->unspecified) + { + if (is_t_integer(selector)) + { + s7_int val = integer(selector); + for (s7_pointer x = cddr(sc->code); is_pair(cdr(x)); x = cdr(x)) + if (integer(opt2_any(x)) == val) + { + sc->code = opt1_clause(x); + return(false); + }} + sc->code = else_clause; + return(false); + } + if (is_t_integer(selector)) + { + s7_int val = integer(selector); + for (s7_pointer x = cddr(sc->code); is_pair(x); x = cdr(x)) + if (integer(opt2_any(x)) == val) + { + sc->code = opt1_clause(x); + return(false); + }} + sc->value = sc->unspecified; + return(true); +} + +static inline s7_pointer fx_case_a_i_s_a(s7_scheme *sc, s7_pointer code) /* inline saves about 30 in tleft */ +{ + s7_pointer selector = fx_call(sc, cdr(code)); + if (is_t_integer(selector)) + { + s7_int val = integer(selector); + for (s7_pointer x = cddr(sc->code); is_pair(cdr(x)); x = cdr(x)) + if (integer(opt2_any(x)) == val) + return(fx_call(sc, cdar(x))); + } + return(fx_call(sc, opt3_any(cdr(code)))); +} +#endif + +static bool op_case_e_g_1(s7_scheme *sc, const s7_pointer selector, bool ok) +{ + s7_pointer x; + if (ok) + { + for (x = cddr(sc->code); is_pair(x); x = cdr(x)) + { + s7_pointer y = opt2_any(x); + if (!is_pair(y)) /* i.e. else? */ + goto ELSE_CASE_1; + do { + if (car(y) == selector) + goto ELSE_CASE_1; + y = cdr(y); + } while (is_pair(y)); + } + sc->value = sc->unspecified; + pop_stack(sc); + return(true); + } + + sc->code = opt3_any(cdr(sc->code)); + if (sc->code == sc->unused) /* set in check_case if no else clause */ + sc->value = sc->unspecified; + else + if (is_pair(sc->code)) + goto ELSE_CASE_2; + pop_stack(sc); + return(true); + + ELSE_CASE_1: + /* x is the entire matching clause, (case 2 ((2) 3)), x: (((2) 3)) */ + sc->code = T_Lst(cdar(x)); + if (is_null(sc->code)) /* sc->value is already the selector */ + { + pop_stack(sc); + return(true); + } + + ELSE_CASE_2: + if (is_null(cdr(sc->code))) + { + sc->code = car(sc->code); + sc->cur_op = optimize_op(sc->code); + return(true); + } + + if (is_undefined_feed_to(sc, car(sc->code))) + return(false); + + push_stack_no_args(sc, sc->begin_op, T_Pair(cdr(sc->code))); + sc->code = car(sc->code); + sc->cur_op = optimize_op(sc->code); + return(true); +} + +static inline s7_pointer fx_call_all(s7_scheme *sc, s7_pointer code) +{ + s7_pointer p; + for (p = code; is_pair(cdr(p)); p = cdr(p)) + fx_call(sc, p); + return(fx_call(sc, p)); +} + +static s7_pointer fx_case_a_s_g_a(s7_scheme *sc, s7_pointer code) +{ + s7_pointer selector = fx_call(sc, cdr(code)); + if (is_case_key(selector)) + for (s7_pointer x = cddr(sc->code); is_pair(x); x = cdr(x)) + { + s7_pointer y = opt2_any(x); + if (!is_pair(y)) /* i.e. else? */ + return(fx_call_all(sc, cdar(x))); /* else clause */ + do { + if (car(y) == selector) + return(fx_call_all(sc, cdar(x))); + y = cdr(y); + } while (is_pair(y)); + } + return(fx_call_all(sc, opt3_any(cdr(code)))); /* selector is not a case-key */ +} + +#define if_pair_set_up_begin(Sc) if (is_pair(cdr(Sc->code))) {check_stack_size(Sc); push_stack_no_args(Sc, Sc->begin_op, cdr(Sc->code));} Sc->code = car(Sc->code); +#define if_pair_set_up_begin_unchecked(Sc) if (is_pair(cdr(Sc->code))) push_stack_no_args(Sc, Sc->begin_op, cdr(Sc->code)); Sc->code = car(Sc->code); +/* using the one_form bit here was slower */ + +static bool op_case_g_g(s7_scheme *sc) +{ + s7_pointer x; + if (has_integer_keys(sc->code)) + { + s7_int selector; + sc->code = cddr(sc->code); + if (is_t_integer(sc->value)) + selector = integer(sc->value); + else + { +#if WITH_GMP + if ((is_t_big_integer(sc->value)) && (mpz_fits_slong_p(big_integer(sc->value)))) + selector = mpz_get_si(big_integer(sc->value)); + else +#endif + { + for (x = sc->code; is_pair(x); x = cdr(x)) + if (!is_pair(caar(x))) + goto ELSE_CASE; + sc->value = sc->unspecified; + pop_stack(sc); + return(true); + }} + for (x = sc->code; is_pair(x); x = cdr(x)) + { + s7_pointer y = caar(x); + if (!is_pair(y)) + goto ELSE_CASE; + for (; is_pair(y); y = cdr(y)) + if (integer(car(y)) == selector) + goto ELSE_CASE; + } + sc->value = sc->unspecified; + pop_stack(sc); + return(true); + } + sc->code = cddr(sc->code); + if (is_simple(sc->value)) + { + for (x = sc->code; is_pair(x); x = cdr(x)) + { + s7_pointer y = caar(x); + if (!is_pair(y)) + goto ELSE_CASE; + do { + if (car(y) == sc->value) + goto ELSE_CASE; + y = cdr(y); + } while (is_pair(y)); + } + sc->value = sc->unspecified; + pop_stack(sc); + return(true); + } + for (x = sc->code; is_pair(x); x = cdr(x)) + { + s7_pointer y = caar(x); + if (!is_pair(y)) + goto ELSE_CASE; + for (; is_pair(y); y = cdr(y)) + if (s7_is_eqv(sc, car(y), sc->value)) + goto ELSE_CASE; + } + sc->value = sc->unspecified; /* this was sc->nil but the spec says case value is unspecified if no clauses match */ + pop_stack(sc); + return(true); + + ELSE_CASE: + /* x is the entire matching clause, (case 2 ((2) 3)), x: (((2) 3)) */ + sc->code = T_Lst(cdar(x)); + if (is_null(sc->code)) /* sc->value is already the selector */ + { + pop_stack(sc); + return(true); + } + if (is_null(cdr(sc->code))) + { + sc->code = car(sc->code); + sc->cur_op = optimize_op(sc->code); + return(true); + } + if (is_undefined_feed_to(sc, car(sc->code))) + return(false); + if_pair_set_up_begin_unchecked(sc); + sc->cur_op = optimize_op(sc->code); + return(true); +} + +static void op_case_e_s(s7_scheme *sc) +{ + s7_pointer selector = sc->value; + if (is_simple(selector)) + for (s7_pointer x = cddr(sc->code); is_pair(x); x = cdr(x)) + if (opt2_any(x) == selector) + { + sc->code = opt1_clause(x); + return; + } + sc->code = opt3_any(cdr(sc->code)); +} + +static s7_pointer fx_case_a_e_s_a(s7_scheme *sc, s7_pointer code) +{ + s7_pointer selector = fx_call(sc, cdr(code)); + if (is_simple(selector)) + for (s7_pointer x = cddr(code); is_pair(x); x = cdr(x)) + if (opt2_any(x) == selector) + return(fx_call(sc, cdar(x))); + return(fx_call(sc, opt3_any(cdr(code)))); +} + +static void op_case_g_s(s7_scheme *sc) +{ + s7_pointer selector = sc->value; + for (s7_pointer x = cddr(sc->code); is_pair(x); x = cdr(x)) + if (s7_is_eqv(sc, opt2_any(x), selector)) + { + sc->code = opt1_clause(x); + return; + } + sc->code = opt3_any(cdr(sc->code)); +} + +static inline s7_pointer fx_case_a_g_s_a(s7_scheme *sc, s7_pointer code) /* split into int/any cases in g_g, via has_integer_keys(sc->code) */ +{ + s7_pointer selector = fx_call(sc, cdr(code)); + for (s7_pointer x = cddr(code); is_pair(x); x = cdr(x)) + if (s7_is_eqv(sc, opt2_any(x), selector)) + return(fx_call(sc, cdar(x))); + return(fx_call(sc, opt3_any(cdr(code)))); +} + + +/* -------------------------------- let -------------------------------- */ +static void check_let_a_body(s7_scheme *sc, s7_pointer form) +{ + s7_pointer code = cdr(form); + if (is_fxable(sc, cadr(code))) + { + fx_annotate_arg(sc, cdr(code), set_plist_1(sc, caaar(code))); /* was sc->curlet) ? */ + fx_tree(sc, cdr(code), caaar(code), NULL, NULL, false); + pair_set_syntax_op(form, OP_LET_A_A_OLD); + } + else + if (is_pair(cadr(code))) + { + pair_set_syntax_op(form, OP_LET_A_P_OLD); + if (is_fx_treeable(cdaar(code))) fx_tree(sc, cdr(code), caaar(code), NULL, NULL, false); + } +} + +static void check_let_one_var(s7_scheme *sc, s7_pointer form, s7_pointer start) +{ + s7_pointer binding = car(start), code = cdr(form); + if (is_pair(cadr(binding))) + { + /* this is not a named let */ + pair_set_syntax_op(form, ((is_pair(cdr(code))) && (is_null(cddr(code)))) ? OP_LET_ONE_P_OLD : OP_LET_ONE_OLD); + set_opt2_sym(cdr(code), car(binding)); /* these don't collide -- cdr(code) and code */ + set_opt2_pair(code, cadr(binding)); + if (is_optimized(cadr(binding))) + { + if ((optimize_op(cadr(binding)) == HOP_SAFE_C_SS) && + (fn_proc(cadr(binding)) == g_assq)) + { + set_opt2_sym(code, cadadr(binding)); + pair_set_syntax_op(form, OP_LET_opaSSq_OLD); + set_opt3_sym(cdr(code), caddadr(binding)); + set_opt1_sym(code, car(binding)); + } + else + if (is_fxable(sc, cadr(binding))) + { + set_opt2_pair(code, binding); + pair_set_syntax_op(form, OP_LET_A_OLD); + fx_annotate_arg(sc, cdr(binding), sc->curlet); + if (is_null(cddr(code))) + check_let_a_body(sc, form); + else + { + s7_pointer p; + for (p = cdr(code); is_pair(p); p = cdr(p)) + if (!is_fxable(sc, car(p))) + break; + if (is_null(p)) + { + pair_set_syntax_op(form, OP_LET_A_NA_OLD); + fx_annotate_args(sc, cdr(code), set_plist_1(sc, car(binding))); + fx_tree(sc, cdr(code), car(binding), NULL, NULL, false); + return; + } + if (is_fx_treeable(cdr(code))) fx_tree(sc, cdr(code), car(binding), NULL, NULL, false); + }}}} + else + { + set_opt2_pair(code, binding); + pair_set_syntax_op(form, OP_LET_A_OLD); + fx_annotate_arg(sc, cdr(binding), sc->curlet); + if (is_null(cddr(code))) + check_let_a_body(sc, form); + else + { + fx_annotate_args(sc, cdr(code), set_plist_1(sc, caaar(code))); /* no effect if not syntactic -- how to fix? */ + if (is_fx_treeable(cdr(code))) fx_tree(sc, cdr(code), car(binding), NULL, NULL, false); + }} + if ((optimize_op(form) == OP_LET_A_OLD) && + (is_pair(cddr(code))) && (is_null(cdddr(code)))) + pair_set_syntax_op(form, OP_LET_A_OLD_2); +} + +static s7_pointer check_named_let(s7_scheme *sc, int32_t vars) +{ + s7_pointer code = cdr(sc->code); + set_opt2_int(code, vars); + if (vars == 0) + { + pair_set_syntax_op(sc->code, OP_NAMED_LET_NO_VARS); + set_opt1_pair(sc->code, cddr(code)); + optimize_lambda(sc, true, car(code), sc->nil, cddr(code)); + } + else + { + bool fx_ok = true; + pair_set_syntax_op(sc->code, OP_NAMED_LET); + /* this is (let name ...) so the initial values need to be removed from the closure arg list */ + + sc->args = T_Pair(safe_list_if_possible(sc, vars)); + for (s7_pointer ex = cadr(code), exp = sc->args; is_pair(ex); ex = cdr(ex), exp = cdr(exp)) + { + s7_pointer val = cdar(ex); + s7_function fx = fx_choose(sc, val, sc->curlet, let_symbol_is_safe); + if (fx) set_fx_direct(val, fx); else fx_ok = false; + car(exp) = caar(ex); + } + if (fx_ok) + { + set_opt1_pair(code, caadr(code)); + if (vars == 2) set_opt3_pair(code, cadadr(code)); + pair_set_syntax_op(sc->code, (vars == 1) ? OP_NAMED_LET_A : ((vars == 2) ? OP_NAMED_LET_AA : OP_NAMED_LET_NA)); + } + optimize_lambda(sc, true, car(code), sc->args, cddr(code)); /* car(code) is the name */ + clear_list_in_use(sc->args); + sc->args = sc->nil; + } + return(code); +} + +static s7_pointer check_let(s7_scheme *sc) /* called only from op_let */ +{ + s7_pointer x, start, code = cdr(sc->code), form = sc->code; + bool named_let; + int32_t vars; + + if (!is_pair(code)) /* (let . 1) */ + { + if (is_null(code)) /* (let) */ + syntax_error_nr(sc, "let has no variables or body: ~A", 32, form); + syntax_error_nr(sc, "let form is an improper list? ~A", 32, form); + } + + if (!is_pair(cdr(code))) /* (let () ) or (let () . 1) */ + syntax_error_nr(sc, "let has no body: ~A", 19, form); + + if ((!is_list(car(code))) && /* (let 1 ...) */ + (!is_normal_symbol(car(code)))) + syntax_error_nr(sc, "let variable list is messed up or missing: ~A", 45, form); + + named_let = (is_symbol(car(code))); + if (named_let) + { + if (!is_list(cadr(code))) /* (let hi #t) */ + syntax_error_nr(sc, "let variable list is messed up: ~A", 34, form); + if (!is_pair(cddr(code))) /* (let hi () . =>) or (let hi () ) */ + { + if (is_null(cddr(code))) + syntax_error_nr(sc, "named let has no body: ~A", 25 , form); + syntax_error_nr(sc, "named let stray dot? ~A", 23, form); + } + if (is_constant_symbol(sc, car(code))) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_3(sc, cant_bind_immutable_string, sc->let_symbol, form)); + set_local(car(code)); + start = cadr(code); + } + else start = car(code); + + clear_symbol_list(sc); + for (vars = 0, x = start; is_pair(x); vars++, x = cdr(x)) + { + s7_pointer y, carx = car(x); + + if ((!is_pair(carx)) || (is_null(cdr(carx)))) /* (let ((x)) ...) or (let ((x 1) . (y 2)) ...) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "let variable declaration, but no value?: ~A in ~A", 49), + x, object_to_truncated_string(sc, form, 80))); + + if (!(is_pair(cdr(carx)))) /* (let ((x . 1))...) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "let variable declaration, ~A, is not a proper list in ~A", 56), + x, object_to_truncated_string(sc, form, 80))); + + if (is_not_null(cddr(carx))) /* (let ((x 1 2 3)) ...) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "let variable declaration, ~A, has more than one value in ~A", 59), + x, object_to_truncated_string(sc, form, 80))); + y = car(carx); + if (!(is_symbol(y))) + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "bad variable ~S in let (it is not a symbol) in ~A", 49), + carx, object_to_truncated_string(sc, form, 80))); + + if (is_constant_symbol(sc, y)) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_3(sc, cant_bind_immutable_string, sc->let_symbol, x)); + + /* check for name collisions -- not sure this is required by Scheme */ + if (symbol_is_in_list(sc, y)) + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "duplicate identifier in let: ~S in ~S", 37), y, form)); + add_symbol_to_list(sc, y); + set_local(y); + } + /* (let ('1) quote) -> 1 */ + + if (is_not_null(x)) /* (let* ((a 1) . b) a) */ + syntax_error_nr(sc, "let variable list improper?: ~A", 31, form); + + if (!s7_is_proper_list(sc, cdr(code))) /* (let ((a 1)) a . 1) */ + syntax_error_nr(sc, "stray dot in let body: ~S", 25, cdr(code)); + + if (named_let) + return(check_named_let(sc, vars)); + + if (vars == 0) /* !in_heap does not happen much here */ + pair_set_syntax_op(form, OP_LET_NO_VARS); + else + { + pair_set_syntax_op(form, OP_LET_UNCHECKED); + if (vars == 1) + check_let_one_var(sc, form, start); + else + { + /* this used to check that vars < gc_trigger_size, but I can't see why */ + opcode_t opt = OP_UNOPT; + for (s7_pointer p = start; is_pair(p); p = cdr(p)) + { + x = car(p); + if (is_fxable(sc, cadr(x))) + { + set_fx_direct(cdr(x), fx_choose(sc, cdr(x), sc->curlet, let_symbol_is_safe)); + if (opt == OP_UNOPT) + opt = OP_LET_NA_OLD; + } + else opt = OP_LET_UNCHECKED; + } + pair_set_syntax_op(form, opt); + if ((opt == OP_LET_NA_OLD) && + (is_null(cddr(code)))) /* 1 form in body */ + { + if (vars == 2) + { + pair_set_syntax_op(form, OP_LET_2A_OLD); + set_opt1_pair(code, caar(code)); + set_opt2_pair(code, cadar(code)); + } + else + if (vars == 3) + { + pair_set_syntax_op(form, OP_LET_3A_OLD); + set_opt1_pair(code, cadar(code)); + set_opt2_pair(code, caddar(code)); + }}}} + + /* if safe_c or safe_closure as car(body), null cdr(body), see if only vars as args + * symbol_list is intact?? + */ + if (optimize_op(form) >= OP_LET_NA_OLD) + { + if ((!in_heap(form)) && + (body_is_safe(sc, sc->unused, cdr(code), true) >= SAFE_BODY)) /* recur_body is apparently never hit */ + set_opt3_let(code, make_semipermanent_let(sc, car(code))); + else + { + set_optimize_op(form, optimize_op(form) + 1); /* *_old -> *_new */ + set_opt3_let(code, sc->nil); + }} + + /* fx_tree inits */ + if ((is_pair(code)) && + /* (is_let(sc->curlet)) && */ /* not rootlet=() but treeable is only in functions */ + (is_fx_treeable(code)) && /* was is_funclet(sc->curlet) 27-Sep-21, but that seems too restrictive */ + (tis_slot(let_slots(sc->curlet)))) + { + s7_pointer s1 = let_slots(sc->curlet), s2 = next_slot(s1), s3 = NULL; + bool more_vars = false; + if (tis_slot(s2)) + { + if (tis_slot(next_slot(s2))) + { + s3 = next_slot(s2); + more_vars = tis_slot(next_slot(s3)); + s3 = slot_symbol(s3); + } + s2 = slot_symbol(s2); + } + s1 = slot_symbol(s1); + for (s7_pointer p = car(code); is_pair(p); p = cdr(p)) /* var list */ + { + s7_pointer init = cdar(p); + fx_tree(sc, init, s1, s2, s3, more_vars); + }} + return(code); +} + +static bool op_named_let_1(s7_scheme *sc, s7_pointer args) /* args = vals in decl order */ +{ + s7_pointer body = cddr(sc->code), x; + s7_int n = opt2_int(sc->code); + for (x = cadr(sc->code), sc->w = sc->nil; is_pair(x); x = cdr(x)) + { + sc->w = cons(sc, caar(x), sc->w); + x = cdr(x); + if (!is_pair(x)) break; + sc->w = cons_unchecked(sc, caar(x), sc->w); + } + sc->w = proper_list_reverse_in_place(sc, sc->w); /* init values (args) are also in "reversed" order */ + sc->curlet = make_let(sc, sc->curlet); + sc->x = make_closure_unchecked(sc, sc->w, body, T_CLOSURE, n); + add_slot(sc, sc->curlet, car(sc->code), sc->x); + sc->curlet = make_let(sc, sc->curlet); /* inner let */ + + for (x = sc->w; is_not_null(args); x = cdr(x)) /* reuse the value cells as the new let slots */ + { + s7_pointer sym = car(x), new_args = cdr(args); + reuse_as_slot(args, sym, unchecked_car(args)); /* args=slot, sym=symbol, car(args)=value */ + slot_set_next(args, let_slots(sc->curlet)); + let_set_slots(sc->curlet, args); + symbol_set_local_slot(sym, let_id(sc->curlet), args); + args = new_args; + } + closure_set_let(sc->x, sc->curlet); + let_set_slots(sc->curlet, reverse_slots(let_slots(sc->curlet))); + sc->x = sc->unused; + sc->code = T_Pair(body); + sc->w = sc->unused; + return(true); +} + +static bool op_let1(s7_scheme *sc) +{ + s7_pointer x, y, e; + uint64_t id; + /* building a list, then reusing it below as the let/slots seems stupid, but if we make the let first, and + * add slots, there are other problems. The let/slot ids (and symbol_set_local_slot) need to wait + * until the args are evaluated, if an arg invokes call/cc, the let on the stack needs to be copied + * including let_dox_code if it is used to save sc->code (there are 3 things that need to be protected), + * (we win currently because copy_stack copies the list), and make-circular-iterator if called twice (s7test) + * hangs -- I can't see why! Otherwise, the let/slots approach is slightly faster (less than 1% however). + */ + while (true) + { + sc->args = cons(sc, sc->value, sc->args); + if (is_pair(sc->code)) + { + x = cdar(sc->code); + if (has_fx(x)) + { +#if S7_DEBUGGING + s7_pointer old_args = sc->args; +#endif + sc->value = fx_call(sc, x); +#if S7_DEBUGGING + if (sc->args != old_args) + { + fprintf(stderr, "%s[%d]: %s %s\n", __func__, __LINE__, display(old_args), display(sc->args)); + gdb_break(); + } +#endif + } + else + { + check_stack_size(sc); + push_stack(sc, OP_LET1, sc->args, cdr(sc->code)); + sc->code = car(x); + return(false); + } + sc->code = cdr(sc->code); + } + else break; + } + x = proper_list_reverse_in_place(sc, sc->args); + sc->code = car(x); /* restore the original form */ + y = cdr(x); /* use sc->args as the new let */ + sc->temp8 = y; + set_curlet(sc, reuse_as_let(sc, x, sc->curlet)); + + if (is_symbol(car(sc->code))) + return(op_named_let_1(sc, y)); /* inner let here */ + + e = sc->curlet; + id = let_id(e); + if (is_pair(y)) + { + s7_pointer sym, args = cdr(y), sp; + x = car(sc->code); + sym = caar(x); + reuse_as_slot(y, sym, unchecked_car(y)); /* if car(y) is a multiple value, should we clear it? How did it get there? */ + symbol_set_local_slot(sym, id, y); + let_set_slots(e, y); + sp = y; + y = args; + + for (x = cdr(x); is_not_null(y); x = cdr(x)) + { + sym = caar(x); + args = cdr(args); + reuse_as_slot(y, sym, unchecked_car(y)); + symbol_set_local_slot(sym, id, y); + slot_set_next(sp, y); + sp = y; + y = args; + } + slot_set_next(sp, slot_end); + } + sc->code = T_Pair(cdr(sc->code)); + sc->temp8 = sc->unused; + return(true); +} + +static bool op_let(s7_scheme *sc) +{ + /* sc->code is everything after the let: (let ((a 1)) a) so sc->code is (((a 1)) a) */ + /* car can be either a list or a symbol ("named let") */ + bool named_let; + + sc->code = check_let(sc); + sc->value = sc->code; + named_let = is_symbol(car(sc->code)); + sc->code = (named_let) ? cadr(sc->code) : car(sc->code); + if (is_null(sc->code)) /* (let [name] () ...): no bindings, so skip that step */ + { + sc->code = sc->value; + sc->curlet = make_let(sc, sc->curlet); + if (named_let) /* see also below -- there are 3 cases */ + { + s7_pointer body = cddr(sc->code); + set_opt2_int(cdr(sc->code), 0); + sc->x = make_closure_unchecked(sc, sc->nil, body, T_CLOSURE, 0); + /* args = () in new closure, see NAMED_LET_NO_VARS above */ + /* if this is a safe closure, we can build its let in advance and name it (a thunk in this case) */ + set_funclet(closure_let(sc->x)); + funclet_set_function(closure_let(sc->x), car(sc->code)); + add_slot_checked(sc, sc->curlet, car(sc->code), sc->x); + sc->curlet = make_let(sc, sc->curlet); /* inner let */ + sc->code = T_Pair(body); + sc->x = sc->unused; + } + else sc->code = T_Pair(cdr(sc->code)); + return(true); + } + sc->args = sc->nil; + return(op_let1(sc)); +} + +static bool op_let_unchecked(s7_scheme *sc) /* not named, but has vars */ +{ + s7_pointer code = cadr(sc->code); + s7_pointer x = cdar(code); + sc->args = list_1(sc, cdr(sc->code)); + if (has_fx(x)) + sc->value = fx_call(sc, x); + else + { + push_stack(sc, OP_LET1, sc->args, cdr(code)); + sc->code = car(x); + return(false); /* goto EVAL */ + } + sc->code = cdr(code); + return(op_let1(sc)); +} + +static bool op_named_let(s7_scheme *sc) +{ + sc->args = sc->nil; + sc->value = cdr(sc->code); + sc->code = cadr(sc->value); + return(op_let1(sc)); +} + +static void op_named_let_no_vars(s7_scheme *sc) +{ + s7_pointer arg = cadr(sc->code); + sc->code = opt1_pair(sc->code); /* cdddr(sc->code) */ + sc->curlet = inline_make_let(sc, sc->curlet); + sc->args = make_closure_unchecked(sc, sc->nil, sc->code, T_CLOSURE, 0); /* sc->args is a temp here */ + add_slot_checked(sc, sc->curlet, arg, sc->args); + sc->curlet = make_let(sc, sc->curlet); /* inner let */ +} + +static void op_named_let_a(s7_scheme *sc) +{ + s7_pointer args = cdr(sc->code); + sc->code = cddr(args); + sc->args = fx_call(sc, cdr(opt1_pair(args))); /* cdaadr(args) */ + sc->curlet = make_let(sc, sc->curlet); + sc->w = list_1_unchecked(sc, car(opt1_pair(args))); /* caaadr(args), subsequent calls will need a normal list of pars in closure_args */ + sc->x = make_closure_unchecked(sc, sc->w, sc->code, T_CLOSURE, 1); /* picks up curlet (this is the funclet?) */ + add_slot(sc, sc->curlet, car(args), sc->x); /* the function */ + sc->curlet = inline_make_let_with_slot(sc, sc->curlet, car(sc->w), sc->args); /* inner let */ + closure_set_let(sc->x, sc->curlet); + sc->x = sc->unused; + sc->w = sc->unused; +} + +static void op_named_let_aa(s7_scheme *sc) +{ + s7_pointer args = cdr(sc->code); + sc->code = cddr(args); + sc->args = fx_call(sc, cdr(opt1_pair(args))); /* cdaadr(args) == init val of first par */ + sc->value = fx_call(sc, cdr(opt3_pair(args))); /* cdadadr = init val of second */ + sc->curlet = make_let(sc, sc->curlet); + sc->w = list_2_unchecked(sc, car(opt1_pair(args)), car(opt3_pair(args))); /* subsequent calls will need a normal list of pars in closure_args */ + sc->x = make_closure_unchecked(sc, sc->w, sc->code, T_CLOSURE, 2); /* picks up curlet (this is the funclet?) */ + add_slot(sc, sc->curlet, car(args), sc->x); /* the function */ + sc->curlet = inline_make_let_with_two_slots(sc, sc->curlet, car(sc->w), sc->args, cadr(sc->w), sc->value); /* inner let */ + closure_set_let(sc->x, sc->curlet); + sc->x = sc->unused; + sc->w = sc->unused; +} + +static bool op_named_let_na(s7_scheme *sc) +{ + sc->code = cdr(sc->code); + sc->args = sc->nil; + for (s7_pointer p = cadr(sc->code); is_pair(p); p = cdr(p)) + { + sc->args = cons(sc, sc->value = fx_call(sc, cdar(p)), sc->args); + p = cdr(p); + if (!is_pair(p)) break; + sc->args = cons_unchecked(sc, sc->value = fx_call(sc, cdar(p)), sc->args); + } + sc->args = proper_list_reverse_in_place(sc, sc->args); + return(op_named_let_1(sc, sc->args)); /* sc->code = (name vars . body), args = vals in decl order, op_named_let_1 handles inner let */ +} + +static void op_let_no_vars(s7_scheme *sc) +{ + sc->curlet = inline_make_let(sc, sc->curlet); + sc->code = T_Pair(cddr(sc->code)); /* ignore the () */ +} + +static void op_let_one_new(s7_scheme *sc) +{ + sc->code = cdr(sc->code); + /* check_stack_size(sc) -- needed if we're in an infinite loop -- maybe let it trigger "stack too big" instead */ + /* e.g. (let ((set! let*)) (let* set! ((x 1234) (y 1/2)) (let ((<1> (list 1 #f))) (set! (<1> 1) ...)))) */ + push_stack_no_args(sc, OP_LET_ONE_NEW_1, cdr(sc->code)); + sc->code = opt2_pair(sc->code); +} + +static void op_let_one_p_new(s7_scheme *sc) +{ + sc->code = cdr(sc->code); + push_stack_no_args(sc, OP_LET_ONE_P_NEW_1, cdr(sc->code)); + sc->code = T_Pair(opt2_pair(sc->code)); +} + +static void op_let_one_old(s7_scheme *sc) +{ + sc->code = cdr(sc->code); + push_stack_no_args_direct(sc, OP_LET_ONE_OLD_1); + sc->code = opt2_pair(sc->code); +} + +static void op_let_one_old_1(s7_scheme *sc) +{ + s7_pointer let = update_let_with_slot(sc, opt3_let(sc->code), sc->value); + let_set_outlet(let, sc->curlet); + set_curlet(sc, let); + sc->code = cdr(sc->code); +} + +static void op_let_one_p_old(s7_scheme *sc) +{ + sc->code = cdr(sc->code); + push_stack_no_args_direct(sc, OP_LET_ONE_P_OLD_1); + sc->code = T_Pair(opt2_pair(sc->code)); +} + +static void op_let_one_p_old_1(s7_scheme *sc) +{ + s7_pointer let = update_let_with_slot(sc, opt3_let(sc->code), sc->value); + let_set_outlet(let, sc->curlet); + set_curlet(sc, let); + sc->code = cadr(sc->code); +} + +static Inline void inline_op_let_a_new(s7_scheme *sc) /* three calls in eval, all get hits */ +{ + sc->code = cdr(sc->code); + sc->curlet = inline_make_let_with_slot(sc, sc->curlet, car(opt2_pair(sc->code)), fx_call(sc, cdr(opt2_pair(sc->code)))); +} + +static Inline void inline_op_let_a_old(s7_scheme *sc) /* tset(2) fb(0) cb(4) left(2) */ +{ + s7_pointer let; + sc->code = cdr(sc->code); + let = update_let_with_slot(sc, opt3_let(sc->code), fx_call(sc, cdr(opt2_pair(sc->code)))); + let_set_outlet(let, sc->curlet); + set_curlet(sc, let); +} + +static inline void op_let_a_old(s7_scheme *sc) {return(inline_op_let_a_old(sc));} + +static void op_let_a_a_new(s7_scheme *sc) +{ + s7_pointer binding; + sc->code = cdr(sc->code); + binding = opt2_pair(sc->code); + sc->curlet = inline_make_let_with_slot(sc, sc->curlet, car(binding), fx_call(sc, cdr(binding))); + sc->value = fx_call(sc, cdr(sc->code)); + free_cell(sc, sc->curlet); /* don't free let_slots here unless checked first (can be null after fx_call above?) */ + /* upon return, we continue, so sc->curlet should be ok */ +} + +static void op_let_a_a_old(s7_scheme *sc) /* these are not called as fx*, and restoring sc->curlet has noticeable cost (e.g. 8 in thash) */ +{ + s7_pointer let; + sc->code = cdr(sc->code); + let = update_let_with_slot(sc, opt3_let(sc->code), fx_call(sc, cdr(opt2_pair(sc->code)))); + let_set_outlet(let, sc->curlet); + set_curlet(sc, let); + sc->value = fx_call(sc, cdr(sc->code)); +} + +static void op_let_a_na_new(s7_scheme *sc) +{ + s7_pointer binding, p; + sc->code = cdr(sc->code); + binding = opt2_pair(sc->code); + sc->curlet = make_let_with_slot(sc, sc->curlet, car(binding), fx_call(sc, cdr(binding))); + for (p = cdr(sc->code); is_pair(cdr(p)); p = cdr(p)) fx_call(sc, p); + sc->value = fx_call(sc, p); + free_cell(sc, sc->curlet); /* see above */ +} + +/* this and others like it could easily be fx funcs, but check_let is called too late, so it's never seen as fxable */ +static void op_let_a_na_old(s7_scheme *sc) +{ + s7_pointer let, p; + sc->code = cdr(sc->code); + let = update_let_with_slot(sc, opt3_let(sc->code), fx_call(sc, cdr(opt2_pair(sc->code)))); + let_set_outlet(let, sc->curlet); + set_curlet(sc, let); + for (p = cdr(sc->code); is_pair(cdr(p)); p = cdr(p)) fx_call(sc, p); + sc->value = fx_call(sc, p); +} + +static inline void op_let_opassq(s7_scheme *sc) +{ + s7_pointer in_val, lst; + sc->code = cdr(sc->code); + in_val = lookup(sc, opt2_sym(sc->code)); /* cadadr(caar(sc->code)); */ + lst = lookup(sc, opt3_sym(cdr(sc->code))); + if (is_pair(lst)) + sc->value = s7_assq(sc, in_val, lst); + else sc->value = (is_null(lst)) ? sc->F : g_assq(sc, set_plist_2(sc, in_val, lst)); +} + +static inline void op_let_opassq_old(s7_scheme *sc) +{ + s7_pointer let; + op_let_opassq(sc); + let = update_let_with_slot(sc, opt3_let(sc->code), sc->value); + let_set_outlet(let, sc->curlet); + set_curlet(sc, let); + sc->code = T_Pair(cdr(sc->code)); +} + +static inline void op_let_opassq_new(s7_scheme *sc) +{ + op_let_opassq(sc); + sc->curlet = inline_make_let_with_slot(sc, sc->curlet, opt1_sym(sc->code), sc->value); + sc->code = T_Pair(cdr(sc->code)); +} + +static Inline void inline_op_let_na_new(s7_scheme *sc) /* called once in eval, case gsl lg mock */ +{ + s7_pointer let, sp = NULL; + new_cell(sc, let, T_LET | T_SAFE_PROCEDURE); + let_set_id(let, sc->let_number + 1); + let_set_slots(let, slot_end); + let_set_outlet(let, sc->curlet); + sc->args = let; + for (s7_pointer p = cadr(sc->code); is_pair(p); p = cdr(p)) + { + s7_pointer arg = cdar(p); + sc->value = fx_call(sc, arg); + if (!sp) + { + add_slot(sc, let, caar(p), sc->value); + sp = let_slots(let); + } + else sp = inline_add_slot_at_end(sc, let_id(let), sp, caar(p), sc->value); + } + sc->let_number++; + set_curlet(sc, let); + sc->code = T_Pair(cddr(sc->code)); +} + +static void op_let_na_old(s7_scheme *sc) +{ + s7_pointer let = opt3_let(cdr(sc->code)); + s7_pointer slot = let_slots(let); + uint64_t id = ++sc->let_number; + sc->args = let; + let_set_id(let, id); + let_set_outlet(let, sc->curlet); + for (s7_pointer p = cadr(sc->code); is_pair(p); p = cdr(p), slot = next_slot(slot)) + { + /* GC protected because it's a semipermanent let? or perhaps use sc->args? */ + slot_set_value(slot, fx_call(sc, cdar(p))); + symbol_set_local_slot_unincremented(slot_symbol(slot), id, slot); + } + set_curlet(sc, let); + sc->code = T_Pair(cddr(sc->code)); +} + +static void op_let_2a_new(s7_scheme *sc) /* 2 vars, 1 expr in body */ +{ + s7_pointer code = cdr(sc->code); + s7_pointer a1 = opt1_pair(code); /* caar(code) */ + s7_pointer a2 = opt2_pair(code); /* cadar(code) */ + sc->curlet = inline_make_let_with_two_slots(sc, sc->curlet, car(a1), fx_call(sc, cdr(a1)), car(a2), fx_call(sc, cdr(a2))); + sc->code = cadr(code); +} + +static inline void op_let_2a_old(s7_scheme *sc) /* 2 vars, 1 expr in body */ +{ + s7_pointer code = cdr(sc->code); + s7_pointer let = update_let_with_two_slots(sc, opt3_let(code), fx_call(sc, cdr(opt1_pair(code))), fx_call(sc, cdr(opt2_pair(code)))); + let_set_outlet(let, sc->curlet); + set_curlet(sc, let); + sc->code = cadr(code); +} + +static void op_let_3a_new(s7_scheme *sc) /* 3 vars, 1 expr in body */ +{ + s7_pointer code = cdr(sc->code); + s7_pointer a1 = caar(code); + s7_pointer a2 = opt1_pair(code); /* cadar */ + s7_pointer a3 = opt2_pair(code); /* caddar */ + gc_protect_via_stack(sc, fx_call(sc, cdr(a1))); /* fx_call might be fx_car_t (etc) so it needs to precede the new let */ + set_stack_protected2(sc, fx_call(sc, cdr(a2))); + sc->curlet = inline_make_let_with_two_slots(sc, sc->curlet, car(a2), stack_protected2(sc), car(a3), fx_call(sc, cdr(a3))); + add_slot(sc, sc->curlet, car(a1), stack_protected1(sc)); + unstack(sc); + sc->code = cadr(code); +} + +static void op_let_3a_old(s7_scheme *sc) /* 3 vars, 1 expr in body */ +{ + s7_pointer code = cdr(sc->code); + s7_pointer let = update_let_with_three_slots(sc, opt3_let(code), fx_call(sc, cdr(caar(code))), fx_call(sc, cdr(opt1_pair(code))), fx_call(sc, cdr(opt2_pair(code)))); + let_set_outlet(let, sc->curlet); + set_curlet(sc, let); + sc->code = cadr(code); +} + + +/* -------------------------------- let* -------------------------------- */ +static bool check_let_star(s7_scheme *sc) +{ + s7_pointer vars, form = sc->code, code = cdr(sc->code); + bool named_let, fxable = true, shadowing = false; + + if (!is_pair(code)) /* (let* . 1) */ + syntax_error_nr(sc, "let* variable list is messed up: ~A", 35, form); + if (!is_pair(cdr(code))) /* (let* ()) */ + syntax_error_nr(sc, "let* has no body: ~A", 20, form); + + named_let = (is_symbol(car(code))); + + if (named_let) + { + if (!is_list(cadr(code))) /* (let* hi #t) */ + syntax_error_nr(sc, "let* variable list is messed up: ~A", 35, form); + if (!is_pair(cddr(code))) /* (let* hi () . =>) or (let* hi () ) */ + { + if (is_null(cddr(code))) + syntax_error_nr(sc, "named let* has no body: ~A", 26, form); + syntax_error_nr(sc, "named let* stray dot? ~A", 24, form); + } + if (is_constant_symbol(sc, car(code))) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_3(sc, cant_bind_immutable_string, sc->let_star_symbol, form)); + set_local(car(code)); + } + else + if (!is_list(car(code))) /* (let* x ... ) */ + syntax_error_nr(sc, "let* variable declaration value is missing: ~A", 46, form); + + clear_symbol_list(sc); + for (vars = ((named_let) ? cadr(code) : car(code)); is_pair(vars); vars = cdr(vars)) + { + s7_pointer var, var_and_val = car(vars); + if (!is_pair(var_and_val)) /* (let* (3) ... */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "let* variable list, ~A, is messed up in ~A", 42), + var_and_val, object_to_truncated_string(sc, form, 80))); + + if (!(is_pair(cdr(var_and_val)))) /* (let* ((x . 1))...) */ + { + if (is_null(cdr(var_and_val))) + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "let* variable declaration, but no value?: ~A in ~A", 50), + var_and_val, object_to_truncated_string(sc, form, 80))); + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "let* variable declaration is not a proper list: ~A in ~A", 56), + var_and_val, object_to_truncated_string(sc, form, 80))); + } + if (!is_null(cddr(var_and_val))) /* (let* ((c 1 2)) ...) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "let* variable declaration has more than one value?: ~A in ~A", 60), + var_and_val, object_to_truncated_string(sc, form, 80))); + + var = car(var_and_val); + if (!(is_symbol(var))) /* (let* ((3 1)) 1) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "bad variable, ~S, in let* (it is not a symbol): ~A", 50), + var, object_to_truncated_string(sc, form, 80))); + + if (is_constant_symbol(sc, var)) /* (let* ((pi 3)) ...) */ + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_3(sc, cant_bind_immutable_string, sc->let_star_symbol, var_and_val)); + + if ((named_let) && (symbol_is_in_arg_list(var, cdr(vars)))) /* (let* loop ((a 1) (a 2)) ...) -- added 2-Dec-19 */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "named let* parameter, ~A, is used twice in the parameter list in ~A", 67), + var, object_to_truncated_string(sc, form, 80))); + /* currently (let* ((a 1) (a (+ a 1))) a) is 2, not an error */ + + if (symbol_is_in_list(sc, var)) shadowing = true; + add_symbol_to_list(sc, var); + set_local(var); + } + if (!is_null(vars)) + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "let* variable list is not a proper list: ~A in ~A", 49), + vars, object_to_truncated_string(sc, form, 80))); + + if (!s7_is_proper_list(sc, cdr(code))) + syntax_error_nr(sc, "stray dot in let* body: ~S", 26, cdr(code)); + + if (shadowing) + fxable = false; + else + for (vars = (named_let) ? cadr(code) : car(code); is_pair(vars); vars = cdr(vars)) + if (is_fxable(sc, cadar(vars))) + set_fx_direct(cdar(vars), fx_choose(sc, cdar(vars), sc->curlet, let_star_symbol_is_safe)); + else fxable = false; + + if (named_let) + { + if (is_null(cadr(code))) + { + pair_set_syntax_op(form, OP_NAMED_LET_NO_VARS); + set_opt1_pair(form, cdddr(form)); + } + else + { + pair_set_syntax_op(form, OP_NAMED_LET_STAR); + set_opt2_con(code, cadr(caadr(code))); + } + sc->value = cdr(code); + if (is_null(car(sc->value))) /* (let* name () ... */ + { + s7_pointer let_sym = car(code); + sc->curlet = make_let(sc, sc->curlet); + sc->code = T_Pair(cdr(sc->value)); + add_slot_checked(sc, sc->curlet, let_sym, make_closure_unchecked(sc, sc->nil, sc->code, T_CLOSURE_STAR, 0)); + sc->curlet = make_let(sc, sc->curlet); /* inner let */ + return(false); + } + sc->curlet = make_let(sc, sc->curlet); + push_stack(sc, OP_LET_STAR1, code, cadr(code)); + sc->code = cadr(caadr(code)); /* first var val */ + return(true); + } + if (is_null(car(code))) + { + pair_set_syntax_op(form, OP_LET_NO_VARS); /* (let* () ...) */ + + sc->curlet = make_let(sc, sc->curlet); + sc->code = T_Pair(cdr(code)); + return(false); + } + else + if (is_null(cdar(code))) + { + check_let_one_var(sc, form, car(code)); /* (let* ((var...))...) -> (let ((var...))...) */ + if (optimize_op(form) >= OP_LET_NA_OLD) + { + if ((!in_heap(form)) && + (body_is_safe(sc, sc->unused, cdr(code), true) >= SAFE_BODY)) + set_opt3_let(code, make_semipermanent_let(sc, car(code))); + else + { + set_optimize_op(form, optimize_op(form) + 1); /* *_old -> *_new */ + set_opt3_let(code, sc->nil); + }}} + else /* multiple variables */ + { + if (fxable) + { + pair_set_syntax_op(form, OP_LET_STAR_NA); + if ((is_null(cddr(code))) && + (is_fxable(sc, cadr(code)))) + { + fx_annotate_arg(sc, cdr(code), sc->curlet); + pair_set_syntax_op(form, OP_LET_STAR_NA_A); + }} + else pair_set_syntax_op(form, OP_LET_STAR2); + set_opt2_con(code, cadaar(code)); + } + push_stack(sc, ((intptr_t)((shadowing) ? OP_LET_STAR_SHADOWED : OP_LET_STAR1)), code, car(code)); + /* args is the let body, saved for later, code is the list of vars+initial-values */ + sc->code = cadr(caar(code)); + /* caar(code) = first var/val pair, we've checked that all these guys are legit, so cadr of that is the value */ + return(true); +} + +static bool op_let_star_shadowed(s7_scheme *sc) +{ + while (true) + { + sc->curlet = inline_make_let_with_slot(sc, sc->curlet, caar(sc->code), sc->value); + sc->code = cdr(sc->code); + if (is_pair(sc->code)) + { + s7_pointer x = cdar(sc->code); + if (has_fx(x)) + sc->value = fx_call(sc, x); + else + { + push_stack_direct(sc, OP_LET_STAR_SHADOWED); + sc->code = car(x); + return(true); + }} + else break; + } + sc->code = cdr(sc->args); /* original sc->code set in push_stack above */ + return(false); +} + +static inline bool op_let_star1(s7_scheme *sc) +{ + uint64_t let_counter = S7_INT64_MAX; + s7_pointer sp = NULL; + while (true) + { + if (let_counter == sc->capture_let_counter) + { + if (sp == NULL) + { + add_slot_checked(sc, sc->curlet, caar(sc->code), sc->value); + sp = let_slots(sc->curlet); + } + else sp = inline_add_slot_at_end(sc, let_id(sc->curlet), sp, caar(sc->code), sc->value); + } + else + { + sc->curlet = inline_make_let_with_slot(sc, sc->curlet, caar(sc->code), sc->value); + sp = let_slots(sc->curlet); + let_counter = sc->capture_let_counter; + } + sc->code = cdr(sc->code); + if (is_pair(sc->code)) + { + s7_pointer x = cdar(sc->code); + if (has_fx(x)) + sc->value = fx_call(sc, x); + else + { + push_stack_direct(sc, OP_LET_STAR1); + sc->code = car(x); + return(true); + }} + else break; + } + sc->code = sc->args; /* original sc->code set in push_stack above */ + if (is_symbol(car(sc->code))) + { + s7_pointer name = car(sc->code), body = cddr(sc->code), args = cadr(sc->code); + /* now we need to declare the new function (in the outer let) -- must delay this because init might reference same-name outer func */ + /* but the let name might be shadowed by a variable: (let* x ((x 1))...) so the name's symbol_id can be incorrect */ + if (symbol_id(name) > let_id(let_outlet(sc->curlet))) + { + s7_int cur_id = symbol_id(name); + s7_pointer cur_slot = local_slot(name); + symbol_set_id_unchecked(name, let_id(let_outlet(sc->curlet))); + add_slot_checked(sc, let_outlet(sc->curlet), name, + make_closure_unchecked(sc, args, body, T_CLOSURE_STAR, (is_null(args)) ? 0 : CLOSURE_ARITY_NOT_SET)); + symbol_set_id_unchecked(name, cur_id); + set_local_slot(name, cur_slot); + } + else add_slot_checked(sc, let_outlet(sc->curlet), name, + make_closure_unchecked(sc, args, body, T_CLOSURE_STAR, (is_null(args)) ? 0 : CLOSURE_ARITY_NOT_SET)); + + sc->code = body; + } + else sc->code = T_Pair(cdr(sc->code)); + return(false); +} + +static void op_let_star_na(s7_scheme *sc) +{ + /* fx safe does not mean we can dispense with the inner lets (curlet is safe for example) */ + s7_pointer sp = NULL; + uint64_t let_counter = S7_INT64_MAX; + sc->code = cdr(sc->code); + for (s7_pointer p = car(sc->code); is_pair(p); p = cdr(p)) + { + s7_pointer val = fx_call(sc, cdar(p)); /* eval in outer let */ + if (let_counter == sc->capture_let_counter) + { + if (!sp) + { + add_slot_checked(sc, sc->curlet, caar(p), val); + sp = let_slots(sc->curlet); + } + else sp = inline_add_slot_at_end(sc, let_id(sc->curlet), sp, caar(p), val); + } + else + { + sc->curlet = inline_make_let_with_slot(sc, sc->curlet, caar(p), val); + sp = let_slots(sc->curlet); + let_counter = sc->capture_let_counter; + }} + sc->code = T_Pair(cdr(sc->code)); +} + +static void op_let_star_na_a(s7_scheme *sc) +{ + s7_pointer sp = NULL; + uint64_t let_counter = S7_INT64_MAX; + sc->code = cdr(sc->code); + for (s7_pointer p = car(sc->code); is_pair(p); p = cdr(p)) + { + s7_pointer val = fx_call(sc, cdar(p)); + if (let_counter == sc->capture_let_counter) + { + if (!sp) + { + add_slot_checked(sc, sc->curlet, caar(p), val); + sp = let_slots(sc->curlet); + } + else sp = inline_add_slot_at_end(sc, let_id(sc->curlet), sp, caar(p), val); + } + else + { + sc->curlet = inline_make_let_with_slot(sc, sc->curlet, caar(p), val); + sp = let_slots(sc->curlet); + let_counter = sc->capture_let_counter; + }} + sc->value = fx_call(sc, cdr(sc->code)); +} + +static void op_named_let_star(s7_scheme *sc) +{ + s7_pointer code = cdr(sc->code); /* code: (name vars ...) */ + sc->curlet = make_let(sc, sc->curlet); + push_stack(sc, OP_LET_STAR1, code, cadr(code)); + sc->code = opt2_con(code); +} + +static void op_let_star2(s7_scheme *sc) +{ + s7_pointer code = cdr(sc->code); + push_stack(sc, OP_LET_STAR1, code, car(code)); + sc->code = opt2_con(code); +} + + +/* -------------------------------- letrec, letrec* -------------------------------- */ +static void check_letrec(s7_scheme *sc, bool letrec) +{ + s7_pointer x, code = cdr(sc->code); + s7_pointer caller = (letrec) ? sc->letrec_symbol : sc->letrec_star_symbol; + + if ((!is_pair(code)) || /* (letrec . 1) */ + (!is_list(car(code)))) /* (letrec 1 ...) */ + syntax_error_with_caller_nr(sc, "~A: variable list is messed up: ~A", 34, caller, sc->code); + + if (!is_pair(cdr(code))) /* (letrec ()) */ + syntax_error_with_caller_nr(sc, "~A has no body: ~A", 18, caller, sc->code); + + clear_symbol_list(sc); + for (x = car(code); is_not_null(x); x = cdr(x)) + { + s7_pointer y, carx; + if (!is_pair(x)) /* (letrec ((a 1) . 2) ...) */ + syntax_error_with_caller_nr(sc, "~A: improper list of variables? ~A", 34, caller, sc->code); + + carx = car(x); + if (!is_pair(carx)) /* (letrec (1 2) #t) */ + syntax_error_with_caller_nr(sc, "~A: bad variable ~S (should be a pair (name value))", 51, caller, carx); + if (!(is_symbol(car(carx)))) + syntax_error_with_caller_nr(sc, "~A: bad variable ~S (it is not a symbol)", 40, caller, car(carx)); + + y = car(carx); + if (is_constant_symbol(sc, y)) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_3(sc, cant_bind_immutable_string, sc->letrec_symbol, x)); + + if (!is_pair(cdr(carx))) /* (letrec ((x . 1))...) */ + { + if (is_null(cdr(carx))) /* (letrec ((x)) x) -- perhaps this is legal? */ + syntax_error_with_caller_nr(sc, "~A: variable declaration has no value?: ~A", 42, caller, carx); + syntax_error_with_caller_nr(sc, "~A: variable declaration is not a proper list?: ~A", 50, caller, carx); + } + if (is_not_null(cddr(carx))) /* (letrec ((x 1 2 3)) ...) */ + syntax_error_with_caller_nr(sc, "~A: variable declaration has more than one value?: ~A", 53, caller, carx); + + /* check for name collisions -- this is needed in letrec* else which of the two legit values does our "rec" refer to, so to speak */ + if (symbol_is_in_list(sc, y)) + syntax_error_with_caller_nr(sc, "~A: duplicate identifier: ~A", 28, caller, y); + add_symbol_to_list(sc, y); + set_local(y); + } + + if (!s7_is_proper_list(sc, cdr(code))) + syntax_error_with_caller_nr(sc, "stray dot in ~A body: ~S", 24, caller, cdr(code)); + + for (x = car(code); is_pair(x); x = cdr(x)) + if (is_fxable(sc, cadar(x))) + set_fx_direct(cdar(x), fx_choose(sc, cdar(x), sc->curlet, let_symbol_is_safe_or_listed)); + + pair_set_syntax_op(sc->code, (letrec) ? OP_LETREC_UNCHECKED : OP_LETREC_STAR_UNCHECKED); +} + +static s7_pointer make_funclet(s7_scheme *sc, s7_pointer new_func, s7_pointer func_name, s7_pointer outer_let); + +static void letrec_setup_closures(s7_scheme *sc) +{ + for (s7_pointer slot = let_slots(sc->curlet); tis_slot(slot); slot = next_slot(slot)) + if (is_closure(slot_value(slot))) + { + s7_pointer func = slot_value(slot); + if ((!is_safe_closure(func)) || + (!is_optimized(car(closure_body(func))))) + optimize_lambda(sc, true, slot_symbol(slot), closure_args(func), closure_body(func)); + if (is_safe_closure_body(closure_body(func))) + { + set_safe_closure(func); + if (is_very_safe_closure_body(closure_body(func))) + set_very_safe_closure(func); + } + make_funclet(sc, func, slot_symbol(slot), closure_let(func)); + } +} + +static void op_letrec2(s7_scheme *sc) +{ + for (s7_pointer slot = let_slots(sc->curlet); tis_slot(slot); slot = next_slot(slot)) + if (is_checked_slot(slot)) + slot_set_value(slot, slot_pending_value(slot)); + letrec_setup_closures(sc); +} + +static bool op_letrec_unchecked(s7_scheme *sc) +{ + s7_pointer code = cdr(sc->code); + /* get all local vars and set to # + * get parallel list of values + * eval each member of values list with let still full of #'s + * assign each value to its variable + * eval body + * which means that (letrec ((x x)) x) is not an error! + * but this assumes the environment is not changed by evaluating the exprs? + * (letrec ((a (define b 1))) b) -- if let, the define takes place in the calling let, not the current let + * (letrec ((f1 (lambda (x) (f2 (* 2 x))))) (define (f2 y) (- y 1)) (f1 3)) -> 5 (Guile says unbound f2) + * I think I need to check here that slot_pending_value is set (using the is_checked bit below): + * (letrec ((i (begin (define xyz 37) 0))) (curlet)): (inlet 'i 0 'xyz 37) -- is this correct? + */ + sc->curlet = make_let(sc, sc->curlet); + if (is_pair(car(code))) + { + s7_pointer slot; + for (s7_pointer x = car(code); is_not_null(x); x = cdr(x)) + { + slot = add_slot_checked(sc, sc->curlet, caar(x), sc->undefined); + slot_set_pending_value(slot, sc->undefined); + slot_set_expression(slot, cdar(x)); + set_checked_slot(slot); + } + for (slot = let_slots(sc->curlet); tis_slot(slot) && (has_fx(slot_expression(slot))); slot = next_slot(slot)) + slot_set_pending_value(slot, fx_call(sc, slot_expression(slot))); + if (tis_slot(slot)) + { + push_stack(sc, OP_LETREC1, slot, code); + sc->code = car(slot_expression(slot)); + return(true); + } + op_letrec2(sc); + } + sc->code = T_Pair(cdr(code)); + return(false); +} + +static bool op_letrec1(s7_scheme *sc) +{ + s7_pointer slot; + slot_set_pending_value(sc->args, sc->value); + for (slot = next_slot(sc->args); tis_slot(slot) && (has_fx(slot_expression(slot))); slot = next_slot(slot)) + slot_set_pending_value(slot, fx_call(sc, slot_expression(slot))); + if (tis_slot(slot)) + { + push_stack(sc, OP_LETREC1, slot, sc->code); + sc->code = car(slot_expression(slot)); + return(true); + } + op_letrec2(sc); + sc->code = T_Pair(cdr(sc->code)); + return(false); +} + + +static bool op_letrec_star_unchecked(s7_scheme *sc) +{ + s7_pointer code = cdr(sc->code); + /* get all local vars and set to # + * eval each member of values list and assign immediately, as in let* + * eval body + */ + sc->curlet = make_let(sc, sc->curlet); + if (is_pair(car(code))) + { + s7_pointer slot; + for (s7_pointer x = car(code); is_not_null(x); x = cdr(x)) + { + slot = add_slot_checked(sc, sc->curlet, caar(x), sc->undefined); + slot_set_expression(slot, cdar(x)); + } + let_set_slots(sc->curlet, reverse_slots(let_slots(sc->curlet))); + + for (slot = let_slots(sc->curlet); tis_slot(slot) && (has_fx(slot_expression(slot))); slot = next_slot(slot)) + slot_set_value(slot, fx_call(sc, slot_expression(slot))); + if (tis_slot(slot)) + { + push_stack(sc, OP_LETREC_STAR1, slot, code); + sc->code = car(slot_expression(slot)); + return(true); + }} + sc->code = T_Pair(cdr(code)); + return(false); +} + +static bool op_letrec_star1(s7_scheme *sc) +{ + s7_pointer slot = sc->args; + slot_set_value(slot, sc->value); + + for (slot = next_slot(slot); tis_slot(slot) && (has_fx(slot_expression(slot))); slot = next_slot(slot)) + slot_set_value(slot, fx_call(sc, slot_expression(slot))); + if (tis_slot(slot)) + { + push_stack(sc, OP_LETREC_STAR1, slot, sc->code); + sc->code = car(slot_expression(slot)); + return(true); + } + letrec_setup_closures(sc); + sc->code = T_Pair(cdr(sc->code)); + return(false); +} + + +/* -------------------------------- let-temporarily -------------------------------- */ +static void check_let_temporarily(s7_scheme *sc) +{ + s7_pointer x, form = sc->code, code = cdr(sc->code); + bool all_fx, all_s7; + + if ((!is_pair(code)) || /* (let-temporarily . 1) */ + (!is_list(car(code)))) /* (let-temporarily 1 ...) */ + syntax_error_nr(sc, "let-temporarily: variable list is messed up: ~A", 47, form); + /* cdr(code) = body can be nil */ + + all_fx = is_pair(car(code)); + all_s7 = all_fx; + + for (x = car(code); is_not_null(x); x = cdr(x)) + { + s7_pointer carx, caarx; + if (!is_pair(x)) /* (let-temporarily ((a 1) . 2) ...) */ + syntax_error_nr(sc, "let-temporarily: improper list of variables? ~A", 47, form); + + carx = car(x); + if (!is_pair(carx)) /* (let-temporarily (1 2) #t) */ + syntax_error_nr(sc, "let-temporarily: bad variable ~S (it should be a pair (name value))", 67, carx); + + caarx = car(carx); + if (is_symbol(caarx)) + { + if (is_constant_symbol(sc, caarx)) /* (let-temporarily ((pi 3)) ...) */ + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_3(sc, cant_bind_immutable_string, sc->let_temporarily_symbol, x)); + } + else + if (!is_pair(caarx)) /* (let-temporarily ((1 2)) ...) */ + syntax_error_nr(sc, "let-temporarily: bad variable ~S (it should be a symbol or a pair)", 66, caarx); + + if (!is_pair(cdr(carx))) /* (let-temporarily ((x . 1))...) */ + syntax_error_nr(sc, "let-temporarily: variable declaration value is messed up: ~S", 60, carx); + + if (is_not_null(cddr(carx))) /* (let-temporarily ((x 1 2 3)) ...) */ + syntax_error_nr(sc, "let-temporarily: variable declaration has more than one value?: ~A", 66, carx); + + if ((all_fx) && + ((!is_symbol(caarx)) || (!is_fxable(sc, cadr(carx))))) /* if all_fx, each var is (symbol fxable-expr) */ + all_fx = false; + if ((all_s7) && + ((!is_pair(caarx)) || (car(caarx) != sc->s7_starlet_symbol) || + (!is_quoted_symbol(cadr(caarx))) || (is_keyword(cadr(cadr(caarx)))) || + (!is_fxable(sc, cadr(carx))))) + all_s7 = false; + } + if (!s7_is_proper_list(sc, cdr(code))) + syntax_error_nr(sc, "stray dot in let-temporarily body: ~S", 37, cdr(code)); + + if ((all_fx) || (all_s7)) + { + pair_set_syntax_op(form, (all_fx) ? ((is_null(cdar(code))) ? OP_LET_TEMP_A : OP_LET_TEMP_NA) : OP_LET_TEMP_S7); + for (x = car(code); is_pair(x); x = cdr(x)) + fx_annotate_arg(sc, cdar(x), sc->curlet); + + if ((optimize_op(form) == OP_LET_TEMP_A) && (is_pair(cdr(code))) && (is_null(cddr(code))) && (is_fxable(sc, cadr(code)))) + { + fx_annotate_arg(sc, cdr(code), sc->curlet); + pair_set_syntax_op(form, OP_LET_TEMP_A_A); + } + else + if (all_s7) /* not OP_LET_TEMP_NA */ + { + s7_pointer var = caar(code); + if ((is_fxable(sc, cadr(var))) && /* code: ((((*s7* 'openlets) fxable-expr)) ...) */ + (is_null(cdar(code)))) + { + if ((is_quoted_symbol(cadar(var))) && + (s7_starlet_symbol(cadr(cadar(var))) == SL_OPENLETS)) /* (cadr(cadar(var)) == make_symbol_with_strlen(sc, "openlets"))) */ + { + pair_set_syntax_op(form, OP_LET_TEMP_S7_DIRECT); + set_opt1_pair(form, cdr(var)); + }}} + + if ((is_fx_treeable(code)) && (tis_slot(let_slots(sc->curlet)))) {fx_curlet_tree(sc, code); fx_curlet_tree_in(sc, code);} + } + else + { + pair_set_syntax_op(form, OP_LET_TEMP_UNCHECKED); + if ((is_pair(car(code))) && (is_null(cdar(code))) && (is_pair(caar(code)))) + { + s7_pointer var = caar(code); + s7_pointer val = cadr(var); + var = car(var); + if ((is_pair(var)) && (car(var) == sc->setter_symbol) && (is_pair(cdr(var))) && (is_pair(cddr(var))) && (val == sc->F)) + { + /* (let-temporarily (((setter (slot-symbol cp) (slot-env cp)) #f)) ...) reactive.scm */ + optimize_expression(sc, cadr(var), 0, sc->curlet, false); + optimize_expression(sc, caddr(var), 0, sc->curlet, false); + if ((is_fxable(sc, cadr(var))) && (is_fxable(sc, caddr(var)))) + { + fx_annotate_args(sc, cdr(var), sc->curlet); + pair_set_syntax_op(form, OP_LET_TEMP_SETTER); + }}}} +} + +static void op_let_temp_unchecked(s7_scheme *sc) +{ + sc->code = cdr(sc->code); /* step past let-temporarily */ + sc->args = list_4(sc, car(sc->code), sc->nil, sc->nil, sc->nil); + push_stack_direct(sc, OP_GC_PROTECT); + /* sc->args: varlist, settees, old_values, new_values */ +} + +static void op_let_temp_init1_1(s7_scheme *sc) +{ + if ((is_symbol(sc->value)) && (is_symbol_from_symbol(sc->value))) /* (let-temporarily (((symbol ...))) ..) */ + { + clear_symbol_from_symbol(sc->value); + if (is_immutable_symbol(sc->value)) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_3(sc, cant_bind_immutable_string, sc->let_temporarily_symbol, sc->value)); + sc->value = s7_symbol_value(sc, sc->value); + } + set_caddr(sc->args, cons(sc, sc->value, caddr(sc->args))); +} + +static bool op_let_temp_init1(s7_scheme *sc) +{ + while (is_pair(car(sc->args))) + { + /* eval car, add result to old-vals list, if any vars undefined, error */ + s7_pointer binding = caar(sc->args); + s7_pointer settee = car(binding); + s7_pointer new_value = cadr(binding); + cadr(sc->args) = cons(sc, settee, cadr(sc->args)); + binding = cdddr(sc->args); + set_car(binding, cons_unchecked(sc, new_value, car(binding))); + car(sc->args) = cdar(sc->args); + if (is_symbol(settee)) /* get initial values */ + set_caddr(sc->args, cons_unchecked(sc, lookup_checked(sc, settee), caddr(sc->args))); + else + { + if (is_pair(settee)) + { + push_stack_direct(sc, OP_LET_TEMP_INIT1); + sc->code = settee; + return(true); + } + set_caddr(sc->args, cons_unchecked(sc, new_value, caddr(sc->args))); + }} + car(sc->args) = cadr(sc->args); + return(false); +} + +typedef enum {goto_start, goto_begin, fall_through, goto_do_end_clauses, goto_safe_do_end_clauses, + goto_eval, goto_apply_lambda, goto_do_end, goto_top_no_pop, goto_apply, + goto_eval_args, goto_eval_args_top, goto_do_unchecked, goto_pop_read_list, + goto_read_tok, goto_feed_to, goto_set_unchecked, goto_unopt} goto_t; + +static goto_t op_let_temp_init2(s7_scheme *sc) +{ + /* now eval set car new-val, cadr=settees, cadddr=new_values */ + while (is_pair(car(sc->args))) + { + s7_pointer settee = caar(sc->args), slot, p = cdddr(sc->args); + s7_pointer new_value = caar(p); + set_car(p, cdar(p)); + car(sc->args) = cdar(sc->args); + if ((!is_symbol(settee)) || (is_pair(new_value))) + { + if (is_symbol(settee)) + { + push_stack_direct(sc, OP_LET_TEMP_INIT2); /* (let-temporarily (((*s7* 'print-length) 32)) ...) */ + push_stack_no_args(sc, OP_SET_FROM_LET_TEMP, settee); + sc->code = new_value; + return(goto_eval); + } + sc->code = set_plist_3(sc, sc->set_symbol, settee, new_value); + push_stack_direct(sc, OP_LET_TEMP_INIT2); + return(goto_set_unchecked); + } + slot = s7_slot(sc, settee); + if (!is_slot(slot)) unbound_variable_error_nr(sc, settee); + if (is_immutable_slot(slot)) immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->let_temporarily_symbol, settee)); + if (is_symbol(new_value)) new_value = lookup_checked(sc, new_value); + slot_set_value(slot, (slot_has_setter(slot)) ? call_setter(sc, slot, new_value) : new_value); + } + car(sc->args) = cadr(sc->args); + /* pop_stack(sc); */ /* this clobbers sc->args! 7-May-22 */ + unstack(sc); /* pop_stack_no_args(sc) in effect */ + sc->code = cdr(stack_end_code(sc)); + if (is_pair(sc->code)) + { + push_stack_direct(sc, OP_LET_TEMP_DONE); + return(goto_begin); + } + sc->value = sc->nil; /* so (let-temporarily ( () like begin I guess */ + return(fall_through); +} + +static bool op_let_temp_done1(s7_scheme *sc) +{ + while (is_pair(car(sc->args))) + { + s7_pointer settee = caar(sc->args), p = cddr(sc->args); + sc->value = caar(p); + set_car(p, cdar(p)); + car(sc->args) = cdar(sc->args); + + if ((is_pair(settee)) && (car(settee) == sc->s7_starlet_symbol) && /* (let-temporarily (((*s7* (symbol "print-length")) 43))...) */ + ((is_symbol_and_keyword(cadr(settee))) || + ((is_pair(cadr(settee))) && (caadr(settee) == sc->quote_symbol) && (is_symbol(cadadr(settee)))))) + { + s7_pointer sym = cadr(settee); + if (is_pair(sym)) sym = cadr(sym); + s7_starlet_set_1(sc, T_Sym(sym), sc->value); + } + else + { + s7_pointer slot; + if (!is_symbol(settee)) + { + push_stack_direct(sc, OP_LET_TEMP_DONE1); /* save args and (pending) body value==sc->code */ + if ((is_pair(sc->value)) || (is_symbol(sc->value))) + sc->code = set_plist_3(sc, sc->set_symbol, settee, set_plist_2(sc, sc->quote_symbol, sc->value)); + else sc->code = set_plist_3(sc, sc->set_symbol, settee, sc->value); + return(false); /* goto set_unchecked */ + } + slot = s7_slot(sc, settee); + if (is_immutable_slot(slot)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->let_temporarily_symbol, settee)); + if (slot_has_setter(slot)) /* maybe setter changed in let-temp body? else setter has already checked the init value */ + slot_set_value(slot, call_setter(sc, slot, sc->value)); + else slot_set_value(slot, sc->value); + }} + pop_stack(sc); /* not unstack */ + sc->value = sc->code; + if (is_multiple_value(sc->value)) + sc->value = splice_in_values(sc, multiple_value(sc->value)); + return(true); /* goto start */ +} + +static bool *s7_starlet_immutable_field = NULL; + +static bool op_let_temp_s7(s7_scheme *sc) /* all entries are of the form ((*s7* 'field) fx-able-value) */ +{ + s7_pointer p, code = cdr(sc->code); /* don't use sc->code here -- it can be changed */ + s7_pointer *end = sc->stack_end; + for (p = car(code); is_pair(p); p = cdr(p)) + { + s7_pointer old_value, field = cadadr(caar(p)); /* p: (((*s7* 'expansions?) #f)) -- no keywords here (see check_let_temporarily) */ + if (s7_starlet_immutable_field[s7_starlet_symbol(field)]) + error_nr(sc, sc->immutable_error_symbol, + set_elist_2(sc, wrap_string(sc, "let-temporarily: can't set! (*s7* '~S)", 38), field)); + old_value = s7_starlet(sc, s7_starlet_symbol(field)); + push_stack(sc, OP_LET_TEMP_S7_UNWIND, old_value, field); + } + for (p = car(code); is_pair(p); p = cdr(p), end += 4) + s7_starlet_set_1(sc, T_Sym(end[0]), fx_call(sc, cdar(p))); + sc->code = cdr(code); + return(is_pair(sc->code)); /* sc->code can be null if no body */ +} + +static void op_let_temp_s7_unwind(s7_scheme *sc) +{ + s7_starlet_set_1(sc, T_Sym(sc->code), sc->args); + if (is_multiple_value(sc->value)) + sc->value = splice_in_values(sc, multiple_value(sc->value)); +} + +static bool op_let_temp_s7_direct(s7_scheme *sc) +{ + s7_pointer new_val; + push_stack_no_code(sc, OP_LET_TEMP_S7_DIRECT_UNWIND, (sc->has_openlets) ? sc->T : sc->F); + new_val = fx_call(sc, opt1_pair(sc->code)); + sc->has_openlets = (new_val != sc->F); + sc->code = cddr(sc->code); /* cddr is body of let-temp */ + return(is_pair(sc->code)); +} + +static void op_let_temp_s7_direct_unwind(s7_scheme *sc) +{ + sc->has_openlets = (sc->args != sc->F); + if (is_multiple_value(sc->value)) + sc->value = splice_in_values(sc, multiple_value(sc->value)); +} + +static void let_temp_done(s7_scheme *sc, s7_pointer args, s7_pointer let) +{ + /* called in call/cc, call-with-exit and, catch (unwind to catch) */ + check_stack_size(sc); + push_stack_direct(sc, OP_GC_PROTECT); + sc->args = T_Ext(args); + set_curlet(sc, let); + op_let_temp_done1(sc); /* an experiment 6-Nov-21, was eval(sc, OP_LET_TEMP_DONE) */ +} + +static void let_temp_unwind(s7_scheme *sc, s7_pointer slot, s7_pointer new_value) +{ + if (slot_has_setter(slot)) /* setter has to be called because it might affect other vars (*clm-srate* -> mus-srate etc), but it should not change sc->value */ + { + s7_pointer old_value = sc->value; + slot_set_value(slot, call_setter(sc, slot, new_value)); /* s7_apply_function(sc, slot_setter(slot), set_plist_2(sc, slot_symbol(slot), new_value))); */ + sc->value = old_value; + } + else slot_set_value(slot, new_value); +} + +static void op_let_temp_unwind(s7_scheme *sc) +{ + let_temp_unwind(sc, sc->code, sc->args); + if (is_multiple_value(sc->value)) + sc->value = splice_in_values(sc, multiple_value(sc->value)); +} + +static bool op_let_temp_na(s7_scheme *sc) /* all entries are of the form (symbol fx-able-value) */ +{ + s7_pointer p, slot; + s7_pointer *end = sc->stack_end; + sc->code = cdr(sc->code); + + for (p = car(sc->code); is_pair(p); p = cdr(p)) + { + s7_pointer var = car(p); + s7_pointer settee = car(var); + slot = s7_slot(sc, settee); + if (!is_slot(slot)) + unbound_variable_error_nr(sc, settee); + if (is_immutable_slot(slot)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->let_temporarily_symbol, settee)); + push_stack(sc, OP_LET_TEMP_UNWIND, slot_value(slot), slot); + } + for (p = car(sc->code); is_pair(p); p = cdr(p), end += 4) + { + s7_pointer var = car(p); + s7_pointer new_val = fx_call(sc, cdr(var)); + slot = end[0]; + if (slot_has_setter(slot)) + slot_set_value(slot, call_setter(sc, slot, new_val)); /* s7_apply_function(sc, slot_setter(slot), set_plist_2(sc, settee, new_val))); */ + else slot_set_value(slot, new_val); + } + sc->code = cdr(sc->code); + return(is_pair(sc->code)); /* sc->code can be null if no body */ +} + +static bool op_let_temp_a(s7_scheme *sc) /* one entry */ +{ + s7_pointer var, settee, new_val, slot; + sc->code = cdr(sc->code); + var = caar(sc->code); + settee = car(var); + slot = s7_slot(sc, settee); + if (!is_slot(slot)) + unbound_variable_error_nr(sc, settee); + if (is_immutable_slot(slot)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->let_temporarily_symbol, settee)); + push_stack(sc, OP_LET_TEMP_UNWIND, slot_value(slot), slot); + new_val = fx_call(sc, cdr(var)); + if (slot_has_setter(slot)) + slot_set_value(slot, call_setter(sc, slot, new_val)); /* s7_apply_function(sc, slot_setter(slot), set_plist_2(sc, settee, new_val))); */ + else slot_set_value(slot, new_val); + sc->code = cdr(sc->code); + return(is_pair(sc->code)); /* sc->code can be null if no body */ +} + +static s7_pointer fx_let_temp_a_a(s7_scheme *sc, s7_pointer code) /* one entry, body is fx'd */ +{ + s7_pointer result; + op_let_temp_a(sc); + result = fx_call(sc, sc->code); + pop_stack(sc); + let_temp_unwind(sc, sc->code, sc->args); + return(result); +} + +static bool op_let_temp_setter(s7_scheme *sc) +{ + s7_pointer var, slot, sym, e = sc->curlet; + sc->code = cdr(sc->code); + var = caaar(sc->code); + sym = fx_call(sc, cdr(var)); + set_curlet(sc, fx_call(sc, cddr(var))); + slot = s7_slot(sc, sym); + set_curlet(sc, e); + push_stack(sc, OP_LET_TEMP_SETTER_UNWIND, slot_setter(slot), slot); + slot_set_setter(slot, sc->F); + sc->code = cdr(sc->code); + return(is_pair(sc->code)); /* sc->code can be null if no body */ +} + +static void op_let_temp_setter_unwind(s7_scheme *sc) +{ + slot_set_setter(sc->code, sc->args); + if (is_multiple_value(sc->value)) + sc->value = splice_in_values(sc, multiple_value(sc->value)); +} + + +/* -------------------------------- quote -------------------------------- */ +static inline s7_pointer check_quote(s7_scheme *sc, s7_pointer code) +{ + if (!is_pair(cdr(code))) /* (quote . -1) */ + { + if (is_null(cdr(code))) + syntax_error_nr(sc, "quote: not enough arguments: ~A", 31, code); + syntax_error_nr(sc, "quote: stray dot?: ~A", 21, code); + } + if (is_not_null(cddr(code))) /* (quote . (1 2)) or (quote 1 1) */ + syntax_error_nr(sc, "quote: too many arguments ~A", 28, code); + pair_set_syntax_op(code, OP_QUOTE_UNCHECKED); + return(cadr(code)); +} + + +/* -------------------------------- and -------------------------------- */ +static bool check_and(s7_scheme *sc, s7_pointer expr) +{ + /* this, check_or and check_if might not be called -- optimize_syntax can short-circuit it to return fx* choices */ + s7_pointer p, code = cdr(expr); + int32_t any_nils = 0, len; + + if (is_null(code)) + { + sc->value = sc->T; + return(true); + } + for (len = 0, p = code; is_pair(p); p = cdr(p), len++) + { + s7_function callee = (has_fx(p)) ? fx_proc(p) : fx_choose(sc, p, sc->curlet, let_symbol_is_safe); /* fx_proc can be nil! */ + if (!callee) any_nils++; + set_fx(p, callee); + } + if (is_not_null(p)) /* (and . 1) (and #t . 1) */ + syntax_error_nr(sc, "and: stray dot?: ~A", 19, expr); + + if ((fx_proc(code)) && + (is_proper_list_1(sc, cdr(code)))) + { + if ((fx_proc(code) == fx_is_pair_s) || (fx_proc(code) == fx_is_pair_t)) + { + pair_set_syntax_op(expr, OP_AND_PAIR_P); + set_opt3_sym(expr, cadar(code)); + set_opt2_con(expr, cadr(code)); + } + else pair_set_syntax_op(expr, (any_nils > 0) ? OP_AND_AP : OP_AND_2A); + } + else + { + pair_set_syntax_op(expr, (any_nils > 0) ? OP_AND_P : OP_AND_N); + if ((any_nils == 1) && (len > 2)) + { + if (!has_fx(code)) + pair_set_syntax_op(expr, OP_AND_SAFE_P1); + else + if (!has_fx(cdr(code))) + pair_set_syntax_op(expr, OP_AND_SAFE_P2); + else + if ((!has_fx(cddr(code))) && (len == 3)) + pair_set_syntax_op(expr, OP_AND_SAFE_P3); + }} + if ((is_fx_treeable(code)) && (tis_slot(let_slots(sc->curlet)))) fx_curlet_tree(sc, code); + set_current_code(sc, sc->code); + return(false); +} + +static bool op_and_pair_p(s7_scheme *sc) +{ + if (!is_pair(lookup(sc, opt3_sym(sc->code)))) /* cadadr(sc->code) */ + { + sc->value = sc->F; + return(true); + } + sc->code = opt2_con(sc->code); /* caddr(sc->code); */ + return(false); +} + +static bool op_and_ap(s7_scheme *sc) +{ + /* we know fx_proc is set on sc->code, and there are only two branches */ + if (is_false(sc, fx_call(sc, cdr(sc->code)))) + { + sc->value = sc->F; + return(true); + } + sc->code = caddr(sc->code); + return(false); +} + +static void op_and_safe_p1(s7_scheme *sc) /* sc->code: (and (func...) (fx...)...) */ +{ + sc->code = cdr(sc->code); /* new value will be pushed below */ + push_stack_no_args_direct(sc, OP_AND_SAFE_P_REST); + sc->code = car(sc->code); +} + +static bool op_and_safe_p2(s7_scheme *sc) +{ + sc->value = fx_call(sc, cdr(sc->code)); + if (is_false(sc, sc->value)) return(true); + sc->code = cddr(sc->code); + push_stack_no_args_direct(sc, OP_AND_SAFE_P_REST); + sc->code = car(sc->code); + return(false); +} + +static bool op_and_safe_p3(s7_scheme *sc) +{ + sc->value = fx_call(sc, cdr(sc->code)); + if (is_false(sc, sc->value)) return(true); + sc->code = cddr(sc->code); + sc->value = fx_call(sc, sc->code); + if (is_false(sc, sc->value)) return(true); + sc->code = cadr(sc->code); + return(false); +} + + +/* -------------------------------- or -------------------------------- */ +static bool check_or(s7_scheme *sc, s7_pointer expr) +{ + s7_pointer p, code = cdr(expr); + bool any_nils = false; + if (is_null(code)) + { + sc->value = sc->F; + return(true); + } + for (p = code; is_pair(p); p = cdr(p)) + { + s7_function callee = (has_fx(p)) ? fx_proc(p) : fx_choose(sc, p, sc->curlet, let_symbol_is_safe); + if (!callee) any_nils = true; + set_fx(p, callee); + } + if (is_not_null(p)) + syntax_error_nr(sc, "or: stray dot?: ~A", 18, expr); + + if ((fx_proc(code)) && + (is_proper_list_1(sc, cdr(code)))) /* list_1 of cdr so there are 2 exprs */ + pair_set_syntax_op(expr, (any_nils) ? OP_OR_AP : OP_OR_2A); + else pair_set_syntax_op(expr, (any_nils) ? OP_OR_P : OP_OR_N); + + if ((is_fx_treeable(code)) && (tis_slot(let_slots(sc->curlet)))) fx_curlet_tree(sc, code); + set_current_code(sc, sc->code); + return(false); +} + +static bool op_or_ap(s7_scheme *sc) +{ + /* we know fx_proc is set on sc->code, and there are only two branches */ + sc->value = fx_call(sc, cdr(sc->code)); + if (is_true(sc, sc->value)) + return(true); + sc->code = caddr(sc->code); + return(false); +} + + +/* -------------------------------- if -------------------------------- */ +static void fb_if_annotate(s7_scheme *sc, s7_pointer code, s7_pointer form) +{ + if (optimize_op(form) == OP_IF_A_P) + { + if (is_fxable(sc, cadr(code))) + { + pair_set_syntax_op(form, OP_IF_A_A); + fx_annotate_arg(sc, cdr(code), sc->curlet); + set_opt1_pair(form, cdr(code)); + fb_annotate(sc, form, code, OP_IF_B_A); + } + else fb_annotate(sc, form, code, OP_IF_B_P); + } + if (optimize_op(form) == OP_IF_A_R) + fb_annotate(sc, form, code, OP_IF_B_R); + if (optimize_op(form) == OP_IF_A_N_N) + fb_annotate(sc, form, cdar(code), OP_IF_B_N_N); + if (optimize_op(form) == OP_IF_A_P_P) + { + if (is_fxable(sc, cadr(code))) + { + set_opt1_pair(form, cdr(code)); + if (is_fxable(sc, caddr(code))) + { + pair_set_syntax_op(form, OP_IF_A_A_A); /* b_a_a never happens? */ + set_opt2_pair(form, cddr(code)); + } + else + { + pair_set_syntax_op(form, OP_IF_A_A_P); + fb_annotate(sc, form, code, OP_IF_B_A_P); + } + fx_annotate_args(sc, cdr(code), sc->curlet); + } + else + if (is_fxable(sc, caddr(code))) + { + pair_set_syntax_op(form, OP_IF_A_P_A); + fx_annotate_args(sc, cdr(code), sc->curlet); + set_opt2_pair(form, cddr(code)); + fb_annotate(sc, form, code, OP_IF_B_P_A); + } + else fb_annotate(sc, form, code, OP_IF_B_P_P); + } +} + +#define choose_if_optc(Opc, One, Reversed, Not) \ + ((One) ? ((Reversed) ? OP_ ## Opc ## _R : \ + ((Not) ? OP_ ## Opc ## _N : OP_ ## Opc ## _P)) : \ + ((Not) ? OP_ ## Opc ## _N_N : OP_ ## Opc ## _P_P)) + +static void set_if_opts(s7_scheme *sc, s7_pointer form, bool one_branch, bool reversed) /* cdr(form) == sc->code */ +{ + s7_pointer code = cdr(form); + s7_pointer test = car(code); + bool not_case = false; + + if ((!reversed) && + (is_pair(test)) && + (car(test) == sc->not_symbol)) + { + if (!is_proper_list_1(sc, cdr(test))) return; /* (not) or (not a b) */ + not_case = true; + test = cadr(test); + } + + set_opt1_any(form, cadr(code)); + if (!one_branch) set_opt2_any(form, caddr(code)); + + if (is_pair(test)) + { + if (is_optimized(test)) + { + if (is_h_safe_c_nc(test)) /* replace these with fx_and* */ + { + pair_set_syntax_op(form, choose_if_optc(IF_A, one_branch, reversed, not_case)); + if (not_case) + { + set_fx(cdar(code), fx_choose(sc, cdar(code), sc->curlet, let_symbol_is_safe)); + if (!reversed) set_opt3_pair(form, cdadr(form)); + } + else set_fx(code, fx_choose(sc, code, sc->curlet, let_symbol_is_safe)); + if ((is_fx_treeable(code)) && (tis_slot(let_slots(sc->curlet)))) fx_curlet_tree(sc, code); + fb_if_annotate(sc, code, form); + return; + } + if ((is_h_safe_c_s(test)) && + (is_symbol(car(test)))) + { + uint8_t typ = symbol_type(car(test)); + if (typ > 0) + { + pair_set_syntax_op(form, choose_if_optc(IF_IS_TYPE_S, one_branch, reversed, not_case)); + set_opt3_byte(code, typ); + if (optimize_op(form) == OP_IF_IS_TYPE_S_P_P) + { + if (is_fxable(sc, caddr(code))) + { + set_opt2_pair(form, cddr(code)); + if (is_fxable(sc, cadr(code))) + { + set_opt1_pair(form, cdr(code)); + fx_annotate_args(sc, cdr(code), sc->curlet); + pair_set_syntax_op(form, OP_IF_IS_TYPE_S_A_A); + } + else + { + set_opt1_any(form, cadr(code)); + pair_set_syntax_op(form, OP_IF_IS_TYPE_S_P_A); + fx_annotate_arg(sc, cddr(code), sc->curlet); + } + if ((is_fx_treeable(code)) && (tis_slot(let_slots(sc->curlet)))) fx_curlet_tree(sc, code); + } + else + if (is_fxable(sc, cadr(code))) + { + set_opt2_any(form, caddr(code)); + set_opt1_pair(form, cdr(code)); + fx_annotate_arg(sc, cdr(code), sc->curlet); + pair_set_syntax_op(form, OP_IF_IS_TYPE_S_A_P); + if ((is_fx_treeable(code)) && (tis_slot(let_slots(sc->curlet)))) fx_curlet_tree(sc, code); + }}} + else + { + pair_set_syntax_op(form, choose_if_optc(IF_opSq, one_branch, reversed, not_case)); + if (not_case) set_opt1_pair(code, cadar(code)); /* code is cdr(if...): ((not (f sym)) ...) */ + } + clear_has_fx(code); + set_opt2_sym(code, cadr(test)); + return; + } + if (is_fxable(sc, test)) + { + if ((optimize_op(test) == OP_OR_2A) || (optimize_op(test) == OP_AND_2A)) + { + if (optimize_op(test) == OP_OR_2A) + pair_set_syntax_op(form, choose_if_optc(IF_OR2, one_branch, reversed, not_case)); + else pair_set_syntax_op(form, choose_if_optc(IF_AND2, one_branch, reversed, not_case)); + clear_has_fx(code); + set_opt2_pair(code, cdr(test)); + set_opt3_pair(code, cddr(test)); + return; + } + if (optimize_op(test) == OP_AND_3A) + { + pair_set_syntax_op(form, choose_if_optc(IF_AND3, one_branch, reversed, not_case)); + clear_has_fx(code); + set_opt2_pair(code, cdr(test)); + set_opt3_pair(code, cddr(test)); + set_opt1_pair(code, cdddr(test)); + return; + } + + pair_set_syntax_op(form, choose_if_optc(IF_A, one_branch, reversed, not_case)); + if (not_case) + { + set_fx_direct(cdar(code), fx_choose(sc, cdar(code), sc->curlet, let_symbol_is_safe)); + if (!reversed) set_opt3_pair(form, cdadr(form)); + } + else set_fx_direct(code, fx_choose(sc, code, sc->curlet, let_symbol_is_safe)); + fb_if_annotate(sc, code, form); + } + else + { + pair_set_syntax_op(form, choose_if_optc(IF_P, one_branch, reversed, not_case)); + set_opt2_any(code, (one_branch) ? cadr(code) : cdr(code)); + set_opt3_any(code, (not_case) ? cadar(code) : car(code)); + } + if ((is_fx_treeable(code)) && (tis_slot(let_slots(sc->curlet)))) + fx_curlet_tree(sc, code); + } + else + { + pair_set_syntax_op(form, choose_if_optc(IF_P, one_branch, reversed, not_case)); + clear_has_fx(code); + set_opt2_any(code, (one_branch) ? cadr(code) : cdr(code)); + set_opt3_any(code, (not_case) ? cadar(code) : car(code)); + if (is_symbol_and_syntactic(car(test))) + { + pair_set_syntax_op(test, symbol_syntax_op_checked(test)); + if ((symbol_syntax_op(car(test)) == OP_AND) || + (symbol_syntax_op(car(test)) == OP_OR)) + { + opcode_t new_op; + if (symbol_syntax_op(car(test)) == OP_AND) + check_and(sc, test); + else check_or(sc, test); + new_op = symbol_syntax_op_checked(test); + if ((new_op == OP_AND_P) || (new_op == OP_AND_AP) || (new_op == OP_AND_PAIR_P) || + (new_op == OP_AND_N) || (new_op == OP_AND_SAFE_P1) || (new_op == OP_AND_SAFE_P2) || (new_op == OP_AND_SAFE_P3)) + { + pair_set_syntax_op(form, choose_if_optc(IF_ANDP, one_branch, reversed, not_case)); + set_opt2_any(code, (one_branch) ? cadr(code) : cdr(code)); + set_opt3_pair(code, (not_case) ? cdadar(code) : cdar(code)); + } + else + if ((new_op == OP_OR_P) || (new_op == OP_OR_AP)) + { + pair_set_syntax_op(form, choose_if_optc(IF_ORP, one_branch, reversed, not_case)); + set_opt2_any(code, (one_branch) ? cadr(code) : cdr(code)); + set_opt3_pair(code, (not_case) ? cdadar(code) : cdar(code)); + }}}}} + else /* test is symbol or constant, but constant here is nutty */ + if (is_safe_symbol(test)) + { + pair_set_syntax_op(form, choose_if_optc(IF_S, one_branch, reversed, not_case)); + if (not_case) set_opt1_sym(code, cadar(code)); /* code is cdr(if...): ((not sym) ...) */ + if (optimize_op(form) == OP_IF_S_P_P) + { + if (is_fxable(sc, caddr(code))) + { + if ((is_fx_treeable(code)) && (tis_slot(let_slots(sc->curlet)))) fx_curlet_tree(sc, code); + set_opt2_pair(form, cddr(code)); /* opt1_any set above to cadr(code) */ + if (is_fxable(sc, cadr(code))) + { + pair_set_syntax_op(form, OP_IF_S_A_A); + fx_annotate_args(sc, cdr(code), sc->curlet); + set_opt1_pair(form, cdr(code)); + } + else + { + pair_set_syntax_op(form, OP_IF_S_P_A); + fx_annotate_arg(sc, cddr(code), sc->curlet); + } + if ((is_fx_treeable(code)) && (tis_slot(let_slots(sc->curlet)))) fx_curlet_tree(sc, code); + } + else + if (is_fxable(sc, cadr(code))) + { + pair_set_syntax_op(form, OP_IF_S_A_P); + fx_annotate_arg(sc, cdr(code), sc->curlet); + if ((is_fx_treeable(code)) && (tis_slot(let_slots(sc->curlet)))) fx_curlet_tree(sc, code); + set_opt1_pair(form, cdr(code)); + set_opt2_any(form, caddr(code)); + }}} +} + +/* (cond <> (else <>)) only happens in old-fashioned code, so set_if_opts covers if/when/unless but not cond */ + +static s7_pointer check_if(s7_scheme *sc, s7_pointer form) +{ + s7_pointer cdr_code, code = cdr(form); + if (!is_pair(code)) /* (if) or (if . 1) */ + syntax_error_nr(sc, "(if): if needs at least 2 expressions: ~A", 41, form); + + cdr_code = cdr(code); + if (!is_pair(cdr_code)) /* (if 1) */ + { + if (is_null(cdr(code))) + syntax_error_nr(sc, "~S: if needs another clause", 27, form); + syntax_error_nr(sc, "~S: stray dot?", 14, form); /* (if 1 . 2) */ + } + + if (is_pair(cdr(cdr_code))) + { + if (is_not_null(cddr(cdr_code))) /* (if 1 2 3 4) */ + syntax_error_nr(sc, "too many clauses for if: ~A", 27, form); + } + else + if (is_not_null(cdr(cdr_code))) /* (if 1 2 . 3) */ + syntax_error_nr(sc, "if: ~A has improper list?", 25, form); + + pair_set_syntax_op(form, OP_IF_UNCHECKED); + set_if_opts(sc, form, is_null(cdr(cdr_code)), false); + set_current_code(sc, sc->code); + return(code); +} + +static void op_if(s7_scheme *sc) +{ + sc->code = check_if(sc, sc->code); + push_stack_no_args(sc, OP_IF1, cdr(sc->code)); + sc->code = car(sc->code); +} + +static void op_if_unchecked(s7_scheme *sc) +{ + push_stack_no_args(sc, OP_IF1, cddr(sc->code)); + sc->code = cadr(sc->code); +} + +static bool op_if1(s7_scheme *sc) +{ + sc->code = (is_true(sc, sc->value)) ? car(sc->code) : unchecked_car(cdr(sc->code)); + /* even pre-optimization, (if #f #f) ==> # because unique_car(sc->nil) = sc->unspecified */ + if (is_pair(sc->code)) + return(true); + sc->value = (is_symbol(sc->code)) ? lookup_checked(sc, sc->code) : sc->code; + return(false); +} + + +/* -------------------------------- when -------------------------------- */ +static void check_when(s7_scheme *sc) +{ + s7_pointer form = sc->code, code = cdr(sc->code); + + if (!is_pair(code)) /* (when) or (when . 1) */ + syntax_error_nr(sc, "when has no expression or body: ~A", 35, form); + if (!is_pair(cdr(code))) /* (when 1) or (when 1 . 1) */ + syntax_error_nr(sc, "when has no body?: ~A", 22, form); + if (!s7_is_proper_list(sc, cddr(code))) + syntax_error_nr(sc, "when: stray dot? ~A", 19, form); + + pair_set_syntax_op(form, OP_WHEN_P); + if (is_null(cddr(code))) + set_if_opts(sc, form, true, false); /* use if where possible */ + else + { + s7_pointer test = car(code); + if (is_safe_symbol(test)) + { + pair_set_syntax_op(form, OP_WHEN_S); + set_opt2_con(form, cadr(code)); + set_opt3_pair(form, cddr(code)); + } + else + /* fxable body doesn't happen very often -- a dozen or so hits in the standard tests */ + if (is_fxable(sc, test)) + { + pair_set_syntax_op(form, OP_WHEN_A); + if (is_pair(car(code))) set_opt2_pair(form, cdar(code)); + set_opt3_pair(form, cdr(code)); + set_fx_direct(code, fx_choose(sc, code, sc->curlet, let_symbol_is_safe)); /* "A" in when_a */ + + if (fx_proc(code) == fx_and_2a) + pair_set_syntax_op(form, OP_WHEN_AND_2A); + else + if (fx_proc(code) == fx_and_3a) + pair_set_syntax_op(form, OP_WHEN_AND_3A); + } + else + if ((is_pair(test)) && (car(test) == sc->and_symbol)) + { + opcode_t new_op; + pair_set_syntax_op(test, symbol_syntax_op_checked(test)); + check_and(sc, test); + new_op = symbol_syntax_op_checked(test); + if (new_op == OP_AND_AP) + pair_set_syntax_op(form, OP_WHEN_AND_AP); + }} + push_stack_no_args(sc, OP_WHEN_PP, cdr(code)); + set_current_code(sc, sc->code); + sc->code = car(code); +} + +static bool op_when_s(s7_scheme *sc) +{ + if (is_true(sc, lookup(sc, cadr(sc->code)))) + { + push_stack_no_args(sc, sc->begin_op, opt3_pair(sc->code)); /* cdddr(sc->code) */ + sc->code = opt2_con(sc->code); /* caddr(sc->code) */ + return(false); + } + sc->value = sc->unspecified; + return(true); +} + +static bool op_when_a(s7_scheme *sc) +{ + if (is_true(sc, fx_call(sc, cdr(sc->code)))) + { + push_stack_no_args(sc, sc->begin_op, T_Pair(cdr(opt3_pair(sc->code)))); /* cdddr(sc->code) */ + sc->code = car(opt3_pair(sc->code)); /* caddr(sc->code) */ + return(false); + } + sc->value = sc->unspecified; + return(true); +} + +static bool op_when_and_2a(s7_scheme *sc) +{ + if ((is_true(sc, fx_call(sc, opt2_pair(sc->code)))) && (is_true(sc, fx_call(sc, cdr(opt2_pair(sc->code)))))) + { + push_stack_no_args(sc, sc->begin_op, T_Pair(cdr(opt3_pair(sc->code)))); /* cdddr(sc->code) */ + sc->code = car(opt3_pair(sc->code)); /* caddr(sc->code) */ + return(false); + } + sc->value = sc->unspecified; + return(true); +} + +static bool op_when_and_3a(s7_scheme *sc) +{ + if ((is_true(sc, fx_call(sc, opt2_pair(sc->code)))) && + (is_true(sc, fx_call(sc, cdr(opt2_pair(sc->code))))) && + (is_true(sc, fx_call(sc, cddr(opt2_pair(sc->code)))))) + { + push_stack_no_args(sc, sc->begin_op, T_Pair(cdr(opt3_pair(sc->code)))); /* cdddr(sc->code) */ + sc->code = car(opt3_pair(sc->code)); /* caddr(sc->code) */ + return(false); + } + sc->value = sc->unspecified; + return(true); +} + +static void op_when_p(s7_scheme *sc) +{ + push_stack_no_args(sc, OP_WHEN_PP, cddr(sc->code)); + sc->code = cadr(sc->code); +} + +static bool op_when_and_ap(s7_scheme *sc) +{ + s7_pointer andp = cdadr(sc->code); + if (is_true(sc, fx_call(sc, andp))) + { + push_stack_no_args(sc, OP_WHEN_PP, cddr(sc->code)); + sc->code = cadr(andp); + return(false); + } + sc->value = sc->unspecified; + return(true); +} + +static bool op_when_pp(s7_scheme *sc) +{ + if (is_true(sc, sc->value)) + { + if_pair_set_up_begin_unchecked(sc); + return(false); + } + sc->value = sc->unspecified; + return(true); +} + + +/* -------------------------------- unless -------------------------------- */ +static void check_unless(s7_scheme *sc) +{ + s7_pointer form = sc->code, code = cdr(sc->code); + + if (!is_pair(code)) /* (unless) or (unless . 1) */ + syntax_error_nr(sc, "unless has no expression or body: ~A", 37, form); + if (!is_pair(cdr(code))) /* (unless 1) or (unless 1 . 1) */ + syntax_error_nr(sc, "unless has no body?: ~A", 24, form); + if (!s7_is_proper_list(sc, cddr(code))) + syntax_error_nr(sc, "unless: stray dot? ~A", 21, form); + + pair_set_syntax_op(form, OP_UNLESS_P); + if (is_null(cddr(code))) + set_if_opts(sc, form, true, true); + else + if (is_safe_symbol(car(code))) + { + pair_set_syntax_op(form, OP_UNLESS_S); + set_opt2_con(form, cadr(code)); + set_opt3_pair(form, cddr(code)); + } + else + if (is_fxable(sc, car(code))) + { + pair_set_syntax_op(form, OP_UNLESS_A); + set_opt2_con(form, cadr(code)); + set_opt3_pair(form, cddr(code)); + set_fx_direct(code, fx_choose(sc, code, sc->curlet, let_symbol_is_safe)); + } + push_stack_no_args(sc, OP_UNLESS_PP, cdr(code)); + set_current_code(sc, sc->code); + sc->code = car(code); +} + +static bool op_unless_s(s7_scheme *sc) +{ + if (is_false(sc, lookup(sc, cadr(sc->code)))) + { + push_stack_no_args(sc, sc->begin_op, opt3_pair(sc->code)); /* cdddr(sc->code) */ + sc->code = opt2_con(sc->code); /* caddr(sc->code) */ + return(false); + } + sc->value = sc->unspecified; + return(true); +} + +static bool op_unless_a(s7_scheme *sc) +{ + if (is_false(sc, fx_call(sc, cdr(sc->code)))) + { + push_stack_no_args(sc, sc->begin_op, opt3_pair(sc->code)); /* cdddr(sc->code) */ + sc->code = opt2_con(sc->code); /* caddr(sc->code) */ + return(false); + } + sc->value = sc->unspecified; + return(true); +} + +static void op_unless_p(s7_scheme *sc) +{ + push_stack_no_args(sc, OP_UNLESS_PP, cddr(sc->code)); + sc->code = cadr(sc->code); +} + +static bool op_unless_pp(s7_scheme *sc) +{ + if (is_false(sc, sc->value)) + { + if_pair_set_up_begin_unchecked(sc); + return(false); + } + sc->value = sc->unspecified; + return(true); +} + + +/* -------------------------------- begin -------------------------------- */ +static bool op_begin(s7_scheme *sc, s7_pointer code) +{ + s7_pointer form = cdr(code); + if (!s7_is_proper_list(sc, form)) /* proper list includes () */ + syntax_error_nr(sc, "unexpected dot? ~A", 18, code); + if (is_null(form)) /* (begin) -> () */ + { + sc->value = sc->nil; + return(true); + } + pair_set_syntax_op(sc->code, ((is_pair(cdr(form))) && (is_null(cddr(form)))) ? OP_BEGIN_2_UNCHECKED : OP_BEGIN_UNCHECKED); /* begin_1 doesn't happen much */ + return(false); +} + + +/* -------------------------------- define -------------------------------- */ +static s7_pointer print_truncate(s7_scheme *sc, s7_pointer code) +{ + if (tree_len(sc, code) > sc->print_length) + return(object_to_truncated_string(sc, code, sc->print_length * 10)); + return(code); +} + +static void check_define(s7_scheme *sc) +{ + s7_pointer func, caller, code = cdr(sc->code); + bool starred = (sc->cur_op == OP_DEFINE_STAR); + if (starred) + { + caller = sc->define_star_symbol; + sc->cur_op = OP_DEFINE_STAR_UNCHECKED; + } + else caller = (sc->cur_op == OP_DEFINE) ? sc->define_symbol : sc->define_constant_symbol; + + if (!is_pair(code)) + syntax_error_with_caller_nr(sc, "~A: nothing to define? ~A", 25, caller, sc->code); /* (define) */ + + if (!is_pair(cdr(code))) + { + if (is_null(cdr(code))) + syntax_error_with_caller_nr(sc, "~A: no value? ~A", 16, caller, sc->code); /* (define var) */ + syntax_error_with_caller_nr(sc, "~A: stray dot? ~A", 17, caller, sc->code); /* (define var . 1) */ + } + if (!is_pair(car(code))) + { + if (is_not_null(cddr(code))) /* (define var 1 . 2) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "~A: more than one value? ~A", 27), caller, print_truncate(sc, sc->code))); + if (starred) + syntax_error_nr(sc, "define* is restricted to functions: ~S", 38, sc->code); + + func = car(code); + if (!is_symbol(func)) /* (define 3 a) */ + syntax_error_with_caller2_nr(sc, "~A: can't define ~W (~A); it should be a symbol", 47, caller, func, object_type_name(sc, func)); + if (is_keyword(func)) /* (define :hi 1) */ + syntax_error_with_caller_nr(sc, "~A ~A: keywords are constants", 29, caller, func); + if (is_syntactic_symbol(func)) /* (define and a) */ + { + if (sc->safety > NO_SAFETY) + s7_warn(sc, 256, "%s: syntactic keywords tend to behave badly if redefined: %s\n", display(func), display_80(sc->code)); + set_local(func); + } + if ((is_pair(cadr(code))) && /* look for (define sym (lambda ...)) and treat it like (define (sym ...)...) */ + ((caadr(code) == sc->lambda_symbol) || + (caadr(code) == sc->lambda_star_symbol)) && + (symbol_id(caadr(code)) == 0)) + { + /* not is_global here because that bit might not be set for initial symbols (why not? -- redef as method etc) */ + if (!is_pair(cdadr(code))) /* (define x (lambda . 1)) */ + syntax_error_with_caller_nr(sc, "~A: stray dot? ~A", 17, caller, sc->code); + if (!is_pair(cddr(cadr(code)))) /* (define f (lambda (arg))) */ + syntax_error_with_caller_nr(sc, "~A: no body: ~A", 15, caller, sc->code); + if (caadr(code) == sc->lambda_star_symbol) + check_lambda_star_args(sc, cadadr(code), cddr(cadr(code)), cadr(code)); + else check_lambda_args(sc, cadadr(code), NULL, cadr(code)); + optimize_lambda(sc, caadr(code) == sc->lambda_symbol, func, cadadr(code), cddr(cadr(code))); + }} + else + { + func = caar(code); + if (!is_symbol(func)) /* (define (3 a) a) */ + syntax_error_with_caller2_nr(sc, "~A: can't define ~S, ~A (should be a symbol)", 44, caller, func, object_type_name(sc, func)); + if (is_syntactic_symbol(func)) /* (define (and a) a) */ + { + if (sc->safety > NO_SAFETY) + s7_warn(sc, 256, "%s: syntactic keywords tend to behave badly if redefined: %s\n", display(func), display_80(sc->code)); + set_local(func); + } + if (starred) + set_cdar(code, check_lambda_star_args(sc, cdar(code), cdr(code), sc->code)); + else check_lambda_args(sc, cdar(code), NULL, sc->code); + optimize_lambda(sc, !starred, func, cdar(code), cdr(code)); + } + + if (sc->cur_op == OP_DEFINE) + { + if ((is_pair(car(code))) && + (!is_possibly_constant(func))) + pair_set_syntax_op(sc->code, OP_DEFINE_FUNCHECKED); + else pair_set_syntax_op(sc->code, OP_DEFINE_UNCHECKED); + } + else pair_set_syntax_op(sc->code, (starred) ? OP_DEFINE_STAR_UNCHECKED : OP_DEFINE_CONSTANT_UNCHECKED); +} + +static bool op_define_unchecked(s7_scheme *sc) +{ + s7_pointer code = cdr(sc->code), locp; + + if ((is_pair(car(code))) && (has_location(car(code)))) + locp = car(code); + else locp = ((is_pair(cadr(code))) && (has_location(cadr(code)))) ? cadr(code) : sc->nil; + + if ((sc->cur_op == OP_DEFINE_STAR_UNCHECKED) && /* sc->cur_op changed above if define* */ + (is_pair(cdar(code)))) + { + sc->value = make_closure(sc, cdar(code), cdr(code), T_CLOSURE_STAR, CLOSURE_ARITY_NOT_SET); + /* closure_body might not be cdr(code) after make_closure (add_trace) */ + if ((is_pair(locp)) && (has_location(locp))) + { + pair_set_location(closure_body(sc->value), pair_location(locp)); + set_has_location(closure_body(sc->value)); + } + sc->code = caar(code); + return(false); + } + + if (!is_pair(car(code))) + { + s7_pointer x = car(code); + sc->code = cadr(code); + if (is_pair(sc->code)) + { + push_stack_no_args(sc, OP_DEFINE1, x); + sc->cur_op = optimize_op(sc->code); + return(true); + } + sc->value = (is_symbol(sc->code)) ? lookup_global(sc, sc->code) : sc->code; + sc->code = x; + } + else + { + s7_pointer args = cdar(code); + /* a closure. If we called this same code earlier (a local define), the only thing + * that is new here is the environment -- we can't blithely save the closure object + * in opt2 somewhere, and pick it up the next time around (since call/cc might take + * us back to the previous case). We also can't re-use opt2(sc->code) because opt2 + * is not cleared in the gc. + */ + s7_pointer x = make_closure(sc, args, cdr(code), T_CLOSURE | ((!s7_is_proper_list(sc, args)) ? T_COPY_ARGS : 0), (is_null(args)) ? 0 : CLOSURE_ARITY_NOT_SET); + if ((is_pair(locp)) && (has_location(locp))) + { + pair_set_location(closure_body(x), pair_location(locp)); + set_has_location(closure_body(x)); + } + sc->value = T_Ext(x); + sc->code = caar(code); + } + return(false); +} + +static s7_pointer make_funclet(s7_scheme *sc, s7_pointer new_func, s7_pointer func_name, s7_pointer outer_let) +{ + s7_pointer new_let, arg; + new_cell_no_check(sc, new_let, T_LET | T_FUNCLET); + let_set_id(new_let, ++sc->let_number); + let_set_outlet(new_let, outer_let); + closure_set_let(new_func, new_let); + funclet_set_function(new_let, func_name); /* *function* returns at least funclet_function */ + let_set_slots(new_let, slot_end); + + arg = closure_args(new_func); + if (is_null(arg)) + { + let_set_slots(new_let, slot_end); + return(new_let); + } + + if (is_safe_closure(new_func)) + { + s7_pointer last_slot = NULL; + if (is_closure(new_func)) + { + if (is_pair(arg)) + { + last_slot = make_slot(sc, car(arg), sc->nil); + slot_set_next(last_slot, slot_end); + let_set_slots(new_let, last_slot); + symbol_set_local_slot(car(arg), let_id(new_let), last_slot); + for (arg = cdr(arg); is_pair(arg); arg = cdr(arg)) + last_slot = inline_add_slot_at_end(sc, let_id(new_let), last_slot, car(arg), sc->nil); + } + if (is_symbol(arg)) + { + if (last_slot) + last_slot = add_slot_at_end(sc, let_id(new_let), last_slot, arg, sc->nil); + else + { + last_slot = make_slot(sc, arg, sc->nil); + slot_set_next(last_slot, slot_end); + let_set_slots(new_let, last_slot); + symbol_set_local_slot(arg, let_id(new_let), last_slot); + } + set_is_rest_slot(last_slot); + }} + else /* closure_star */ + { + s7_pointer slot, first_default = sc->nil; + let_set_slots(new_let, slot_end); + for (; is_pair(arg); arg = cdr(arg)) + { + s7_pointer par = car(arg); + if (is_pair(par)) + { + s7_pointer val = cadr(par); + slot = add_slot_checked(sc, new_let, car(par), sc->nil); + slot_set_expression(slot, val); + if ((is_symbol(val)) || (is_pair(val))) + { + if (is_null(first_default)) + first_default = slot; + set_slot_defaults(slot); + }} + else + if (is_keyword(par)) + { + if (par == sc->rest_keyword) + { + arg = cdr(arg); + slot = add_slot_checked(sc, new_let, car(arg), sc->nil); + slot_set_expression(slot, sc->nil); + }} + else + { + slot = add_slot_checked(sc, new_let, par, sc->nil); + slot_set_expression(slot, sc->F); + }} + if (is_symbol(arg)) + { + slot = add_slot_checked(sc, new_let, arg, sc->nil); /* set up rest arg */ + set_is_rest_slot(slot); + slot_set_expression(slot, sc->nil); + } + if (tis_slot(let_slots(new_let))) + { + let_set_slots(new_let, reverse_slots(let_slots(new_let))); + slot_set_pending_value(let_slots(new_let), first_default); + }} + set_immutable_let(new_let); + } + else let_set_slots(new_let, slot_end); /* if unsafe closure, arg-holding-let will be created on each call */ + return(new_let); +} + +static bool op_define_constant(s7_scheme *sc) +{ + s7_pointer code = cdr(sc->code); + if ((!is_pair(code)) || (!is_pair(cdr(code)))) /* (define-constant) */ + syntax_error_nr(sc, "define-constant: not enough arguments: ~S", 41, sc->code); + + if (is_symbol_and_keyword(car(code))) /* (define-constant :rest :allow-other-keys) */ + { + if (car(code) == cadr(code)) /* (define-constant pi pi) returns pi */ + { + sc->value = car(code); + return(true); + } + syntax_error_with_caller_nr(sc, "~A ~A: keywords are constants", 29, sc->define_constant_symbol, car(code)); + } + if ((is_symbol(car(code))) && /* (define-constant abs abs): "abs will not be touched" */ + (car(code) == cadr(code)) && + (symbol_id(car(code)) == 0) && /* else (let iter ... (define-constant iter iter) ...) -> segfault on later calls */ + (is_null(cddr(code)))) + { + s7_pointer sym = car(code); + set_immutable_slot(global_slot(sym)); /* id == 0 so its global */ + set_possibly_constant(sym); + sc->value = lookup_checked(sc, car(code)); + return(true); + } + push_stack_no_args(sc, OP_DEFINE_CONSTANT1, car(code)); + return(false); +} + +static void op_define_constant1(s7_scheme *sc) +{ + if (is_pair(sc->code)) + sc->code = car(sc->code); /* (define-constant (ex3 a)...) */ + if (is_symbol(sc->code)) + { + s7_pointer slot = s7_slot(sc, sc->code); + set_possibly_constant(sc->code); + set_immutable_slot(slot); + if (is_any_closure(slot_value(slot))) + set_immutable(slot_value(slot)); /* for the optimizer mainly */ + } +} + +static inline void define_funchecked(s7_scheme *sc) +{ + s7_pointer new_func, code = cdr(sc->code); + sc->value = caar(code); /* func name */ + + new_cell(sc, new_func, T_CLOSURE | ((!s7_is_proper_list(sc, cdar(code))) ? T_COPY_ARGS : 0)); + closure_set_args(new_func, cdar(code)); + closure_set_body(new_func, cdr(code)); + if (is_pair(cddr(code))) set_closure_has_multiform(new_func); else set_closure_has_one_form(new_func); + closure_set_setter(new_func, sc->F); + closure_set_arity(new_func, CLOSURE_ARITY_NOT_SET); + sc->capture_let_counter++; + + if (is_safe_closure_body(cdr(code))) + { + set_safe_closure(new_func); + if (is_very_safe_closure_body(cdr(code))) + set_very_safe_closure(new_func); + make_funclet(sc, new_func, sc->value, sc->curlet); + } + else closure_set_let(new_func, sc->curlet); /* unsafe closures created by other functions do not support *function* */ + + if (let_id(sc->curlet) < symbol_id(sc->value)) + sc->let_number++; /* dummy let, force symbol lookup */ + add_slot_unchecked(sc, sc->curlet, sc->value, new_func, sc->let_number); + sc->value = new_func; +} + +static s7_pointer cur_op_to_caller(s7_scheme *sc, opcode_t op) +{ + switch (op) + { + case OP_DEFINE_MACRO: return(sc->define_macro_symbol); + case OP_DEFINE_MACRO_STAR: return(sc->define_macro_star_symbol); + case OP_DEFINE_BACRO: return(sc->define_bacro_symbol); + case OP_DEFINE_BACRO_STAR: return(sc->define_bacro_star_symbol); + case OP_DEFINE_EXPANSION: return(sc->define_expansion_symbol); + case OP_DEFINE_EXPANSION_STAR: return(sc->define_expansion_star_symbol); + case OP_MACRO: return(sc->macro_symbol); + case OP_MACRO_STAR: return(sc->macro_star_symbol); + case OP_BACRO: return(sc->bacro_symbol); + case OP_BACRO_STAR: return(sc->bacro_star_symbol); + } + return(sc->define_macro_symbol); +} + +static s7_pointer check_define_macro(s7_scheme *sc, opcode_t op, s7_pointer form) +{ + s7_pointer mac_name, args, caller = cur_op_to_caller(sc, op); + + if (!is_pair(sc->code)) /* (define-macro . 1) */ + syntax_error_with_caller_nr(sc, "~A name missing (stray dot?): ~A", 32, caller, sc->code); + if (!is_pair(car(sc->code))) /* (define-macro a ...) */ + wrong_type_error_nr(sc, caller, 1, car(sc->code), wrap_string(sc, "a list: (name ...)", 18)); + + mac_name = caar(sc->code); + if (!is_symbol(mac_name)) + syntax_error_with_caller_nr(sc, "~A: ~S is not a symbol?", 23, caller, mac_name); + if (is_syntactic_symbol(mac_name)) + { + if (sc->safety > NO_SAFETY) + s7_warn(sc, 256, "%s: syntactic keywords tend to behave badly if redefined: %s\n", display(mac_name), display_80(sc->code)); + set_local(mac_name); + } + if (is_constant_symbol(sc, mac_name)) + syntax_error_with_caller_nr(sc, "~A: ~S is constant", 18, caller, mac_name); + + if (!is_pair(cdr(sc->code))) /* (define-macro (...)) */ + syntax_error_with_caller_nr(sc, "~A ~A, but no body?", 19, caller, mac_name); + + if (s7_list_length(sc, cdr(sc->code)) < 0) /* (define-macro (hi) 1 . 2) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "~A: macro body messed up, ~A", 28), caller, sc->code)); + + args = cdar(sc->code); + if ((!is_list(args)) && + (!is_symbol(args))) + error_nr(sc, sc->syntax_error_symbol, /* (define-macro (mac . 1) ...) */ + set_elist_3(sc, wrap_string(sc, "macro ~A argument list is ~S?", 29), mac_name, args)); + + if ((op == OP_DEFINE_MACRO) || (op == OP_DEFINE_BACRO) || (op == OP_DEFINE_EXPANSION)) + { + for (; is_pair(args); args = cdr(args)) + if (!is_symbol(car(args))) + error_nr(sc, sc->syntax_error_symbol, /* (define-macro (mac 1) ...) */ + set_elist_3(sc, wrap_string(sc, "~A parameter name, ~A, is not a symbol", 38), caller, car(args))); + check_lambda_args(sc, cdar(sc->code), NULL, form); + } + else set_cdar(sc->code, check_lambda_star_args(sc, args, NULL, form)); + return(sc->code); +} + +static s7_pointer check_macro(s7_scheme *sc, opcode_t op, s7_pointer form) +{ + s7_pointer args, caller = cur_op_to_caller(sc, op); + + if (!is_pair(sc->code)) /* sc->code = cdr(form) */ /* (macro) or (macro . 1) */ + syntax_error_with_caller_nr(sc, "~S: ~S has no parameters or body?", 33, caller, form); + if (!is_pair(cdr(sc->code))) /* (macro (a)) */ + syntax_error_with_caller_nr(sc, "~S: ~S has no body?", 19, caller, form); + + args = car(sc->code); + if ((!is_list(args)) && + (!is_symbol(args))) + error_nr(sc, sc->syntax_error_symbol, /* (macro #(0) ...) */ + set_elist_2(sc, wrap_string(sc, "macro parameter list is ~S?", 27), args)); + + if ((op == OP_MACRO) || (op == OP_BACRO)) + { + for (; is_pair(args); args = cdr(args)) + if (!is_symbol(car(args))) + error_nr(sc, sc->syntax_error_symbol, /* (macro (1) ...) */ + set_elist_3(sc, wrap_string(sc, "~A parameter name, ~A, is not a symbol", 38), caller, car(args))); + check_lambda_args(sc, car(sc->code), NULL, form); + } + else set_car(sc->code, check_lambda_star_args(sc, args, NULL, form)); + + if (s7_list_length(sc, cdr(sc->code)) < 0) /* (macro () 1 . 2) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "~A: macro body messed up, ~A", 28), caller, form)); + + return(sc->code); +} + +static void op_macro(s7_scheme *sc) /* (macro (x) `(+ ,x 1)) */ +{ + s7_pointer form = sc->code; + sc->code = cdr(sc->code); + if ((!is_pair(sc->code)) || (!mac_is_ok(sc->code))) /* (macro)? or (macro . #\a)? */ + { + check_macro(sc, sc->cur_op, form); + set_mac_is_ok(sc->code); + } + sc->value = make_macro(sc, sc->cur_op, false); +} + +static void op_define_macro(s7_scheme *sc) +{ + s7_pointer form = sc->code; + sc->code = cdr(sc->code); + check_define_macro(sc, sc->cur_op, form); + if ((is_immutable(sc->curlet)) && + (is_let(sc->curlet))) /* not () */ + syntax_error_nr(sc, "define-macro ~S: let is immutable", 33, caar(sc->code)); /* need syntax_error_any_with_caller? */ + sc->value = make_macro(sc, sc->cur_op, true); +} + +static bool unknown_any(s7_scheme *sc, s7_pointer f, s7_pointer code); + +static opcode_t fixup_macro_d(s7_scheme *sc, opcode_t op, s7_pointer mac) +{ + if (closure_arity_unknown(mac)) + closure_set_arity(mac, s7_list_length(sc, closure_args(mac))); + return(op); +} + +static inline bool op_macro_d(s7_scheme *sc, uint8_t typ) +{ + sc->value = lookup(sc, car(sc->code)); + if (type(sc->value) != typ) /* for-each (etc) called a macro before, now it's something else -- a very rare case */ + return(unknown_any(sc, sc->value, sc->code)); + + /* it's probably safer to always copy the list here, but that costs 4-5% in tmac, whereas this costs 3% -- maybe not worth the code? */ + if (closure_arity(sc->value) <= 0) + sc->args = copy_proper_list(sc, cdr(sc->code)); + else sc->args = cdr(sc->code); + + sc->code = sc->value; /* the macro */ + check_stack_size(sc); /* (define-macro (f) (f)) (f) */ + push_stack_op_let(sc, OP_EVAL_MACRO); + sc->curlet = inline_make_let(sc, closure_let(sc->code)); + return(false); /* fall into apply_lambda */ +} + +static void apply_macro_star_1(s7_scheme *sc); + +static bool op_macro_star_d(s7_scheme *sc) +{ + if (op_macro_d(sc, T_MACRO_STAR)) return(true); + apply_macro_star_1(sc); + return(false); +} + +static void transfer_macro_info(s7_scheme *sc, s7_pointer mac) +{ + s7_pointer body = closure_body(mac); + if (has_pair_macro(mac)) + { + set_maclet(sc->curlet); + funclet_set_function(sc->curlet, pair_macro(body)); + } + if (has_location(body)) + { + let_set_file(sc->curlet, pair_file_number(body)); + let_set_line(sc->curlet, pair_line_number(body)); + set_has_let_file(sc->curlet); + } +} + +static goto_t op_expansion(s7_scheme *sc) +{ + s7_pointer caller = (is_pair(stack_top_args(sc))) ? car(stack_top_args(sc)) : sc->F; /* this can be garbage */ + if ((sc->stack_end > sc->stack_start) && /* there is a stack... */ + (stack_top_op(sc) != OP_READ_QUOTE) && /* '(expansion ...) */ + (stack_top_op(sc) != OP_READ_VECTOR) && /* #(expansion ...) */ + (caller != sc->quote_symbol) && /* (quote (expansion ...)) */ + (caller != sc->macroexpand_symbol) && /* (macroexpand (expansion ...)) */ + (caller != sc->define_expansion_symbol) && /* (define-expansion ...) being reloaded/redefined */ + (caller != sc->define_expansion_star_symbol)) /* (define-expansion* ...) being reloaded/redefined */ + { + s7_pointer symbol = car(sc->value), slot; + /* we're playing fast and loose with sc->curlet in the reader, so here we need a disaster check */ + if (!is_let(sc->curlet)) sc->curlet = sc->nil; + + if ((symbol_id(symbol) == 0) || + (sc->curlet == sc->nil)) + slot = global_slot(symbol); + else slot = s7_slot(sc, symbol); + + sc->code = (is_slot(slot)) ? slot_value(slot) : sc->undefined; + if ((!is_either_macro(sc->code)) || (!is_expansion(sc->code))) + clear_expansion(symbol); + else + { + /* call the reader macro */ + sc->args = cdr(sc->value); + push_stack_no_code(sc, OP_EXPANSION, sc->nil); + sc->curlet = make_let(sc, closure_let(sc->code)); + transfer_macro_info(sc, sc->code); + if (!is_macro_star(sc->code)) + return(goto_apply_lambda); + apply_macro_star_1(sc); + return(goto_begin); + /* bacros don't seem to make sense here -- they are tied to the run-time environment, + * procedures would need to evaluate their arguments in rootlet + */ + }} + return(fall_through); +} + +static void macroexpand_c_macro(s7_scheme *sc) /* callgrind shows this when it's actually calling apply_c_function (code is identical) */ +{ + s7_int len = proper_list_length(sc->args); + if (len < c_macro_min_args(sc->code)) + error_nr(sc, sc->wrong_number_of_args_symbol, set_elist_3(sc, not_enough_arguments_string, sc->code, sc->args)); + if (c_macro_max_args(sc->code) < len) + error_nr(sc, sc->wrong_number_of_args_symbol, set_elist_3(sc, too_many_arguments_string, sc->code, sc->args)); + sc->value = c_macro_call(sc->code)(sc, sc->args); +} + +static goto_t macroexpand(s7_scheme *sc) +{ + switch (type(sc->code)) + { + case T_MACRO: + sc->curlet = make_let(sc, closure_let(sc->code)); + return(goto_apply_lambda); + case T_BACRO: + sc->curlet = make_let(sc, sc->curlet); + return(goto_apply_lambda); + case T_MACRO_STAR: + sc->curlet = make_let(sc, closure_let(sc->code)); + apply_macro_star_1(sc); + return(goto_begin); + case T_BACRO_STAR: + sc->curlet = make_let(sc, sc->curlet); + apply_macro_star_1(sc); + return(goto_begin); + case T_C_MACRO: + macroexpand_c_macro(sc); + return(goto_start); + default: + syntax_error_nr(sc, "macroexpand argument is not a macro call: ~A", 44, sc->args); + } + return(fall_through); /* for the compiler */ +} + +static goto_t op_macroexpand(s7_scheme *sc) +{ + s7_pointer form = sc->code; + sc->code = cdr(sc->code); + /* mimic APPLY, but don't push OP_EVAL_MACRO or OP_EXPANSION + * (define-macro (mac a) `(+ ,a 1)) (macroexpand (mac 3)), sc->code: ((mac 3)) + */ + if ((!is_pair(sc->code)) || + (!is_pair(car(sc->code)))) + syntax_error_nr(sc, "macroexpand argument is not a macro call: ~A", 44, form); + + if (!is_null(cdr(sc->code))) + syntax_error_nr(sc, "macroexpand: too many arguments: ~A", 35, form); + + if (is_pair(caar(sc->code))) /* (macroexpand ((symbol->value 'mac) (+ 1 2))) */ + { + push_stack_no_args_direct(sc, OP_MACROEXPAND_1); + sc->code = caar(sc->code); + return(goto_eval); + } + + sc->args = cdar(sc->code); + if (!is_list(sc->args)) /* (macroexpand (mac . 7)) */ + syntax_error_nr(sc, "can't macroexpand ~S: the macro's argument list is not a list", 61, car(sc->code)); + + if (!is_symbol(caar(sc->code))) + { + if (!is_any_macro(caar(sc->code))) + syntax_error_nr(sc, "macroexpand argument is not a macro call: ~A", 44, sc->code); + sc->code = caar(sc->code); + return(macroexpand(sc)); + } + sc->code = lookup_checked(sc, caar(sc->code)); + return(macroexpand(sc)); +} + +static goto_t op_macroexpand_1(s7_scheme *sc) +{ + sc->args = cdar(sc->code); + sc->code = sc->value; + return(macroexpand(sc)); +} + +static void op_eval_macro(s7_scheme *sc) /* after (scheme-side) macroexpansion, evaluate the resulting expression */ +{ + /* (define-macro (hi a) `(+ ,a 1)), (hi 2), here with value: (+ 2 1) */ + if (is_multiple_value(sc->value)) + { + /* a normal macro's result is evaluated (below) and its value replaces the macro invocation, + * so if a macro returns multiple values, evaluate each one, then replace the macro + * invocation with (apply values evaluated-results-in-a-list). We need to save the + * new list of results, and where we are in the macro's output list, so code=macro output, + * args=new list. If it returns (values), should we use #? I think that + * happens now without generating a multiple_value object: + * (define-macro (hi) (values)) (hi) -> # + * (define-macro (ho) (values '(+ 1 2) '(* 3 4))) (+ 1 (ho) 3) -> 19 + * (define-macro (ha) (values '(define a 1) '(define b 2))) (let () (ha) (+ a b)) -> 3 + */ + push_stack(sc, OP_EVAL_MACRO_MV, sc->nil, cdr(sc->value)); + sc->code = car(sc->value); + } + else sc->code = sc->value; +} + +static bool op_eval_macro_mv(s7_scheme *sc) +{ + if (is_null(sc->code)) /* end of values list */ + { + sc->value = splice_in_values(sc, multiple_value(proper_list_reverse_in_place(sc, cons(sc, sc->value, sc->args)))); + return(true); + } + push_stack(sc, OP_EVAL_MACRO_MV, cons(sc, sc->value, sc->args), cdr(sc->code)); + sc->code = car(sc->code); + return(false); +} + +static void op_finish_expansion(s7_scheme *sc) +{ + /* after the expander has finished, if a list was returned, we need to add some annotations. + * if the expander returned (values), the list-in-progress vanishes! (This mimics map and *#readers*). + */ + if (SHOW_EVAL_OPS) fprintf(stderr, "%s[%d]: op: %s, value: %s\n", __func__, __LINE__, op_names[stack_top_op(sc)], display(sc->value)); + if (sc->value == sc->no_value) + { + if (stack_top_op(sc) != OP_LOAD_RETURN_IF_EOF) /* latter op if empty expansion at top-level */ + set_stack_top_op(sc, OP_READ_NEXT); + } + else + if (is_pair(sc->value)) + sc->value = copy_body(sc, sc->value); +} + + +/* -------------------------------- with-let -------------------------------- */ +static void check_with_let(s7_scheme *sc) +{ + s7_pointer form = cdr(sc->code); + + if (!is_pair(form)) /* (with-let . "hi") */ + syntax_error_nr(sc, "with-let takes an environment argument: ~A", 42, sc->code); + if (!is_pair(cdr(form))) /* (with-let e) -> an error? */ + syntax_error_nr(sc, "with-let body is messed up: ~A", 30, sc->code); + if (!s7_is_proper_list(sc, cdr(form))) /* (with-let e . 3) */ + syntax_error_nr(sc, "stray dot in with-let body: ~S", 30, sc->code); + + pair_set_syntax_op(sc->code, ((is_normal_symbol(car(form))) && + (is_normal_symbol(cadr(form))) && /* (with-let lt a) is not the same as (with-let lt :a) */ + (is_null(cddr(form)))) ? OP_WITH_LET_S : OP_WITH_LET_UNCHECKED); + set_current_code(sc, sc->code); +} + +static bool op_with_let_unchecked(s7_scheme *sc) +{ + sc->code = cdr(sc->code); + sc->value = car(sc->code); + if (!is_pair(sc->value)) + { + if (is_symbol(sc->value)) + sc->value = lookup_checked(sc, sc->value); + sc->code = cdr(sc->code); + return(false); + } + push_stack_no_args(sc, OP_WITH_LET1, cdr(sc->code)); + sc->code = sc->value; /* eval let arg */ + return(true); +} + +static s7_pointer fx_with_let_s(s7_scheme *sc, s7_pointer arg) +{ + s7_pointer code = cdr(arg); + s7_pointer e = lookup_checked(sc, car(code)); + s7_pointer sym = cadr(code); + s7_pointer val; + if ((!is_let(e)) && (e != sc->rootlet)) + { + e = find_let(sc, e); + if (!is_let(e)) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "with-let takes an environment argument: ~A", 42), car(code))); + } + val = let_ref(sc, e, sym); /* (with-let e s) -> (let-ref e s), "s" unevalled? */ + if (val == sc->undefined) /* but sym can have the value #: (with-let (inlet 'x #) x) */ + { + if ((e == sc->s7_starlet) && (is_slot(global_slot(sym)))) /* (let () (define (func) (with-let *s7* letrec*)) (func) (func)), .5 tlet */ + return(global_value(sym)); /* perhaps the e=*s7* check is not needed */ + if (is_slot(lookup_slot_with_let(sym, e))) + return(sc->undefined); + unbound_variable_error_nr(sc, sym); + } + return(val); +} + +static void activate_with_let(s7_scheme *sc, s7_pointer e) +{ + if (!is_let(e)) /* (with-let . "hi") */ + { + s7_pointer new_e = find_let(sc, e); + if (!is_let(new_e)) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "with-let takes an environment argument: ~A", 42), e)); + e = new_e; + } + if (e == sc->rootlet) + sc->curlet = sc->nil; /* (with-let (rootlet) ...) */ + else + { + set_with_let_let(e); + let_set_id(e, ++sc->let_number); + set_curlet(sc, e); + update_symbol_ids(sc, e); + } +} + + +/* -------------------------------- cond -------------------------------- */ +static void check_cond(s7_scheme *sc) +{ + bool has_feed_to = false, result_fx = true, result_single = true; + s7_pointer x, code = cdr(sc->code), form = sc->code; + + if (!is_pair(code)) /* (cond) or (cond . 1) */ + syntax_error_nr(sc, "cond, but no body: ~A", 21, form); + + for (x = code; is_pair(x); x = cdr(x)) + if (!is_pair(car(x))) /* (cond 1) or (cond (#t 1) 3) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "every clause in cond must be a pair: ~S in ~A", 45), + car(x), object_to_truncated_string(sc, form, 80))); + else + { + s7_pointer y = car(x); + if (!s7_is_proper_list(sc, cdr(y))) /* (cond (xxx . 1)) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "stray dot? ~S in ~A", 19), + y, object_to_truncated_string(sc, form, 80))); + if (is_pair(cdr(y))) + { + if (is_pair(cddr(y))) result_single = false; + if (is_undefined_feed_to(sc, cadr(y))) + { + has_feed_to = true; + if (!is_pair(cddr(y))) /* (cond (#t =>)) or (cond (#t => . 1)) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "cond: '=>' target missing? ~S in ~A", 36), + x, object_to_truncated_string(sc, form, 80))); + if (is_pair(cdddr(y))) /* (cond (1 => + abs)) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "cond: '=>' has too many targets: ~S in ~A", 41), + x, object_to_truncated_string(sc, form, 80))); + }} + else result_single = false; + } + if (is_not_null(x)) /* (cond ((1 2)) . 1) */ + error_nr(sc, sc->syntax_error_symbol, set_elist_2(sc, wrap_string(sc, "cond: stray dot? ~S", 19), form)); + + for (x = code; is_pair(x); x = cdr(x)) + { + s7_pointer p = car(x); + /* clear_has_fx(p); */ /* a kludge -- if has_fx here (and not re-fx'd below), someone messed up earlier -- but was fx_treeable set? */ + if (is_fxable(sc, car(p))) + fx_annotate_arg(sc, p, sc->curlet); + for (p = cdr(p); is_pair(p); p = cdr(p)) + if (!has_fx(p)) + { + s7_function f = fx_choose(sc, p, sc->curlet, let_symbol_is_safe); + if (f) set_fx_direct(p, f); else result_fx = false; + }} + if ((is_fx_treeable(code)) && (tis_slot(let_slots(sc->curlet)))) fx_curlet_tree(sc, code); + + if (has_feed_to) + { + pair_set_syntax_op(form, OP_COND_UNCHECKED); + if (is_null(cdr(code))) + { + s7_pointer expr = car(code), f; + f = caddr(expr); + if ((is_proper_list_3(sc, f)) && + (car(f) == sc->lambda_symbol)) + { + s7_pointer arg = cadr(f); + if ((is_pair(arg)) && + (is_null(cdr(arg))) && + (is_symbol(car(arg)))) /* (define (hi) (cond (#t => (lambda (s) s)))) */ + { + set_opt2_lambda(code, caddar(code)); /* (lambda ...) above */ + pair_set_syntax_op(form, OP_COND_FEED); + }}}} + else + { + s7_pointer p; + bool xopt = true; + int32_t i; + + pair_set_syntax_op(form, OP_COND_SIMPLE); + for (i = 0, p = code; xopt && (is_pair(p)); i++, p = cdr(p)) + xopt = ((has_fx(car(p))) && (is_pair(cdar(p)))); + if (xopt) + { + pair_set_syntax_op(form, (result_fx) ? OP_COND_NA_NA : ((result_single) ? OP_COND_NA_NP_O : OP_COND_NA_NP)); + if (result_single) + { + if (i == 2) + { + p = caadr(code); + if ((p == sc->else_symbol) || (p == sc->T)) + pair_set_syntax_op(form, OP_COND_NA_2E); + } + else + if (i == 3) + { + p = caaddr(code); + if ((p == sc->else_symbol) || (p == sc->T)) + pair_set_syntax_op(form, OP_COND_NA_3E); + }}} + else + if (result_single) + pair_set_syntax_op(form, OP_COND_SIMPLE_O); + } + set_opt3_any(code, caar(code)); +} + +static bool op_cond_unchecked(s7_scheme *sc) +{ + sc->code = cdr(sc->code); + if (has_fx(car(sc->code))) + { + sc->value = fx_call(sc, car(sc->code)); /* false -> fall through into cond1 */ + return(false); + } + push_stack_no_args_direct(sc, OP_COND1); /* true -> push cond1, goto eval */ + sc->code = opt3_any(sc->code); /* caar */ + return(true); +} + +static bool op_cond_simple(s7_scheme *sc) /* no => */ +{ + sc->code = cdr(sc->code); + if (has_fx(car(sc->code))) + { + sc->value = fx_call(sc, car(sc->code)); + return(false); + } + push_stack_no_args_direct(sc, OP_COND1_SIMPLE); + sc->code = opt3_any(sc->code); /* caar */ + return(true); +} + +static bool op_cond_simple_o(s7_scheme *sc) /* no =>, no null or multiform consequent */ +{ + sc->code = cdr(sc->code); + if (has_fx(car(sc->code))) + { + sc->value = fx_call(sc, car(sc->code)); + return(false); + } + push_stack_no_args_direct(sc, OP_COND1_SIMPLE_O); + sc->code = opt3_any(sc->code); /* caar */ + return(true); +} + +static bool op_cond1(s7_scheme *sc) +{ + while (true) + { + if (is_true(sc, sc->value)) /* test is true, so evaluate result */ + { + sc->code = cdar(sc->code); + if (is_pair(sc->code)) + { + if (is_null(cdr(sc->code))) + { + if (has_fx(sc->code)) + { + sc->value = fx_call(sc, sc->code); + pop_stack(sc); + return(true); /* goto top_no_pop */ + } + sc->code = car(sc->code); + sc->cur_op = optimize_op(sc->code); + return(true); + } + /* check_cond catches stray dots */ + if (is_undefined_feed_to(sc, car(sc->code))) + return(false); + if (has_fx(sc->code)) + { + sc->value = fx_call(sc, sc->code); + sc->code = cdr(sc->code); + if (is_pair(cdr(sc->code))) + push_stack_no_args(sc, sc->begin_op, cdr(sc->code)); + } + else push_stack_no_args(sc, sc->begin_op, T_Pair(cdr(sc->code))); + sc->code = car(sc->code); + sc->cur_op = optimize_op(sc->code); + return(true); + } + /* sc->code is () */ + if (is_multiple_value(sc->value)) /* (+ 1 (cond ((values 2 3)))) */ + sc->value = splice_in_values(sc, multiple_value(sc->value)); + /* no result clause, so return test, (cond (#t)) -> #t, (cond ((+ 1 2))) -> 3 */ + pop_stack(sc); + return(true); + } + sc->code = cdr(sc->code); /* go to next clause */ + if (is_null(sc->code)) + { + sc->value = sc->unspecified; + pop_stack(sc); + return(true); + } + if (has_fx(car(sc->code))) + sc->value = fx_call(sc, car(sc->code)); + else + { + push_stack_no_args_direct(sc, OP_COND1); + sc->code = caar(sc->code); + sc->cur_op = optimize_op(sc->code); + return(true); + }} + return(true); /* make the compiler happy */ +} + +static bool op_cond1_simple(s7_scheme *sc) +{ + while (true) + { + if (is_true(sc, sc->value)) + { + sc->code = T_Lst(cdar(sc->code)); + if (is_null(sc->code)) + { + if (is_multiple_value(sc->value)) + sc->value = splice_in_values(sc, multiple_value(sc->value)); + pop_stack(sc); + return(true); + } + if (!has_fx(sc->code)) + return(false); + sc->value = fx_call(sc, sc->code); + sc->code = cdr(sc->code); + if (is_pair(sc->code)) return(false); /* goto begin */ + pop_stack(sc); + return(true); /* goto top_no_pop */ + } + sc->code = cdr(sc->code); + if (is_null(sc->code)) + { + sc->value = sc->unspecified; + pop_stack(sc); + return(true); + } + if (has_fx(car(sc->code))) + sc->value = fx_call(sc, car(sc->code)); + else + { + push_stack_no_args_direct(sc, OP_COND1_SIMPLE); + sc->code = caar(sc->code); + sc->cur_op = optimize_op(sc->code); + return(true); + }} +} + +static bool op_cond1_simple_o(s7_scheme *sc) +{ + while (true) + { + if (is_true(sc, sc->value)) + { + sc->code = cdar(sc->code); + if (has_fx(sc->code)) + { + sc->value = fx_call(sc, sc->code); + return(true); /* goto start */ + } + sc->code = car(sc->code); + return(false); + } + sc->code = cdr(sc->code); + if (is_null(sc->code)) + { + sc->value = sc->unspecified; + return(true); + } + if (has_fx(car(sc->code))) + sc->value = fx_call(sc, car(sc->code)); + else + { + check_stack_size(sc); /* 4-May-21 snd-test */ + push_stack_no_args_direct(sc, OP_COND1_SIMPLE_O); + sc->code = caar(sc->code); + return(false); + }} +} + +static bool op_cond_na_np(s7_scheme *sc) /* all tests are fxable, results may be a mixture, no =>, no missing results */ +{ + for (s7_pointer p = cdr(sc->code); is_pair(p); p = cdr(p)) + if (is_true(sc, fx_call(sc, car(p)))) + { + for (p = T_Lst(cdar(p)); is_pair(p); p = cdr(p)) + if (has_fx(T_Pair(p))) + sc->value = fx_call(sc, p); + else + { + if (is_pair(cdr(p))) + push_stack_no_args(sc, OP_COND_NA_NP_1, cdr(p)); + sc->code = car(p); + return(false); + } + return(true); + } + sc->value = sc->unspecified; + return(true); +} + +static bool op_cond_na_np_1(s7_scheme *sc) /* continuing to handle a multi-statement result from cond_na_np */ +{ + for (s7_pointer p = sc->code; is_pair(p); p = cdr(p)) + if (has_fx(T_Pair(p))) + sc->value = fx_call(sc, p); + else + { + if (is_pair(cdr(p))) + push_stack_no_args(sc, OP_COND_NA_NP_1, cdr(p)); + sc->code = car(p); + return(false); + } + return(true); +} + +static Inline bool inline_op_cond_na_np_o(s7_scheme *sc) /* all tests are fxable, results may be a mixture, no =>, no missing results, all result one expr */ +{ /* called once in eval, b case cb lg rclo str */ + for (s7_pointer p = cdr(sc->code); is_pair(p); p = cdr(p)) + if (is_true(sc, fx_call(sc, car(p)))) + { + p = cdar(p); + if (has_fx(T_Pair(p))) + { + sc->value = fx_call(sc, p); + return(true); + } + sc->code = car(p); + return(false); + } + sc->value = sc->unspecified; + return(true); +} + +static inline bool fx_cond_value(s7_scheme *sc, s7_pointer p) +{ + if (has_fx(p)) + { + sc->value = fx_call(sc, p); + return(true); + } + sc->code = car(p); + return(false); +} + +static bool op_cond_na_2e(s7_scheme *sc) +{ + s7_pointer p = cdr(sc->code); + return(fx_cond_value(sc, (is_true(sc, fx_call(sc, car(p)))) ? cdar(p) : cdadr(p))); +} + +static bool op_cond_na_3e(s7_scheme *sc) +{ + s7_pointer p = cdr(sc->code); + if (is_true(sc, fx_call(sc, car(p)))) + return(fx_cond_value(sc, cdar(p))); + p = cdr(p); + return(fx_cond_value(sc, (is_true(sc, fx_call(sc, car(p)))) ? cdar(p) : cdadr(p))); +} + +static bool op_cond_feed(s7_scheme *sc) +{ + /* (cond (expr => p)) where p is (lambda (s) ...) -- see check_cond */ + sc->code = cdr(sc->code); + if (has_fx(car(sc->code))) + sc->value = fx_call(sc, car(sc->code)); + else + { + push_stack_no_args_direct(sc, OP_COND_FEED_1); + sc->code = caar(sc->code); + return(true); + } + return(false); +} + +static void op_cond_feed_1(s7_scheme *sc) +{ + if (is_multiple_value(sc->value)) + sc->code = cons(sc, opt2_lambda(sc->code), multiple_value(sc->value)); + else + { + sc->curlet = inline_make_let_with_slot(sc, sc->curlet, caadr(opt2_lambda(sc->code)), sc->value); + sc->code = caddr(opt2_lambda(sc->code)); + } +} + +static bool feed_to(s7_scheme *sc) +{ + if (is_multiple_value(sc->value)) + { + sc->args = multiple_value(sc->value); + clear_multiple_value(sc->args); + if (is_symbol(cadr(sc->code))) + { + sc->code = lookup_global(sc, cadr(sc->code)); /* car is => */ + return(true); + }} + else + { + if (is_symbol(cadr(sc->code))) + { + sc->code = lookup_global(sc, cadr(sc->code)); /* car is => */ + sc->args = (needs_copied_args(sc->code)) ? list_1(sc, sc->value) : set_plist_1(sc, sc->value); + return(true); + } + sc->args = list_1(sc, sc->value); /* not plist here */ + } + push_stack_direct(sc, OP_FEED_TO_1); + sc->code = cadr(sc->code); /* need to evaluate the target function */ + return(false); +} + + +/* -------------------------------- set! -------------------------------- */ +static void check_set(s7_scheme *sc) +{ + s7_pointer form = sc->code, code = cdr(sc->code); + if (!is_pair(code)) + { + if (is_null(code)) /* (set!) */ + syntax_error_nr(sc, "set!: not enough arguments: ~A", 30, form); + syntax_error_nr(sc, "set!: stray dot? ~A", 19, form); /* (set! . 1) */ + } + if (!is_pair(cdr(code))) + { + if (is_null(cdr(code))) /* (set! var) */ + syntax_error_nr(sc, "set!: not enough arguments: ~A", 30, form); + syntax_error_nr(sc, "set!: stray dot? ~A", 19, form); /* (set! var . 1) */ + } + if (is_not_null(cddr(code))) /* (set! var 1 2) */ + syntax_error_nr(sc, "~A: too many arguments to set!", 30, form); + + /* cadr (the value) has not yet been evaluated */ + if (is_pair(car(code))) + { + if ((is_pair(caar(code))) && + (!is_list(cdar(code)))) /* (set! ('(1 2) . 0) 1) */ + syntax_error_nr(sc, "improper list of arguments to set!: ~A", 38, form); + if (!s7_is_proper_list(sc, car(code))) /* (set! ("hi" . 1) #\a) or (set! (#(1 2) . 1) 0) */ + syntax_error_nr(sc, "set! target is an improper list: (set! ~A ...)", 46, car(code)); + } + else + if (!is_symbol(car(code))) /* (set! 12345 1) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "set! can't change ~S, ~S", 24), car(code), form)); + else + if (is_constant_symbol(sc, car(code))) /* (set! pi 3) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, (is_keyword(car(code))) ? "set!: can't change keyword's value: ~S in ~S" : + "set!: can't alter constant's value: ~S in ~S", 44), + car(code), form)); + if (is_pair(car(code))) + { + /* here we have (set! (...) ...) */ + s7_pointer inner = car(code), value = cadr(code); + pair_set_syntax_op(form, OP_SET_UNCHECKED); /* if not pair car, op_set_normal below */ + if (is_symbol(car(inner))) + { + if (is_null(cdr(inner))) /* (set! (symbol) ...) */ + { + if (is_fxable(sc, value)) + { + pair_set_syntax_op(form, OP_SET_opSq_A); /* (set! (symbol) fxable) */ + fx_annotate_arg(sc, cdr(code), sc->curlet); /* cdr(code) = value */ + }} + else + if (is_null(cddr(inner))) /* we check cddr(code) above */ /* this leaves (set! (vect i j) 1) unhandled so we go to OP_SET_UNCHECKED */ + { + s7_pointer index = cadr(inner); + if (is_fxable(sc, index)) + { + if ((car(inner) == sc->let_ref_symbol) && (!is_pair(cddr(inner)))) /* perhaps also check for hash-table-ref */ + /* (let () (define (func) (catch #t (lambda () (set! (let-ref (list 1)) 1)) (lambda args 'error))) (func) (func)) */ + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_2(sc, wrap_string(sc, "set!: not enough arguments for let-ref: ~S", 42), sc->code)); + fx_annotate_arg(sc, cdar(code), sc->curlet); /* cdr(inner) -> index */ + if (is_fxable(sc, value)) + { + pair_set_syntax_op(form, OP_SET_opSAq_A); /* (set! (symbol fxable) fxable) */ + fx_annotate_arg(sc, cdr(code), sc->curlet); /* cdr(code) -> value */ + + if (car(inner) == sc->s7_starlet_symbol) /* (set! (*s7* 'field) value) */ + { + s7_pointer sym = (is_symbol(index)) ? + ((is_keyword(index)) ? keyword_symbol(index) : index) : + ((is_quoted_symbol(index)) ? cadr(index) : index); + if ((is_symbol(sym)) && (s7_starlet_symbol(sym) != SL_NO_FIELD)) + { + set_safe_optimize_op(form, OP_IMPLICIT_S7_STARLET_SET); + set_opt3_sym(form, sym); + }}} + else pair_set_syntax_op(form, OP_SET_opSAq_P); /* (set! (symbol fxable) any) */ + }} + else + if ((is_null(cdddr(inner))) && + (car(inner) != sc->with_let_symbol)) /* (set! (with-let lt a) 32) needs to be handled by op_set_with_let_1 */ + { + s7_pointer index1 = cadr(inner), index2 = caddr(inner); + if ((is_fxable(sc, index1)) && (is_fxable(sc, index2))) + { + fx_annotate_args(sc, cdar(code), sc->curlet); /* cdr(inner) -> index1 and 2 */ + if (is_fxable(sc, value)) + { + pair_set_syntax_op(form, OP_SET_opSAAq_A); /* (set! (symbol fxable fxable) fxable) */ + fx_annotate_arg(sc, cdr(code), sc->curlet); /* cdr(code) -> value */ + } + else pair_set_syntax_op(form, OP_SET_opSAAq_P); /* (set! (symbol fxable fxable) any) */ + }}} + return; + } + pair_set_syntax_op(form, OP_SET_NORMAL); + if (is_symbol(car(code))) + { + s7_pointer settee = car(code), value = cadr(code); + s7_pointer slot = s7_slot(sc, settee); + if ((is_slot(slot)) && + (!slot_has_setter(slot)) && + (!is_syntactic_symbol(settee))) + { + if (is_normal_symbol(value)) + { + s7_pointer slot1 = s7_slot(sc, value); + if ((is_slot(slot1)) && (!slot_has_setter(slot1))) + { + pair_set_syntax_op(form, OP_SET_S_S); + set_opt2_sym(code, value); + }} + else + if ((!is_pair(value)) || + ((car(value) == sc->quote_symbol) && (is_pair(cdr(value))))) /* (quote . 1) ? */ + { + pair_set_syntax_op(form, OP_SET_S_C); + set_opt2_con(code, (is_pair(value)) ? cadr(value) : value); + } + else + { + pair_set_syntax_op(form, OP_SET_S_P); + if (is_optimized(value)) + { + if (optimize_op(value) == HOP_SAFE_C_SS) + { + if (settee == cadr(value)) + { + pair_set_syntax_op(form, OP_INCREMENT_SA); + fx_annotate_arg(sc, cddr(value), sc->curlet); /* this sets fx_proc(arg) */ + set_opt2_pair(code, cddr(value)); + } + else + { + pair_set_syntax_op(form, OP_SET_S_A); + fx_annotate_arg(sc, cdr(code), sc->curlet); + }} + else + { + if (is_fxable(sc, value)) /* value = cadr(code) */ + { + pair_set_syntax_op(form, OP_SET_S_A); + fx_annotate_arg(sc, cdr(code), sc->curlet); + } + if ((is_safe_c_op(optimize_op(value))) && + (is_pair(cdr(value))) && + (settee == cadr(value)) && + (!is_null(cddr(value)))) + { + if (is_null(cdddr(value))) + { + if (is_fxable(sc, caddr(value))) + { + pair_set_syntax_op(form, OP_INCREMENT_SA); + fx_annotate_arg(sc, cddr(value), sc->curlet); /* this sets fx_proc(arg) */ + set_opt2_pair(code, cddr(value)); + }} + else + if ((is_null(cddddr(value))) && + (is_fxable(sc, caddr(value))) && + (is_fxable(sc, cadddr(value)))) + { + pair_set_syntax_op(form, OP_INCREMENT_SAA); + fx_annotate_args(sc, cddr(value), sc->curlet); + /* fx_annotate_arg(sc, cdddr(value), sc->curlet); */ + set_opt2_pair(code, cddr(value)); + }}}} + if ((is_h_optimized(value)) && + (is_safe_c_op(optimize_op(value))) && /* else might not be opt1_cfunc? (opt1_lambda probably) */ + (!is_unsafe(value)) && /* is_unsafe(value) can happen! */ + (is_not_null(cdr(value)))) /* (set! x (y)) */ + { + if (is_not_null(cddr(value))) + { + if ((caddr(value) == int_one) && + (cadr(value) == settee)) + { + if (opt1_cfunc(value) == sc->add_x1) + pair_set_syntax_op(form, OP_INCREMENT_BY_1); + else + if (opt1_cfunc(value) == sc->subtract_x1) + pair_set_syntax_op(form, OP_DECREMENT_BY_1); + } + else + if ((cadr(value) == int_one) && + (caddr(value) == settee) && + (opt1_cfunc(value) == sc->add_1x)) + pair_set_syntax_op(form, OP_INCREMENT_BY_1); + else + if ((settee == caddr(value)) && + (is_safe_symbol(cadr(value))) && + (caadr(code) == sc->cons_symbol)) + { + pair_set_syntax_op(form, OP_SET_CONS); + set_opt2_sym(code, cadr(value)); + }}}}}} +} + +static void op_set_s_c(s7_scheme *sc) +{ + s7_pointer slot = T_Slt(s7_slot(sc, cadr(sc->code))); + if (is_immutable(slot)) + error_nr(sc, sc->immutable_error_symbol, set_elist_3(sc, wrap_string(sc, "~S, but ~S is immutable", 23), sc->code, cadr(sc->code))); + slot_set_value(slot, sc->value = opt2_con(cdr(sc->code))); +} + +static inline void op_set_s_s(s7_scheme *sc) +{ + s7_pointer slot = T_Slt(s7_slot(sc, cadr(sc->code))); + if (is_immutable(slot)) + error_nr(sc, sc->immutable_error_symbol, set_elist_3(sc, wrap_string(sc, "~S, but ~S is immutable", 23), sc->code, cadr(sc->code))); + slot_set_value(slot, sc->value = lookup(sc, opt2_sym(cdr(sc->code)))); +} + +static Inline void op_set_s_a(s7_scheme *sc) +{ + s7_pointer slot = T_Slt(s7_slot(sc, cadr(sc->code))); + if (is_immutable(slot)) + error_nr(sc, sc->immutable_error_symbol, set_elist_3(sc, wrap_string(sc, "~S, but ~S is immutable", 23), sc->code, cadr(sc->code))); + slot_set_value(slot, sc->value = fx_call(sc, cddr(sc->code))); +} + +static void op_set_s_p(s7_scheme *sc) +{ + check_stack_size(sc); + push_stack_no_args(sc, OP_SET_SAFE, cadr(sc->code)); /* only path to op_set_safe, but we're not safe! cadr(sc->code) might be immutable */ + sc->code = caddr(sc->code); +} + +static void op_set_safe(s7_scheme *sc) /* name is misleading -- we need to check for immutable slot */ +{ + s7_pointer slot = s7_slot(sc, sc->code); + if (is_slot(slot)) + { + if (is_immutable_slot(slot)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->set_symbol, sc->code)); + slot_set_value(slot, sc->value); + } + else + if (has_let_set_fallback(sc->curlet)) + sc->value = call_let_set_fallback(sc, sc->curlet, sc->code, sc->value); + else unbound_variable_error_nr(sc, sc->code); +} + +static void op_set_from_let_temp(s7_scheme *sc) +{ + s7_pointer settee = sc->code; + s7_pointer slot = s7_slot(sc, settee); + if (!is_slot(slot)) + unbound_variable_error_nr(sc, settee); + if (is_immutable_slot(slot)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->let_temporarily_symbol, settee)); + slot_set_value(slot, (slot_has_setter(slot)) ? call_setter(sc, slot, sc->value) : sc->value); +} + +static inline void op_set_cons(s7_scheme *sc) +{ + s7_pointer slot = s7_slot(sc, cadr(sc->code)); + slot_set_value(slot, sc->value = cons(sc, lookup(sc, opt2_sym(cdr(sc->code))), slot_value(slot))); /* ([set!] bindings (cons v bindings)) */ +} + +static void op_increment_saa(s7_scheme *sc) +{ + s7_pointer slot, arg, val; + sc->code = cdr(sc->code); + slot = s7_slot(sc, car(sc->code)); + arg = opt2_pair(sc->code); /* cddr(value) */ + val = fx_call(sc, cdr(arg)); + set_car(sc->t3_2, fx_call(sc, arg)); + set_car(sc->t3_3, val); + set_car(sc->t3_1, slot_value(slot)); + slot_set_value(slot, sc->value = fn_proc(cadr(sc->code))(sc, sc->t3_1)); +} + +static void op_increment_sa(s7_scheme *sc) +{ + s7_pointer slot, arg; + sc->code = cdr(sc->code); + slot = s7_slot(sc, car(sc->code)); + arg = opt2_pair(sc->code); + set_car(sc->t2_2, fx_call(sc, arg)); + set_car(sc->t2_1, slot_value(slot)); + slot_set_value(slot, sc->value = fn_proc(cadr(sc->code))(sc, sc->t2_1)); +} + +static noreturn void no_setter_error_nr(s7_scheme *sc, s7_pointer obj) +{ + /* sc->code here is form without set!: ((abs 1) 2) from (set! (abs 1) 2) + * but in implicit case, (let ((L (list 0))) (set! (L 0 0) 2)), code is ((0 0) 2) + * at entry to s7_error: ((0 0 2)?? but we print something from define-hook-function if in the repl + * add indices and new-value args, is unevaluated code always available? + */ + int32_t typ = type(obj); + if (!is_pair(car(sc->code))) sc->code = cdr(sc->code); + + if (type(caar(sc->code)) >= T_C_FUNCTION_STAR) + error_nr(sc, sc->no_setter_symbol, + set_elist_6(sc, wrap_string(sc, "~W (~A) does not have a setter: (set! (~W~{~^ ~S~}) ~S)", 55), + caar(sc->code), sc->type_names[typ], caar(sc->code), cdar(sc->code), cadr(sc->code))); + error_nr(sc, sc->no_setter_symbol, + set_elist_5(sc, wrap_string(sc, "~A (~A) does not have a setter: (set! ~S ~S)", 44), + caar(sc->code), sc->type_names[typ], + (is_pair(car(sc->code))) ? copy_proper_list(sc, car(sc->code)) : car(sc->code), + (is_pair(cadr(sc->code))) ? copy_proper_list(sc, cadr(sc->code)) : cadr(sc->code))); + /* copy is necessary due to the way quoted lists|symbols are handled in op_set_with_let_1|2 and copy_tree */ +} + +static bool pair3_cfunc(s7_scheme *sc, s7_pointer obj, s7_pointer setf, s7_pointer arg, s7_pointer value) +{ + if (!c_function_is_aritable(setf, 2)) + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_6(sc, wrap_string(sc, "set!: two arguments? (~A ~S ~S), ~A is (setter ~A)", 50), setf, arg, value, setf, obj)); + if (!is_safe_procedure(setf)) /* if unsafe, we can't call c_function_call(setf) directly (need drop into eval+apply) */ + { + sc->code = setf; + sc->args = list_2(sc, arg, value); + return(true); + } + sc->value = c_function_call(setf)(sc, with_list_t2(arg, value)); + return(false); +} + +static bool set_pair3(s7_scheme *sc, s7_pointer obj, s7_pointer arg, s7_pointer value) +{ + switch (type(obj)) + { + case T_C_OBJECT: + sc->value = (*(c_object_set(sc, obj)))(sc, with_list_t3(obj, arg, value)); + break; + + case T_FLOAT_VECTOR: + sc->value = g_fv_set_3(sc, with_list_t3(obj, arg, value)); + break; + case T_INT_VECTOR: + sc->value = g_iv_set_3(sc, with_list_t3(obj, arg, value)); + break; + case T_BYTE_VECTOR: + sc->value = g_bv_set_3(sc, with_list_t3(obj, arg, value)); + break; + case T_VECTOR: +#if WITH_GMP + sc->value = g_vector_set_3(sc, with_list_t3(obj, arg, value)); +#else + if (vector_rank(obj) > 1) + sc->value = g_vector_set(sc, with_list_t3(obj, arg, value)); + else + { + s7_int index; + if (!is_t_integer(arg)) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "vector-set!: index must be an integer: ~S", 41), sc->code)); + index = integer(arg); + if (index < 0) + error_nr(sc, sc->out_of_range_symbol, set_elist_2(sc, wrap_string(sc, "vector-set!: index must not be negative: ~S", 43), sc->code)); + if (index >= vector_length(obj)) + error_nr(sc, sc->out_of_range_symbol, set_elist_2(sc, wrap_string(sc, "vector-set!: index must be less than vector length: ~S", 54), sc->code)); + if (is_immutable_vector(obj)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->vector_set_symbol, obj)); + if (is_typed_vector(obj)) + value = typed_vector_setter(sc, obj, index, value); + else vector_element(obj, index) = value; + sc->value = T_Ext(value); + } +#endif + break; + + case T_STRING: +#if WITH_GMP + sc->value = g_string_set(sc, with_list_t3(obj, arg, value)); +#else + { + s7_int index; + if (!is_t_integer(arg)) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "index must be an integer: ~S", 28), sc->code)); + index = integer(arg); + if (index < 0) + error_nr(sc, sc->out_of_range_symbol, set_elist_2(sc, wrap_string(sc, "index must not be negative: ~S", 30), sc->code)); + if (index >= string_length(obj)) + error_nr(sc, sc->out_of_range_symbol, set_elist_2(sc, wrap_string(sc, "index must be less than sequence length: ~S", 43), sc->code)); + if (is_immutable_string(obj)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->string_set_symbol, obj)); + if (!is_character(value)) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "string-set!: value must be a character: ~S", 42), sc->code)); + string_value(obj)[index] = (char)s7_character(value); + sc->value = value; + } +#endif + break; + + case T_PAIR: + sc->value = g_list_set(sc, with_list_t3(obj, arg, value)); + break; + + case T_HASH_TABLE: + if (is_immutable_hash_table(obj)) /* not checked in s7_hash_table_set */ + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->hash_table_set_symbol, obj)); + sc->value = s7_hash_table_set(sc, obj, arg, value); + break; + + case T_LET: + sc->value = let_set(sc, obj, arg, value); /* this checks immutable */ + break; + + case T_C_RST_NO_REQ_FUNCTION: case T_C_FUNCTION: + case T_C_FUNCTION_STAR: /* obj here is a c_function, but its setter could be a closure and vice versa below */ + if (is_c_function(c_function_setter(obj))) + return(pair3_cfunc(sc, obj, c_function_setter(obj), arg, value)); + sc->code = c_function_setter(obj); /* closure/macro */ + sc->args = (needs_copied_args(sc->code)) ? list_2(sc, arg, value) : set_plist_2(sc, arg, value); + return(true); /* goto APPLY; not redundant -- setter type might not match getter type */ + + case T_C_MACRO: /* (set! (setter quasiquote) (lambda args args)) (define (f) (set! (quasiquote 1) (setter 'i))) (f) (f) */ + if (is_c_function(c_macro_setter(obj))) + return(pair3_cfunc(sc, obj, c_macro_setter(obj), arg, value)); + sc->code = c_macro_setter(obj); + sc->args = (needs_copied_args(sc->code)) ? list_2(sc, arg, value) : set_plist_2(sc, arg, value); + return(true); /* goto APPLY; */ + + case T_MACRO: case T_MACRO_STAR: + case T_BACRO: case T_BACRO_STAR: + case T_CLOSURE: case T_CLOSURE_STAR: + if (is_c_function(closure_setter(obj))) + return(pair3_cfunc(sc, obj, closure_setter(obj), arg, value)); + sc->code = closure_setter(obj); + sc->args = (needs_copied_args(sc->code)) ? list_2(sc, arg, value) : set_plist_2(sc, arg, value); + return(true); /* goto APPLY; */ + + default: + no_setter_error_nr(sc, obj); /* possibly a continuation/goto? */ + } + return(false); +} + +static bool op_set_opsq_a(s7_scheme *sc) /* (set! (symbol) fxable) */ +{ + s7_pointer setf, value, code = cdr(sc->code); + s7_pointer obj = lookup_checked(sc, caar(code)); + + if ((is_sequence(obj)) && (!is_c_object(obj))) + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_3(sc, wrap_string(sc, "set!: not enough arguments for ~S: ~S", 37), caar(code), sc->code)); + + setf = setter_p_pp(sc, obj, sc->curlet); + if (is_any_macro(setf)) + { + sc->code = setf; + sc->args = cdr(code); + return(true); + } + value = fx_call(sc, cdr(code)); + if (is_c_function(setf)) + { + if (c_function_min_args(setf) > 1) + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_3(sc, wrap_string(sc, "set!: not enough arguments: (~A ~S)", 35), setf, value)); + sc->value = c_function_call(setf)(sc, with_list_t1(value)); + return(false); + } + sc->code = setf; + sc->args = list_1(sc, value); + return(true); +} + +static bool op_set_opsaq_a(s7_scheme *sc) /* (set! (symbol fxable) fxable) */ +{ + s7_pointer index, value, code = cdr(sc->code); + s7_pointer obj = lookup_checked(sc, caar(code)); + bool result; + if (could_be_macro_setter(obj)) + { + s7_pointer setf = setter_p_pp(sc, obj, sc->curlet); + if (is_any_macro(setf)) + { + sc->code = setf; + sc->args = pair_append(sc, cdar(code), cdr(code)); + return(true); + }} + value = fx_call(sc, cdr(code)); + gc_protect_via_stack(sc, value); + if (dont_eval_args(obj)) /* this check is ridiculously expensive! 60 in tstar, similar lg, but it's faster than is_any_macro */ + index = cadar(code); /* if obj is a c_macro, surely we don't want to evaluate cdar(code)? */ + else index = fx_call(sc, cdar(code)); + set_stack_protected2(sc, index); + result = set_pair3(sc, obj, index, value); + unstack(sc); + return(result); +} + +static inline bool op_set_opsaq_p(s7_scheme *sc) +{ + s7_pointer code = cdr(sc->code); + /* ([set!] (car a) (cadr a)) */ + /* here the pair can't generate multiple values, or if it does, it's an error (caught below) + * splice_in_values will notice the OP_SET_opSAq_P_1 and complain. + * (let () (define (hi) (let ((str "123")) (set! (str 0) (values #\a)) str)) (hi) (hi)) is "a23" + * (let ((v (make-vector '(2 3) 0))) (set! (v (values 0 1)) 23) v) -> #2D((0 23 0) (0 0 0)) + */ + s7_pointer obj = lookup_checked(sc, caar(code)); + if (could_be_macro_setter(obj)) + { + s7_pointer setf = setter_p_pp(sc, obj, sc->curlet); + if (is_any_macro(setf)) + { + sc->code = setf; + sc->args = pair_append(sc, cdar(code), cdr(code)); + return(true); + }} + push_stack(sc, OP_SET_opSAq_P_1, obj, code); + sc->code = cadr(code); + return(false); +} + +static inline bool op_set_opsaq_p_1(s7_scheme *sc) +{ + s7_pointer value = sc->value; + s7_pointer index; + if (dont_eval_args(sc->args)) /* see above */ + index = cadar(sc->code); + else index = fx_call(sc, cdar(sc->code)); + return(set_pair3(sc, sc->args, index, value)); /* not lookup, (set! (_!asdf!_ 3) 'a) -> unbound_variable */ +} + +static bool pair4_cfunc(s7_scheme *sc, s7_pointer obj, s7_pointer setf, s7_pointer index1, s7_pointer index2, s7_pointer value) +{ + if (!c_function_is_aritable(setf, 3)) + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_7(sc, wrap_string(sc, "set!: three arguments? (~A ~S ~S ~S), ~A is (setter ~A)", 55), setf, index1, index2, value, setf, obj)); + if (!is_safe_procedure(setf)) + { + sc->code = setf; + sc->args = list_3(sc, index1, index2, value); + return(true); + } + sc->value = c_function_call(setf)(sc, with_list_t3(index1, index2, value)); + return(false); +} + +static bool set_pair4(s7_scheme *sc, s7_pointer obj, s7_pointer index1, s7_pointer index2, s7_pointer value) +{ + switch (type(obj)) + { + case T_C_OBJECT: + sc->value = (*(c_object_ref(sc, obj)))(sc, with_list_t2(obj, index1)); + return(set_pair3(sc, sc->value, index2, value)); + + case T_FLOAT_VECTOR: + sc->value = g_float_vector_set(sc, with_list_t4(obj, index1, index2, value)); /* would set_plist_4 be faster? or fv_unchecked_set_4? */ + break; + case T_INT_VECTOR: + sc->value = g_int_vector_set(sc, with_list_t4(obj, index1, index2, value)); + break; + case T_BYTE_VECTOR: + sc->value = g_byte_vector_set(sc, with_list_t4(obj, index1, index2, value)); + break; + case T_VECTOR: + if (vector_rank(obj) == 2) + sc->value = g_vector_set_4(sc, with_list_t4(obj, index1, index2, value)); + else + { + sc->value = g_vector_ref(sc, with_list_t2(obj, index1)); + return(set_pair3(sc, sc->value, index2, value)); + } + break; + + case T_PAIR: + sc->value = g_list_ref(sc, with_list_t2(obj, index1)); + return(set_pair3(sc, sc->value, index2, value)); + + case T_HASH_TABLE: + sc->value = s7_hash_table_ref(sc, obj, index1); + return(set_pair3(sc, sc->value, index2, value)); + + case T_LET: + sc->value = let_ref(sc, obj, index1); + return(set_pair3(sc, sc->value, index2, value)); + + case T_C_RST_NO_REQ_FUNCTION: case T_C_FUNCTION: + case T_C_FUNCTION_STAR: /* obj here is a c_function, but its setter could be a closure and vice versa below */ + if (is_c_function(c_function_setter(obj))) + return(pair4_cfunc(sc, obj, c_function_setter(obj), index1, index2, value)); + sc->code = c_function_setter(obj); /* closure|macro */ + sc->args = (needs_copied_args(sc->code)) ? list_3(sc, index1, index2, value) : set_plist_3(sc, index1, index2, value); + return(true); /* goto APPLY; not redundant -- setter type might not match getter type */ + + case T_C_MACRO: /* (set! (setter quasiquote) (lambda (a . b) a)) (let () (define (func) (set! (quasiquote 'a 0) 3)) (func) (func)) */ + if (is_c_function(c_macro_setter(obj))) + return(pair4_cfunc(sc, obj, c_macro_setter(obj), index1, index2, value)); + sc->code = c_macro_setter(obj); + sc->args = (needs_copied_args(sc->code)) ? list_3(sc, index1, index2, value) : set_plist_3(sc, index1, index2, value); + return(true); /* goto APPLY; */ + + case T_MACRO: case T_MACRO_STAR: + case T_BACRO: case T_BACRO_STAR: + case T_CLOSURE: case T_CLOSURE_STAR: + if (is_c_function(closure_setter(obj))) + return(pair4_cfunc(sc, obj, closure_setter(obj), index1, index2, value)); + sc->code = closure_setter(obj); + sc->args = (needs_copied_args(sc->code)) ? list_3(sc, index1, index2, value) : set_plist_3(sc, index1, index2, value); + return(true); /* goto APPLY; */ + + default: + no_setter_error_nr(sc, obj); /* possibly a continuation/goto or string */ + } + return(false); /* goto start */ +} + +static bool op_set_opsaaq_a(s7_scheme *sc) /* (set! (symbol fxable fxable) fxable) */ +{ + s7_pointer index1, value, code = cdr(sc->code); + s7_pointer obj = lookup_checked(sc, caar(code)); + bool result; + if (could_be_macro_setter(obj)) + { + s7_pointer setf = setter_p_pp(sc, obj, sc->curlet); + if (is_any_macro(setf)) + { + sc->code = setf; + sc->args = pair_append(sc, cdar(code), cdr(code)); + return(true); + }} + value = fx_call(sc, cdr(code)); + gc_protect_via_stack(sc, value); + index1 = fx_call(sc, cdar(code)); + set_stack_protected2(sc, index1); + result = set_pair4(sc, obj, index1, fx_call(sc, cddar(code)), value); + unstack(sc); + return(result); +} + +static bool op_set_opsaaq_p(s7_scheme *sc) +{ + s7_pointer code = cdr(sc->code); + s7_pointer obj = lookup_checked(sc, caar(code)); + if (could_be_macro_setter(obj)) + { + s7_pointer setf = setter_p_pp(sc, obj, sc->curlet); + if (is_any_macro(setf)) + { + sc->code = setf; + sc->args = pair_append(sc, cdar(code), cdr(code)); + return(true); + }} + push_stack(sc, OP_SET_opSAAq_P_1, obj, code); + sc->code = cadr(code); + return(false); +} + +static bool op_set_opsaaq_p_1(s7_scheme *sc) +{ + s7_pointer value = sc->value; + bool result; + s7_pointer index1 = fx_call(sc, cdar(sc->code)); + gc_protect_via_stack(sc, index1); + result = set_pair4(sc, sc->args, index1, fx_call(sc, cddar(sc->code)), value); + unstack(sc); + return(result); +} + +static bool op_set1(s7_scheme *sc) +{ + s7_pointer lx = s7_slot(sc, sc->code); /* if unbound variable hook here, we need the binding, not the current value */ + if (is_slot(lx)) + { + if (is_immutable_slot(lx)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->set_symbol, slot_symbol(lx))); + if (slot_has_setter(lx)) + { + s7_pointer func = slot_setter(lx); + if (is_c_function(func)) + sc->value = call_c_function_setter(sc, func, sc->code, sc->value); /* perhaps better: apply_c_function -- has argnum error checks */ + else + if (is_any_procedure(func)) + { + /* don't push OP_EVAL_DONE here and call eval(sc, OP_APPLY) below -- setter might hit an error */ + /* 41297 (set! (v) val) where v=vector gets the setter, but calls vector-set! with no args */ + push_stack_no_args(sc, OP_SET_FROM_SETTER, lx); + if (has_let_arg(func)) + sc->args = list_3(sc, sc->code, sc->value, sc->curlet); + else sc->args = list_2(sc, sc->code, sc->value); /* these lists are reused as the closure_let slots in apply_lambda via apply_closure */ + sc->code = func; + return(false); /* goto APPLY */ + }} + slot_set_value(lx, sc->value); + symbol_increment_ctr(sc->code); /* see define setfib example in s7test.scm -- I'm having second thoughts about this... */ + return(true); /* continue */ + } + if (!has_let_set_fallback(sc->curlet)) /* (with-let (mock-hash-table 'b 2) (set! b 3)) */ + error_nr(sc, sc->unbound_variable_symbol, set_elist_4(sc, wrap_string(sc, "~S is unbound in (set! ~S ~S)", 29), sc->code, sc->code, sc->value)); + sc->value = call_let_set_fallback(sc, sc->curlet, sc->code, sc->value); + return(true); +} + +static bool op_set_with_let_1(s7_scheme *sc) +{ + s7_pointer e, b, x = sc->value; + /* from the T_SYNTAX branch of op_set_pair: (set! (with-let e b) x) as in let-temporarily + * here sc->value is the new value for the settee = x, args has the (as yet unevaluated) let and settee-expression. + * 'b above can be a pair = generalized set in the 'e environment. + */ + if (!is_pair(sc->args)) /* (set! (with-let) ...) */ + syntax_error_nr(sc, "with-let needs a let and a symbol: (set! (with-let) ~$)", 55, sc->value); + if (!is_pair(cdr(sc->args))) /* (set! (with-let e) ...) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "with-let in (set! (with-let ~S) ~$) has no symbol to set?", 57), car(sc->args), sc->value)); + + e = car(sc->args); + b = cadr(sc->args); + if (is_multiple_value(x)) /* (set! (with-let lt) (values 1 2)) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_4(sc, wrap_string(sc, "can't (set! (with-let ~S ~S) (values ~{~S~^ ~})): too many values", 65), e, b, x)); + + if (is_symbol(e)) + { + if (is_symbol(b)) + { + e = lookup_checked(sc, e); /* the let */ + if (!is_let(e)) + wrong_type_error_nr(sc, sc->let_set_symbol, 1, e, a_let_string); + sc->value = let_set_1(sc, e, b, x); + pop_stack(sc); + return(true); + } + sc->value = lookup_checked(sc, e); + sc->code = set_plist_3(sc, sc->set_symbol, b, ((is_symbol(x)) || (is_pair(x))) ? set_plist_2(sc, sc->quote_symbol, x) : x); + /* (let* ((x (vector 1 2)) (lt (curlet))) (set! (with-let lt (x 0)) 32) x) here: (set! (x 0) 32) */ + return(false); /* goto SET_WITH_LET */ + } + sc->code = e; /* 'e above, an expression we need to evaluate */ + sc->args = set_plist_2(sc, b, x); /* can't reuse sc->args here via set-car! etc */ + push_stack_direct(sc, OP_SET_WITH_LET_2); + sc->cur_op = optimize_op(sc->code); + return(true); /* goto top_no_pop */ +} + +static bool op_set_with_let_2(s7_scheme *sc) +{ + s7_pointer b, x; + /* here sc->value = let = 'e, args = '(b x) where 'b might be a pair */ + if (!is_let(sc->value)) + wrong_type_error_nr(sc, sc->let_set_symbol, 1, sc->value, a_let_string); + b = car(sc->args); + if ((!is_symbol(b)) && (!is_pair(b))) + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "can't set ~S in ~$", 18), b, set_ulist_1(sc, global_value(sc->set_symbol), sc->args))); + x = cadr(sc->args); + if (is_symbol(b)) /* b is a symbol -- everything else is ready so call let-set! */ + { + sc->value = let_set_1(sc, sc->value, b, x); + return(true); /* continue */ + } + if ((is_symbol(x)) || (is_pair(x))) /* (set! (with-let (inlet :v (vector 1 2)) (v 0)) 'a) */ + sc->code = list_3(sc, sc->set_symbol, b, + ((is_symbol(x)) || (is_pair(x))) ? list_2(sc, sc->quote_symbol, x) : x); + else sc->code = cons(sc, sc->set_symbol, sc->args); /* (set! (with-let (curlet) (*s7* 'print-length)) 16), x=16 b=(*s7* 'print-length) */ + return(false); /* fall into SET_WITH_LET */ +} + +static bool op_set_normal(s7_scheme *sc) +{ + s7_pointer x; + sc->code = cdr(sc->code); + x = cadr(sc->code); + if (is_pair(x)) + { + push_stack_no_args(sc, OP_SET1, car(sc->code)); + sc->code = x; + return(true); + } + sc->value = (is_symbol(x)) ? lookup_checked(sc, x) : T_Ext(x); + sc->code = car(sc->code); + return(false); +} + +static Inline void inline_op_increment_by_1(s7_scheme *sc) /* ([set!] ctr (+ ctr 1)) -- why is this always inlined? saves 22 in concordance */ +{ + s7_pointer val, y = T_Slt(s7_slot(sc, cadr(sc->code))); + val = slot_value(y); + if (is_t_integer(val)) + sc->value = make_integer(sc, integer(val) + 1); + else + switch (type(val)) + { + case T_RATIO: + new_cell(sc, sc->value, T_RATIO); + numerator(sc->value) = numerator(val) + denominator(val); + denominator(sc->value) = denominator(val); + break; + case T_REAL: + sc->value = make_real(sc, real(val) + 1.0); + break; + case T_COMPLEX: + new_cell(sc, sc->value, T_COMPLEX); + set_real_part(sc->value, real_part(val) + 1.0); + set_imag_part(sc->value, imag_part(val)); + break; + default: + sc->value = add_p_pp(sc, val, int_one); + break; + } + slot_set_value(y, sc->value); +} + +static void op_decrement_by_1(s7_scheme *sc) /* ([set!] ctr (- ctr 1)) */ +{ + s7_pointer val, y = T_Slt(s7_slot(sc, cadr(sc->code))); + val = slot_value(y); + if (is_t_integer(val)) + sc->value = make_integer(sc, integer(val) - 1); /* increment (set!) returns the new value in sc->value */ + else + switch (type(val)) + { + case T_RATIO: + new_cell(sc, sc->value, T_RATIO); + numerator(sc->value) = numerator(val) - denominator(val); + denominator(sc->value) = denominator(val); + break; + case T_REAL: + sc->value = make_real(sc, real(val) - 1.0); + break; + case T_COMPLEX: + new_cell(sc, sc->value, T_COMPLEX); + set_real_part(sc->value, real_part(val) - 1.0); + set_imag_part(sc->value, imag_part(val)); + break; + default: + sc->value = g_subtract(sc, set_plist_2(sc, val, int_one)); + break; + } + slot_set_value(y, sc->value); +} + + +/* ---------------- implicit ref/set ---------------- */ +static Inline bool inline_op_implicit_vector_ref_a(s7_scheme *sc) /* called once in eval */ +{ + s7_pointer x; + s7_pointer v = lookup_checked(sc, car(sc->code)); + if (!is_any_vector(v)) + { + sc->last_function = v; + return(false); + } + x = fx_call(sc, cdr(sc->code)); + if ((s7_is_integer(x)) && + (vector_rank(v) == 1)) + { + s7_int index = s7_integer_clamped_if_gmp(sc, x); + if ((index < vector_length(v)) && (index >= 0)) + { + sc->value = (is_float_vector(v)) ? make_real(sc, float_vector(v, index)) : vector_getter(v)(sc, v, index); + return(true); + }} + sc->value = vector_ref_1(sc, v, set_plist_1(sc, x)); + return(true); +} + +static bool op_implicit_vector_ref_aa(s7_scheme *sc) /* if Inline 70 in concordance */ +{ + s7_pointer x, y, code; + s7_pointer v = lookup_checked(sc, car(sc->code)); + if ((!is_any_vector(v)) || (vector_rank(v) != 2)) + { + sc->last_function = v; + return(false); + } + code = cdr(sc->code); + x = fx_call(sc, code); + gc_protect_via_stack(sc, x); + y = fx_call(sc, cdr(code)); + set_stack_protected2(sc, y); + if ((s7_is_integer(x)) && (s7_is_integer(y)) && + (vector_rank(v) == 2)) + { + s7_int ix = s7_integer_clamped_if_gmp(sc, x); + s7_int iy = s7_integer_clamped_if_gmp(sc, y); + if ((ix >= 0) && (iy >= 0) && + (ix < vector_dimension(v, 0)) && (iy < vector_dimension(v, 1))) + { + s7_int index = (ix * vector_offset(v, 0)) + iy; + sc->value = vector_getter(v)(sc, v, index); /* check for normal vector saves in some cases, costs in others */ + unstack(sc); + return(true); + }} + sc->value = vector_ref_1(sc, v, set_plist_2(sc, x, y)); + unstack(sc); + return(true); +} + +static /* inline */ bool op_implicit_vector_set_3(s7_scheme *sc) /* inline no diffs? */ +{ + s7_pointer i1, code = cdr(sc->code); + s7_pointer v = lookup(sc, caar(code)); + if (!is_any_vector(v)) + { + /* this could be improved -- set_pair3 perhaps: pair3 set opt3? but this calls g_vector_set_3 */ + pair_set_syntax_op(sc->code, OP_SET_UNCHECKED); + return(true); + } + i1 = fx_call(sc, cdar(code)); /* gc protect? */ + set_car(sc->t3_3, fx_call(sc, cdr(code))); + set_car(sc->t3_1, v); + set_car(sc->t3_2, i1); + sc->value = g_vector_set_3(sc, sc->t3_1); /* calls vector_setter handling any vector type whereas vector_set_p_ppp wants a normal vector */ + /* sc->value = vector_set_p_ppp(sc, v, i1, fx_call(sc, cdr(code))); */ + return(false); +} + +static bool op_implicit_vector_set_4(s7_scheme *sc) +{ + s7_pointer i1, i2, code = cdr(sc->code); + s7_pointer v = lookup(sc, caar(code)); + if (!is_any_vector(v)) + { + pair_set_syntax_op(sc->code, OP_SET_UNCHECKED); + return(true); + } + i1 = fx_call(sc, cdar(code)); + i2 = fx_call(sc, opt3_pair(sc->code)); /* cddar(code) */ + set_car(sc->t3_3, fx_call(sc, cdr(code))); + set_car(sc->t4_1, v); + set_car(sc->t3_1, i1); + set_car(sc->t3_2, i2); + sc->value = g_vector_set_4(sc, sc->t4_1); + set_car(sc->t4_1, sc->F); + return(false); +} + +static goto_t call_set_implicit(s7_scheme *sc, s7_pointer obj, s7_pointer inds, s7_pointer val, s7_pointer form); + +static goto_t set_implicit_vector(s7_scheme *sc, s7_pointer vect, s7_pointer inds, s7_pointer val, s7_pointer form) +{ + /* vect is the vector, sc->code is expr without the set!, form is the full expr, args have not been evaluated! */ + s7_pointer index; + s7_int argnum; + + if (!is_pair(inds)) + wrong_number_of_arguments_error_nr(sc, "no index for implicit vector-set!: ~S", 37, form); + if (is_immutable_vector(vect)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->vector_set_symbol, vect)); + + argnum = proper_list_length(inds); + if ((argnum > 1) && + (is_normal_vector(vect)) && + (argnum != vector_rank(vect))) + { + /* this block needs to be first to handle (eg): + * (let ((v (vector (inlet 'a 0)))) (set! (v 0 'a) 32) v): #((inlet 'a 32)) + * sc->code here: ((v 0 'a) 32) + */ + if (vector_rank(vect) == 1) + { + s7_pointer ind = car(inds); + if (is_symbol(ind)) ind = lookup_checked(sc, ind); + if (is_t_integer(ind)) + { + s7_pointer obj; + s7_int index1 = integer(ind); + if ((index1 < 0) || (index1 >= vector_length(vect))) + out_of_range_error_nr(sc, sc->vector_ref_symbol, int_two, car(inds), (index1 < 0) ? it_is_negative_string : it_is_too_large_string); + obj = vector_element(vect, index1); + if (!is_applicable(obj)) + error_nr(sc, sc->no_setter_symbol, + set_elist_5(sc, wrap_string(sc, "in ~S, (~S ~S) is ~S which can't take arguments", 47), form, vect, car(inds), obj)); + return(call_set_implicit(sc, obj, cdr(inds), val, form)); + }} + push_stack(sc, OP_SET2, cdr(inds), val); + sc->code = list_2(sc, vect, car(inds)); + return(goto_unopt); + } + + if ((argnum > 1) || (vector_rank(vect) > 1)) + { + if ((argnum == 2) && + (cdr(form) == sc->code) && /* form == cdr(sc->code) only on the outer call, thereafter form is the old form for better error messages */ + (is_fxable(sc, car(inds))) && + (is_fxable(sc, cadr(inds))) && + (is_fxable(sc, car(val)))) /* (set! (v fx fx) fx) */ + { + fx_annotate_args(sc, inds, sc->curlet); + fx_annotate_arg(sc, val, sc->curlet); + set_opt3_pair(form, cdr(inds)); + pair_set_syntax_op(form, OP_IMPLICIT_VECTOR_SET_4); + } + if ((argnum == vector_rank(vect)) && + (!is_pair(car(val)))) + { + s7_pointer p; + for (p = inds; is_pair(p); p = cdr(p)) + if (is_pair(car(p))) break; + if (is_null(p)) + { + s7_pointer pa; + s7_pointer args = safe_list_if_possible(sc, argnum + 2); + if (in_heap(args)) gc_protect_via_stack(sc, args); + car(args) = vect; + for (p = inds, pa = cdr(args); is_pair(p); p = cdr(p), pa = cdr(pa)) + { + index = car(p); + if (is_symbol(index)) + index = lookup_checked(sc, index); + if (!s7_is_integer(index)) + { + if (in_heap(args)) unstack(sc); + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "vector-set!: index must be an integer: ~S", 41), form)); + } + car(pa) = index; + } + car(pa) = car(val); + if (is_symbol(car(pa))) + car(pa) = lookup_checked(sc, car(pa)); + sc->value = g_vector_set(sc, args); + if (in_heap(args)) unstack(sc); + else clear_list_in_use(args); + return(goto_start); + }} + push_op_stack(sc, sc->vector_set_function); /* vector_setter(vect) has wrong args */ + sc->code = (is_null(cdr(inds))) ? val : pair_append(sc, cdr(inds), val); /* i.e. rest(args) + val */ + push_stack(sc, OP_EVAL_ARGS4, list_1(sc, vect), sc->code); + sc->code = car(inds); + sc->cur_op = optimize_op(sc->code); + return(goto_top_no_pop); + } + + /* one index, rank == 1 */ + index = car(inds); + if ((is_symbol(car(sc->code))) && /* not (set! (#(a 0 (3)) 1) 0) -- implicit_vector_set_3 assumes symbol vect ref */ + (cdr(form) == sc->code) && + (is_fxable(sc, index)) && + (is_fxable(sc, car(val)))) + { + fx_annotate_arg(sc, inds, sc->curlet); + fx_annotate_arg(sc, val, sc->curlet); + pair_set_syntax_op(form, OP_IMPLICIT_VECTOR_SET_3); + } + if (!is_pair(index)) + { + s7_int ind; + s7_pointer value; + + if (is_symbol(index)) + index = lookup_checked(sc, index); + if (!s7_is_integer(index)) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "vector-set!: index must be an integer: ~S", 41), sc->code)); + ind = s7_integer_clamped_if_gmp(sc, index); + if ((ind < 0) || (ind >= vector_length(vect))) + out_of_range_error_nr(sc, sc->vector_set_symbol, int_two, index, (ind < 0) ? it_is_negative_string : it_is_too_large_string); + value = car(val); + if (!is_pair(value)) + { + if (is_symbol(value)) + value = lookup_checked(sc, value); + if (is_typed_vector(vect)) + typed_vector_setter(sc, vect, ind, value); + else vector_setter(vect)(sc, vect, ind, value); + sc->value = T_Ext(value); + return(goto_start); + } + push_op_stack(sc, sc->vector_set_function); + sc->args = list_2(sc, index, vect); + sc->code = val; + return(goto_eval_args); + } + /* here the index calc might be trivial -- (+ i 1) or (- j 1) but this branch hardly ever happens */ + push_stack(sc, OP_EVAL_ARGS4, list_1(sc, vect), val); + push_op_stack(sc, sc->vector_set_function); + sc->code = car(inds); + sc->cur_op = optimize_op(sc->code); + return(goto_top_no_pop); +} + +static goto_t set_implicit_c_object(s7_scheme *sc, s7_pointer c_obj, s7_pointer inds, s7_pointer val, s7_pointer form) +{ + s7_pointer index; + /* c_obj's set! method needs to provide error checks */ + + if ((!is_pair(inds)) || (!is_null(cdr(inds)))) + { + push_op_stack(sc, sc->c_object_set_function); + if (is_null(inds)) + { + push_stack(sc, OP_EVAL_ARGS1, list_1(sc, c_obj), sc->nil); + sc->code = car(val); + } + else + { + sc->code = pair_append(sc, cdr(inds), val); + push_stack(sc, OP_EVAL_ARGS4, list_1(sc, c_obj), sc->code); + sc->code = car(inds); + } + sc->cur_op = optimize_op(sc->code); + return(goto_top_no_pop); + } + index = car(inds); + if (!is_pair(index)) + { + s7_pointer value = car(val); + if (is_symbol(index)) + index = lookup_checked(sc, index); + if (!is_pair(value)) + { + if (is_symbol(value)) + value = lookup_checked(sc, value); + sc->value = (*(c_object_set(sc, c_obj)))(sc, with_list_t3(c_obj, index, value)); + return(goto_start); + } + push_op_stack(sc, sc->c_object_set_function); + sc->args = list_2(sc, index, c_obj); + sc->code = val; + return(goto_eval_args); + } + push_stack(sc, OP_EVAL_ARGS4, list_1(sc, c_obj), val); + push_op_stack(sc, sc->c_object_set_function); + sc->code = car(inds); + sc->cur_op = optimize_op(sc->code); + return(goto_top_no_pop); +} + +static bool op_implicit_string_ref_a(s7_scheme *sc) +{ + s7_int index; + s7_pointer s = lookup_checked(sc, car(sc->code)); + s7_pointer x = fx_call(sc, cdr(sc->code)); + if (!is_string(s)) + { + sc->last_function = s; + return(false); + } + if (!s7_is_integer(x)) + { + sc->value = string_ref_1(sc, s, set_plist_1(sc, x)); + return(true); + } + index = s7_integer_clamped_if_gmp(sc, x); + if ((index < string_length(s)) && (index >= 0)) + { + sc->value = chars[((uint8_t *)string_value(s))[index]]; + return(true); + } + sc->value = string_ref_1(sc, s, x); + return(true); +} + +static goto_t set_implicit_string(s7_scheme *sc, s7_pointer str, s7_pointer inds, s7_pointer val, s7_pointer form) +{ + /* here only one index makes sense and it is required, so (set! ("str") #\a), (set! ("str" . 1) #\a) and (set! ("str" 1 2) #\a) are all errors (but see below!) */ + s7_pointer index; + + if (!is_pair(inds)) + wrong_number_of_arguments_error_nr(sc, "no index for string set!: ~S", 28, form); + if (!is_null(cdr(inds))) + wrong_number_of_arguments_error_nr(sc, "too many indices for string set!: ~S", 36, form); + + index = car(inds); + if (!is_pair(index)) + { + s7_int ind; + if (is_symbol(index)) + index = lookup_checked(sc, index); + if (!s7_is_integer(index)) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "index must be an integer: ~S", 28), form)); + ind = s7_integer_clamped_if_gmp(sc, index); + if ((ind < 0) || (ind >= string_length(str))) + out_of_range_error_nr(sc, sc->string_set_symbol, int_two, index, (ind < 0) ? it_is_negative_string : it_is_too_large_string); + if (is_immutable_string(str)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->string_set_symbol, str)); + + val = car(val); + if (!is_pair(val)) + { + if (is_symbol(val)) + val = lookup_checked(sc, val); + if (is_character(val)) + { + string_value(str)[ind] = character(val); + sc->value = val; + return(goto_start); + } + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "value must be a character: ~S", 29), form)); + } + /* maybe op_implicit_string_set_a as in vector someday, but this code isn't (currently) called much */ + push_op_stack(sc, sc->string_set_function); + sc->args = list_2(sc, index, str); + sc->code = cdr(sc->code); + return(goto_eval_args); + } + push_stack(sc, OP_EVAL_ARGS4, list_1(sc, str), val); /* args4 not 1 because we know cdr(sc->code) is a pair */ + push_op_stack(sc, sc->string_set_function); + sc->code = car(inds); + sc->cur_op = optimize_op(sc->code); + return(goto_top_no_pop); +} + +static goto_t set_implicit_pair(s7_scheme *sc, s7_pointer lst, s7_pointer inds, s7_pointer val, s7_pointer form) +{ + s7_pointer index, index_val = NULL, value = car(val); + + if (!is_pair(inds)) /* (!is_pair(val)) and (!is_null(cdr(val))) are apparently caught somewhere else */ + wrong_number_of_arguments_error_nr(sc, "no index for list-set!: ~S", 26, form); + + index = car(inds); + if (!is_pair(index)) + index_val = (is_normal_symbol(index)) ? lookup_checked(sc, index) : index; + + if (!is_null(cdr(inds))) + { + /* split (set! (a b c...) v) into (set! ((a b) c ...) v), eval (a b), return (let ((L (list (list 1 2)))) (set! (L 0 0) 3) L) */ + if (index_val) + { + s7_pointer obj = list_ref_1(sc, lst, index_val); + if (!is_applicable(obj)) + error_nr(sc, sc->no_setter_symbol, + set_elist_5(sc, wrap_string(sc, "in ~S, (~S ~$) is ~S which can't take arguments", 47), form, lst, index_val, obj)); + return(call_set_implicit(sc, obj, cdr(inds), val, form)); + } + push_stack(sc, OP_SET2, cdr(inds), val); /* (let ((L (list (list 1 2 3)))) (set! (L (- (length L) 1) 2) 0) L) */ + sc->code = list_2(sc, caadr(form), car(inds)); + return(goto_unopt); + } + if (index_val) + { + if (!is_pair(value)) + { + set_car(sc->t2_1, index_val); + set_car(sc->t2_2, (is_symbol(value)) ? lookup_checked(sc, value) : value); + sc->value = g_list_set_1(sc, lst, sc->t2_1, 2); + return(goto_start); + } + push_op_stack(sc, sc->list_set_function); /* because cdr(inds) is nil, we're definitely calling list_set */ + sc->args = list_2(sc, index_val, lst); /* plist unsafe here */ + sc->code = val; + return(goto_eval_args); + } + push_stack(sc, OP_EVAL_ARGS4, list_1(sc, lst), val); /* plist unsafe here */ + push_op_stack(sc, sc->list_set_function); + sc->code = car(inds); + sc->cur_op = optimize_op(sc->code); + return(goto_top_no_pop); +} + +static goto_t set_implicit_hash_table(s7_scheme *sc, s7_pointer table, s7_pointer inds, s7_pointer val, s7_pointer form) +{ + s7_pointer key, keyval = NULL; + + if (!is_pair(inds)) /* (!is_pair(val)) and (!is_null(cdr(val))) are apparently caught elsewhere */ + wrong_number_of_arguments_error_nr(sc, "no key for hash-table-set!: ~S", 30, form); + if (is_immutable_hash_table(table)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->hash_table_set_symbol, table)); + + key = car(inds); + if (is_pair(key)) + { + if (car(key) == sc->quote_symbol) + keyval = cadr(key); + } + else keyval = (is_normal_symbol(key)) ? lookup_checked(sc, key) : key; + + if (!is_null(cdr(inds))) + { + if (keyval) + { + s7_pointer obj = s7_hash_table_ref(sc, table, keyval); + if (obj == sc->F) /* (let ((h (hash-table 'b 1))) (set! (h 'a 'asdf) 32)) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_4(sc, wrap_string(sc, "in ~S, ~$ does not exist in ~S", 30), form, keyval, table)); + else + if (!is_applicable(obj)) /* (let ((h (hash-table 'b 1))) (set! (h 'b 'asdf) 32)) */ + error_nr(sc, sc->no_setter_symbol, + set_elist_5(sc, wrap_string(sc, "in ~S, (~S ~$) is ~S which can't take arguments", 47), form, table, keyval, obj)); + /* (let ((v (hash-table 'a (hash-table 'b 1)))) (set! (v 'a 'b 'b) 32) v) -> + * error: in (set! (v 'a 'b 'b) 32), ((hash-table 'b 1) 'b) is 1 which can't take arguments + * (let ((v (hash-table 'a (list 1 2)))) (set! (v 'a 1) 5)) -> code: (set! ((1 2) 1) 5) -> 5 (v: (hash-table 'a (1 5))) + */ + return(call_set_implicit(sc, obj, cdr(inds), val, form)); + } + push_stack(sc, OP_SET2, cdr(inds), val); /* (let ((L (hash-table 'b (hash-table 'a 1)))) (set! (L (symbol "b") (symbol "a")) 0) L) */ + sc->code = list_2(sc, caadr(form), key); /* plist unsafe */ + return(goto_unopt); + } + if (keyval) + { + s7_pointer value = car(val); + if (is_pair(value)) + { + if (car(value) == sc->quote_symbol) + { + sc->value = s7_hash_table_set(sc, table, keyval, cadr(value)); + return(goto_start); + }} + else + { + sc->value = s7_hash_table_set(sc, table, keyval, (is_normal_symbol(value)) ? lookup_checked(sc, value) : value); + return(goto_start); + } + push_op_stack(sc, sc->hash_table_set_function); /* because cdr(inds) is nil, we're definitely calling hash_table_set */ + sc->args = list_2(sc, keyval, table); /* plist unsafe here */ + sc->code = val; + return(goto_eval_args); + } + push_stack(sc, OP_EVAL_ARGS4, list_1(sc, table), val); /* plist unsafe here */ + push_op_stack(sc, sc->hash_table_set_function); + sc->code = car(inds); + sc->cur_op = optimize_op(sc->code); + return(goto_top_no_pop); +} + +static goto_t set_implicit_let(s7_scheme *sc, s7_pointer let, s7_pointer inds, s7_pointer val, s7_pointer form) +{ + s7_pointer sym, symval = NULL; + + if (!is_pair(inds)) /* as above, bad val caught elsewhere */ + wrong_number_of_arguments_error_nr(sc, "no symbol (variable name) for let-set!: ~S", 42, form); + + sym = car(inds); + if (is_pair(sym)) + { + if (car(sym) == sc->quote_symbol) + symval = cadr(sym); + } + else symval = (is_normal_symbol(sym)) ? lookup_checked(sc, sym) : sym; + + if (!is_null(cdr(inds))) + { + if (symval) + { + s7_pointer obj = let_ref(sc, let, symval); + if (!is_applicable(obj)) /* (let ((h (hash-table 'b 1))) (set! (h 'b 'asdf) 32)) */ + error_nr(sc, sc->no_setter_symbol, + set_elist_5(sc, wrap_string(sc, "in ~S, (~S ~$) is ~S which can't take arguments", 47), form, let, symval, obj)); + return(call_set_implicit(sc, obj, cdr(inds), val, form)); + } + push_stack(sc, OP_SET2, cdr(inds), val); + sc->code = list_2(sc, let, car(inds)); + return(goto_unopt); + } + if (symval) + { + s7_pointer value = car(val); + if (!is_pair(value)) + { + if (is_symbol(value)) + value = lookup_checked(sc, value); + sc->value = let_set(sc, let, symval, value); + return(goto_start); + } + push_op_stack(sc, sc->let_set_function); + sc->args = list_2(sc, symval, let); + sc->code = val; + return(goto_eval_args); + } + push_stack(sc, OP_EVAL_ARGS4, list_1(sc, let), val); + push_op_stack(sc, sc->let_set_function); + sc->code = car(inds); + sc->cur_op = optimize_op(sc->code); + return(goto_top_no_pop); +} + +static goto_t set_implicit_function(s7_scheme *sc, s7_pointer fnc) /* (let ((lst (list 1 2))) (set! (list-ref lst 0) 2) lst) */ +{ + if (!is_t_procedure(c_function_setter(fnc))) + { + if (!is_any_macro(c_function_setter(fnc))) + no_setter_error_nr(sc, fnc); + sc->args = (is_null(cdar(sc->code))) ? cdr(sc->code) : pair_append(sc, cdar(sc->code), cdr(sc->code)); + sc->code = c_function_setter(fnc); + /* here multiple-values can't happen because we don't eval the new-value argument */ + return(goto_apply); + } + /* here the setter can be anything, so we need to check the needs_copied_args bit. (set! ((dilambda / (let ((x 3)) (lambda (y) (+ x y))))) 3)! */ + push_op_stack(sc, c_function_setter(fnc)); + if (is_pair(cdar(sc->code))) + { + sc->value = (is_null(cddar(sc->code))) ? cdr(sc->code) : pair_append(sc, cddar(sc->code), cdr(sc->code)); + push_stack(sc, OP_EVAL_ARGS1, sc->nil, sc->value); + sc->code = cadar(sc->code); + } + else + { + push_stack(sc, OP_EVAL_ARGS1, sc->nil, sc->nil); + sc->code = cadr(sc->code); /* new value */ + } + sc->cur_op = optimize_op(sc->code); + return(goto_top_no_pop); +} + +static goto_t set_implicit_closure(s7_scheme *sc, s7_pointer fnc) +{ + s7_pointer setter = closure_setter(fnc); + if ((setter == sc->F) && (!closure_no_setter(fnc))) /* maybe closure_setter hasn't been set yet: see fset3 in s7test.scm */ + setter = setter_p_pp(sc, fnc, sc->curlet); + if (is_t_procedure(setter)) + { + /* (set! (o g) ...), here fnc = o, sc->code = ((o g) ...) */ + push_op_stack(sc, setter); + if (is_null(cdar(sc->code))) + { + push_stack(sc, OP_EVAL_ARGS1, sc->nil, cddr(sc->code)); + sc->code = cadr(sc->code); + } + else + { + if (is_null(cddar(sc->code))) + push_stack(sc, OP_EVAL_ARGS1, sc->nil, cdr(sc->code)); + else + { + sc->value = pair_append(sc, cddar(sc->code), cdr(sc->code)); + push_stack(sc, OP_EVAL_ARGS4, sc->nil, sc->value); + } + sc->code = cadar(sc->code); + }} + else + { + if (!is_any_macro(setter)) + no_setter_error_nr(sc, fnc); + sc->args = (is_null(cdar(sc->code))) ? cdr(sc->code) : pair_append(sc, cdar(sc->code), cdr(sc->code)); + sc->code = setter; + return(goto_apply); + } + sc->cur_op = optimize_op(sc->code); + return(goto_top_no_pop); +} + +static goto_t set_implicit_iterator(s7_scheme *sc, s7_pointer iter) +{ + s7_pointer setter = iterator_sequence(iter); + + if ((is_any_closure(setter)) || (is_any_macro(setter))) + setter = closure_setter(iterator_sequence(iter)); + else no_setter_error_nr(sc, iter); + + if (!is_null(cdar(sc->code))) /* (set! (iter ...) val) but iter is a thunk */ + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_3(sc, wrap_string(sc, "~S (an iterator): too many arguments: ~S", 40), iter, sc->code)); + + if (is_procedure(setter)) + { + push_op_stack(sc, setter); + push_stack(sc, OP_EVAL_ARGS1, sc->nil, sc->nil); + sc->code = cadr(sc->code); /* the (as yet unevaluated) value, incoming code was ((obj) val) */ + sc->cur_op = optimize_op(sc->code); + return(goto_top_no_pop); + } + sc->args = cdr(sc->code); + sc->code = setter; + return(goto_apply); +} + +static goto_t set_implicit_syntax(s7_scheme *sc, s7_pointer wlet) +{ + if (wlet != global_value(sc->with_let_symbol)) + no_setter_error_nr(sc, wlet); + + /* (set! (with-let a b) x), wlet = with-let, sc->code = ((with-let a b) x) + * a and x are in the current let, b is in a, we need to evaluate a and x, then + * call (with-let a-value (set! b x-value)) + */ + sc->args = cdar(sc->code); + sc->code = cadr(sc->code); + push_stack_direct(sc, OP_SET_WITH_LET_1); + sc->cur_op = optimize_op(sc->code); + return(goto_top_no_pop); +} + +static goto_t call_set_implicit(s7_scheme *sc, s7_pointer obj, s7_pointer inds, s7_pointer val, s7_pointer form) +{ + /* these depend on sc->code making sense given obj as the sequence being set */ + switch (type(obj)) + { + case T_STRING: return(set_implicit_string(sc, obj, inds, val, form)); + case T_PAIR: return(set_implicit_pair(sc, obj, inds, val, form)); + case T_HASH_TABLE: return(set_implicit_hash_table(sc, obj, inds, val, form)); + case T_LET: return(set_implicit_let(sc, obj, inds, val, form)); + case T_C_OBJECT: return(set_implicit_c_object(sc, obj, inds, val, form)); + case T_ITERATOR: return(set_implicit_iterator(sc, obj)); /* not sure this makes sense */ + case T_SYNTAX: return(set_implicit_syntax(sc, obj)); + + case T_INT_VECTOR: case T_FLOAT_VECTOR: case T_VECTOR: case T_BYTE_VECTOR: + return(set_implicit_vector(sc, obj, inds, val, form)); + + case T_C_MACRO: case T_C_FUNCTION_STAR: + case T_C_RST_NO_REQ_FUNCTION: case T_C_FUNCTION: + return(set_implicit_function(sc, obj)); /* (set! (setter...) ...) also comes here */ + + case T_MACRO: case T_MACRO_STAR: case T_BACRO: case T_BACRO_STAR: + case T_CLOSURE: case T_CLOSURE_STAR: + return(set_implicit_closure(sc, obj)); + + default: /* (set! (1 2) 3) */ + if (is_applicable(obj)) + no_setter_error_nr(sc, obj); /* this is reachable if obj is a goto or continuation: (set! (go 1) 2) in s7test.scm */ + error_nr(sc, sc->no_setter_symbol, + list_3(sc, wrap_string(sc, "in ~S, ~S has no setter", 23), + cons_unchecked(sc, sc->set_symbol, /* copy_tree(sc, form) also works but copies too much: we want to copy the ulists */ + cons(sc, copy_proper_list(sc, cadr(form)), cddr(form))), + obj)); + } + return(goto_top_no_pop); +} + +static goto_t set_implicit(s7_scheme *sc) /* sc->code incoming is (set! (...) ...) */ +{ + s7_pointer caar_code, obj, form = sc->code; + sc->code = cdr(sc->code); + caar_code = caar(sc->code); + if (is_symbol(caar_code)) + { + obj = s7_slot(sc, caar_code); + obj = (is_slot(obj)) ? slot_value(obj) : unbound_variable(sc, caar_code); + } + else + if (!is_pair(caar_code)) + obj = caar_code; + else + { + push_stack(sc, OP_SET2, cdar(sc->code), T_Pair(cdr(sc->code))); + sc->code = caar_code; + sc->cur_op = optimize_op(sc->code); + return(goto_top_no_pop); + } + /* code here is the setter and the value without the "set!": ((window-width) 800), (set! (hi 0) (* 2 3)) -> ((hi 0) (* 2 3)) */ + /* for gmp case, indices need to be decoded via s7_integer, not just integer */ + return(call_set_implicit(sc, obj, cdar(sc->code), cdr(sc->code), form)); +} + +static goto_t op_set2(s7_scheme *sc) +{ + if (is_pair(sc->value)) + { + /* (let ((L '((1 2 3)))) (set! ((L 0) 1) 32) L), (let ((L '(((1 2 3))))) (set! ((L 0) 0 1) 32) L) + * any deeper nesting was handled already by the first eval + * set! looks at its first argument, if it's a symbol, it sets the associated value, + * if it's a list, it looks at the car of that list to decide which setter to call, + * if it's a list of lists, it passes the embedded lists to eval, then looks at the + * car of the result. This means that we can do crazy things like: + * (let ((x '(1)) (y '(2))) (set! ((if #t x y) 0) 32) x) + * the other args need to be evaluated (but not the list as if it were code): + * (let ((L '((1 2 3))) (index 1)) (set! ((L 0) index) 32) L) + */ + if (!s7_is_proper_list(sc, sc->args)) /* (set! ('(1 2) 1 . 2) 1) */ + syntax_error_nr(sc, "set! target arguments are an improper list: ~A", 46, sc->args); + if (is_multiple_value(sc->value)) /* this has to be at least 2 args, sc->args and sc->code make 2 more, so... */ + syntax_error_nr(sc, "set!: too many arguments: ~S", 28, + set_ulist_1(sc, sc->set_symbol, pair_append(sc, multiple_value(sc->value), pair_append(sc, sc->args, sc->code)))); + if (sc->args == sc->nil) + syntax_error_nr(sc, "list set!: not enough arguments: ~S", 35, sc->code); + push_op_stack(sc, sc->list_set_function); + if (!is_null(cdr(sc->args))) sc->code = pair_append(sc, cdr(sc->args), sc->code); + push_stack(sc, OP_EVAL_ARGS4, list_1(sc, sc->value), T_Pair(sc->code)); + sc->code = car(sc->args); + return(goto_eval); + } + if ((is_any_vector(sc->value)) && + (vector_rank(sc->value) == proper_list_length(sc->args))) /* sc->code == new value? */ + { + /* (let ((L #(#(1 2 3) #(4 5 6)))) (set! ((L 1) 0) 32) L) + * bad case when args is nil: (let ((L #(#(1 2 3) #(4 5 6)))) (set! ((L 1)) 32) L) + */ + if (sc->args == sc->nil) + syntax_error_nr(sc, "vector set!: not enough arguments: ~S", 37, sc->code); + push_op_stack(sc, sc->vector_set_function); + if (!is_null(cdr(sc->args))) sc->code = pair_append(sc, cdr(sc->args), sc->code); + push_stack(sc, OP_EVAL_ARGS4, list_1(sc, sc->value), T_Pair(sc->code)); + sc->code = car(sc->args); + return(goto_eval); + } + sc->code = cons_unchecked(sc, sc->set_symbol, cons(sc, set_ulist_1(sc, sc->value, sc->args), sc->code)); /* (let ((x 32)) (set! ((curlet) 'x) 3) x) */ + return(set_implicit(sc)); +} + + +/* -------------------------------- do -------------------------------- */ +static bool safe_stepper_expr(s7_pointer expr, const s7_pointer var) +{ + /* for now, just look for stepper as last element of any list + * any embedded set is handled by do_is_safe, so we don't need to descend into the depths + */ + s7_pointer p; + if (cadr(expr) == var) return(false); + for (p = cdr(expr); is_pair(cdr(p)); p = cdr(p)); + if (is_pair(p)) + { + if ((is_optimized(p)) && + (op_has_hop(p)) && + (is_safe_c_op(optimize_op(p)))) + return(true); + if (car(p) == var) return(false); + } + else + if (p == var) return(false); + return(true); +} + +static bool tree_match(s7_pointer tree) +{ + if (is_symbol(tree)) + return(is_matched_symbol(tree)); + return((is_pair(tree)) && + ((tree_match(car(tree))) || (tree_match(cdr(tree))))); +} + +static bool do_is_safe(s7_scheme *sc, s7_pointer body, s7_pointer stepper, s7_pointer var_list, bool *has_set) +{ + /* here any (unsafe?) closure or jumping-op (call/cc) or shadowed variable is trouble + * we can free var_list if return(false) not after (!do_is_safe...), but it seems to make no difference, or be slightly slower + */ + /* sc->code is the complete do form (do ...) */ + + for (s7_pointer p = body; is_pair(p); p = cdr(p)) + { + s7_pointer expr = car(p); + if (is_pair(expr)) + { + s7_pointer x = car(expr); + + if ((!is_symbol(x)) && (!is_safe_c_function(x))) + return(false); + /* car(expr) ("x") is not a symbol: ((mus-data loc) chan) for example, but that's actually safe since it's + * just in effect vector-ref, there are several examples in dlocsig: ((group-speakers group) i) etc + */ + + if (is_symbol_and_syntactic(x)) + { + s7_pointer func = global_value(x), vars, cp; + opcode_t op = syntax_opcode(func); + switch (op) + { + case OP_MACROEXPAND: + return(false); + + case OP_QUOTE: + if ((!is_pair(cdr(expr))) || (!is_null(cddr(expr)))) /* (quote . 1) or (quote 1 2) etc */ + return(false); + break; + + case OP_LET: case OP_LET_STAR: + case OP_LETREC: case OP_LETREC_STAR: + if ((!is_pair(cdr(expr))) || + (!is_list(cadr(expr))) || + (!is_pair(cddr(expr)))) + return(false); + cp = var_list; + for (vars = cadr(expr); is_pair(vars); vars = cdr(vars)) + { + s7_pointer var; + if (!is_pair(car(vars))) return(false); + var = caar(vars); + if (direct_memq(var, ((op == OP_LET) || (op == OP_LETREC)) ? cp : var_list)) return(false); + if ((!is_symbol(var)) || (is_keyword(var))) return(false); + cp = cons(sc, var, cp); + sc->x = cp; + } + sc->x = sc->unused; + if (!do_is_safe(sc, cddr(expr), stepper, cp, has_set)) return(false); + break; + + case OP_DO: + if ((!is_pair(cdr(expr))) || (!is_pair(cddr(expr)))) /* (do) or (do (...)) */ + return(false); + cp = var_list; + for (vars = cadr(expr); is_pair(vars); vars = cdr(vars)) + { + s7_pointer var; + if (!is_pair(car(vars))) return(false); + var = caar(vars); + if ((direct_memq(var, cp)) || (var == stepper)) return(false); + cp = cons(sc, var, cp); + sc->x = cp; + if ((is_pair(cdar(vars))) && + (!do_is_safe(sc, cdar(vars), stepper, cp, has_set))) + { + sc->x = sc->unused; + return(false); + }} + sc->x = sc->unused; + if (!do_is_safe(sc, caddr(expr), stepper, cp, has_set)) return(false); + if ((is_pair(cdddr(expr))) && + (!do_is_safe(sc, cdddr(expr), stepper, cp, has_set))) + return(false); + break; + + case OP_SET: + { + s7_pointer settee; + if (!is_pair(cdr(expr))) /* (set!) */ + return(false); + settee = cadr(expr); + if (!is_symbol(settee)) /* (set! (...) ...) which is tricky due to setter functions/macros */ + { + s7_pointer setv; + if ((!is_pair(settee)) || (!is_symbol(car(settee)))) + return(false); + setv = lookup_unexamined(sc, car(settee)); + if (!((setv) && + ((is_sequence(setv)) || + ((is_c_function(setv)) && + (is_safe_procedure(c_function_setter(setv))))))) + return(false); + if (has_set) (*has_set) = true; + } + else + { + if ((is_pair(caddr(sc->code))) && /* sc->code = do-form (formerly (cdr(do-form)) causing a bug here) */ + (is_pair(caaddr(sc->code)))) + { + bool res; + set_match_symbol(settee); + res = tree_match(caaddr(sc->code)); /* (set! end ...) in some fashion */ + clear_match_symbol(settee); + if (res) return(false); + } + if ((has_set) && (!direct_memq(cadr(expr), var_list))) /* is some non-local variable being set? */ + (*has_set) = true; + } + if (!do_is_safe(sc, cddr(expr), stepper, var_list, has_set)) return(false); + if (!safe_stepper_expr(expr, stepper)) /* is step var's value used as the stored value by set!? */ + return(false); + } + break; + + case OP_LET_TEMPORARILY: + if ((!is_pair(cdr(expr))) || + (!is_pair(cadr(expr))) || + (!is_pair(cddr(expr)))) + return(false); + for (cp = cadr(expr); is_pair(cp); cp = cdr(cp)) + if ((!is_pair(car(cp))) || + (!is_pair(cdar(cp))) || + (!do_is_safe(sc, cdar(cp), stepper, var_list, has_set))) + return(false); + if (!do_is_safe(sc, cddr(expr), stepper, var_list, has_set)) return(false); + break; + + case OP_COND: + for (cp = cdr(expr); is_pair(cp); cp = cdr(cp)) + if (!do_is_safe(sc, car(cp), stepper, var_list, has_set)) + return(false); + break; + + case OP_CASE: + if ((!is_pair(cdr(expr))) || + (!do_is_safe(sc, cadr(expr), stepper, var_list, has_set))) + return(false); + for (cp = cddr(expr); is_pair(cp); cp = cdr(cp)) + if ((!is_pair(car(cp))) || /* (case x #(123)...) */ + (!do_is_safe(sc, cdar(cp), stepper, var_list, has_set))) + return(false); + break; + + case OP_IF: case OP_WHEN: case OP_UNLESS: + case OP_AND: case OP_OR: case OP_BEGIN: + case OP_WITH_BAFFLE: + if (!do_is_safe(sc, cdr(expr), stepper, var_list, has_set)) + return(false); + break; + + case OP_WITH_LET: + return(true); /* ?? did I mean false here?? */ + + default: + return(false); + }} /* is_syntax(x=car(expr)) */ + else + { + /* if a macro, we'll eventually expand it (if *_optimize), but that requires a symbol lookup here and macroexpand */ + if ((!is_optimized(expr)) || + (optimize_op(expr) == OP_UNKNOWN_NP) || + (!do_is_safe(sc, cdr(expr), stepper, var_list, has_set))) + return(false); + + /* is this still needed? fx_c_optcq bug -- tests seem ok without it -- 3.5 in tmat */ + if ((is_symbol(x)) && (is_slot(global_slot(x))) && (is_syntax(global_value(x)))) + return(false); /* syntax hidden behind some other name */ + + if ((is_symbol(x)) && (is_setter(x))) /* "setter" includes stuff like cons and vector -- x is a symbol */ + { + /* (hash-table-set! ht i 0) -- caddr is being saved, so this is not safe + * similarly (vector-set! v 0 i) etc + */ + if (is_null(cdr(expr))) + { + if (is_null(cdr(p))) /* (vector) for example */ + return((x == sc->vector_symbol) || (x == sc->list_symbol) || (x == sc->string_symbol)); + } + else + { + if ((has_set) && + (!direct_memq(cadr(expr), var_list)) && /* non-local is being changed */ + ((cadr(expr) == stepper) || /* stepper is being set? */ + (!is_pair(cddr(expr))) || + (!is_pair(cdddr(expr))) || + (is_pair(cddddr(expr))) || + ((x == sc->hash_table_set_symbol) && (caddr(expr) == stepper)) || + (cadddr(expr) == stepper) || /* used to check is_symbol here and above but that's unnecessary */ + ((is_pair(cadddr(expr))) && (s7_tree_memq(sc, stepper, cadddr(expr)))))) + (*has_set) = true; + + if (!do_is_safe(sc, cddr(expr), stepper, var_list, has_set)) + return(false); + if (!safe_stepper_expr(expr, stepper)) + return(false); + }}}}} + return(true); +} + +static bool preserves_type(s7_scheme *sc, uint32_t x) +{ + return((x == sc->add_class) || + (x == sc->subtract_class) || + (x == sc->multiply_class)); +} + +static s7_pointer simple_stepper(s7_scheme *sc, s7_pointer v) +{ + if ((is_proper_list_3(sc, v)) && + (is_fxable(sc, cadr(v)))) + { + s7_pointer step_expr = caddr(v); + if ((is_optimized(step_expr)) && + (((optimize_op(step_expr) == HOP_SAFE_C_SC) && (car(v) == cadr(step_expr))) || + ((is_h_safe_c_nc(step_expr)) && /* replace with is_fxable? */ + (is_pair(cdr(step_expr))) && /* ((v 0 (+))) */ + (car(v) == cadr(step_expr)) && + ((opt1_cfunc(step_expr) == sc->add_x1) || (opt1_cfunc(step_expr) == sc->subtract_x1))) || + ((optimize_op(step_expr) == HOP_SAFE_C_CS) && (car(v) == caddr(step_expr))))) + return(step_expr); + } + return(NULL); +} + +static bool is_simple_end(s7_scheme *sc, s7_pointer end) +{ + return((is_optimized(end)) && + (is_safe_c_op(optimize_op(end))) && + (is_pair(cddr(end))) && /* end: (zero? n) */ + (cadr(end) != caddr(end)) && + ((opt1_cfunc(end) == sc->num_eq_xi) || + (optimize_op(end) == HOP_SAFE_C_SS) || (optimize_op(end) == HOP_SAFE_C_SC))); +} + +static s7_pointer fxify_step_exprs(s7_scheme *sc, s7_pointer code) +{ + s7_pointer vars = car(code); + s7_pointer e = collect_variables(sc, vars, sc->nil); /* only valid in step exprs, not in inits */ + + for (s7_pointer p = vars; is_pair(p); p = cdr(p)) + { + s7_function callee = NULL; + s7_pointer expr = cdar(p); /* init */ + if (is_pair(expr)) + { + callee = fx_choose(sc, expr, sc->nil, do_symbol_is_safe); /* not vars -- they aren't defined yet */ + if (callee) set_fx(expr, callee); + } + expr = cddar(p); /* step */ + if (is_pair(expr)) + { + if ((is_pair(car(expr))) && + (!is_checked(car(expr)))) + optimize_expression(sc, car(expr), 0, e, false); + callee = fx_choose(sc, expr, vars, do_symbol_is_safe); /* fx_proc can be nil! */ + if (callee) set_fx(expr, callee); + }} + if ((is_pair(cdr(code))) && + (is_pair(cadr(code)))) + { + s7_pointer result = cdadr(code); + if ((is_pair(result)) && + (is_fxable(sc, car(result)))) + set_fx_direct(result, fx_choose(sc, result, vars, do_symbol_is_safe)); + } + return(code); +} + +static bool do_vector_has_definers(s7_pointer v) +{ + s7_int len = vector_length(v); + s7_pointer *els = vector_elements(v); + for (s7_int i = 0; i < len; i++) + if ((is_pair(els[i])) && + (is_symbol(car(els[i]))) && + (is_definer(car(els[i])))) /* this is a desperate kludge */ + return(true); + return(false); +} + +static /* inline */ bool do_tree_has_definers(s7_scheme *sc, s7_pointer tree) +{ + /* we can't be very fancy here because quote gloms up everything: (cond '(define x 0) ...) etc, and the tree here can + * be arbitrarily messed up, and we need to be reasonably fast. So we accept some false positives: (case ((define)...)...) or '(define...) + * but what about ((f...)...) where (f...) returns a macro that defines something? Or (for-each or ...) where for-each and or might be + * obfuscated and the args might contain a definer? + */ + for (s7_pointer p = tree; is_pair(p); p = cdr(p)) + { + s7_pointer pp = car(p); + if (is_symbol(pp)) + { + if (is_definer(pp)) + { + if (pp == sc->varlet_symbol) /* tlet case (varlet e1 ...) */ + { + if ((is_pair(cdr(p))) && (is_symbol(cadr(p))) && (!symbol_is_in_list(sc, cadr(p)))) + return(true); + } + else + if (pp == sc->apply_symbol) + { + s7_pointer val; + if ((!is_pair(cdr(p))) || (!is_symbol(cadr(p)))) return(true); + val = lookup_unexamined(sc, cadr(p)); + if ((!val) || (!is_c_function(val))) return(true); + } + else return(true); + }} + else + if (is_pair(pp)) + { + if (do_tree_has_definers(sc, pp)) + return(true); + } + else + if ((is_applicable(pp)) && + (((is_normal_vector(pp)) && (do_vector_has_definers(pp))) || + ((is_c_function(pp)) && (is_func_definer(pp))) || + ((is_syntax(pp)) && (is_syntax_definer(pp))))) + return(true); + } + return(false); +} + +static void check_do_for_obvious_errors(s7_scheme *sc, s7_pointer form) +{ + s7_pointer x, code = cdr(form); + + if ((!is_pair(code)) || /* (do . 1) */ + ((!is_pair(car(code))) && /* (do 123) */ + (is_not_null(car(code))))) /* (do () ...) is ok */ + syntax_error_nr(sc, "do: variable list is not a list: ~S", 35, form); + + if (!is_pair(cdr(code))) /* (do () . 1) */ + syntax_error_nr(sc, "do body is messed up: ~A", 24, form); + + if ((!is_pair(cadr(code))) && /* (do ((i 0)) 123) */ + (is_not_null(cadr(code)))) /* no end-test? */ + syntax_error_nr(sc, "do: end-test and end-value list is not a list: ~A", 49, form); + + if (is_pair(car(code))) + { + clear_symbol_list(sc); + for (x = car(code); is_pair(x); x = cdr(x)) + { + s7_pointer y = car(x); + if (!(is_pair(y))) /* (do (4) (= 3)) */ + syntax_error_nr(sc, "do: variable name missing? ~A", 29, form); + + if (!is_symbol(car(y))) /* (do ((3 2)) ()) */ + syntax_error_nr(sc, "do step variable: ~S is not a symbol?", 37, y); + + if (is_constant_symbol(sc, car(y))) /* (do ((pi 3 (+ pi 1))) ((= pi 4)) pi) */ + syntax_error_nr(sc, "do step variable: ~S is immutable", 33, y); + + if (!is_pair(cdr(y))) + syntax_error_nr(sc, "do: step variable has no initial value: ~A", 42, x); + if (!is_pair(cddr(y))) + { + if (is_not_null(cddr(y))) /* (do ((i 0 . 1)) ...) */ + syntax_error_nr(sc, "do: step variable info is an improper list?: ~A", 47, x); + } + else + if (is_not_null(cdddr(y))) /* (do ((i 0 1 (+ i 1))) ...) */ + syntax_error_nr(sc, "do: step variable info has extra stuff after the increment: ~A", 62, x); + set_local(car(y)); + + if (symbol_is_in_list(sc, car(y))) /* (do ((i 0 (+ i 1)) (i 2))...) */ + syntax_error_nr(sc, "duplicate identifier in do: ~A", 30, x); + add_symbol_to_list(sc, car(y)); + } + if (is_not_null(x)) /* (do ((i 0 i) . 1) ((= i 1))) */ + syntax_error_nr(sc, "do: list of variables is improper: ~A", 37, form); + } + if (is_pair(cadr(code))) + { + for (x = cadr(code); is_pair(x); x = cdr(x)); + if (is_not_null(x)) /* (do ((i 0 (+ i 1))) ((= i 2) . 3) */ + syntax_error_nr(sc, "stray dot in do end section? ~A", 31, form); + } + for (x = cddr(code); is_pair(x); x = cdr(x)); + if (is_not_null(x)) + syntax_error_nr(sc, "stray dot in do body? ~A", 24, form); +} + +static s7_pointer do_end_bad(s7_scheme *sc, s7_pointer form) +{ + s7_pointer code = cdr(form); + if (is_null(cddr(code))) + { + s7_pointer p; + /* no body, end not fxable (if eval car(end) might be unopt) */ + for (p = car(code); is_pair(p); p = cdr(p)) /* gather var names */ + { + s7_pointer var = car(p); + if (is_pair(cddr(var))) /* if no step expr it's safe in other step exprs 16-Apr-19 */ + set_match_symbol(car(var)); + } + for (p = car(code); is_pair(p); p = cdr(p)) /* look for stuff like (do ((i 0 j) (j 0 (+ j 1))) ((= j 3) i)) */ + { + s7_pointer var = car(p); + s7_pointer val = cddr(var); + if (is_pair(val)) + { + clear_match_symbol(car(var)); /* ignore current var */ + if (tree_match(car(val))) + { + for (s7_pointer q = car(code); is_pair(q); q = cdr(q)) + clear_match_symbol(caar(q)); + return(code); + }} + set_match_symbol(car(var)); + } + for (p = car(code); is_pair(p); p = cdr(p)) /* clear var names */ + clear_match_symbol(caar(p)); + + if (is_null(p)) + { + if ((is_null(cadr(code))) && /* (do () ()) or (do (fxable vars) ()) */ + (is_null(cddr(code)))) + { + if (sc->safety > NO_SAFETY) + s7_warn(sc, 256, "%s: infinite do loop: %s\n", __func__, display(form)); + return(code); + } + fxify_step_exprs(sc, code); + for (p = car(code); is_pair(p); p = cdr(p)) + { + s7_pointer var = car(p); + if ((!has_fx(cdr(var))) || + ((is_pair(cddr(var))) && (!has_fx(cddr(var))))) + return(code); + } + pair_set_syntax_op(form, OP_DO_NO_BODY_NA_VARS); + return(sc->nil); + }} + return(fxify_step_exprs(sc, code)); +} + +static s7_pointer check_do(s7_scheme *sc) +{ + /* returns nil if optimizable */ + s7_pointer form = sc->code, code, vars, end, body, p; + + check_do_for_obvious_errors(sc, form); + pair_set_syntax_op(form, OP_DO_UNCHECKED); + code = cdr(form); + end = cadr(code); + + if ((!is_pair(end)) || (!is_fxable(sc, car(end)))) + return(do_end_bad(sc, form)); + + /* sc->curlet is the outer environment, local vars are in the symbol_list via check_do_for_obvious_error, and it's only needed for fx_unsafe_s */ + set_fx_direct(end, fx_choose(sc, end, sc->curlet, let_symbol_is_safe_or_listed)); + if ((is_pair(cdr(end))) && + (is_fxable(sc, cadr(end)))) + set_fx_direct(cdr(end), fx_choose(sc, cdr(end), sc->curlet, let_symbol_is_safe_or_listed)); + + vars = car(code); + if (is_null(vars)) + { + pair_set_syntax_op(form, OP_DO_NO_VARS); + if (is_fx_treeable(end)) + { + if ((is_pair(car(end))) && /* this code is repeated below */ + (has_fx(end)) && + (!(is_syntax(caar(end)))) && + (!((is_symbol(caar(end))) && (is_definer_or_binder(caar(end)))))) + { + s7_pointer v1 = NULL, v2 = NULL, v3 = NULL; + bool more_vs = false; + if (tis_slot(let_slots(sc->curlet))) /* outer vars */ + { + p = let_slots(sc->curlet); + v1 = slot_symbol(p); + p = next_slot(p); + if (tis_slot(p)) + { + v2 = slot_symbol(p); + p = next_slot(p); + if (tis_slot(p)) + { + v3 = slot_symbol(p); + more_vs = tis_slot(next_slot(p)); + }}} + if (v1) fx_tree_outer(sc, end, v1, v2, v3, more_vs); + }} + return(sc->nil); + } + if (do_tree_has_definers(sc, form)) /* we don't want definers in body, vars, or end test */ + return(fxify_step_exprs(sc, code)); + + body = cddr(code); + if ((is_pair(end)) && (is_pair(car(end))) && /* end test is a pair */ + (is_pair(vars)) && (is_null(cdr(vars))) && /* one stepper */ + (is_pair(body)) && (is_pair(car(body))) && /* body is normal-looking */ + ((is_symbol(caar(body))) || (is_safe_c_function(caar(body))))) + { + /* loop has one step variable, and normal-looking end test */ + s7_pointer v = car(vars), step_expr; + + fx_tree(sc, end, car(v), NULL, NULL, false); + if (is_fx_treeable(body)) /* this is thwarted by gotos */ + fx_tree(sc, body, car(v), NULL, NULL, false); + + step_expr = simple_stepper(sc, v); + if (step_expr) + { + s7_pointer orig_end = end; + set_fx(cdr(v), fx_choose(sc, cdr(v), vars, do_symbol_is_safe)); /* v is (i 0 (+ i 1)) or the like */ + + /* step var is (var const|symbol (op var const)|(op const var)) */ + end = car(end); + if ((is_simple_end(sc, end)) && + (car(v) == cadr(end))) + { + /* end var is (op var const|symbol) using same var as step so at least we can use SIMPLE_DO */ + bool has_set = false; + bool one_line = ((is_null(cdr(body))) && (is_pair(car(body)))); + if ((car(end) == sc->num_eq_symbol) && (is_symbol(cadr(end))) && (is_t_integer(caddr(end)))) + { + set_c_function(end, sc->num_eq_2); + set_opt2_con(cdr(end), caddr(end)); + set_fx_direct(orig_end, (integer(caddr(end)) == 0) ? fx_num_eq_s0 : fx_num_eq_si); + } + set_opt1_any(code, caddr(end)); /* symbol or int(?) */ + set_opt2_pair(code, step_expr); /* caddr(caar(code)) */ + pair_set_syntax_op(form, OP_SIMPLE_DO); /* simple_do: 1 var easy step/end */ + + if ((c_function_class(opt1_cfunc(step_expr)) == sc->add_class) && /* we check above that (car(v) == cadr(step_expr)) and (car(v) == cadr(end)) */ + ((c_function_class(opt1_cfunc(end)) == sc->num_eq_class) || + (opt1_cfunc(end) == sc->geq_2))) + { + if ((one_line) && + ((!is_optimized(car(body))) || (op_no_hop(car(body)) != OP_SAFE_C_NC)) && /* this does happen: (if (= i 3) (vector-set! j 0 i)) */ + (is_symbol_and_syntactic(caar(body))) && + (s7_is_integer(caddr(step_expr))) && /* this currently blocks s7_optimize of float steppers */ + (s7_integer_clamped_if_gmp(sc, caddr(step_expr)) == 1)) + { + pair_set_syntax_op(car(body), symbol_syntax_op_checked(car(body))); + pair_set_syntax_op(form, OP_DOTIMES_P); /* dotimes_p: simple + syntax body + 1 expr */ + } + + if (((caddr(step_expr) == int_one) || (cadr(step_expr) == int_one)) && + (do_is_safe(sc, body, car(v), sc->nil, &has_set))) + { + pair_set_syntax_op(form, OP_SAFE_DO); /* safe_do: body is safe, step by 1 */ + /* no semipermanent let here because apparently do_is_safe accepts recursive calls? */ + if ((!has_set) && + (c_function_class(opt1_cfunc(end)) == sc->num_eq_class)) + { + pair_set_syntax_op(form, OP_SAFE_DOTIMES); /* safe_dotimes: end is = */ + if (is_fxable(sc, car(body))) + fx_annotate_arg(sc, body, collect_variables(sc, vars, sc->nil)); + } + fx_tree(sc, body, car(v), NULL, NULL, false); + /* an experiment (this never works...) */ + if (stack_top_op(sc) == OP_SAFE_DO_STEP) + fx_tree_outer(sc, body, caaar(stack_top_code(sc)), NULL, NULL, true); + }} + return(sc->nil); + }}} + + /* we get here if there is more than one local var or anything "non-simple" about the rest */ + for (p = vars; is_pair(p); p = cdr(p)) + { + s7_pointer var = car(p); + if ((!is_fxable(sc, cadr(var))) || + ((is_pair(cddr(var))) && (!is_fxable(sc, caddr(var)))) || + ((is_symbol(cadr(var))) && (is_definer_or_binder(cadr(var))))) + { + for (s7_pointer q = vars; q != p; q = cdr(q)) + clear_match_symbol(caar(q)); + return(fxify_step_exprs(sc, code)); + } + if (is_pair(cddr(var))) /* if no step expr it's safe in other step exprs 16-Apr-19 */ + set_match_symbol(car(var)); + } + + { + s7_pointer stepper0 = NULL, stepper1 = NULL, stepper2 = NULL, stepper3 = NULL; + bool got_pending = false, outer_shadowed = false; + + for (p = vars; is_pair(p); p = cdr(p)) + { + s7_pointer var = car(p); + s7_pointer val = cddr(var); + stepper3 = stepper2; + stepper2 = stepper1; + stepper1 = stepper0; + stepper0 = car(var); + if (is_pair(val)) + { + var = car(var); + clear_match_symbol(var); /* ignore current var */ + if (tree_match(car(val))) + { + for (s7_pointer q = vars; is_pair(q); q = cdr(q)) + clear_match_symbol(caar(q)); + if (is_null(body)) + got_pending = true; + else return(fxify_step_exprs(sc, code)); + } + set_match_symbol(var); + }} + + for (p = vars; is_pair(p); p = cdr(p)) + set_match_symbol(caar(p)); + for (p = let_slots(sc->curlet); tis_slot(p); p = next_slot(p)) + if (is_matched_symbol(slot_symbol(p))) + { + outer_shadowed = true; + break; + } + for (p = vars; is_pair(p); p = cdr(p)) + clear_match_symbol(caar(p)); + + /* end and steps look ok! */ + for (p = vars; is_pair(p); p = cdr(p)) + { + s7_pointer var = car(p); + set_fx_direct(cdr(var), fx_choose(sc, cdr(var), sc->curlet, let_symbol_is_safe)); /* init val */ + if (is_pair(cddr(var))) + { + s7_pointer step_expr = caddr(var); + set_fx_direct(cddr(var), fx_choose(sc, cddr(var), vars, do_symbol_is_safe)); /* sets opt2(cddr(var)), not opt1 */ + if (!is_pair(step_expr)) /* (i 0 0) */ + { + if (cadr(var) == caddr(var)) /* not types match: (i x y) etc */ + set_safe_stepper_expr(cddr(var)); + } + else + { + s7_pointer endp = car(end); + s7_pointer var1 = car(var); + if ((car(step_expr) != sc->quote_symbol) && /* opt1_cfunc(==opt1) might not be set in this case (sigh) */ + (is_safe_c_op(optimize_op(step_expr))) && + ((preserves_type(sc, c_function_class(opt1_cfunc(step_expr)))) || /* add etc */ + (car(step_expr) == sc->cdr_symbol) || + (car(step_expr) == sc->cddr_symbol) || + ((is_pair(cadr(var))) && + (is_pair(c_function_signature(c_function_base(opt1_cfunc(step_expr))))) && + (car(c_function_signature(c_function_base(opt1_cfunc(step_expr)))) != sc->T) && + (caadr(var) == car(step_expr))))) /* i.e. accept char-position as init/step, but not iterate */ + set_safe_stepper_expr(cddr(var)); + + if ((is_proper_list_3(sc, endp)) && (is_proper_list_3(sc, step_expr)) && + ((car(endp) == sc->num_eq_symbol) || (car(endp) == sc->geq_symbol)) && + (is_symbol(cadr(endp))) && + ((is_t_integer(caddr(endp))) || (is_symbol(caddr(endp)))) && + (car(step_expr) == sc->add_symbol) && + (var1 == cadr(endp)) && (var1 == cadr(step_expr)) && + ((car(endp) != sc->num_eq_symbol) || ((caddr(step_expr) == int_one)))) + set_loop_end_possible(end); + }}} + pair_set_syntax_op(form, (got_pending) ? OP_DOX_PENDING_NO_BODY : OP_DOX); + /* there are only a couple of cases in snd-test where a multi-statement do body is completely fx-able */ + + if ((is_null(body)) && + (is_null(cdr(vars))) && + (is_pair(cdr(end))) && + (is_null(cddr(end))) && + (has_fx(cdr(end))) && + (is_pair(cdar(vars))) && + (is_pair(cddar(vars)))) + { + s7_pointer var = caar(vars); + s7_pointer step = cddar(vars); + set_opt3_any(code, (in_heap(code)) ? sc->F : make_semipermanent_let(sc, vars)); + if (!got_pending) + pair_set_syntax_op(form, OP_DOX_NO_BODY); + if (is_safe_stepper_expr(step)) + { + step = car(step); + if ((is_pair(step)) && (is_proper_list_3(sc, step))) + { + if ((car(step) == sc->add_symbol) && + (((cadr(step) == var) && (caddr(step) == int_one)) || + (caddr(step) == var)) && (cadr(step) == int_one)) + set_opt2_con(code, int_one); + else + if ((car(step) == sc->subtract_symbol) && + (cadr(step) == var) && + (caddr(step) == int_one)) + set_opt2_con(code, minus_one); + else set_opt2_con(code, int_zero); + } + else set_opt2_con(code, int_zero); + } + else set_opt2_con(code, int_zero); + } + + if (do_passes_safety_check(sc, body, sc->nil, NULL)) + { + s7_pointer var1 = NULL, var2 = NULL, var3 = NULL; + bool more_vars = false; + if (tis_slot(let_slots(sc->curlet))) /* outer vars */ + { + p = let_slots(sc->curlet); + var1 = slot_symbol(p); + p = next_slot(p); + if (tis_slot(p)) + { + var2 = slot_symbol(p); + p = next_slot(p); + if (tis_slot(p)) + { + var3 = slot_symbol(p); + more_vars = tis_slot(next_slot(p)); + }}} + + for (p = vars; is_pair(p); p = cdr(p)) + { + s7_pointer var = car(p); + if (is_pair(cdr(var))) + { + if (var1) fx_tree_in(sc, cdr(var), var1, var2, var3, more_vars); /* init vals, more_vars refers to outer let, stepper3 == local let more_vars */ + if (is_pair(cddr(var))) + { + if (stepper0) fx_tree(sc, cddr(var), stepper0, stepper1, stepper2, stepper3); + if ((var1) && (!outer_shadowed) && (!stepper3)) fx_tree_outer(sc, cddr(var), var1, var2, var3, more_vars); + }}} + + if ((is_pair(cdr(end))) && + (is_null(cddr(end))) && + (has_fx(cdr(end)))) + { + if (!fx_tree_in(sc, cdr(end), stepper0, stepper1, stepper2, stepper3)) + fx_tree(sc, cadr(end), stepper0, stepper1, stepper2, stepper3); + if ((var1) && (!outer_shadowed) && (!stepper3)) fx_tree_outer(sc, cdr(end), var1, var2, var3, more_vars); + } + + if ((is_pair(car(end))) && + (has_fx(end)) && + (!(is_syntax(caar(end)))) && + (!((is_symbol(caar(end))) && (is_definer_or_binder(caar(end)))))) + { + if (!fx_tree_in(sc, end, stepper0, stepper1, stepper2, stepper3)) /* just the end-test, not the results */ + fx_tree(sc, car(end), stepper0, stepper1, stepper2, stepper3); /* car(end) might be (or ...) */ + if ((var1) && (!outer_shadowed) && (!stepper3)) fx_tree_outer(sc, end, var1, var2, var3, more_vars); + } + + if ((is_pair(body)) && (is_null(cdr(body))) && + (is_fxable(sc, car(body)))) + { + fx_annotate_arg(sc, body, collect_variables(sc, vars, sc->nil)); + if (stepper0) fx_tree(sc, body, stepper0, stepper1, stepper2, stepper3); + if ((var1) && (!outer_shadowed) && (!stepper3)) fx_tree_outer(sc, body, var1, var2, var3, more_vars); + }}} + return(sc->nil); +} + +static bool has_safe_steppers(s7_scheme *sc, s7_pointer let) +{ + for (s7_pointer slot = let_slots(let); tis_slot(slot); slot = next_slot(slot)) + { + s7_pointer val = slot_value(slot); + if (slot_has_expression(slot)) + { + s7_pointer step_expr = T_Pair(slot_expression(slot)); + if (is_safe_stepper_expr(step_expr)) + { + if (is_t_integer(val)) + { + if (is_int_optable(step_expr)) + set_safe_stepper(slot); + else + if (no_int_opt(step_expr)) + clear_safe_stepper(slot); + else + { + sc->pc = 0; + if (int_optimize(sc, step_expr)) + { + set_safe_stepper(slot); + set_is_int_optable(step_expr); + } + else + { + clear_safe_stepper(slot); + set_no_int_opt(step_expr); + }}} + else + if (is_small_real(val)) + { + if (is_float_optable(step_expr)) + set_safe_stepper(slot); + else + if (no_float_opt(step_expr)) + clear_safe_stepper(slot); + else + { + sc->pc = 0; + if (float_optimize(sc, step_expr)) + { + set_safe_stepper(slot); + set_is_float_optable(step_expr); + } + else + { + clear_safe_stepper(slot); + set_no_float_opt(step_expr); + }}} + else set_safe_stepper(slot); /* ?? shouldn't this check types ?? */ + }} + else + { + if (is_t_real(val)) + slot_set_value(slot, s7_make_mutable_real(sc, real(val))); + if (is_t_integer(val)) + slot_set_value(slot, make_mutable_integer(sc, integer(val))); + set_safe_stepper(slot); + } + if (!is_safe_stepper(slot)) + return(false); + } + return(true); +} + +static bool copy_if_end_ok(s7_scheme *sc, s7_pointer dest, s7_pointer source, s7_int i, s7_pointer endp, s7_pointer stepper) +{ + if ((fn_proc(endp) == g_num_eq_2) && (is_symbol(cadr(endp))) && (is_symbol(caddr(endp)))) + { + s7_pointer end_slot = s7_slot(sc, (cadr(endp) == slot_symbol(stepper)) ? caddr(endp) : cadr(endp)); + if ((is_slot(end_slot)) && (is_t_integer(slot_value(end_slot)))) + { + copy_to_same_type(sc, dest, source, i, integer(slot_value(end_slot)), i); + return(true); + }} + return(false); +} + +static bool op_dox_init(s7_scheme *sc) +{ + s7_pointer test, code = cdr(sc->code); + s7_pointer let = inline_make_let(sc, sc->curlet); + sc->temp1 = let; + + for (s7_pointer vars = car(code); is_pair(vars); vars = cdr(vars)) + { + add_slot(sc, let, caar(vars), fx_call(sc, cdar(vars))); + if (is_pair(cddar(vars))) + slot_set_expression(let_slots(let), cddar(vars)); + else slot_just_set_expression(let_slots(let), sc->nil); + } + set_curlet(sc, let); + sc->temp1 = sc->unused; + test = cadr(code); + if (is_true(sc, sc->value = fx_call(sc, test))) + { + sc->code = cdr(test); + return(true); /* goto DO_END_CLAUSES */ + } + sc->code = T_Pair(cddr(code)); + push_stack_no_args(sc, (intptr_t)((is_null(cdr(sc->code))) ? OP_DOX_STEP_O : OP_DOX_STEP), code); + return(false); /* goto BEGIN */ +} + +static goto_t op_dox_no_body_1(s7_scheme *sc, s7_pointer slots, s7_pointer end, int32_t steppers, s7_pointer stepper) +{ + s7_function endf = fx_proc(end); + s7_pointer endp = car(end); + if ((endf == fx_c_nc) || (endf == fx_c_0c)) + { + endf = fn_proc(endp); + endp = cdr(endp); + } + if (steppers == 1) + { + s7_function f = fx_proc(slot_expression(stepper)); /* e.g. fx_add_s1 */ + s7_pointer a = car(slot_expression(stepper)); + if ((f == fx_c_nc) || (f == fx_c_0c)) + { + f = fn_proc(a); + a = cdr(a); + } + if (((f == fx_cdr_s) || (f == fx_cdr_t)) && + (cadr(a) == slot_symbol(stepper))) + { + do {slot_set_value(stepper, cdr(slot_value(stepper)));} while (endf(sc, endp) == sc->F); + sc->value = sc->T; + } + else /* (- n 1) tpeak dup */ + if (((f == fx_add_t1) || (f == fx_add_u1)) && (is_t_integer(slot_value(stepper)))) + { + s7_pointer p = make_mutable_integer(sc, integer(slot_value(stepper))); + slot_set_value(stepper, p); + if (!no_bool_opt(end)) + { + sc->pc = 0; + if (bool_optimize(sc, end)) /* in dup.scm this costs more than the fb(o) below saves (search is short) */ + { /* but tc is much slower (and bool|int_optimize dominates) */ + opt_info *o = sc->opts[0]; + bool (*fb)(opt_info *o) = o->v[0].fb; + do {integer(p)++;} while (!fb(o)); /* do {integer(p)++;} while ((sc->value = optf(sc, endp)) == sc->F); */ + clear_mutable_integer(p); + sc->value = sc->T; + sc->code = cdr(end); + return(goto_do_end_clauses); + } + set_no_bool_opt(end); + } + do {integer(p)++;} while ((sc->value = endf(sc, endp)) == sc->F); + clear_mutable_integer(p); + } + else do {slot_set_value(stepper, f(sc, a));} while ((sc->value = endf(sc, endp)) == sc->F); + + sc->code = cdr(end); + return(goto_do_end_clauses); + } + if ((steppers == 2) && + (!tis_slot(next_slot(next_slot(slots))))) + { + s7_pointer step1 = slots; + s7_pointer expr1 = slot_expression(step1); + s7_pointer step2 = next_slot(step1); + s7_pointer expr2 = slot_expression(step2); /* presetting fx_proc/car(expr) is not faster */ + if ((fx_proc(expr2) == fx_subtract_u1) && + (is_t_integer(slot_value(step2))) && + (endf == fx_num_eq_ui)) + { + s7_int lim = integer(caddr(endp)); + for (s7_int i = integer(slot_value(step2)) - 1; i >= lim; i--) + slot_set_value(step1, fx_call(sc, expr1)); + } + else + do { + slot_set_value(step1, fx_call(sc, expr1)); + slot_set_value(step2, fx_call(sc, expr2)); + } while ((sc->value = endf(sc, endp)) == sc->F); + sc->code = cdr(end); + if (!is_pair(sc->code)) return(goto_start); /* no result: (define (f) (do ((x 0 (+ x 1)) (i 0 (+ i 1))) ((= i 1)))) (f) (f) */ + if ((!is_symbol(car(sc->code))) || (is_pair(cdr(sc->code)))) /* more than one result: (define (f) (do ((x 0 (+ x 1)) (i 0 (+ i 1))) ((= i 1) x 3 4))) (f) */ + return(goto_do_end_clauses); + step1 = s7_slot(sc, car(sc->code)); + if (!is_slot(step1)) unbound_variable_error_nr(sc, car(sc->code)); + sc->value = slot_value(step1); + if (is_t_real(sc->value)) + clear_mutable_number(sc->value); + return(goto_start); + } + do { + s7_pointer slt = slots; + do { + if (slot_has_expression(slt)) + slot_set_value(slt, fx_call(sc, slot_expression(slt))); + slt = next_slot(slt); + } while (tis_slot(slt)); + } while ((sc->value = endf(sc, endp)) == sc->F); + sc->code = cdr(end); + return(goto_do_end_clauses); +} + +static goto_t op_dox(s7_scheme *sc) +{ + /* any number of steppers using dox exprs, end also dox, body and end result arbitrary. + * since all these exprs are local, we don't need to jump until the body + */ + int64_t id, steppers = 0; + s7_pointer code, end, endp, stepper = NULL, form = sc->code, slots; + s7_function endf; +#if WITH_GMP + bool got_bignum = false; +#endif + s7_pointer let = inline_make_let(sc, sc->curlet); /* new let is not tied into the symbol lookup process yet */ + sc->temp1 = let; + sc->code = cdr(sc->code); + for (s7_pointer vars = car(sc->code); is_pair(vars); vars = cdr(vars)) + { + s7_pointer expr = cdar(vars), slot; + s7_pointer val = fx_call(sc, expr); + s7_pointer stp = cdr(expr); /* cddar(vars) */ +#if WITH_GMP + if (!got_bignum) got_bignum = is_big_number(val); +#endif + new_cell_no_check(sc, slot, T_SLOT); + slot_set_symbol_and_value(slot, caar(vars), val); + if (is_pair(stp)) + { + steppers++; + stepper = slot; + slot_set_expression(slot, stp); + } + else slot_just_set_expression(slot, sc->nil); + slot_set_next(slot, let_slots(let)); + let_set_slots(let, slot); + } + set_curlet(sc, let); + slots = let_slots(sc->curlet); + sc->temp1 = sc->unused; + id = let_id(let); + + /* the fn_calls above could have redefined a previous stepper, so that its symbol_id is > let let_id when we get here, + * so we use symbol_set_local_slot_unchecked below to sidestep the debugger (see zauto.scm: i is a stepper, but then mock-vector-ref uses i as its index) + */ + for (s7_pointer slot = slots; tis_slot(slot); slot = next_slot(slot)) + symbol_set_local_slot_unchecked_and_unincremented(slot_symbol(slot), id, slot); + + end = cadr(sc->code); + endp = car(end); + endf = fx_proc(end); + + /* an experiment */ + if ((loop_end_possible(end)) && (steppers == 1) && + (is_t_integer(slot_value(stepper)))) + { + s7_pointer stop_slot = (is_symbol(caddr(endp))) ? opt_integer_symbol(sc, caddr(endp)) : sc->nil; + if (stop_slot) /* sc->nil -> it's an integer */ + { + set_has_loop_end(stepper); + set_loop_end(stepper, (is_slot(stop_slot)) ? integer(slot_value(stop_slot)) : integer(caddr(endp))); + }} + + if (is_true(sc, sc->value = endf(sc, endp))) + { + sc->code = cdr(end); + return(goto_do_end_clauses); + } + code = cddr(sc->code); + if (is_null(code)) /* no body -- how does this happen? */ + return(op_dox_no_body_1(sc, slots, end, steppers, stepper)); + + if ((is_null(cdr(code))) && /* 1 expr, code is cdddr(form) here */ + (is_pair(car(code)))) + { + s7_pointer body = car(code); + s7_pfunc bodyf = NULL; + if ((!no_cell_opt(code)) && +#if WITH_GMP + (!got_bignum) && +#endif + (has_safe_steppers(sc, sc->curlet))) + bodyf = s7_optimize_nv(sc, code); + + if ((!bodyf) && + (is_fxable(sc, body)) && /* happens very rarely, #_* as car etc */ + (is_c_function(car(body)))) + bodyf = s7_optimize_nv(sc, set_dlist_1(sc, set_ulist_1(sc, c_function_name_to_symbol(sc, car(body)), cdr(body)))); + + if (bodyf) + { + if (steppers == 1) /* one expr body, 1 stepper */ + { + s7_pointer stepa = car(slot_expression(stepper)); + s7_function stepf = fx_proc(slot_expression(stepper)); + if (((stepf == fx_add_t1) || (stepf == fx_add_u1)) && (is_t_integer(slot_value(stepper)))) + { + s7_int i = integer(slot_value(stepper)); + opt_info *o = sc->opts[0]; + if (bodyf == opt_cell_any_nv) + { + s7_pointer (*fp)(opt_info *o) = o->v[0].fp; + /* a laborious experiment... */ + if (!((fp == opt_p_pip_sso) && (o->v[2].p == o->v[4].p) && + (((o->v[5].p_pip_f == string_set_p_pip_unchecked) && (o->v[6].p_pi_f == string_ref_p_pi_unchecked)) || + ((o->v[5].p_pip_f == string_set_p_pip_direct) && (o->v[6].p_pi_f == string_ref_p_pi_direct)) || + ((o->v[5].p_pip_f == vector_set_p_pip_unchecked) && (o->v[6].p_pi_f == normal_vector_ref_p_pi_unchecked)) || + ((o->v[5].p_pip_f == normal_vector_set_p_pip_direct) && (o->v[6].p_pi_f == normal_vector_ref_p_pi_direct)) || + ((o->v[5].p_pip_f == list_set_p_pip_unchecked) && (o->v[6].p_pi_f == list_ref_p_pi_unchecked))) && + (copy_if_end_ok(sc, slot_value(o->v[1].p), slot_value(o->v[3].p), i, endp, stepper)))) + { + if (has_loop_end(stepper)) + { + s7_int lim = loop_end(stepper); + if ((i >= 0) && (lim < NUM_SMALL_INTS)) + do {fp(o); slot_set_value(stepper, small_int(++i));} while (i < lim); + else do {fp(o); slot_set_value(stepper, make_integer(sc, ++i));} while (i < lim); + sc->value = sc->T; + } + else + do { + fp(o); + slot_set_value(stepper, make_integer(sc, ++i)); + } while ((sc->value = endf(sc, endp)) == sc->F); + }} + else + if (!(((bodyf == opt_float_any_nv) && (o->v[0].fd == opt_d_7pid_ss_ss) && + (o->v[2].p == o->v[6].p) && + ((o->v[4].d_7pid_f == float_vector_set_d_7pid) || (o->v[4].d_7pid_f == float_vector_set_d_7pid_direct)) && + (o->v[3].d_7pi_f == float_vector_ref_d_7pi) && + (copy_if_end_ok(sc, slot_value(o->v[1].p), slot_value(o->v[5].p), i, endp, stepper))) || + + ((bodyf == opt_int_any_nv) && ((o->v[0].fi == opt_i_7pii_ssf) || (o->v[0].fi == opt_i_7pii_ssf_vset)) && + (o->v[2].p == o->v[4].o1->v[2].p) && + (((o->v[3].i_7pii_f == int_vector_set_i_7pii) && (o->v[4].o1->v[3].i_7pi_f == int_vector_ref_i_7pi)) || + ((o->v[3].i_7pii_f == int_vector_set_i_7pii_direct) && (o->v[4].o1->v[3].i_7pi_f == int_vector_ref_i_7pi_direct))) && + (copy_if_end_ok(sc, slot_value(o->v[1].p), slot_value(o->v[4].o1->v[1].p), i, endp, stepper))))) + /* here the has_loop_end business doesn't happen much */ + do { + bodyf(sc); + slot_set_value(stepper, make_integer(sc, ++i)); + } while ((sc->value = endf(sc, endp)) == sc->F); + sc->code = cdr(end); + return(goto_do_end_clauses); + } + do { + bodyf(sc); + slot_set_value(stepper, stepf(sc, stepa)); + } while ((sc->value = endf(sc, endp)) == sc->F); + sc->code = cdr(end); + return(goto_do_end_clauses); + } + + if ((steppers == 2) && + (!tis_slot(next_slot(next_slot(slots))))) + { + s7_pointer s1 = slots, s2 = next_slot(slots); + s7_function f1 = fx_proc(slot_expression(s1)); + s7_function f2 = fx_proc(slot_expression(s2)); + s7_pointer p1 = car(slot_expression(s1)); + s7_pointer p2 = car(slot_expression(s2)); + /* split out opt_float_any_nv gained nothing (see tmp), same for opt_cell_any_nv */ + if (bodyf == opt_cell_any_nv) + { + opt_info *o = sc->opts[0]; + s7_pointer (*fp)(opt_info *o) = o->v[0].fp; + /* maybe this can be generalized (thash:79) -- explicit integer stepper, but there must be a simpler way */ + if ((f2 == fx_add_u1) && (is_t_integer(slot_value(s2))) && (endf == fx_num_eq_ui) && + (is_symbol(cadr(endp))) && (cadr(endp) == slot_symbol(s2)) && + (is_t_integer(caddr(endp))) && (!s7_tree_memq(sc, cadr(endp), body))) + { + s7_int i = integer(slot_value(s2)), endi = integer(caddr(endp)); + do { + fp(o); + slot_set_value(s1, f1(sc, p1)); + i++; + } while (i < endi); + } + else + do { + fp(o); + slot_set_value(s1, f1(sc, p1)); + slot_set_value(s2, f2(sc, p2)); + } while ((sc->value = endf(sc, endp)) == sc->F); + } + else + do { + bodyf(sc); + slot_set_value(s1, f1(sc, p1)); + slot_set_value(s2, f2(sc, p2)); + } while ((sc->value = endf(sc, endp)) == sc->F); + sc->code = cdr(end); + return(goto_do_end_clauses); + } + if (bodyf == opt_cell_any_nv) + { + opt_info *o = sc->opts[0]; + s7_pointer (*fp)(opt_info *o) = o->v[0].fp; + do { + s7_pointer slot1 = slots; + fp(o); + do { + if (slot_has_expression(slot1)) + slot_set_value(slot1, fx_call(sc, slot_expression(slot1))); + slot1 = next_slot(slot1); + } while (tis_slot(slot1)); + } while ((sc->value = endf(sc, endp)) == sc->F); + } + else + do { + s7_pointer slot1 = slots; + bodyf(sc); + do { + if (slot_has_expression(slot1)) + slot_set_value(slot1, fx_call(sc, slot_expression(slot1))); + slot1 = next_slot(slot1); + } while (tis_slot(slot1)); + } while ((sc->value = endf(sc, endp)) == sc->F); + sc->code = cdr(end); + return(goto_do_end_clauses); + } + + if ((steppers == 1) && + (car(body) == sc->set_symbol) && + (is_pair(cdr(body))) && + (is_symbol(cadr(body))) && + (is_pair(cddr(body))) && + ((has_fx(cddr(body))) || (is_fxable(sc, caddr(body)))) && + (is_null(cdddr(body)))) + { + s7_pointer val = cddr(body), stepa; + s7_function stepf, valf; + s7_pointer slot = s7_slot(sc, cadr(body)); + if (!has_fx(val)) + set_fx(val, fx_choose(sc, val, sc->curlet, let_symbol_is_safe)); + valf = fx_proc(val); + val = car(val); + if (slot == sc->undefined) + unbound_variable_error_nr(sc, cadr(body)); + stepf = fx_proc(slot_expression(stepper)); + stepa = car(slot_expression(stepper)); + do { + slot_set_value(slot, valf(sc, val)); + slot_set_value(stepper, stepf(sc, stepa)); + } while ((sc->value = endf(sc, endp)) == sc->F); + sc->code = cdr(end); + return(goto_do_end_clauses); + } + + /* not fxable body (bodyf nil) but body might be gxable here: is_gxable(body) */ + if ((has_gx(body)) || (gx_annotate_arg(sc, code, sc->curlet))) + { + s7_function f = fx_proc_unchecked(code); + do { + s7_pointer slot1 = slots; + f(sc, body); + do { + if (slot_has_expression(slot1)) + slot_set_value(slot1, fx_call(sc, slot_expression(slot1))); + slot1 = next_slot(slot1); + } while (tis_slot(slot1)); + } while ((sc->value = endf(sc, endp)) == sc->F); + sc->code = cdr(end); + return(goto_do_end_clauses); + }} + else /* more than one expr */ + { + s7_pointer p = code; + bool use_opts = false; + int32_t body_len = 0; + opt_info *body[32]; + #define MAX_OPT_BODY_SIZE 32 + + if ((!no_cell_opt(code)) && +#if WITH_GMP + (!got_bignum) && +#endif + (has_safe_steppers(sc, sc->curlet))) + { + sc->pc = 0; + for (int32_t k = 0; (is_pair(p)) && (k < MAX_OPT_BODY_SIZE); k++, p = cdr(p), body_len++) + { + opt_info *start = sc->opts[sc->pc]; + if (!cell_optimize(sc, p)) + { + set_no_cell_opt(code); + p = code; + break; + } + oo_idp_nr_fixup(start); + body[k] = start; + } + use_opts = is_null(p); + } + + if (p == code) + for (; is_pair(p); p = cdr(p)) + if (!is_fxable(sc, car(p))) + break; + + if (is_null(p)) + { + s7_pointer stepa = NULL; + s7_function stepf = NULL; + if (!use_opts) + fx_annotate_args(sc, code, sc->curlet); + + if (stepper) + { + stepf = fx_proc(slot_expression(stepper)); + stepa = car(slot_expression(stepper)); + } + while (true) + { + if (use_opts) + for (int32_t i = 0; i < body_len; i++) + body[i]->v[0].fp(body[i]); + else + for (p = code; is_pair(p); p = cdr(p)) + fx_call(sc, p); + + if (steppers == 1) + slot_set_value(stepper, stepf(sc, stepa)); + else + { + s7_pointer slot = slots; + do { + if (slot_has_expression(slot)) + slot_set_value(slot, fx_call(sc, slot_expression(slot))); + slot = next_slot(slot); + } while (tis_slot(slot)); + } + if (is_true(sc, sc->value = endf(sc, endp))) + { + sc->code = cdr(end); + return(goto_do_end_clauses); + }}}} + if ((is_null(cdr(code))) && /* one expr */ + (is_pair(car(code)))) + { + code = car(code); + if ((is_syntactic_pair(code)) || + (is_symbol_and_syntactic(car(code)))) + { + push_stack_no_args_direct(sc, OP_DOX_STEP_O); + if (is_syntactic_pair(code)) + sc->cur_op = (opcode_t)optimize_op(code); + else + { + sc->cur_op = (opcode_t)symbol_syntax_op_checked(code); + pair_set_syntax_op(code, sc->cur_op); + } + sc->code = code; + return(goto_top_no_pop); + }} + pair_set_syntax_op(form, OP_DOX_INIT); + sc->code = T_Pair(cddr(sc->code)); + push_stack_no_args(sc, (intptr_t)((is_null(cdr(sc->code))) ? OP_DOX_STEP_O : OP_DOX_STEP), cdr(form)); + return(goto_begin); +} + +static inline bool op_dox_step_1(s7_scheme *sc) +{ + s7_pointer slot = let_slots(sc->curlet); + do { /* every dox case has vars (else op_do_no_vars) */ + if (slot_has_expression(slot)) + slot_set_value(slot, fx_call(sc, slot_expression(slot))); + slot = next_slot(slot); + } while (tis_slot(slot)); + sc->value = fx_call(sc, cadr(sc->code)); + if (is_true(sc, sc->value)) + { + sc->code = cdadr(sc->code); + return(true); + } + return(false); +} + +static void op_dox_step(s7_scheme *sc) +{ + push_stack_no_args_direct(sc, OP_DOX_STEP); + sc->code = T_Pair(cddr(sc->code)); +} + +static void op_dox_step_o(s7_scheme *sc) +{ + push_stack_no_args_direct(sc, OP_DOX_STEP_O); + sc->code = caddr(sc->code); +} + +static void op_dox_no_body(s7_scheme *sc) +{ + s7_pointer slot, var, test, result; + s7_function testf; + + sc->code = cdr(sc->code); + var = caar(sc->code); + testf = fx_proc(cadr(sc->code)); + test = caadr(sc->code); + result = cdadr(sc->code); + + if ((!in_heap(sc->code)) && + (is_let(opt3_any(sc->code)))) /* (*repl* 'keymap) anything -> segfault because opt3_any here is #f. (see line 80517) */ + { + s7_pointer let = update_let_with_slot(sc, opt3_any(sc->code), fx_call(sc, cdr(var))); + let_set_outlet(let, sc->curlet); + set_curlet(sc, let); + } + else sc->curlet = make_let_with_slot(sc, sc->curlet, car(var), fx_call(sc, cdr(var))); + + slot = let_slots(sc->curlet); + if ((is_t_integer(slot_value(slot))) && + ((integer(opt2_con(sc->code))) != 0)) + { + s7_int incr = integer(opt2_con(sc->code)); + s7_pointer istep = make_mutable_integer(sc, integer(slot_value(slot))); + /* this can cause unexpected, but correct behavior: (do ((x 0) (i 0 (+ i 1))) ((= i 1) x) (set! x (memq x '(0)))) -> #f + * because (eq? 0 x) here is false -- memv will return '(0). tree-count is similar. + */ + slot_set_value(slot, istep); + if (testf == fx_or_2a) + { + s7_pointer t1 = cadr(test); + s7_pointer t2 = caddr(test); + s7_function f1 = fx_proc(cdr(test)); + s7_function f2 = fx_proc(cddr(test)); + while ((f1(sc, t1) == sc->F) && (f2(sc, t2) == sc->F)) + integer(istep) += incr; + } + else while (testf(sc, test) == sc->F) {integer(istep) += incr;} + if (is_small_int(integer(istep))) + slot_set_value(slot, small_int(integer(istep))); + else clear_mutable_integer(istep); + sc->value = fx_call(sc, result); + } + else + { + s7_function stepf = fx_proc(cddr(var)); + s7_pointer step = caddr(var); + if (testf == fx_or_and_2a) + { + s7_pointer f1_arg = cadr(test), p = opt3_pair(test); /* cdadr(p) */ + s7_function f1 = fx_proc(cdr(test)); + s7_pointer f2_arg = car(p); + s7_pointer f3_arg = cadr(p); + s7_function f2 = fx_proc(p); + s7_function f3 = fx_proc(cdr(p)); + if (((stepf == fx_add_t1) || (stepf == fx_add_u1)) && (is_t_integer(slot_value(slot)))) + { + s7_pointer ip = make_mutable_integer(sc, integer(slot_value(slot))); + slot_set_value(slot, ip); + while ((f1(sc, f1_arg) == sc->F) && + ((f2(sc, f2_arg) == sc->F) || (f3(sc, f3_arg) == sc->F))) + integer(ip)++; + clear_mutable_integer(ip); + } + else + while ((f1(sc, f1_arg) == sc->F) && + ((f2(sc, f2_arg) == sc->F) || (f3(sc, f3_arg) == sc->F))) + slot_set_value(slot, stepf(sc, step)); + } + else while (testf(sc, test) == sc->F) {slot_set_value(slot, stepf(sc, step));} + sc->value = fx_call(sc, result); + } +} + +static void op_dox_pending_no_body(s7_scheme *sc) +{ + s7_pointer test, slots; + bool all_steps = true; + s7_pointer let = inline_make_let(sc, sc->curlet); + sc->temp1 = let; + sc->code = cdr(sc->code); + for (s7_pointer vars = car(sc->code); is_pair(vars); vars = cdr(vars)) + { + add_slot(sc, let, caar(vars), fx_call(sc, cdar(vars))); + if (is_pair(cddar(vars))) + slot_set_expression(let_slots(let), cddar(vars)); + else + { + all_steps = false; + slot_just_set_expression(let_slots(let), sc->nil); + }} + slots = let_slots(let); + set_curlet(sc, let); + sc->temp1 = sc->unused; + test = cadr(sc->code); + + let_set_has_pending_value(sc->curlet); + if ((all_steps) && + (!tis_slot(next_slot(next_slot(slots)))) && + (is_pair(cdr(test)))) + { + s7_pointer slot1 = slots; + s7_pointer expr1 = slot_expression(slot1); + s7_pointer slot2 = next_slot(slot1); + s7_pointer expr2 = slot_expression(slot2); + while (fx_call(sc, test) == sc->F) + { + slot_simply_set_pending_value(slot1, fx_call(sc, expr1)); /* use pending_value for GC protection */ + slot_set_value(slot2, fx_call(sc, expr2)); + slot_set_value(slot1, slot_pending_value(slot1)); + } + sc->code = cdr(test); + let_clear_has_pending_value(sc->curlet); + return; + } + while ((sc->value = fx_call(sc, test)) == sc->F) + { + s7_pointer slt = slots; + do { + if (slot_has_expression(slt)) + slot_simply_set_pending_value(slt, fx_call(sc, slot_expression(slt))); + slt = next_slot(slt); + } while (tis_slot(slt)); + slt = slots; + do { + if (slot_has_expression(slt)) + slot_set_value(slt, slot_pending_value(slt)); + slt = next_slot(slt); + } while (tis_slot(slt)); + } + sc->code = cdr(test); + let_clear_has_pending_value(sc->curlet); +} + +static bool op_do_no_vars(s7_scheme *sc) +{ + s7_pointer p, form = sc->code; + int32_t i; + opt_info *body[32]; + sc->code = cdr(sc->code); + sc->pc = 0; + + for (i = 0, p = cddr(sc->code); (is_pair(p)) && (i < 32); i++, p = cdr(p)) + { + body[i] = sc->opts[sc->pc]; + if (!cell_optimize(sc, p)) + break; + } + if (is_null(p)) + { + s7_pointer end = cadr(sc->code); + sc->curlet = inline_make_let(sc, sc->curlet); + if (i == 1) + { + while ((sc->value = fx_call(sc, end)) == sc->F) body[0]->v[0].fp(body[0]); + sc->code = cdr(end); + return(true); + } + if (i == 0) /* null body! */ + { + s7_function endf = fx_proc(end); + s7_pointer endp = car(end); + while (!is_true(sc, sc->value = endf(sc, endp))); /* the assignment is (normally) in the noise */ + sc->code = cdr(end); + return(true); + } + while ((sc->value = fx_call(sc, end)) == sc->F) + for (int32_t k = 0; k < i; k++) + body[k]->v[0].fp(body[k]); + sc->code = cdr(end); + return(true); + } + /* back out */ + pair_set_syntax_op(form, OP_DO_NO_VARS_NO_OPT); + sc->curlet = make_let(sc, sc->curlet); + sc->value = fx_call(sc, cadr(sc->code)); + if (is_true(sc, sc->value)) + { + sc->code = cdadr(sc->code); + return(true); + } + push_stack_no_args_direct(sc, OP_DO_NO_VARS_NO_OPT_1); + sc->code = T_Pair(cddr(sc->code)); + return(false); +} + +static void op_do_no_vars_no_opt(s7_scheme *sc) +{ + sc->code = cdr(sc->code); + sc->curlet = inline_make_let(sc, sc->curlet); +} + +static bool op_do_no_vars_no_opt_1(s7_scheme *sc) +{ + sc->value = fx_call(sc, cadr(sc->code)); + if (is_true(sc, sc->value)) + { + sc->code = cdadr(sc->code); + return(true); + } + push_stack_no_args_direct(sc, OP_DO_NO_VARS_NO_OPT_1); + sc->code = T_Pair(cddr(sc->code)); + return(false); +} + +static void op_do_no_body_na_vars(s7_scheme *sc) /* vars fxable, end-test not */ +{ + s7_pointer stepper = NULL; + s7_int steppers = 0; + s7_pointer let = inline_make_let(sc, sc->curlet); + sc->temp1 = let; + sc->code = cdr(sc->code); + for (s7_pointer vars = car(sc->code); is_pair(vars); vars = cdr(vars)) + { + add_slot(sc, let, caar(vars), fx_call(sc, cdar(vars))); + if (is_pair(cddar(vars))) + { + slot_set_expression(let_slots(let), cddar(vars)); + steppers++; + stepper = let_slots(let); + } + else slot_just_set_expression(let_slots(let), sc->nil); + } + if (steppers == 1) let_set_dox_slot1(let, stepper); + set_curlet(sc, let); + sc->temp1 = sc->unused; + push_stack_no_args_direct(sc, (intptr_t)((steppers == 1) ? OP_DO_NO_BODY_NA_VARS_STEP_1 : OP_DO_NO_BODY_NA_VARS_STEP)); + sc->code = caadr(sc->code); +} + +static bool op_do_no_body_na_vars_step(s7_scheme *sc) +{ + if (sc->value != sc->F) + { + sc->code = cdadr(sc->code); + return(true); + } + for (s7_pointer slot = let_slots(sc->curlet); tis_slot(slot); slot = next_slot(slot)) + if (slot_has_expression(slot)) + slot_set_value(slot, fx_call(sc, slot_expression(slot))); + push_stack_no_args_direct(sc, OP_DO_NO_BODY_NA_VARS_STEP); + sc->code = caadr(sc->code); + return(false); +} + +static bool op_do_no_body_na_vars_step_1(s7_scheme *sc) +{ + if (sc->value != sc->F) + { + sc->code = cdadr(sc->code); + return(true); + } + slot_set_value(let_dox_slot1(sc->curlet), fx_call(sc, slot_expression(let_dox_slot1(sc->curlet)))); + push_stack_no_args_direct(sc, OP_DO_NO_BODY_NA_VARS_STEP_1); + sc->code = caadr(sc->code); + return(false); +} + +static bool do_step1(s7_scheme *sc) +{ + /* fprintf(stderr, "%s[%d]: sc->args: %s\n", __func__, __LINE__, display(sc->args)); */ + while (true) + { + s7_pointer code; + if (is_null(sc->args)) /* after getting the new values, transfer them into the slot_values */ + { + for (s7_pointer x = sc->code; is_pair(x); x = cdr(x)) /* sc->code here is the original sc->args list */ + { + slot_set_value(car(x), slot_pending_value(car(x))); + slot_clear_has_pending_value(car(x)); + } + pop_stack_no_op(sc); + return(true); + } + code = T_Pair(slot_expression(car(sc->args))); /* get the next stepper new value */ + if (has_fx(code)) + { + sc->value = fx_call(sc, code); + slot_set_pending_value(car(sc->args), sc->value); /* consistently slower if slot_simply_set... here? */ + sc->args = T_Lst(cdr(sc->args)); /* go to next step var */ + } + else + { + push_stack_direct(sc, OP_DO_STEP2); + sc->code = car(code); + return(false); + }} +} + +static bool op_do_step2(s7_scheme *sc) +{ + if (is_multiple_value(sc->value)) + syntax_error_nr(sc, "do: variable step value can't be ~S", 35, set_ulist_1(sc, sc->values_symbol, sc->value)); + slot_set_pending_value(car(sc->args), sc->value); /* save current value */ + sc->args = cdr(sc->args); /* go to next step var */ + return(do_step1(sc)); +} + +static bool op_do_step(s7_scheme *sc) /* called only in eval OP_DO_STEP via op_do_end_false */ +{ + /* increment all vars, return to endtest + * these are also updated in parallel at the end, so we gather all the incremented values first + * here we know car(sc->args) is not null, args is the list of steppable vars, + * any unstepped vars in the do var section are not in this list, so + * (do ((i 0 (+ i 1)) (j 2)) ...) arrives here with sc->args: '(slot<((+ i 1)=expr, 0=pending_value>)) -- is this comment correct? + */ + /* fprintf(stderr, "%s[%d]: sc->args: %s\n", __func__, __LINE__, display(sc->args)); */ + push_stack_direct(sc, OP_DO_END); + sc->args = car(sc->args); /* the var data lists */ + sc->code = T_Lst(sc->args); /* save the top of the list */ + return(do_step1(sc)); +} + +static goto_t do_end_code(s7_scheme *sc) +{ + if (is_pair(cdr(sc->code))) + { + if (is_undefined_feed_to(sc, car(sc->code))) + return(goto_feed_to); + /* never has_fx(sc->code) here (first of a body) */ + push_stack_no_args(sc, sc->begin_op, cdr(sc->code)); + sc->code = car(sc->code); + return(goto_eval); + } + if (has_fx(sc->code)) + { + sc->value = fx_call(sc, sc->code); + return(goto_start); + } + sc->code = T_Pair(car(sc->code)); + return(goto_eval); +} + +static bool do_end_clauses(s7_scheme *sc) +{ + if (!is_null(sc->code)) + return(false); + if (is_multiple_value(sc->value)) + sc->value = splice_in_values(sc, multiple_value(sc->value)); + return(true); +} + +static bool opt_do_copy(s7_scheme *sc, opt_info *o, s7_int start, s7_int stop) +{ + s7_pointer (*fp)(opt_info *o) = o->v[0].fp; /* o->v[6].p_pi_f is getter, o->v[5].p_pip_f is setter */ + if (start >= stop) return(true); + if ((fp == opt_p_pip_sso) && + (type(slot_value(o->v[1].p)) == type(slot_value(o->v[3].p))) && + (o->v[2].p == o->v[4].p)) + { + s7_pointer caller = NULL; + s7_pointer dest = slot_value(o->v[1].p); + s7_pointer source = slot_value(o->v[3].p); + if ((is_normal_vector(dest)) && + (((o->v[5].p_pip_f == vector_set_p_pip_unchecked) || + (o->v[5].p_pip_f == normal_vector_set_p_pip_direct)) && + ((o->v[6].p_pi_f == normal_vector_ref_p_pi_unchecked) || + (o->v[6].p_pi_f == vector_ref_p_pi_unchecked) || + (o->v[6].p_pi_f == normal_vector_ref_p_pi_direct)))) + caller = sc->vector_set_symbol; + else + if ((is_string(dest)) && + (((o->v[5].p_pip_f == string_set_p_pip_unchecked) || (o->v[5].p_pip_f == string_set_p_pip_direct)) && + ((o->v[6].p_pi_f == string_ref_p_pi_unchecked) || (o->v[6].p_pi_f == string_ref_p_pi_direct)))) + caller = sc->string_set_symbol; + else + if ((is_pair(dest)) && + ((o->v[5].p_pip_f == list_set_p_pip_unchecked) && (o->v[6].p_pi_f == list_ref_p_pi_unchecked))) + caller = sc->list_set_symbol; + else return(false); + if (start < 0) + out_of_range_error_nr(sc, caller, wrap_integer(sc, 2), wrap_integer(sc, start), it_is_negative_string); + if ((stop > integer(s7_length(sc, source))) || (stop > integer(s7_length(sc, dest)))) + out_of_range_error_nr(sc, caller, wrap_integer(sc, 2), wrap_integer(sc, stop), it_is_too_large_string); + if ((caller) && (copy_to_same_type(sc, dest, source, start, stop, start))) + return(true); + } + return(false); +} + +static bool op_simple_do_1(s7_scheme *sc, s7_pointer code) +{ + s7_pointer step_expr, step_var, ctr_slot, end_slot; + s7_function stepf, endf; + s7_pfunc func; + + if (no_cell_opt(cddr(code))) + return(false); + func = s7_optimize_nv(sc, cddr(code)); + if (!func) + { + set_no_cell_opt(cddr(code)); + return(false); + } + /* func must be set */ + step_expr = opt2_pair(code); /* caddr(caar(code)) */ + stepf = fn_proc(step_expr); + endf = fn_proc(caadr(code)); + ctr_slot = let_dox_slot1(sc->curlet); + end_slot = let_dox_slot2(sc->curlet); + step_var = caddr(step_expr); + /* use g* funcs (not fx) because we're passing the actual values, not the expressions */ + + if ((stepf == g_add_x1) && + (is_t_integer(slot_value(ctr_slot))) && + ((endf == g_num_eq_2) || (endf == g_num_eq_xi) || (endf == g_geq_2)) && + (is_t_integer(slot_value(end_slot)))) + { + s7_int i; + s7_int start = integer(slot_value(ctr_slot)); + s7_int stop = integer(slot_value(end_slot)); + + if (func == opt_cell_any_nv) + { + opt_info *o = sc->opts[0]; + s7_pointer (*fp)(opt_info *o) = o->v[0].fp; + if ((fp == opt_p_ppp_sss) || (fp == opt_p_ppp_sss_mul) || (fp == opt_p_ppp_sss_hset)) + { + s7_p_ppp_t fpt = o->v[4].p_ppp_f; + for (i = start; i < stop; i++) + { + slot_set_value(ctr_slot, make_integer(sc, i)); + fpt(sc, slot_value(o->v[1].p), slot_value(o->v[2].p), slot_value(o->v[3].p)); + }} + else + if (fp == opt_p_ppp_sfs) + { + s7_p_ppp_t fpt = o->v[3].p_ppp_f; + for (i = start; i < stop; i++) + { + slot_set_value(ctr_slot, make_integer(sc, i)); + fpt(sc, slot_value(o->v[1].p), o->v[5].fp(o->v[4].o1), slot_value(o->v[2].p)); + }} + else + if ((fp == opt_p_pip_sss_vset) && (start >= 0) && (stop <= vector_length(slot_value(o->v[1].p)))) + { + s7_pointer *vels = vector_elements(slot_value(o->v[1].p)); /* better in callgrind, possibly slightly slower in time */ + check_free_heap_size(sc, stop - start); + for (i = start; i < stop; i++) + { + slot_set_value(ctr_slot, make_integer_unchecked(sc, i)); + vels[integer(slot_value(o->v[2].p))] = slot_value(o->v[3].p); + }} + else + for (i = start; i < stop; i++) + { + slot_set_value(ctr_slot, make_integer(sc, i)); + fp(o); + }} + else + /* splitting out opt_float_any_nv here saves almost nothing */ + for (i = start; i < stop; i++) + { + slot_set_value(ctr_slot, make_integer(sc, i)); + func(sc); + } + sc->value = sc->T; + sc->code = cdadr(code); + return(true); + } + if ((stepf == g_subtract_x1) && + (is_t_integer(slot_value(ctr_slot))) && + ((endf == g_less_x0) || (endf == g_less_2) || (endf == g_less_xi)) && + (is_t_integer(slot_value(end_slot)))) + { + s7_int i, start = integer(slot_value(ctr_slot)), stop = integer(slot_value(end_slot)); + if (func == opt_cell_any_nv) + { + opt_info *o = sc->opts[0]; + if (!opt_do_copy(sc, o, stop, start + 1)) + { + s7_pointer (*fp)(opt_info *o) = o->v[0].fp; + for (i = start; i >= stop; i--) + { + slot_set_value(ctr_slot, make_integer(sc, i)); + fp(o); + }}} + else + for (i = start; i >= stop; i--) + { + slot_set_value(ctr_slot, make_integer(sc, i)); + func(sc); + } + sc->value = sc->T; + sc->code = cdadr(code); + return(true); + } + if ((stepf == g_add_2_xi) && + (is_t_integer(slot_value(ctr_slot))) && + ((endf == g_num_eq_2) || (endf == g_num_eq_xi) || (endf == g_geq_2)) && + (is_t_integer(slot_value(end_slot)))) + { + s7_int i, start = integer(slot_value(ctr_slot)), stop = integer(slot_value(end_slot)), incr = integer(caddr(step_expr)); + if (func == opt_cell_any_nv) + { + opt_info *o = sc->opts[0]; + s7_pointer (*fp)(opt_info *o) = o->v[0].fp; + for (i = start; i < stop; i += incr) + { + slot_set_value(ctr_slot, make_integer(sc, i)); + fp(o); + }} + else + for (i = start; i < stop; i += incr) + { + slot_set_value(ctr_slot, make_integer(sc, i)); + func(sc); + } + sc->value = sc->T; + sc->code = cdadr(code); + return(true); + } + if (func == opt_cell_any_nv) + { + opt_info *o = sc->opts[0]; + s7_pointer (*fp)(opt_info *o) = o->v[0].fp; + if ((stepf == g_add_x1) && (is_t_integer(slot_value(ctr_slot))) && + (endf == g_greater_2) && (is_t_integer(slot_value(end_slot)))) + { + s7_int start = integer(slot_value(ctr_slot)); + s7_int stop = integer(slot_value(end_slot)); + if (fp == opt_cond_1b) + { + s7_pointer (*test_fp)(opt_info *o) = o->v[4].o1->v[O_WRAP].fp; + opt_info *test_o1 = o->v[4].o1; + opt_info *o2 = o->v[6].o1; + for (s7_int i = start; i <= stop; i++) + { + slot_set_value(ctr_slot, make_integer(sc, i)); + if (test_fp(test_o1) != sc->F) cond_value(o2); + }} + else + for (s7_int i = start; i <= stop; i++) + { + slot_set_value(ctr_slot, make_integer(sc, i)); + fp(o); + }} + else + do { + fp(o); + set_car(sc->t2_1, slot_value(ctr_slot)); + set_car(sc->t2_2, step_var); + slot_set_value(ctr_slot, stepf(sc, sc->t2_1)); + set_car(sc->t2_1, slot_value(ctr_slot)); + set_car(sc->t2_2, slot_value(end_slot)); + } while ((sc->value = endf(sc, sc->t2_1)) == sc->F); + } + else + do { + func(sc); + set_car(sc->t2_1, slot_value(ctr_slot)); + set_car(sc->t2_2, step_var); + slot_set_value(ctr_slot, stepf(sc, sc->t2_1)); + set_car(sc->t2_1, slot_value(ctr_slot)); + set_car(sc->t2_2, slot_value(end_slot)); + } while ((sc->value = endf(sc, sc->t2_1)) == sc->F); + + sc->code = cdadr(code); + return(true); +} + +static bool op_simple_do(s7_scheme *sc) +{ + /* body might not be safe in this case, but the step and end exprs are easy */ + s7_pointer code = cdr(sc->code); + s7_pointer end = opt1_any(code); /* caddr(caadr(code)) */ + s7_pointer body = cddr(code); + + sc->curlet = make_let(sc, sc->curlet); + sc->value = fx_call(sc, cdaar(code)); + let_set_dox_slot1(sc->curlet, add_slot_checked(sc, sc->curlet, caaar(code), sc->value)); + + if (is_symbol(end)) + let_set_dox_slot2(sc->curlet, s7_slot(sc, end)); + else let_set_dox_slot2(sc->curlet, make_slot(sc, caaar(code), end)); + set_car(sc->t2_1, let_dox1_value(sc->curlet)); + set_car(sc->t2_2, let_dox2_value(sc->curlet)); + sc->value = fn_proc(caadr(code))(sc, sc->t2_1); + if (is_true(sc, sc->value)) + { + sc->code = cdadr(code); + return(true); /* goto DO_END_CLAUSES */ + } + if ((is_null(cdr(body))) && /* one expr in body */ + (is_pair(car(body))) && /* and it is a pair */ + (is_symbol(cadr(opt2_pair(code)))) && /* caddr(caar(code)), caar=(i 0 (+ i 1)), caddr=(+ i 1), so this checks that stepf is reasonable? */ + (is_t_integer(caddr(opt2_pair(code)))) && + (op_simple_do_1(sc, cdr(sc->code)))) + return(true); /* goto DO_END_CLAUSES */ + + push_stack_no_args(sc, OP_SIMPLE_DO_STEP, code); + sc->code = body; + return(false); /* goto BEGIN */ +} + +static bool op_simple_do_step(s7_scheme *sc) +{ + s7_pointer ctr = let_dox_slot1(sc->curlet); + s7_pointer end = let_dox_slot2(sc->curlet); + s7_pointer code = sc->code; + s7_pointer step = opt2_pair(code); /* caddr(caar(code)) */ + if (is_symbol(cadr(step))) + { + set_car(sc->t2_1, slot_value(ctr)); + set_car(sc->t2_2, caddr(step)); + } + else /* is_symbol(caddr(step)) I think: (+ 1 x) vs (+ x 1) */ + { + set_car(sc->t2_2, slot_value(ctr)); + set_car(sc->t2_1, cadr(step)); + } + slot_set_value(ctr, fn_proc(step)(sc, sc->t2_1)); + set_car(sc->t2_1, slot_value(ctr)); + set_car(sc->t2_2, slot_value(end)); + end = cadr(code); + sc->value = fn_proc(car(end))(sc, sc->t2_1); + if (is_true(sc, sc->value)) + { + sc->code = cdr(end); + return(true); + } + push_stack_direct(sc, OP_SIMPLE_DO_STEP); + sc->code = T_Pair(cddr(code)); + return(false); +} + +static bool op_safe_do_step(s7_scheme *sc) +{ + s7_int end = integer(let_dox2_value(sc->curlet)); + s7_pointer slot = let_dox_slot1(sc->curlet); + s7_int step = integer(slot_value(slot)) + 1; + slot_set_value(slot, make_integer(sc, step)); + if ((step == end) || + ((step > end) && (opt1_cfunc(caadr(sc->code)) == sc->geq_2))) + { + sc->value = sc->T; + sc->code = cdadr(sc->code); + return(true); + } + push_stack_direct(sc, OP_SAFE_DO_STEP); + sc->code = T_Pair(opt2_pair(sc->code)); + return(false); +} + +static bool op_safe_dotimes_step(s7_scheme *sc) +{ + s7_pointer arg = slot_value(sc->args); + numerator(arg)++; + if (numerator(arg) == loop_end(sc->args)) + { + sc->value = sc->T; + sc->code = cdadr(sc->code); + return(true); + } + push_stack_direct(sc, OP_SAFE_DOTIMES_STEP); + sc->code = opt2_pair(sc->code); /* here we know the body has more than one form */ + push_stack_no_args(sc, sc->begin_op, T_Pair(cdr(sc->code))); + sc->code = car(sc->code); + return(false); +} + +static bool op_safe_dotimes_step_o(s7_scheme *sc) +{ + s7_pointer arg = slot_value(sc->args); + numerator(arg)++; + if (numerator(arg) == loop_end(sc->args)) + { + sc->value = sc->T; + sc->code = cdadr(sc->code); + return(true); + } + push_stack_direct(sc, OP_SAFE_DOTIMES_STEP_O); + sc->code = opt2_pair(sc->code); + return(false); +} + +static /* inline */ bool op_dotimes_step_o(s7_scheme *sc) /* called once in eval, mat(10+6), num(7+1) */ +{ + s7_pointer ctr = let_dox_slot1(sc->curlet); + s7_pointer end = let_dox2_value(sc->curlet); + s7_pointer now = slot_value(ctr); + s7_pointer code = sc->code; + s7_pointer end_test = opt2_pair(code); + + if (is_t_integer(now)) + { + slot_set_value(ctr, make_integer(sc, integer(now) + 1)); + now = slot_value(ctr); + if (is_t_integer(end)) + { + if ((integer(now) == integer(end)) || + ((integer(now) > integer(end)) && (opt1_cfunc(end_test) == sc->geq_2))) + { + sc->value = sc->T; + sc->code = cdadr(code); + return(true); + }} + else + { + set_car(sc->t2_1, now); + set_car(sc->t2_2, end); + end = cadr(code); + sc->value = fn_proc(car(end))(sc, sc->t2_1); + if (is_true(sc, sc->value)) + { + sc->code = cdr(end); + return(true); + }}} + else + { + slot_set_value(ctr, g_add_x1(sc, with_list_t1(now))); + /* (define (hi) (let ((x 0.0) (y 1.0)) (do ((i y (+ i 1))) ((= i 6)) (do ((i i (+ i 1))) ((>= i 7)) (set! x (+ x i)))) x)) */ + set_car(sc->t2_1, slot_value(ctr)); + set_car(sc->t2_2, end); + end = cadr(code); + sc->value = fn_proc(car(end))(sc, sc->t2_1); + if (is_true(sc, sc->value)) + { + sc->code = cdr(end); + return(true); + }} + push_stack_direct(sc, OP_DOTIMES_STEP_O); + sc->code = caddr(code); + return(false); +} + +static bool opt_dotimes(s7_scheme *sc, s7_pointer code, s7_pointer scc, bool safe_step) +{ + if (safe_step) /* I think safe_step means the stepper is completely unproblematic */ + set_safe_stepper(sc->args); + else set_safe_stepper(let_dox_slot1(sc->curlet)); + + if (is_null(cdr(code))) + { + s7_pfunc func; + if (no_cell_opt(code)) return(false); + func = s7_optimize_nv(sc, code); + if (!func) + { + set_no_cell_opt(code); + return(false); + } + if (safe_step) + { + s7_int end = loop_end(sc->args); + s7_pointer stepper = make_mutable_integer(sc, integer(slot_value(sc->args))); + slot_set_value(sc->args, stepper); + if ((func == opt_float_any_nv) || + (func == opt_cell_any_nv)) + { + opt_info *o = sc->opts[0]; + if (func == opt_float_any_nv) + { + s7_double (*fd)(opt_info *o) = o->v[0].fd; + if ((fd == opt_d_id_sf) && /* by far the most common case in clm: (outa i ...) etc */ + (is_slot(o->v[1].p)) && + (stepper == slot_value(o->v[1].p))) + { + opt_info *o1 = sc->opts[1]; + s7_int end8 = end - 8; + s7_d_id_t f0 = o->v[3].d_id_f; + fd = o1->v[0].fd; + while (integer(stepper) < end8) + LOOP_8(f0(integer(stepper), fd(o1)); integer(stepper)++); + while (integer(stepper) < end) + { + f0(integer(stepper), fd(o1)); + integer(stepper)++; + }} + else + if ((o->v[0].fd == opt_d_7pid_ss_ss) && (o->v[4].d_7pid_f == float_vector_set_d_7pid_direct) && + (o->v[3].d_7pi_f == float_vector_ref_d_7pi) && (o->v[2].p == o->v[6].p)) + copy_to_same_type(sc, slot_value(o->v[1].p), slot_value(o->v[5].p), integer(stepper), end, integer(stepper)); + else + if ((o->v[0].fd == opt_d_7pid_ssc) && + (o->v[4].d_7pid_f == float_vector_set_d_7pid_direct) && + (stepper == slot_value(o->v[2].p))) + s7_fill(sc, set_elist_4(sc, slot_value(o->v[1].p), make_real(sc, o->v[3].x), stepper, make_integer(sc, end))); + else + { + s7_int end4 = end - 4; + while (integer(stepper) < end4) + LOOP_4(fd(o); integer(stepper)++); + for (; integer(stepper) < end; integer(stepper)++) + fd(o); + }} + else + { + s7_pointer (*fp)(opt_info *o) = o->v[0].fp; + if ((fp == opt_p_pip_ssc) && + (stepper == slot_value(o->v[2].p)) && /* i.e. index by do counter */ + ((o->v[3].p_pip_f == string_set_p_pip_direct) || + (o->v[3].p_pip_f == normal_vector_set_p_pip_direct) || + (o->v[3].p_pip_f == list_set_p_pip_unchecked))) + s7_fill(sc, set_elist_4(sc, slot_value(o->v[1].p), o->v[4].p, stepper, make_integer(sc, end))); + else + if (fp == opt_if_bp) + { + for (; integer(stepper) < end; integer(stepper)++) + if (o->v[3].fb(o->v[2].o1)) o->v[5].fp(o->v[4].o1); + } + else + if (fp == opt_if_nbp_fs) + { + for (; integer(stepper) < end; integer(stepper)++) + if (!(o->v[2].b_pi_f(sc, o->v[5].fp(o->v[4].o1), integer(slot_value(o->v[3].p))))) o->v[11].fp(o->v[10].o1); + } + else + if (fp == opt_unless_p_1) + { + for (; integer(stepper) < end; integer(stepper)++) + if (!(o->v[4].fb(o->v[3].o1))) o->v[5].o1->v[0].fp(o->v[5].o1); + } + else for (; integer(stepper) < end; integer(stepper)++) fp(o); + }} + else + if (func == opt_int_any_nv) + { + opt_info *o = sc->opts[0]; + s7_int (*fi)(opt_info *o) = o->v[0].fi; + if ((fi == opt_i_7pii_ssc) && (stepper == slot_value(o->v[2].p)) && (o->v[3].i_7pii_f == int_vector_set_i_7pii_direct)) + s7_fill(sc, set_elist_4(sc, slot_value(o->v[1].p), make_integer(sc, o->v[4].i), stepper, make_integer(sc, end))); + else + if ((o->v[3].i_7pii_f == int_vector_set_i_7pii_direct) && (o->v[5].fi == opt_7pi_ss_ivref) && (o->v[2].p == o->v[4].o1->v[2].p)) + copy_to_same_type(sc, slot_value(o->v[1].p), slot_value(o->v[4].o1->v[1].p), integer(stepper), end, integer(stepper)); + else + for (; integer(stepper) < end; integer(stepper)++) + fi(o); + } + else /* (((i 0 (+ i 1))) ((= i 1)) (char-alphabetic? (string-ref #u(0 1) 1))) or (logbit? i -1): kinda nutty */ + for (; integer(stepper) < end; integer(stepper)++) + func(sc); + + clear_mutable_integer(stepper); + } + else /* not safe_step */ + { + s7_pointer step_slot = let_dox_slot1(sc->curlet); + s7_pointer end_slot = let_dox_slot2(sc->curlet); + s7_int step = integer(slot_value(step_slot)); + s7_int stop = integer(slot_value(end_slot)); + if (func == opt_cell_any_nv) + { + opt_info *o = sc->opts[0]; + s7_pointer (*fp)(opt_info *o) = o->v[0].fp; + if (!opt_do_copy(sc, o, step, stop)) + { + if ((step >= 0) && (stop < NUM_SMALL_INTS)) + { + if (fp == opt_when_p_2) + { + while (step < stop) + { + slot_set_value(step_slot, small_int(step)); + if (o->v[4].fb(o->v[3].o1)) + { + o->v[6].fp(o->v[5].o1); + o->v[8].fp(o->v[7].o1); + } + step = integer(slot_value(step_slot)) + 1; + }} + else + while (step < stop) + { + slot_set_value(step_slot, small_int(step)); + fp(o); + step = integer(slot_value(step_slot)) + 1; + }} + else + while (step < stop) + { + slot_set_value(step_slot, make_integer(sc, step)); + fp(o); + step = integer(slot_value(step_slot)) + 1; + }}} + else + if ((step >= 0) && (stop < NUM_SMALL_INTS)) + while (step < stop) + { + slot_set_value(step_slot, small_int(step)); + func(sc); + step = integer(slot_value(step_slot)) + 1; + } + else + if (func == opt_int_any_nv) + { + opt_info *o = sc->opts[0]; + s7_int (*fi)(opt_info *o) = o->v[0].fi; + while (step < stop) + { + slot_set_value(step_slot, make_integer(sc, step)); + fi(o); + step = integer(slot_value(step_slot)) + 1; + }} + else + while (step < stop) + { + slot_set_value(step_slot, make_integer(sc, step)); + func(sc); + step = integer(slot_value(step_slot)) + 1; + }} + sc->value = sc->T; + sc->code = cdadr(scc); + return(true); + } + { + s7_pointer p; + s7_int body_len = s7_list_length(sc, code); + opt_info *body[32]; + int32_t k; + + sc->pc = 0; + if (body_len >= 32) return(false); + + if (!no_float_opt(code)) + { + for (k = 0, p = code; is_pair(p); k++, p = cdr(p)) + { + body[k] = sc->opts[sc->pc]; + if (!float_optimize(sc, p)) + break; + } + if (is_pair(p)) + { + sc->pc = 0; + set_no_float_opt(code); + } + else + { + if (safe_step) + { + s7_int end = loop_end(sc->args); + s7_pointer stepper = make_mutable_integer(sc, integer(slot_value(sc->args))); + slot_set_value(sc->args, stepper); + for (; integer(stepper) < end; integer(stepper)++) + for (int32_t i = 0; i < body_len; i++) body[i]->v[0].fd(body[i]); + clear_mutable_integer(stepper); + } + else + { + s7_pointer step_slot = let_dox_slot1(sc->curlet); + s7_pointer end_slot = let_dox_slot2(sc->curlet); + s7_int stop = integer(slot_value(end_slot)); + for (s7_int step = integer(slot_value(step_slot)); step < stop; step = integer(slot_value(step_slot)) + 1) + { + slot_set_value(step_slot, make_integer(sc, step)); + for (int32_t i = 0; i < body_len; i++) body[i]->v[0].fd(body[i]); + }} + sc->value = sc->T; + sc->code = cdadr(scc); + return(true); + }} + + /* not float opt */ + sc->pc = 0; + for (k = 0, p = code; is_pair(p); k++, p = cdr(p)) + { + opt_info *start = sc->opts[sc->pc]; + if (!cell_optimize(sc, p)) + break; + oo_idp_nr_fixup(start); + body[k] = start; + } + + if (is_null(p)) + { +#if S7_DEBUGGING + if ((safe_step) && (!has_loop_end(sc->args))) fprintf(stderr, "%s[%d]: safe_step but not has_loop_end\n", __func__, __LINE__); +#endif + if ((safe_step) && (has_loop_end(sc->args))) /* has_loop_end is perhaps redundant (there is at least one more case of this) */ + { + s7_int end = loop_end(sc->args); + s7_pointer stepper = make_mutable_integer(sc, integer(slot_value(sc->args))); + slot_set_value(sc->args, stepper); + if ((body_len & 0x3) == 0) + for (; integer(stepper) < end; integer(stepper)++) + for (int32_t i = 0; i < body_len; ) + LOOP_4(body[i]->v[0].fp(body[i]); i++); + else + for (; integer(stepper) < end; integer(stepper)++) + for (int32_t i = 0; i < body_len; i++) body[i]->v[0].fp(body[i]); + clear_mutable_integer(stepper); + } + else + { + s7_pointer step_slot = let_dox_slot1(sc->curlet); + s7_pointer end_slot = let_dox_slot2(sc->curlet); + s7_int stop = integer(slot_value(end_slot)); + for (s7_int step = integer(slot_value(step_slot)); step < stop; step = integer(slot_value(step_slot)) + 1) + { + slot_set_value(step_slot, make_integer(sc, step)); + for (int32_t i = 0; i < body_len; i++) body[i]->v[0].fp(body[i]); + }} + sc->value = sc->T; + sc->code = cdadr(scc); + return(true); + }} + return(false); +} + +static bool do_let(s7_scheme *sc, s7_pointer step_slot, s7_pointer scc) +{ + s7_pointer let_body, p = NULL, let_vars, let_code = caddr(scc), ip; + bool let_star; + s7_pointer old_e, stepper; + s7_int body_len, var_len, k, end; + #define O_SIZE 32 + opt_info *body[O_SIZE], *vars[O_SIZE]; + memclr((void *)body, O_SIZE * sizeof(opt_info *)); /* placate the damned compiler */ + memclr((void *)vars, O_SIZE * sizeof(opt_info *)); + + /* do_let with non-float vars doesn't get many fixable hits */ + if ((!is_pair(cdr(let_code))) || (!is_list(cadr(let_code)))) /* (do ((j 0 (+ j 1))) ((= j 1)) (let name 123)) */ + return(false); + let_body = cddr(let_code); + body_len = s7_list_length(sc, let_body); + if ((body_len <= 0) || (body_len >= 32)) return(false); + let_star = (symbol_syntax_op_checked(let_code) == OP_LET_STAR); + let_vars = cadr(let_code); + set_safe_stepper(step_slot); + stepper = slot_value(step_slot); + old_e = sc->curlet; + sc->curlet = make_let(sc, sc->curlet); + + sc->pc = 0; + for (var_len = 0, p = let_vars; (is_pair(p)) && (var_len < 32); var_len++, p = cdr(p)) + { + if ((!is_pair(car(p))) || + (!is_normal_symbol(caar(p))) || + (!is_pair(cdar(p)))) + return(false); + vars[var_len] = sc->opts[sc->pc]; + if (!float_optimize(sc, cdar(p))) /* each of these needs to set the associated variable */ + { + set_curlet(sc, old_e); + return(false); + } + if (let_star) + add_slot_checked(sc, sc->curlet, caar(p), s7_make_mutable_real(sc, 1.5)); + } + + if (!let_star) + for (p = let_vars; is_pair(p); p = cdr(p)) + add_slot_checked(sc, sc->curlet, caar(p), s7_make_mutable_real(sc, 1.5)); + + for (k = 0, p = let_body; is_pair(p); k++, p = cdr(p)) + { + body[k] = sc->opts[sc->pc]; + if (!float_optimize(sc, p)) + { + set_curlet(sc, old_e); + return(false); + }} + if (!is_null(p)) /* no hits in s7test or snd-test */ + { + set_curlet(sc, old_e); + return(false); + } + end = loop_end(step_slot); + let_set_slots(sc->curlet, reverse_slots(let_slots(sc->curlet))); + ip = slot_value(step_slot); + + if (body_len == 1) + { + if (var_len == 1) + { + opt_info *first = sc->opts[0]; + opt_info *o = body[0]; + s7_pointer xp = t_lookup(sc, caar(let_vars), let_vars); + s7_double (*f1)(opt_info *o) = first->v[0].fd; + s7_double (*f2)(opt_info *o) = o->v[0].fd; + integer(ip) = numerator(stepper); + set_real(xp, f1(first)); + f2(o); + if ((f2 == opt_fmv) && + (f1 == opt_d_dd_ff_o2) && + (first->v[3].d_dd_f == add_d_dd) && + (slot_symbol(step_slot) == slot_symbol(o->v[2].p))) + { + opt_info *o1 = o->v[12].o1; + opt_info *o2 = o->v[13].o1; + opt_info *o3 = o->v[14].o1; + s7_d_vid_t vf7 = o->v[4].d_vid_f; + s7_d_v_t vf1 = first->v[4].d_v_f; + s7_d_v_t vf2 = first->v[5].d_v_f; + s7_d_v_t vf3 = o1->v[2].d_v_f; + s7_d_v_t vf4 = o3->v[5].d_v_f; + s7_d_vd_t vf5 = o2->v[3].d_vd_f; + s7_d_vd_t vf6 = o3->v[6].d_vd_f; + void *obj1 = first->v[1].obj; + void *obj2 = first->v[2].obj; + void *obj3 = o1->v[1].obj; + void *obj4 = o3->v[1].obj; + void *obj5 = o->v[5].obj; + void *obj6 = o2->v[5].obj; + void *obj7 = o3->v[2].obj; + for (k = numerator(stepper) + 1; k < end; k++) + { + s7_double vib = vf1(obj1) + vf2(obj2); + s7_double amp_env = vf3(obj3); + vf7(obj5, k, amp_env * vf5(obj6, vib + (vf4(obj4) * vf6(obj7, vib)))); + }} + else + for (k = numerator(stepper) + 1; k < end; k++) + { + integer(ip) = k; + set_real(xp, f1(first)); + f2(o); + }} /* body_len == 1 and var_len == 1 */ + else + { + if (var_len == 2) + { + s7_pointer s1 = let_slots(sc->curlet); + s7_pointer s2 = next_slot(s1); + for (k = numerator(stepper); k < end; k++) + { + integer(ip) = k; + set_real(slot_value(s1), vars[0]->v[0].fd(vars[0])); + set_real(slot_value(s2), vars[1]->v[0].fd(vars[1])); + body[0]->v[0].fd(body[0]); + }} /* body_len == 1 and var_len == 2 */ + else + for (k = numerator(stepper); k < end; k++) + { + integer(ip) = k; + p = let_slots(sc->curlet); + for (int32_t n = 0; tis_slot(p); n++, p = next_slot(p)) + set_real(slot_value(p), vars[n]->v[0].fd(vars[n])); + body[0]->v[0].fd(body[0]); + }}} /* end body_len == 1 */ + else + if ((body_len == 2) && (var_len == 1)) + { + s7_pointer s1 = let_slots(sc->curlet); + for (k = numerator(stepper); k < end; k++) + { + integer(ip) = k; + set_real(slot_value(s1), vars[0]->v[0].fd(vars[0])); + body[0]->v[0].fd(body[0]); + body[1]->v[0].fd(body[1]); + }} + else + for (k = numerator(stepper); k < end; k++) + { + int32_t i; + integer(ip) = k; + for (i = 0, p = let_slots(sc->curlet); tis_slot(p); i++, p = next_slot(p)) + set_real(slot_value(p), vars[i]->v[0].fd(vars[i])); + for (i = 0; i < body_len; i++) body[i]->v[0].fd(body[i]); + } + set_curlet(sc, old_e); + sc->value = sc->T; + sc->code = cdadr(scc); + return(true); +} + +static bool dotimes(s7_scheme *sc, s7_pointer code, bool safe_case) +{ + s7_pointer body = caddr(code); /* here we assume one expr in body */ + if (((is_syntactic_pair(body)) || + (is_symbol_and_syntactic(car(body)))) && + ((symbol_syntax_op_checked(body) == OP_LET) || + (symbol_syntax_op(car(body)) == OP_LET_STAR))) + return(do_let(sc, sc->args, code)); + return(opt_dotimes(sc, cddr(code), code, safe_case)); +} + +static goto_t op_safe_dotimes(s7_scheme *sc) +{ + s7_pointer init_val, form = sc->code; + sc->code = cdr(sc->code); + + init_val = fx_call(sc, cdaar(sc->code)); + if (s7_is_integer(init_val)) + { + s7_pointer end_expr = caadr(sc->code); + s7_pointer code = sc->code; + s7_pointer end_val = caddr(end_expr); + if (is_symbol(end_val)) + end_val = lookup_checked(sc, end_val); + + if (s7_is_integer(end_val)) + { + sc->code = cddr(code); + sc->curlet = make_let(sc, sc->curlet); + sc->args = add_slot_checked(sc, sc->curlet, caaar(code), make_mutable_integer(sc, s7_integer_clamped_if_gmp(sc, init_val))); + set_loop_end(sc->args, s7_integer_clamped_if_gmp(sc, end_val)); + set_has_loop_end(sc->args); /* safe_dotimes step is by 1 */ + + /* (define (hi) (do ((i 1 (+ 1 i))) ((= i 1) i))) -- we need the let even if the loop is not evaluated */ + + /* safe_dotimes: (car(body) is known to be a pair here) + * if 1-expr body look for syntactic case, if let(*) goto do_let, else opt_dotimes + * if they are unhappy, got safe_dotimes_step_o + * else goto opt_dotimes then safe_dotimes_step_o + * if multi-line body, check opt_dotimes, then safe_dotimes_step + */ + if (s7_integer_clamped_if_gmp(sc, init_val) == s7_integer_clamped_if_gmp(sc, end_val)) + { + sc->value = sc->T; + sc->code = cdadr(code); + return(goto_safe_do_end_clauses); + } + if ((is_null(cdr(sc->code))) && + (is_pair(car(sc->code)))) + { + sc->code = car(sc->code); + set_opt2_pair(code, sc->code); /* is_pair above */ + if ((is_syntactic_pair(sc->code)) || + (is_symbol_and_syntactic(car(sc->code)))) + { + if (!is_unsafe_do(code)) + { + if (dotimes(sc, code, true)) + return(goto_safe_do_end_clauses); + set_unsafe_do(code); + } + push_stack(sc, OP_SAFE_DOTIMES_STEP_O, sc->args, code); + if (is_syntactic_pair(sc->code)) + sc->cur_op = (opcode_t)optimize_op(sc->code); + else + { + sc->cur_op = (opcode_t)symbol_syntax_op_checked(sc->code); + pair_set_syntax_op(sc->code, sc->cur_op); + } + return(goto_top_no_pop); + } + /* car not syntactic? */ + if ((!is_unsafe_do(code)) && + (opt_dotimes(sc, cddr(code), code, true))) + return(goto_safe_do_end_clauses); + set_unsafe_do(code); + + if (has_fx(cddr(code))) /* this almost never happens and the func case below is only in timing tests */ + { + s7_int end = s7_integer_clamped_if_gmp(sc, end_val); + s7_pointer body = cddr(code), stepper = slot_value(sc->args); + for (; integer(stepper) < end; integer(stepper)++) + fx_call(sc, body); + sc->value = sc->T; + sc->code = cdadr(code); + return(goto_safe_do_end_clauses); + } + push_stack(sc, OP_SAFE_DOTIMES_STEP_O, sc->args, code); /* arg is local step var slot, code is do form - do, sc->code is the body */ + return(goto_eval); + } + /* multi-line body */ + if ((!is_unsafe_do(code)) && + (opt_dotimes(sc, sc->code, code, true))) + return(goto_safe_do_end_clauses); + set_unsafe_do(code); + set_opt2_pair(code, sc->code); + push_stack(sc, OP_SAFE_DOTIMES_STEP, sc->args, code); + return(goto_begin); + }} + pair_set_syntax_op(form, OP_SIMPLE_DO); + sc->code = form; + if (op_simple_do(sc)) return(goto_do_end_clauses); + return(goto_begin); +} + +static goto_t op_safe_do(s7_scheme *sc) +{ + /* body is safe, step = +1, end is = or >=, but stepper and end might be set (or at least indirectly exported) in the body: + * (let ((lst ())) (do ((i 0 (+ i 1))) ((= i 10)) (let ((j (min i 100))) (set! lst (cons j lst)))) lst) + * however, we're very restrictive about this in check_do and do_is_safe; even this is considered trouble: + * (let ((x 0)) (do ((i i (+ i 1))) ((= i 7)) (set! x (+ x i))) x) + * but end might not be an integer -- need to catch this earlier. + */ + s7_pointer end, init_val, end_val, code, form = sc->code; + + /* inits, if not >= opt_dotimes else safe_do_step */ + sc->code = cdr(sc->code); + code = sc->code; + init_val = fx_call(sc, cdaar(code)); + end = opt1_any(code); /* caddr(caadr(code)) */ + end_val = (is_symbol(end)) ? lookup_checked(sc, end) : end; + + if ((!s7_is_integer(init_val)) || (!s7_is_integer(end_val))) /* this almost never happens */ + { + pair_set_syntax_op(form, OP_DO_UNCHECKED); + return(goto_do_unchecked); + } + + /* (let ((sum 0)) (define (hi) (do ((i 10 (+ i 1))) ((= i 10) i) (set! sum (+ sum i)))) (hi)) */ + sc->curlet = make_let(sc, sc->curlet); + let_set_dox_slot1(sc->curlet, add_slot_checked(sc, sc->curlet, caaar(code), init_val)); /* define the step var -- might be needed in the end clauses */ + + if ((s7_integer_clamped_if_gmp(sc, init_val) == s7_integer_clamped_if_gmp(sc, end_val)) || + ((s7_integer_clamped_if_gmp(sc, init_val) > s7_integer_clamped_if_gmp(sc, end_val)) && + (opt1_cfunc(caadr(code)) == sc->geq_2))) + { + sc->value = sc->T; + sc->code = cdadr(code); + return(goto_safe_do_end_clauses); + } + + if (is_symbol(end)) + let_set_dox_slot2(sc->curlet, s7_slot(sc, end)); + else let_set_dox_slot2(sc->curlet, make_slot(sc, caaar(code), end)); + sc->args = let_dox_slot2(sc->curlet); /* the various safe steps assume sc->args is the end slot */ + + { + s7_pointer step_slot = let_dox_slot1(sc->curlet); + set_has_loop_end(step_slot); + slot_set_value(step_slot, make_mutable_integer(sc, integer(slot_value(step_slot)))); + set_loop_end(step_slot, s7_integer_clamped_if_gmp(sc, end_val)); + } + + if (!is_unsafe_do(sc->code)) + { + s7_pointer old_let = sc->curlet; + sc->temp7 = old_let; + if (opt_dotimes(sc, cddr(sc->code), sc->code, false)) + return(goto_safe_do_end_clauses); + set_curlet(sc, old_let); /* apparently s7_optimize can step on sc->curlet? */ + sc->temp7 = sc->unused; + } + if (is_null(cdddr(sc->code))) + { + s7_pointer body = caddr(sc->code); + if ((car(body) == sc->set_symbol) && + (is_pair(cdr(body))) && + (is_symbol(cadr(body))) && + (is_pair(cddr(body))) && + (has_fx(cddr(body))) && + (is_null(cdddr(body)))) + { + s7_pointer step_slot = let_dox_slot1(sc->curlet); + if (slot_symbol(step_slot) != cadr(body)) + { + s7_int endi = integer(let_dox2_value(sc->curlet)); + s7_pointer fx_p = cddr(body); + s7_pointer val_slot = s7_slot(sc, cadr(body)); + s7_int step = integer(slot_value(step_slot)); + s7_pointer step_val = make_mutable_integer(sc, step); + slot_set_value(step_slot, step_val); + do { + slot_set_value(val_slot, fx_call(sc, fx_p)); + integer(step_val) = ++step; + } while (step != endi); /* geq not needed here -- we're leq endi and stepping by +1 all ints */ + clear_mutable_integer(step_val); + sc->value = sc->T; + sc->code = cdadr(code); + return(goto_safe_do_end_clauses); + }}} + sc->code = cddr(code); + set_unsafe_do(sc->code); + set_opt2_pair(code, sc->code); + push_stack_no_args(sc, OP_SAFE_DO_STEP, code); /* (do ((i 0 (+ i 1))) ((= i 2)) (set! (str i) #\a)) */ + return(goto_begin); +} + +static goto_t op_dotimes_p(s7_scheme *sc) +{ + s7_pointer code = cdr(sc->code), end_val, slot, old_e; + s7_pointer end = opt1_any(code); /* caddr(opt2_pair(code)) */ + /* (do ... (set! args ...)) -- one line, syntactic */ + + s7_pointer init_val = fx_call(sc, cdaar(code)); + sc->value = init_val; + set_opt2_pair(code, caadr(code)); + if (is_symbol(end)) + { + slot = s7_slot(sc, end); + end_val = slot_value(slot); + } + else + { + slot = make_slot(sc, make_symbol(sc, "___end___", 9), end); /* name is ignored, but needs to be > 8 chars for gcc's benefit (version 10.2.1)! */ + end_val = end; + } + if ((!s7_is_integer(init_val)) || (!s7_is_integer(end_val))) + { + pair_set_syntax_op(sc->code, OP_DO_UNCHECKED); + sc->code = cdr(sc->code); + return(goto_do_unchecked); + } + + old_e = sc->curlet; + sc->curlet = make_let(sc, sc->curlet); + let_set_dox_slot1(sc->curlet, add_slot_checked(sc, sc->curlet, caaar(code), init_val)); + let_set_dox_slot2(sc->curlet, slot); + + set_car(sc->t2_1, let_dox1_value(sc->curlet)); + set_car(sc->t2_2, let_dox2_value(sc->curlet)); + if (is_true(sc, sc->value = fn_proc(caadr(code))(sc, sc->t2_1))) + { + sc->code = cdadr(code); + return(goto_do_end_clauses); + } + if ((!is_unsafe_do(code)) && + (opt1_cfunc(caadr(code)) != sc->geq_2)) + { + s7_pointer old_args = sc->args; + s7_pointer old_init = let_dox1_value(sc->curlet); + sc->args = T_Slt(let_dox_slot1(sc->curlet)); /* used in opt_dotimes */ + slot_set_value(sc->args, make_mutable_integer(sc, integer(let_dox1_value(sc->curlet)))); + set_loop_end(sc->args, integer(let_dox2_value(sc->curlet))); + set_has_loop_end(sc->args); /* dotimes step is by 1 */ + sc->code = cdr(sc->code); + if (dotimes(sc, code, false)) + return(goto_do_end_clauses); /* not safe_do here */ + slot_set_value(sc->args, old_init); + set_curlet(sc, old_e); /* free_cell(sc, sc->curlet) beforehand is not safe */ + sc->args = old_args; + set_unsafe_do(code); + return(goto_do_unchecked); + } + push_stack_no_args(sc, OP_DOTIMES_STEP_O, code); + sc->code = caddr(code); + return(goto_eval); +} + +static bool op_do_init_1(s7_scheme *sc) +{ + s7_pointer y, z; + while (true) /* at start, first value is the loop (for GC protection?), returning sc->value is the next value */ + { + s7_pointer init; + sc->args = cons(sc, sc->value, sc->args); /* code will be last element (first after reverse), these cons's will be used below for the new let/slots */ + if (!is_pair(sc->code)) break; + /* here sc->code is a list like: ((i 0 (+ i 1)) ...) so cadar gets the init value */ + init = cdar(sc->code); + if (has_fx(init)) + sc->value = fx_call(sc, init); + else + { + init = car(init); + if (is_pair(init)) + { + push_stack(sc, OP_DO_INIT, sc->args, cdr(sc->code)); /* OP_DO_INIT only used here */ + sc->code = init; + return(true); /* goto eval */ + } + sc->value = (is_symbol(init)) ? lookup_checked(sc, init) : init; + } + sc->code = cdr(sc->code); + } + /* all the initial values are now in the args list */ + sc->args = proper_list_reverse_in_place(sc, sc->args); + sc->code = car(sc->args); /* saved at the start */ + z = sc->args; + sc->args = cdr(sc->args); /* init values */ + + /* sc->args was cons'd above, so it should be safe to reuse it as the new let */ + set_curlet(sc, reuse_as_let(sc, z, sc->curlet)); /* sc->curlet = make_let(sc, sc->curlet); */ + + /* run through sc->code and sc->args adding '( caar(car(code)) . car(args) ) to sc->curlet, also reuse sc->args as the new let slots */ + sc->value = sc->nil; + y = sc->args; + for (s7_pointer x = car(sc->code); is_not_null(y); x = cdr(x)) + { + s7_pointer sym = caar(x), args = cdr(y); + reuse_as_slot(y, sym, unchecked_car(y)); + slot_set_next(y, let_slots(sc->curlet)); + let_set_slots(sc->curlet, y); + symbol_set_local_slot(sym, let_id(sc->curlet), y); + if (is_pair(cddar(x))) /* else no incr expr, so ignore it henceforth */ + { + slot_set_expression(y, cddar(x)); + sc->value = cons_unchecked(sc, y, sc->value); + } + y = args; + } + sc->args = cons(sc, sc->value = proper_list_reverse_in_place(sc, sc->value), cadr(sc->code)); + sc->code = cddr(sc->code); + return(false); /* fall through */ +} + +static bool op_do_init(s7_scheme *sc) +{ + if (is_multiple_value(sc->value)) /* (do ((i (values 1 2)))...) */ + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_2(sc, wrap_string(sc, "do: variable initial value can't be ~S", 38), + set_ulist_1(sc, sc->values_symbol, sc->value))); + return(!op_do_init_1(sc)); +} + +static void op_do_unchecked(s7_scheme *sc) +{ + push_stack_no_code(sc, OP_GC_PROTECT, sc->code); + sc->code = cdr(sc->code); +} + +static bool do_unchecked(s7_scheme *sc) +{ + if (is_null(car(sc->code))) /* (do () ...) -- (let ((i 0)) (do () ((= i 1)) (set! i 1))) */ + { + sc->curlet = make_let(sc, sc->curlet); + sc->args = cons_unchecked(sc, sc->nil, cadr(sc->code)); + sc->code = cddr(sc->code); + return(false); + } + /* eval each init value, then set up the new let (like let, not let*) */ + sc->args = sc->nil; /* the evaluated var-data */ + sc->value = sc->code; /* protect it */ + sc->code = car(sc->code); /* the vars */ + return(op_do_init_1(sc)); +} + +static bool op_do_end(s7_scheme *sc) +{ + /* car(sc->args) here is the var list used by do_end2 */ + if (is_pair(cdr(sc->args))) + { + if (!has_fx(cdr(sc->args))) + { + push_stack_direct(sc, OP_DO_END1); + sc->code = cadr(sc->args); /* evaluate the end expr */ + return(true); + } + sc->value = fx_call(sc, cdr(sc->args)); + } + else sc->value = sc->F; /* goto "if (is_pair(sc->code))..." below */ + return(false); +} + +static goto_t op_do_end_false(s7_scheme *sc) +{ + if (!is_pair(sc->code)) + return((is_null(car(sc->args))) ? /* no steppers */ goto_do_end : fall_through); + /* fprintf(stderr, "%s[%d]: vars: %s\n", __func__, __LINE__, display(sc->args)); */ + if (is_null(car(sc->args))) + push_stack_direct(sc, OP_DO_END); + else push_stack_direct(sc, OP_DO_STEP); + return(goto_begin); +} + +static goto_t op_do_end_true(s7_scheme *sc) +{ + /* we're done -- deal with result exprs, if there isn't an end test, there also isn't a result (they're in the same list) + * multiple-value end-test result is ok + */ + sc->code = T_Lst(cddr(sc->args)); /* result expr (a list -- implicit begin) */ + free_cell(sc, sc->args); + sc->args = sc->nil; + if (is_null(sc->code)) + { + if (is_multiple_value(sc->value)) /* (define (f) (+ 1 (do ((i 2 (+ i 1))) ((values i (+ i 1)))))) -> 6 */ + sc->value = splice_in_values(sc, multiple_value(sc->value)); + /* similarly, if the result is a multiple value: (define (f) (+ 1 (do ((i 2 (+ i 1))) ((= i 3) (values i (+ i 1)))))) -> 8 */ + return(goto_start); + } + /* might be => here as in cond and case */ + if (is_null(cdr(sc->code))) + { + if (has_fx(sc->code)) + { + sc->value = fx_call(sc, sc->code); + return(goto_start); + } + sc->code = car(sc->code); + return(goto_eval); + } + if (is_undefined_feed_to(sc, car(sc->code))) + return(goto_feed_to); + push_stack_no_args(sc, sc->begin_op, cdr(sc->code)); + sc->code = car(sc->code); + return(goto_eval); +} + + +/* -------------------------------- apply functions -------------------------------- */ +static inline s7_pointer apply_c_function(s7_scheme *sc, s7_pointer func, s7_pointer args) /* -------- C-based function -------- */ +{ + s7_int len = proper_list_length(args); + if (len < c_function_min_args(func)) + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_4(sc, wrap_string(sc, "~A: not enough arguments: (~A~{~^ ~S~})", 39), func, func, args)); + if (c_function_max_args(func) < len) + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_4(sc, wrap_string(sc, "~A: too many arguments: (~A~{~^ ~S~})", 37), func, func, args)); + return(c_function_call(func)(sc, args)); + /* just by chance, this code is identical to macroexpand_c_macro's code (after macro expansion)! So, + * gcc -O2 uses the macroexpand code, but then valgrind shows us calling macros all the time, and + * gdb with break apply_c_function breaks at macroexpand -- confusing! + */ +} + +static void apply_c_rst_no_req_function(s7_scheme *sc) /* -------- C-based function that can take any number of arguments -------- */ +{ + sc->value = c_function_call(sc->code)(sc, sc->args); +} + +static void apply_c_macro(s7_scheme *sc) /* -------- C-based macro -------- */ +{ + s7_int len = proper_list_length(sc->args); + if (len < c_macro_min_args(sc->code)) + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_4(sc, wrap_string(sc, "~A: not enough arguments: (~A~{~^ ~S~})", 39), sc->code, sc->code, sc->args)); + if (c_macro_max_args(sc->code) < len) + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_4(sc, wrap_string(sc, "~A: too many arguments: (~A~{~^ ~S~})", 37), sc->code, sc->code, sc->args)); + sc->code = c_macro_call(sc->code)(sc, sc->args); +} + +static void apply_syntax(s7_scheme *sc) /* -------- syntactic keyword as applicable object -------- */ +{ /* current reader-cond macro uses this via (map quote ...) */ + s7_int len; /* ((apply lambda '((x) (+ x 1))) 4) */ + if (is_pair(sc->args)) /* this is ((pars) . body) */ + { + len = s7_list_length(sc, sc->args); + if (len == 0) + syntax_error_nr(sc, "attempt to evaluate a circular list: ~S", 39, sc->args); + if ((sc->safety > NO_SAFETY) && + (tree_is_cyclic(sc, sc->args))) + error_nr(sc, sc->syntax_error_symbol, + set_elist_3(sc, wrap_string(sc, "apply ~S: body is circular: ~S", 30), sc->code, sc->args)); + } + else len = 0; + + if (len < syntax_min_args(sc->code)) + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_4(sc, wrap_string(sc, "~A: not enough arguments: (~A~{~^ ~S~})", 39), sc->code, sc->code, sc->args)); + if ((syntax_max_args(sc->code) < len) && + (syntax_max_args(sc->code) != -1)) + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_4(sc, wrap_string(sc, "~A: too many arguments: (~A~{~^ ~S~})", 37), sc->code, sc->code, sc->args)); + sc->cur_op = syntax_opcode(sc->code); /* (apply begin '((define x 3) (+ x 2))) */ + /* I had elaborate checks here for embedded circular lists, but now I think that is the caller's problem */ + sc->code = cons(sc, sc->code, sc->args); + set_current_code(sc, sc->code); + pair_set_syntax_op(sc->code, sc->cur_op); +} + +static void apply_vector(s7_scheme *sc) /* -------- vector as applicable object -------- */ +{ + /* sc->code is the vector, sc->args is the list of indices */ + if (is_null(sc->args)) /* (#2d((1 2) (3 4))) */ + wrong_number_of_arguments_error_nr(sc, "vector ref: no index: (~A)", 26, sc->code); + if ((is_null(cdr(sc->args))) && + (s7_is_integer(car(sc->args))) && + (vector_rank(sc->code) == 1)) + { + s7_int index = s7_integer_clamped_if_gmp(sc, car(sc->args)); + if ((index >= 0) && + (index < vector_length(sc->code))) + sc->value = vector_getter(sc->code)(sc, sc->code, index); + else out_of_range_error_nr(sc, sc->vector_ref_symbol, int_two, car(sc->args), (index < 0) ? it_is_negative_string : it_is_too_large_string); + } + else sc->value = vector_ref_1(sc, sc->code, sc->args); +} + +static void apply_string(s7_scheme *sc) /* -------- string as applicable object -------- */ +{ + if (!is_pair(sc->args)) + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_3(sc, wrap_string(sc, "string ref: no index: (~S~{~^ ~S~})", 35), sc->code, sc->args)); + if (!is_null(cdr(sc->args))) + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_3(sc, wrap_string(sc, "string ref: too many indices: (~S~{~^ ~S~})", 43), sc->code, sc->args)); + + if (s7_is_integer(car(sc->args))) + { + s7_int index = s7_integer_clamped_if_gmp(sc, car(sc->args)); + if ((index >= 0) && + (index < string_length(sc->code))) + { + sc->value = chars[((uint8_t *)string_value(sc->code))[index]]; + return; + }} + sc->value = string_ref_1(sc, sc->code, car(sc->args)); +} + +static bool apply_pair(s7_scheme *sc) /* -------- list as applicable object -------- */ +{ + if (is_multiple_value(sc->code)) /* ((values + 2 3) 4) */ + { + /* car of values can be anything, so conjure up a new expression, and apply again */ + sc->args = pair_append(sc, cdr(sc->code), sc->args); + sc->code = car(sc->code); + return(false); + } + if (is_null(sc->args)) + wrong_number_of_arguments_error_nr(sc, "list ref: no index: (~S)", 24, sc->code); + sc->value = list_ref_1(sc, sc->code, car(sc->args)); /* (L 1) */ + if (!is_null(cdr(sc->args))) + sc->value = implicit_index_checked(sc, sc->code, sc->value, sc->args); + return(true); +} + +static void apply_hash_table(s7_scheme *sc) /* -------- hash-table as applicable object -------- */ +{ + if (is_null(sc->args)) + wrong_number_of_arguments_error_nr(sc, "hash-table ref: no key: (~S)", 28, sc->code); + sc->value = s7_hash_table_ref(sc, sc->code, car(sc->args)); + if (!is_null(cdr(sc->args))) + sc->value = implicit_index_checked(sc, sc->code, sc->value, sc->args); +} + +static void apply_let(s7_scheme *sc) /* -------- environment as applicable object -------- */ +{ + if (is_null(sc->args)) + wrong_number_of_arguments_error_nr(sc, "let ref: no field: (~S)", 23, sc->code); + sc->value = let_ref(sc, sc->code, car(sc->args)); + if (is_pair(cdr(sc->args))) + sc->value = implicit_index_checked(sc, sc->code, sc->value, sc->args); + /* (let ((v #(1 2 3))) (let ((e (curlet))) ((e 'v) 1))) -> 2 + * so (let ((v #(1 2 3))) (let ((e (curlet))) (e 'v 1))) -> 2 + */ +} + +static void apply_iterator(s7_scheme *sc) /* -------- iterator as applicable object -------- */ +{ + if (!is_null(sc->args)) + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_3(sc, wrap_string(sc, "iterator takes no arguments: (~A~{~^ ~S~})", 42), sc->code, sc->args)); + sc->value = s7_iterate(sc, sc->code); +} + +static Inline void inline_apply_lambda(s7_scheme *sc) /* -------- normal function (lambda), or macro --------, called once in eval */ +{ /* load up the current args into the ((args) (lambda)) layout [via the current environment] */ + s7_pointer x, z, e = sc->curlet, slot, last_slot = slot_end; + uint64_t id = let_id(sc->curlet); + + for (x = closure_args(sc->code), z = T_Lst(sc->args); is_pair(x); x = cdr(x), z = cdr(z)) /* closure_args can be a symbol, for example */ + { + s7_pointer sym = car(x); + if (is_null(z)) + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_5(sc, wrap_string(sc, "~S: not enough arguments: ((~S ~S ...)~{~^ ~S~})", 48), + closure_name(sc, sc->code), + (is_closure(sc->code)) ? sc->lambda_symbol : ((is_bacro(sc->code)) ? sc->bacro_symbol : sc->macro_symbol), + closure_args(sc->code), sc->args)); + slot = make_slot(sc, sym, T_Ext(unchecked_car(z))); + symbol_set_local_slot(sym, id, slot); + if (tis_slot(last_slot)) + slot_set_next(last_slot, slot); + else let_set_slots(e, slot); + last_slot = slot; + slot_set_next(slot, slot_end); + } + if (is_null(x)) + { + if (is_not_null(z)) + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_5(sc, wrap_string(sc, "~S: too many arguments: ((~S ~S ...)~{~^ ~S~})", 46), + closure_name(sc, sc->code), + (is_closure(sc->code)) ? sc->lambda_symbol : ((is_bacro(sc->code)) ? sc->bacro_symbol : sc->macro_symbol), + closure_args(sc->code), sc->args)); + } + else + { + slot = make_slot(sc, x, z); + symbol_set_local_slot(x, id, slot); + if (tis_slot(last_slot)) + slot_set_next(last_slot, slot); + else let_set_slots(e, slot); + slot_set_next(slot, slot_end); + } + sc->code = closure_body(sc->code); +} + +static void op_f(s7_scheme *sc) /* sc->code: ((lambda () 32)) -> (let () 32) */ +{ + sc->curlet = make_let(sc, sc->curlet); + sc->code = opt3_pair(sc->code); /* cddar */ +} + +static void op_f_a(s7_scheme *sc) /* sc->code: ((lambda (x) (+ x 1)) i) -> (let ((x i)) (+ x 1)) */ +{ + /* if caddar(sc->code) is fxable [(+ x 1) above], this could call fx and return to the top */ + sc->curlet = inline_make_let_with_slot(sc, sc->curlet, opt3_sym(cdr(sc->code)), fx_call(sc, cdr(sc->code))); + sc->code = opt3_pair(sc->code); +} + +static void op_f_aa(s7_scheme *sc) /* sc->code: ((lambda (x y) (+ x y)) i j) -> (let ((x i) (y j)) (+ x y)) */ +{ + gc_protect_via_stack(sc, fx_call(sc, cdr(sc->code))); + sc->curlet = make_let_with_two_slots(sc, sc->curlet, opt3_sym(cdr(sc->code)), stack_protected1(sc), cadadr(car(sc->code)), fx_call(sc, cddr(sc->code))); + unstack(sc); + sc->code = opt3_pair(sc->code); +} + +static void op_f_np(s7_scheme *sc) /* sc->code: ((lambda (x y) (+ x y)) (values i j)) -> (let ((x i) (y j)) (+ x y)) after splice */ +{ + s7_pointer pars = cadar(sc->code); + s7_pointer e = make_let(sc, sc->curlet); + if (is_pair(pars)) + { + s7_pointer last_slot; + if (is_null(cdr(sc->code))) /* ((lambda (x) 21)) */ + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_3(sc, wrap_string(sc, "not enough arguments: ((lambda ~S ...)~{~^ ~S~})", 48), + cadar(sc->code), cdr(sc->code))); + if (is_constant(sc, car(pars))) + error_nr(sc, sc->syntax_error_symbol, /* (lambda (a) 1) where 'a is immutable (locally perhaps) */ + set_elist_4(sc, wrap_string(sc, "lambda parameter ~S is a constant: ((lambda ~S ...)~{~^ ~S~})", 61), + car(pars), cadar(sc->code), cdr(sc->code))); + + add_slot_unchecked_no_local(sc, e, car(pars), sc->undefined); + last_slot = let_slots(e); + for (pars = cdr(pars); is_pair(pars); pars = cdr(pars)) + last_slot = add_slot_at_end_no_local(sc, last_slot, car(pars), sc->undefined); + /* last par might be rest par (dotted) */ + if (!is_null(pars)) + { + last_slot = add_slot_at_end_no_local(sc, last_slot, pars, sc->undefined); + set_is_rest_slot(last_slot); + }} + /* check_stack_size(sc); */ + if ((sc->stack_end + 4) >= sc->stack_resize_trigger) resize_stack(sc); + push_stack(sc, OP_GC_PROTECT, let_slots(e), cddr(sc->code)); /* not for gc-protection, but as implicit loop vars */ + push_stack(sc, OP_F_NP_1, e, sc->code); + sc->code = cadr(sc->code); +} + +static bool op_f_np_1(s7_scheme *sc) +{ + s7_pointer e, slot = stack_protected1(sc), arg = stack_protected2(sc); + if (is_multiple_value(sc->value)) + { + s7_pointer p, oslot = slot; + for (p = sc->value; (is_pair(p)) && (tis_slot(slot)); p = cdr(p), oslot = slot, slot = next_slot(slot)) + if (is_rest_slot(slot)) + { + if (slot_value(slot) == sc->undefined) + slot_set_value(slot, copy_proper_list(sc, p)); + else slot_set_value(slot, pair_append(sc, slot_value(slot), copy_proper_list(sc, p))); + p = sc->nil; + break; + } + else slot_set_value(slot, car(p)); + if (is_pair(p)) + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_3(sc, wrap_string(sc, "not enough arguments: ((lambda ~S ...)~{~^ ~S~})", 48), + cadar(sc->code), cdr(sc->code))); + slot = oslot; /* snd-test 22 grani */ + } + else /* not mv */ + if (!is_rest_slot(slot)) + slot_set_value(slot, sc->value); + else + if (slot_value(slot) == sc->undefined) + slot_set_value(slot, list_1(sc, sc->value)); + else slot_set_value(slot, pair_append(sc, slot_value(slot), list_1(sc, sc->value))); + + if (is_pair(arg)) + { + if ((!tis_slot(next_slot(slot))) && (!is_rest_slot(slot))) + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_3(sc, wrap_string(sc, "too many arguments: ((lambda ~S ...)~{~^ ~S~})", 46), + cadar(sc->code), cdr(sc->code))); + set_stack_protected1(sc, (is_rest_slot(slot)) ? slot : next_slot(slot)); + set_stack_protected2(sc, cdr(arg)); + push_stack_direct(sc, OP_F_NP_1); /* sc->args=e, sc->code from start */ + sc->code = car(arg); + return(true); + } + if (tis_slot(next_slot(slot))) + { + if (!is_rest_slot(next_slot(slot))) + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_3(sc, wrap_string(sc, "not enough arguments: ((lambda ~S ...)~{~^ ~S~})", 48), + cadar(sc->code), cdr(sc->code))); + if (slot_value(next_slot(slot)) == sc->undefined) + slot_set_value(next_slot(slot), sc->nil); + } + e = sc->args; + let_set_id(e, ++sc->let_number); + set_curlet(sc, e); + update_symbol_ids(sc, e); + sc->code = cddar(sc->code); + unstack(sc); + return(false); +} + +static void op_lambda_star(s7_scheme *sc) +{ + check_lambda_star(sc); + if (!is_pair(car(sc->code))) + sc->value = make_closure(sc, car(sc->code), cdr(sc->code), + (is_symbol(car(sc->code))) ? (T_CLOSURE | T_COPY_ARGS) : T_CLOSURE, + CLOSURE_ARITY_NOT_SET); + else sc->value = make_closure(sc, car(sc->code), cdr(sc->code), + (!arglist_has_rest(sc, car(sc->code))) ? T_CLOSURE_STAR : (T_CLOSURE_STAR | T_COPY_ARGS), + CLOSURE_ARITY_NOT_SET); +} + +static void op_lambda_star_unchecked(s7_scheme *sc) +{ + s7_pointer code = cdr(sc->code); + if (!is_pair(car(code))) + sc->value = make_closure(sc, car(code), cdr(code), + (is_symbol(car(code))) ? (T_CLOSURE | T_COPY_ARGS) : T_CLOSURE, + CLOSURE_ARITY_NOT_SET); + else sc->value = make_closure(sc, car(code), cdr(code), + (!arglist_has_rest(sc, car(code))) ? T_CLOSURE_STAR : (T_CLOSURE_STAR | T_COPY_ARGS), + CLOSURE_ARITY_NOT_SET); +} + +static s7_pointer star_set(s7_scheme *sc, s7_pointer slot, s7_pointer val, bool check_rest) +{ + if (is_checked_slot(slot)) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_3(sc, parameter_set_twice_string, slot_symbol(slot), sc->args)); + if ((check_rest) && (is_rest_slot(slot))) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_3(sc, wrap_string(sc, "can't set rest argument ~S to ~S via keyword", 44), slot_symbol(slot), val)); + set_checked_slot(slot); + slot_set_value(slot, val); + return(val); +} + +static s7_pointer lambda_star_argument_set_value(s7_scheme *sc, const s7_pointer sym, s7_pointer val, s7_pointer slot, bool check_rest) +{ + if (val == sc->no_value) val = sc->unspecified; + if (sym == slot_symbol(slot)) + return(star_set(sc, slot, val, check_rest)); + for (s7_pointer x = let_slots(sc->curlet) /* presumably the arglist */; tis_slot(x); x = next_slot(x)) + if (slot_symbol(x) == sym) + return(star_set(sc, x, val, check_rest)); + return(sc->no_value); +} + +static s7_pointer lambda_star_set_args(s7_scheme *sc) +{ + s7_pointer arg_vals = sc->args, rest_key = sc->nil, code = sc->code, args = sc->args; + s7_pointer slot = let_slots(sc->curlet); + s7_pointer pars = closure_args(code); + bool allow_other_keys = ((is_pair(pars)) && (allows_other_keys(pars))); + + while ((is_pair(pars)) && + (is_pair(arg_vals))) + { + if (car(pars) == sc->rest_keyword) /* the rest arg: a default is not allowed here (see check_lambda_star_args) */ + { + /* next arg is bound to trailing args from this point as a list */ + pars = cdr(pars); + if ((is_symbol_and_keyword(car(arg_vals))) && + (is_pair(cdr(arg_vals))) && + (keyword_symbol(car(arg_vals)) == car(pars))) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_3(sc, wrap_string(sc, "can't set rest argument ~S to ~S via keyword", 44), + car(pars), cadr(arg_vals))); + lambda_star_argument_set_value(sc, car(pars), (in_heap(arg_vals)) ? arg_vals : copy_proper_list(sc, arg_vals), slot, false); /* sym5 :rest bug */ + rest_key = sc->rest_keyword; + arg_vals = cdr(arg_vals); + pars = cdr(pars); + slot = next_slot(slot); + } + else + { + s7_pointer arg_val = car(arg_vals); + if (is_symbol_and_keyword(arg_val)) + { + if (!is_pair(cdr(arg_vals))) + { + if (!sc->accept_all_keyword_arguments) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_4(sc, keyword_value_missing_string, closure_name(sc, code), arg_vals, args)); + slot_set_value(slot, arg_val); + set_checked_slot(slot); + arg_vals = cdr(arg_vals); + } + else + { + s7_pointer sym = keyword_symbol(arg_val); + if (lambda_star_argument_set_value(sc, sym, cadr(arg_vals), slot, true) == sc->no_value) + { + /* if default value is a key, go ahead and use this value. (define* (f (a :b)) a) (f :c), this has become much trickier than I anticipated... */ + if (allow_other_keys) + /* in CL: (defun hi (&key (a 1) &allow-other-keys) a) (hi :b :a :a 3) -> 3 + * in s7: (define* (hi (a 1) :allow-other-keys) a) (hi :b :a :a 3) -> 3 + */ + arg_vals = cddr(arg_vals); + else + { + if (!sc->accept_all_keyword_arguments) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_4(sc, wrap_string(sc, "~A: unknown key: ~S in ~S", 25), closure_name(sc, code), arg_vals, args)); + slot_set_value(slot, arg_val); + set_checked_slot(slot); + arg_vals = cdr(arg_vals); + pars = cdr(pars); + slot = next_slot(slot); + } + continue; + } + arg_vals = cddr(arg_vals); + } + slot = next_slot(slot); + } + else /* not a key/value pair */ + { + if (is_checked_slot(slot)) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_3(sc, parameter_set_twice_string, slot_symbol(slot), sc->args)); + set_checked_slot(slot); + slot_set_value(slot, car(arg_vals)); + slot = next_slot(slot); + arg_vals = cdr(arg_vals); + } + pars = cdr(pars); + }} + /* (let () (define* (hi (a 1) :allow-other-keys) a) (hi :a 2 32)) -> 'error */ + /* (let () (define* (f (a :b)) a) (list (f) (f 1) (f :c) (f :a :c) (f :a 1) (f))) -> 'error */ + + /* check for trailing args with no :rest arg */ + if (is_not_null(arg_vals)) + { + if ((is_not_null(pars)) || + (rest_key == sc->rest_keyword)) + { + if (is_symbol(pars)) + { + if ((is_symbol_and_keyword(car(arg_vals))) && + (is_pair(cdr(arg_vals))) && + (keyword_symbol(car(arg_vals)) == pars)) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_3(sc, wrap_string(sc, "can't set rest argument ~S to ~S via keyword", 44), pars, cadr(arg_vals))); + slot_set_value(slot, (in_heap(arg_vals)) ? arg_vals : copy_proper_list(sc, arg_vals)); /* sym5 :rest bug */ + }} + else + { + if (!allow_other_keys) /* ((lambda* (a) a) :a 1 2) */ + error_nr(sc, sc->wrong_number_of_args_symbol, + set_elist_4(sc, wrap_string(sc, "too many arguments: (~S ~S ...)~{~^ ~S~})", 41), + (is_closure_star(code)) ? sc->lambda_star_symbol : ((is_bacro_star(sc->code)) ? sc->bacro_star_symbol : sc->macro_star_symbol), + closure_args(code), args)); + /* check trailing args for repeated keys or keys with no values or values with no keys */ + while (is_pair(arg_vals)) + { + if ((!is_symbol_and_keyword(car(arg_vals))) || /* ((lambda* (a :allow-other-keys) a) :a 1 :b 2 3) */ + (!is_pair(cdr(arg_vals)))) /* ((lambda* (a :allow-other-keys) a) :a 1 :b) */ + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_3(sc, wrap_string(sc, "~A: not a key/value pair: ~S", 28), closure_name(sc, code), arg_vals)); + slot = symbol_to_local_slot(sc, keyword_symbol(car(arg_vals)), sc->curlet); + if ((is_slot(slot)) && + (is_checked_slot(slot))) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_3(sc, parameter_set_twice_string, slot_symbol(slot), sc->args)); + arg_vals = cddr(arg_vals); + }}} + return(sc->nil); +} + +static inline bool lambda_star_default(s7_scheme *sc) +{ + for (s7_pointer z = sc->args; tis_slot(z); z = next_slot(z)) + { + if ((slot_value(z) == sc->undefined) && /* trouble: (lambda* ((e #))...) */ + (slot_has_expression(z)) && /* if default val is not a pair or a symbol, this is false */ + (!is_checked_slot(z))) + { + s7_pointer val = slot_expression(z); + if (is_symbol(val)) + { + slot_set_value(z, lookup_checked(sc, val)); + if (slot_value(z) == sc->undefined) + { + /* the current environment here contains the function parameters which defaulted to # + * (or maybe #?) earlier in apply_*_closure_star_1, so (define (f f) (define* (f (f f)) f) (f)) (f 0) + * looks for the default f, finds itself currently undefined, and raises an error! So, before + * claiming it is unbound, we need to check outlet as well. But in the case above, the inner + * define* shadows the caller's parameter before checking the default arg values, so the default f + * refers to the define* -- I'm not sure this is a bug. It means that (define* (f (a f)) a) + * returns f: (equal? f (f)) -> #t, so any outer f needs an extra let and endless outlets: + * (let ((f 3)) (let () (define* (f (a ((outlet (outlet (outlet (curlet)))) 'f))) a) (f))) -> 3 + * We want the shadowing once the define* is done, so the current mess is simplest. + */ + slot_set_value(z, s7_symbol_local_value(sc, val, let_outlet(sc->curlet))); + if (slot_value(z) == sc->undefined) + syntax_error_nr(sc, "lambda* defaults: ~A is unbound", 31, slot_symbol(z)); + }} + else + if (!is_pair(val)) + slot_set_value(z, val); + else + if (car(val) == sc->quote_symbol) + { + if ((!is_pair(cdr(val))) || /* (lambda* ((a (quote))) a) or (lambda* ((a (quote 1 1))) a) etc */ + (is_pair(cddr(val)))) + syntax_error_nr(sc, "lambda* default: ~A is messed up", 32, val); + slot_set_value(z, cadr(val)); + } + else + { + push_stack(sc, OP_LAMBDA_STAR_DEFAULT, z, sc->code); + sc->code = val; + return(true); /* goto eval */ + }}} + return(false); +} + +static bool op_lambda_star_default(s7_scheme *sc) +{ + /* sc->args is the current let slots position, sc->value is the default expression's value */ + if (is_multiple_value(sc->value)) + syntax_error_nr(sc, "lambda*: argument default value can't be ~S", 43, set_ulist_1(sc, sc->values_symbol, sc->value)); + slot_set_value(sc->args, sc->value); + sc->args = next_slot(sc->args); + if (lambda_star_default(sc)) return(true); + pop_stack_no_op(sc); + sc->code = T_Pair(closure_body(sc->code)); + return(false); +} + +static inline bool set_star_args(s7_scheme *sc, s7_pointer top) +{ + lambda_star_set_args(sc); /* load up current arg vals */ + sc->args = top; + if (is_slot(sc->args)) + { + /* get default values, which may involve evaluation -- see also OP_LAMBDA_STAR_DEFAULT */ + push_stack_direct(sc, OP_GC_PROTECT); + if (lambda_star_default(sc)) return(true); /* else fall_through */ + pop_stack_no_op(sc); /* get original args and code back */ + } + sc->code = closure_body(sc->code); + return(false); +} + +static inline bool apply_safe_closure_star_1(s7_scheme *sc) /* -------- define* (lambda*) -------- */ +{ + /* slots are in "reverse order" -- in the same order as the args, despite let printout (which reverses the order!) */ + set_curlet(sc, closure_let(sc->code)); + if (has_no_defaults(sc->code)) + { + for (s7_pointer z = let_slots(sc->curlet); tis_slot(z); z = next_slot(z)) + { + clear_checked_slot(z); + slot_set_value(z, sc->F); + } + if (!is_null(sc->args)) + lambda_star_set_args(sc); /* load up current arg vals */ + sc->code = closure_body(sc->code); + return(false); /* goto BEGIN */ + } + for (s7_pointer z = let_slots(sc->curlet); tis_slot(z); z = next_slot(z)) + { + clear_checked_slot(z); + slot_set_value(z, (slot_defaults(z)) ? sc->undefined : slot_expression(z)); + } + return(set_star_args(sc, slot_pending_value(let_slots(sc->curlet)))); +} + +static bool apply_unsafe_closure_star_1(s7_scheme *sc) +{ + s7_pointer z, top = sc->nil; + for (z = closure_args(sc->code); is_pair(z); z = cdr(z)) + { + s7_pointer car_z = car(z); + if (is_pair(car_z)) /* arg has a default value */ + { + s7_pointer slot, val = cadr(car_z); + if ((!is_pair(val)) && + (!is_symbol(val))) + slot = add_slot_checked(sc, sc->curlet, car(car_z), val); + else + { + add_slot(sc, sc->curlet, car(car_z), sc->undefined); + slot = let_slots(sc->curlet); + slot_set_expression(slot, val); + } + if (is_null(top)) + top = slot; + } + else + if (!is_keyword(car_z)) + add_slot(sc, sc->curlet, car_z, sc->F); + else + if (car_z == sc->rest_keyword) /* else it's :allow-other-keys? */ + { + set_is_rest_slot(add_slot_checked(sc, sc->curlet, cadr(z), sc->nil)); + z = cdr(z); + }} + if (is_symbol(z)) + set_is_rest_slot(add_slot_checked(sc, sc->curlet, z, sc->nil)); /* set up rest arg */ + let_set_slots(sc->curlet, reverse_slots(let_slots(sc->curlet))); + return(set_star_args(sc, top)); +} + +static void apply_macro_star_1(s7_scheme *sc) +{ + /* here the defaults (if any) are not evalled, and there is not an existing let */ + s7_pointer p; + for (p = closure_args(sc->code); is_pair(p); p = cdr(p)) + { + s7_pointer par = car(p); + if (is_pair(par)) + add_slot_checked(sc, sc->curlet, car(par), cadr(par)); + else + if (!is_keyword(par)) + add_slot_checked(sc, sc->curlet, par, sc->F); + else + if (par == sc->rest_keyword) + { + set_is_rest_slot(add_slot_checked(sc, sc->curlet, cadr(p), sc->nil)); + p = cdr(p); + }} + if (is_symbol(p)) + set_is_rest_slot(add_slot_checked(sc, sc->curlet, p, sc->nil)); + let_set_slots(sc->curlet, reverse_slots(let_slots(sc->curlet))); + lambda_star_set_args(sc); + sc->code = T_Pair(closure_body(sc->code)); +} + +static void clear_absolutely_all_optimizations(s7_pointer p) +{ + if ((is_pair(p)) && (!is_matched_pair(p))) + { + clear_has_fx(p); + clear_optimized(p); + clear_optimize_op(p); + set_match_pair(p); + clear_absolutely_all_optimizations(cdr(p)); + clear_absolutely_all_optimizations(car(p)); + } +} + +static void clear_matches(s7_pointer p) +{ + if ((is_pair(p)) && (is_matched_pair(p))) + { + clear_match_pair(p); + clear_matches(car(p)); + clear_matches(cdr(p)); + } +} + +static void apply_macro(s7_scheme *sc) /* this is not from the reader, so treat expansions here as normal macros */ +{ + check_stack_size(sc); + if (closure_arity_to_int(sc, sc->code) < 0) + { + clear_absolutely_all_optimizations(sc->args); /* desperation... */ + clear_matches(sc->args); + } + push_stack_op_let(sc, OP_EVAL_MACRO); + sc->curlet = inline_make_let(sc, closure_let(sc->code)); /* closure_let -> sc->curlet, sc->code is the macro */ + transfer_macro_info(sc, sc->code); +} + +static void apply_bacro(s7_scheme *sc) +{ + check_stack_size(sc); + push_stack_op_let(sc, OP_EVAL_MACRO); + sc->curlet = make_let(sc, sc->curlet); /* like let* -- we'll be adding macro args, so might as well sequester things here */ + transfer_macro_info(sc, sc->code); +} + +static void apply_macro_star(s7_scheme *sc) +{ + check_stack_size(sc); + push_stack_op_let(sc, OP_EVAL_MACRO); + sc->curlet = make_let(sc, closure_let(sc->code)); + transfer_macro_info(sc, sc->code); + apply_macro_star_1(sc); +} + +static void apply_bacro_star(s7_scheme *sc) +{ + check_stack_size(sc); + push_stack_op_let(sc, OP_EVAL_MACRO); + sc->curlet = make_let(sc, sc->curlet); + transfer_macro_info(sc, sc->code); + apply_macro_star_1(sc); +} + +static void apply_closure(s7_scheme *sc) +{ + /* we can get safe_closures here, but can't easily determine whether we have the expected saved funclet */ + check_stack_size(sc); + sc->curlet = inline_make_let(sc, closure_let(sc->code)); +} + +static bool apply_closure_star(s7_scheme *sc) +{ + if (is_safe_closure(sc->code)) + return(apply_safe_closure_star_1(sc)); + check_stack_size(sc); + sc->curlet = make_let(sc, closure_let(sc->code)); + return(apply_unsafe_closure_star_1(sc)); +} + +static inline s7_pointer op_safe_closure_star_a1(s7_scheme *sc, s7_pointer code) /* called in eval and below, tlamb */ +{ + s7_pointer func = opt1_lambda(code); + s7_pointer val = fx_call(sc, cdr(code)); + if ((is_symbol_and_keyword(val)) && + (!sc->accept_all_keyword_arguments)) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_4(sc, keyword_value_missing_string, closure_name(sc, func), val, sc->args)); + sc->curlet = update_let_with_slot(sc, closure_let(func), val); + sc->code = T_Pair(closure_body(func)); + return(func); +} + +static void op_safe_closure_star_a(s7_scheme *sc, s7_pointer code) +{ + s7_pointer func = op_safe_closure_star_a1(sc, code); + s7_pointer p = cdr(closure_args(func)); + if (is_pair(p)) + for (s7_pointer x = next_slot(let_slots(closure_let(func))); is_pair(p); p = cdr(p), x = next_slot(x)) + { + if (is_pair(car(p))) + { + s7_pointer defval = cadar(p); + slot_set_value(x, (is_pair(defval)) ? cadr(defval) : defval); + } + else slot_set_value(x, sc->F); + symbol_set_local_slot(slot_symbol(x), let_id(sc->curlet), x); + } +} + +static void op_safe_closure_star_ka(s7_scheme *sc, s7_pointer code) /* two args, but k=arg key, key has been checked. no trailing pars */ +{ + s7_pointer func = opt1_lambda(code); + sc->curlet = update_let_with_slot(sc, closure_let(func), fx_call(sc, cddr(code))); + sc->code = T_Pair(closure_body(func)); +} + +static void op_safe_closure_star_aa(s7_scheme *sc, s7_pointer code) +{ + /* here closure_arity == 2 and we have 2 args and those args' defaults are simple (no eval or lookup needed) */ + s7_pointer arg2, func = opt1_lambda(code); + s7_pointer arg1 = fx_call(sc, cdr(code)); + sc->w = arg1; /* weak GC protection */ + arg2 = fx_call(sc, cddr(code)); + + if (is_symbol_and_keyword(arg1)) + { + if (keyword_symbol(arg1) == slot_symbol(let_slots(closure_let(func)))) + { + arg1 = arg2; + arg2 = cadr(closure_args(func)); + if (is_pair(arg2)) arg2 = (is_pair(cadr(arg2))) ? cadadr(arg2) : cadr(arg2); else arg2 = sc->F; + } + else + if (keyword_symbol(arg1) == slot_symbol(next_slot(let_slots(closure_let(func))))) + { + arg1 = car(closure_args(func)); + if (is_pair(arg1)) arg1 = (is_pair(cadr(arg1))) ? cadadr(arg1) : cadr(arg1); else arg1 = sc->F; + } + else + if (!sc->accept_all_keyword_arguments) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_4(sc, wrap_string(sc, "~A: unknown keyword argument: ~S in ~S", 38), + closure_name(sc, func), arg1, code)); /* arg1 is already the value */ + } + else + if ((is_symbol_and_keyword(arg2)) && + (!sc->accept_all_keyword_arguments)) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_4(sc, keyword_value_missing_string, closure_name(sc, func), arg2, code)); + sc->curlet = update_let_with_two_slots(sc, closure_let(func), arg1, arg2); + sc->code = T_Pair(closure_body(func)); +} + +static bool call_lambda_star(s7_scheme *sc, s7_pointer code, s7_pointer arglist) +{ + bool target; + sc->code = opt1_lambda(code); + target = apply_safe_closure_star_1(sc); + clear_list_in_use(arglist); + return(target); +} + +static bool op_safe_closure_star_aaa(s7_scheme *sc, s7_pointer code) +{ + s7_pointer arg2, arg3, func = opt1_lambda(code); + s7_pointer arg1 = fx_call(sc, cdr(code)); + gc_protect_via_stack(sc, arg1); + arg2 = fx_call(sc, cddr(code)); + set_stack_protected2(sc, arg2); + arg3 = fx_call(sc, cdddr(code)); + if ((is_symbol_and_keyword(arg1)) || (is_symbol_and_keyword(arg2)) || (is_symbol_and_keyword(arg3))) + { + s7_pointer arglist = make_safe_list(sc, 3); + sc->args = arglist; + set_car(arglist, arg1); + set_cadr(arglist, arg2); + set_caddr(arglist, arg3); + unstack(sc); + return(call_lambda_star(sc, code, arglist)); /* this clears list_in_use */ + } + sc->curlet = update_let_with_three_slots(sc, closure_let(func), arg1, arg2, arg3); + unstack(sc); + sc->code = T_Pair(closure_body(func)); + if_pair_set_up_begin_unchecked(sc); + return(true); +} + +static bool op_safe_closure_star_na_0(s7_scheme *sc, s7_pointer code) +{ + sc->args = sc->nil; + sc->code = opt1_lambda(code); + return(apply_safe_closure_star_1(sc)); +} + +static bool op_safe_closure_star_na_1(s7_scheme *sc, s7_pointer code) +{ + s7_pointer arglist = safe_list_1(sc); + sc->args = arglist; + set_car(arglist, fx_call(sc, cdr(code))); + return(call_lambda_star(sc, code, arglist)); /* clears list_in_use */ +} + +static bool op_safe_closure_star_na_2(s7_scheme *sc, s7_pointer code) +{ + s7_pointer arglist = safe_list_2(sc); + sc->args = arglist; + set_car(arglist, fx_call(sc, cdr(code))); + set_cadr(arglist, fx_call(sc, cddr(code))); + return(call_lambda_star(sc, code, arglist)); /* clears list_in_use */ +} + +static inline bool op_safe_closure_star_na(s7_scheme *sc, s7_pointer code) /* called once in eval, clo */ +{ + s7_pointer arglist = safe_list_if_possible(sc, opt3_arglen(cdr(code))); + sc->args = arglist; + for (s7_pointer p = arglist, old_args = cdr(code); is_pair(p); p = cdr(p), old_args = cdr(old_args)) + set_car(p, fx_call(sc, old_args)); + if ((S7_DEBUGGING) && (sc->args != arglist)) fprintf(stderr, "%s[%d]: lost gc\n", __func__, __LINE__); + return(call_lambda_star(sc, code, arglist)); /* clears list_in_use */ +} + +static void op_closure_star_ka(s7_scheme *sc, s7_pointer code) +{ + s7_pointer func = opt1_lambda(code); + s7_pointer p = car(closure_args(func)); + sc->value = fx_call(sc, cddr(code)); + sc->curlet = inline_make_let_with_slot(sc, closure_let(func), (is_pair(p)) ? car(p) : p, sc->value); + sc->code = T_Pair(closure_body(func)); +} + +static void op_closure_star_a(s7_scheme *sc, s7_pointer code) +{ + s7_pointer p, func = opt1_lambda(code); + sc->value = fx_call(sc, cdr(code)); + if ((is_symbol_and_keyword(sc->value)) && + (!sc->accept_all_keyword_arguments)) + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_4(sc, keyword_value_missing_string, closure_name(sc, opt1_lambda(code)), sc->value, code)); + p = car(closure_args(func)); + sc->curlet = make_let_with_slot(sc, closure_let(func), (is_pair(p)) ? car(p) : p, sc->value); + if (closure_star_arity_to_int(sc, func) > 1) + { + s7_pointer last_slot = let_slots(sc->curlet); + s7_int id = let_id(sc->curlet); + for (p = cdr(closure_args(func)); is_pair(p); p = cdr(p)) + { + s7_pointer par = car(p); + if (is_pair(par)) + last_slot = add_slot_at_end(sc, id, last_slot, car(par), (is_pair(cadr(par))) ? cadadr(par) : cadr(par)); /* possible quoted list as default value */ + else last_slot = add_slot_at_end(sc, id, last_slot, par, sc->F); + }} + sc->code = T_Pair(closure_body(func)); +} + +static inline bool op_closure_star_na(s7_scheme *sc, s7_pointer code) +{ + /* check_stack_size(sc); */ + if (is_pair(cdr(code))) + { + sc->w = cdr(code); /* args aren't evaluated yet */ + sc->args = make_list(sc, opt3_arglen(cdr(code)), sc->unused); + for (s7_pointer p = sc->args, old_args = sc->w; is_pair(p); p = cdr(p), old_args = cdr(old_args)) + set_car(p, fx_call(sc, old_args)); + sc->w = sc->unused; + } + else sc->args = sc->nil; + sc->code = opt1_lambda(code); + sc->curlet = inline_make_let(sc, closure_let(sc->code)); + return(apply_unsafe_closure_star_1(sc)); +} + +static s7_pointer define1_caller(s7_scheme *sc) +{ + /* we can jump to op_define1, so this is not fool-proof */ + if (sc->cur_op == OP_DEFINE_CONSTANT) return(sc->define_constant_symbol); + if ((sc->cur_op == OP_DEFINE_STAR) || (sc->cur_op == OP_DEFINE_STAR_UNCHECKED)) return(sc->define_star_symbol); + return(sc->define_symbol); +} + +static bool op_define1(s7_scheme *sc) +{ + /* sc->code is the symbol being defined, sc->value is its value + * if sc->value is a closure, car is of the form ((args...) body...) + * it's not possible to expand and replace macros at this point without evaluating + * the body. Just as examples, say we have a macro "mac", + * (define (hi) (call/cc (lambda (mac) (mac 1)))) + * (define (hi) (quote (mac 1))) or macroexpand etc + * (define (hi mac) (mac 1)) assuming mac here is a function passed as an arg, etc... + * the immutable constant check needs to wait until we have the actual new value because + * we want to ignore the rebinding (not raise an error) if it is the existing value. + * This happens when we reload a file that calls define-constant. But we want a + * warning if we got define (as opposed to the original define-constant). + */ + s7_pointer x; + if (is_multiple_value(sc->value)) /* (define x (values 1 2)) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_5(sc, wrap_string(sc, "~A: more than one value: (~A ~A ~S)", 35), + define1_caller(sc), define1_caller(sc), sc->code, sc->value)); + if (is_constant_symbol(sc, sc->code)) /* (define pi 3) or (define (pi a) a) */ + { + x = (is_slot(global_slot(sc->code))) ? global_slot(sc->code) : s7_slot(sc, sc->code); + /* local_slot can be free even if sc->code is immutable (local constant now defunct) */ + + if (!((is_slot(x)) && + (type(sc->value) == unchecked_type(slot_value(x))) && + (s7_is_equivalent(sc, sc->value, slot_value(x))))) /* if value is unchanged, just ignore this (re)definition */ + syntax_error_with_caller_nr(sc, "~A: ~S is immutable", 19, define1_caller(sc), sc->code); /* can't use s7_is_equal because value might be NaN, etc */ + + if ((sc->safety > 0) && /* (define-constant x 3) (define x 3)... */ + (sc->cur_op == OP_DEFINE)) + s7_warn(sc, 256, "(define %s %s), but %s is a constant\n", display(sc->code), display(sc->value), display(sc->code)); + } + else x = s7_slot(sc, sc->code); + if ((is_slot(x)) && (slot_has_setter(x))) + { + sc->value = bind_symbol_with_setter(sc, OP_DEFINE_WITH_SETTER, sc->code, sc->value); + if (sc->value == sc->no_value) + return(true); /* goto apply, if all goes well, OP_DEFINE_WITH_SETTER will jump to DEFINE2 */ + } + return(false); /* fall through */ +} + +static void set_let_file_and_line(s7_scheme *sc, s7_pointer new_let, s7_pointer new_func) +{ + if (port_file(current_input_port(sc)) != stdin) + { + if ((is_pair(closure_args(new_func))) && + (has_location(closure_args(new_func)))) + { + let_set_file(new_let, pair_file_number(closure_args(new_func))); + let_set_line(new_let, pair_line_number(closure_args(new_func))); + } + else + if (has_location(closure_body(new_func))) + { + let_set_file(new_let, pair_file_number(closure_body(new_func))); + let_set_line(new_let, pair_line_number(closure_body(new_func))); + } + else + { + s7_pointer p; + for (p = cdr(closure_body(new_func)); is_pair(p); p = cdr(p)) + if ((is_pair(car(p))) && (has_location(car(p)))) + break; + let_set_file(new_let, (is_pair(p)) ? pair_file_number(car(p)) : port_file_number(current_input_port(sc))); + let_set_line(new_let, (is_pair(p)) ? pair_line_number(car(p)) : port_line_number(current_input_port(sc))); + } + set_has_let_file(new_let); + } + else + { + let_set_file(new_let, 0); + let_set_line(new_let, 0); + clear_has_let_file(new_let); + } +} + +static void op_define_with_setter(s7_scheme *sc) +{ + s7_pointer code = sc->code; + if ((is_immutable(sc->curlet)) && + (is_let(sc->curlet))) /* not () */ + error_nr(sc, sc->immutable_error_symbol, + set_elist_2(sc, wrap_string(sc, "can't define ~S: curlet is immutable", 36), code)); + + if ((is_any_closure(sc->value)) && + ((!(is_let(closure_let(sc->value)))) || + (!(is_funclet(closure_let(sc->value)))))) /* otherwise it's (define f2 f1) or something similar */ + { + s7_pointer new_func = sc->value, new_let; + if (is_safe_closure_body(closure_body(new_func))) + { + set_safe_closure(new_func); + if (is_very_safe_closure_body(closure_body(new_func))) + set_very_safe_closure(new_func); + } + new_let = make_funclet(sc, new_func, code, closure_let(new_func)); + + /* this should happen only if the closure* default values do not refer in any way to + * the enclosing environment (else we can accidentally shadow something that happens + * to share an argument name that is being used as a default value -- kinda dumb!). + * I think I'll check this before setting the safe_closure bit. + */ + set_let_file_and_line(sc, new_let, new_func); + /* add the newly defined thing to the current environment */ + if (is_let(sc->curlet)) + { + if (let_id(sc->curlet) <= symbol_id(code)) /* we're adding a later-bound symbol to an old let (?) */ + { /* was < 16-Aug-22: (let ((a 3)) (define (a) 4) (curlet)) */ + s7_pointer slot; + sc->let_number++; /* dummy let, force symbol lookup */ + for (slot = let_slots(sc->curlet); tis_slot(slot); slot = next_slot(slot)) + if (slot_symbol(slot) == code) + { + if (is_immutable_slot(slot)) + syntax_error_nr(sc, "define ~S, but it is immutable", 30, code); /* someday give the location of the immutable definition or setting */ + slot_set_value(slot, new_func); + symbol_set_local_slot(code, sc->let_number, slot); + set_local(code); + sc->value = new_func; /* probably not needed? */ + return; + } + new_cell_no_check(sc, slot, T_SLOT); + slot_set_symbol_and_value(slot, code, new_func); + symbol_set_local_slot(code, sc->let_number, slot); + slot_set_next(slot, let_slots(sc->curlet)); + let_set_slots(sc->curlet, slot); + } + else add_slot(sc, sc->curlet, code, new_func); + set_local(code); + } + else + { + if ((is_slot(global_slot(code))) && + (is_immutable_slot(global_slot(code)))) + { + s7_pointer old_symbol = code, old_value = global_value(code); + if ((type(old_value) != type(new_func)) || + (!s7_is_equivalent(sc, old_value, new_func))) /* if value is unchanged, just ignore this (re)definition */ + syntax_error_nr(sc, "define ~S, but it is immutable", 30, old_symbol); + } + else s7_make_slot(sc, sc->curlet, code, new_func); + } + sc->value = new_func; /* 25-Jul-14 so define returns the value not the name */ + } + else + { + s7_pointer slot = symbol_to_local_slot(sc, code, sc->curlet); + if (is_slot(slot)) + { + if (is_immutable_slot(slot)) + { + s7_pointer old_symbol = code, old_value = slot_value(slot); + if ((type(old_value) != type(sc->value)) || + (!s7_is_equivalent(sc, old_value, sc->value))) /* if value is unchanged, just ignore this (re)definition */ + syntax_error_nr(sc, "define ~S, but it is immutable", 30, old_symbol); + } + else + { + slot_set_value_with_hook(slot, sc->value); + symbol_increment_ctr(code); + }} + else s7_make_slot(sc, sc->curlet, code, sc->value); + if ((is_any_macro(sc->value)) && (!is_c_macro(sc->value))) + { + set_pair_macro(closure_body(sc->value), code); + set_has_pair_macro(sc->value); + }} +} + + +/* -------------------------------- eval -------------------------------- */ +static void check_for_cyclic_code(s7_scheme *sc, s7_pointer code) +{ + if (tree_is_cyclic(sc, code)) + { + sc->stack_resize_trigger = (s7_pointer *)(sc->stack_start + (sc->stack_size - ((STACK_RESIZE_TRIGGER) / 2))); + syntax_error_nr(sc, "attempt to evaluate a circular list: ~A", 39, code); + } + resize_stack(sc); /* we've already checked that resize_stack is needed */ +} + +static void op_thunk(s7_scheme *sc) +{ + s7_pointer p = opt1_lambda(sc->code); + sc->curlet = inline_make_let(sc, closure_let(p)); + sc->code = T_Pair(closure_body(p)); + if_pair_set_up_begin(sc); +} + +static void op_thunk_o(s7_scheme *sc) +{ + s7_pointer p = opt1_lambda(sc->code); + sc->curlet = inline_make_let(sc, closure_let(p)); + sc->code = car(closure_body(p)); +} + +static void op_safe_thunk(s7_scheme *sc) /* no let needed */ +{ + s7_pointer p = opt1_lambda(sc->code); + sc->curlet = closure_let(p); + sc->code = T_Pair(closure_body(p)); + if_pair_set_up_begin_unchecked(sc); +} + +static s7_pointer op_safe_thunk_a(s7_scheme *sc, s7_pointer code) +{ + s7_pointer f = opt1_lambda(code); + set_curlet(sc, closure_let(f)); + return(fx_call(sc, closure_body(f))); +} + +static void op_thunk_any(s7_scheme *sc) +{ + s7_pointer p = opt1_lambda(sc->code); + sc->curlet = make_let_with_slot(sc, closure_let(p), closure_args(p), sc->nil); + sc->code = closure_body(p); +} + +static void op_safe_thunk_any(s7_scheme *sc) +{ + s7_pointer p = opt1_lambda(sc->code); + sc->curlet = closure_let(p); + slot_set_value(let_slots(sc->curlet), sc->nil); + sc->code = T_Pair(closure_body(p)); + if_pair_set_up_begin_unchecked(sc); +} + +static void op_closure_s(s7_scheme *sc) +{ + s7_pointer p = opt1_lambda(sc->code); + check_stack_size(sc); + sc->curlet = inline_make_let_with_slot(sc, closure_let(p), car(closure_args(p)), lookup(sc, opt2_sym(sc->code))); + sc->code = T_Pair(closure_body(p)); + if_pair_set_up_begin_unchecked(sc); +} + +static inline void op_closure_s_o(s7_scheme *sc) +{ + s7_pointer f = opt1_lambda(sc->code); + sc->curlet = inline_make_let_with_slot(sc, closure_let(f), car(closure_args(f)), lookup(sc, opt2_sym(sc->code))); + sc->code = car(closure_body(f)); +} + +static void op_safe_closure_s(s7_scheme *sc) +{ + s7_pointer p = opt1_lambda(sc->code); + sc->curlet = update_let_with_slot(sc, closure_let(p), lookup(sc, opt2_sym(sc->code))); + sc->code = T_Pair(closure_body(p)); + if_pair_set_up_begin_unchecked(sc); +} + +static void op_safe_closure_s_o(s7_scheme *sc) +{ + s7_pointer f = opt1_lambda(sc->code); + sc->curlet = update_let_with_slot(sc, closure_let(f), lookup(sc, opt2_sym(sc->code))); + sc->code = car(closure_body(f)); +} + +static void op_safe_closure_p(s7_scheme *sc) +{ + check_stack_size(sc); + push_stack_no_args(sc, OP_SAFE_CLOSURE_P_1, opt1_lambda(sc->code)); + sc->code = cadr(sc->code); +} + +static void op_safe_closure_p_1(s7_scheme *sc) +{ + sc->curlet = update_let_with_slot(sc, closure_let(sc->code), sc->value); + sc->code = T_Pair(closure_body(sc->code)); +} + +static void op_safe_closure_p_a(s7_scheme *sc) +{ + check_stack_size(sc); + push_stack_no_args_direct(sc, OP_SAFE_CLOSURE_P_A_1); + sc->code = cadr(sc->code); +} + +static void op_safe_closure_p_a_1(s7_scheme *sc) +{ + s7_pointer f = opt1_lambda(sc->code); + sc->curlet = update_let_with_slot(sc, closure_let(f), sc->value); + sc->value = fx_call(sc, closure_body(f)); +} + +static Inline void inline_op_closure_a(s7_scheme *sc) /* called twice in eval */ +{ + s7_pointer f = opt1_lambda(sc->code); + sc->value = fx_call(sc, cdr(sc->code)); + sc->curlet = inline_make_let_with_slot(sc, closure_let(f), car(closure_args(f)), sc->value); + sc->code = T_Pair(closure_body(f)); +} + +static void op_safe_closure_3s(s7_scheme *sc) +{ + s7_pointer args = cddr(sc->code); + s7_pointer f = opt1_lambda(sc->code); + sc->curlet = update_let_with_three_slots(sc, closure_let(f), lookup(sc, cadr(sc->code)), lookup(sc, car(args)), lookup(sc, cadr(args))); + sc->code = T_Pair(closure_body(f)); + if_pair_set_up_begin_unchecked(sc); +} + +static void op_safe_closure_ssa(s7_scheme *sc) /* possibly inline b */ +{ /* ssa_a is hit once, but is only about 3/4% faster -- there's the fx overhead, etc */ + s7_pointer args = cdr(sc->code); + s7_pointer f = opt1_lambda(sc->code); + sc->curlet = update_let_with_three_slots(sc, closure_let(f), lookup(sc, car(args)), lookup(sc, cadr(args)), fx_call(sc, cddr(args))); + sc->code = T_Pair(closure_body(f)); + if_pair_set_up_begin_unchecked(sc); +} + +static void op_safe_closure_saa(s7_scheme *sc) +{ + s7_pointer f = opt1_lambda(sc->code); + s7_pointer args = cddr(sc->code); + s7_pointer arg2 = lookup(sc, cadr(sc->code)); /* I don't see fx_t|u here? */ + sc->code = fx_call(sc, args); + sc->curlet = update_let_with_three_slots(sc, closure_let(f), arg2, sc->code, fx_call(sc, cdr(args))); + sc->code = T_Pair(closure_body(f)); + if_pair_set_up_begin_unchecked(sc); +} + +static void op_safe_closure_agg(s7_scheme *sc) /* possibly inline tleft */ +{ + s7_pointer args = cdr(sc->code); + s7_pointer f = opt1_lambda(sc->code); + sc->curlet = update_let_with_three_slots(sc, closure_let(f), fx_call(sc, args), fx_call(sc, cdr(args)), fx_call(sc, cddr(args))); + sc->code = T_Pair(closure_body(f)); + if_pair_set_up_begin_unchecked(sc); +} + +static void op_closure_p(s7_scheme *sc) +{ + check_stack_size(sc); + push_stack_no_args(sc, OP_CLOSURE_P_1, opt1_lambda(sc->code)); + sc->code = cadr(sc->code); +} + +static void op_closure_p_1(s7_scheme *sc) +{ + sc->curlet = inline_make_let_with_slot(sc, closure_let(sc->code), car(closure_args(sc->code)), sc->value); + sc->code = T_Pair(closure_body(sc->code)); +} + +static void op_safe_closure_a(s7_scheme *sc) +{ + s7_pointer f = opt1_lambda(sc->code); + sc->curlet = update_let_with_slot(sc, closure_let(f), fx_call(sc, cdr(sc->code))); + sc->code = T_Pair(closure_body(f)); + push_stack_no_args(sc, sc->begin_op, T_Pair(cdr(sc->code))); + sc->code = car(sc->code); +} + +static void op_safe_closure_a_o(s7_scheme *sc) +{ + s7_pointer f = opt1_lambda(sc->code); + sc->curlet = update_let_with_slot(sc, closure_let(f), fx_call(sc, cdr(sc->code))); + sc->code = car(closure_body(f)); +} + +static void op_closure_ap(s7_scheme *sc) +{ + s7_pointer code = sc->code; + sc->args = fx_call(sc, cdr(code)); + /* (hook-push (undo-hook ind 0) (lambda (hook) (set! u0 #t))) -> # + * g_undo_hook calls s7_eval_c_string so it obviously should be declared unsafe! + */ + push_stack(sc, OP_CLOSURE_AP_1, opt1_lambda(sc->code), sc->args); + sc->code = caddr(code); +} + +static void op_closure_ap_1(s7_scheme *sc) +{ + /* sc->value is presumably the "P" argument value, "A" is sc->args->sc->code above (sc->args here is opt1_lambda(original sc->code)) */ + sc->curlet = inline_make_let_with_two_slots(sc, closure_let(sc->args), car(closure_args(sc->args)), sc->code, cadr(closure_args(sc->args)), sc->value); + sc->code = T_Pair(closure_body(sc->args)); +} + +static void op_closure_pa(s7_scheme *sc) +{ + s7_pointer code = sc->code; + sc->args = fx_call(sc, cddr(code)); + check_stack_size(sc); + push_stack(sc, OP_CLOSURE_PA_1, sc->args, opt1_lambda(sc->code)); /* "p" can be self-call changing func locally! so pass opt1_lambda(sc->code), not sc->code */ + sc->code = cadr(code); +} + +static void op_closure_pa_1(s7_scheme *sc) +{ + sc->curlet = inline_make_let_with_two_slots(sc, closure_let(sc->code), car(closure_args(sc->code)), sc->value, cadr(closure_args(sc->code)), sc->args); + sc->code = T_Pair(closure_body(sc->code)); +} + +static void op_closure_pp(s7_scheme *sc) +{ + check_stack_size(sc); + push_stack(sc, OP_CLOSURE_PP_1, opt1_lambda(sc->code), sc->code); + sc->code = cadr(sc->code); +} + +static void op_closure_pp_1(s7_scheme *sc) +{ + push_stack(sc, OP_CLOSURE_AP_1, sc->args, sc->value); + sc->code = caddr(sc->code); +} + +static void op_safe_closure_ap(s7_scheme *sc) +{ + check_stack_size(sc); + sc->args = fx_call(sc, cdr(sc->code)); + push_stack(sc, OP_SAFE_CLOSURE_AP_1, sc->args, opt1_lambda(sc->code)); + sc->code = caddr(sc->code); +} + +static void op_safe_closure_ap_1(s7_scheme *sc) +{ + sc->curlet = update_let_with_two_slots(sc, closure_let(sc->code), sc->args, sc->value); + sc->code = T_Pair(closure_body(sc->code)); +} + +static void op_safe_closure_pa(s7_scheme *sc) +{ + check_stack_size(sc); + sc->args = fx_call(sc, cddr(sc->code)); + push_stack(sc, OP_SAFE_CLOSURE_PA_1, sc->args, opt1_lambda(sc->code)); + sc->code = cadr(sc->code); +} + +static void op_safe_closure_pa_1(s7_scheme *sc) +{ + sc->curlet = update_let_with_two_slots(sc, closure_let(sc->code), sc->value, sc->args); + sc->code = T_Pair(closure_body(sc->code)); +} + +static void op_safe_closure_pp(s7_scheme *sc) +{ + check_stack_size(sc); + push_stack(sc, OP_SAFE_CLOSURE_PP_1, opt1_lambda(sc->code), sc->code); + sc->code = cadr(sc->code); +} + +static void op_safe_closure_pp_1(s7_scheme *sc) +{ + push_stack(sc, OP_SAFE_CLOSURE_AP_1, sc->value, sc->args); + sc->code = caddr(sc->code); +} + +static void op_any_closure_3p(s7_scheme *sc) +{ + s7_pointer p = cdr(sc->code); + if (has_fx(p)) + { + sc->args = fx_call(sc, p); + p = cdr(p); + if (has_fx(p)) + { + stack_end_code(sc) = sc->code; /* push_stack_direct(sc, OP_ANY_CLOSURE_3P_3) here but trying to be too clever? */ + stack_end_args(sc) = sc->args; /* stack[args] == arg1 to closure) */ + stack_end_op(sc) = (s7_pointer)(OP_ANY_CLOSURE_3P_3); + sc->stack_end += 4; + set_stack_protected3_with(sc, fx_call(sc, p), OP_ANY_CLOSURE_3P_3); + /* (i.e. stack[curlet] == arg2 of closure), fx_call might push_stack gc_protect etc, so push_stack via +4 before it */ + sc->code = cadr(p); + } + else + { + push_stack_direct(sc, OP_ANY_CLOSURE_3P_2); /* arg1 == stack[args] */ + sc->code = car(p); + }} + else + { + push_stack_no_args_direct(sc, OP_ANY_CLOSURE_3P_1); + sc->code = car(p); + } +} + +static bool closure_3p_end(s7_scheme *sc, s7_pointer p) +{ + if (has_fx(p)) + { + s7_pointer func = opt1_lambda(sc->code); + gc_protect_2_via_stack(sc, sc->args, sc->value); /* sc->args == arg1, sc->value == arg2 */ + set_stack_protected3(sc, fx_call(sc, p)); + if (is_safe_closure(func)) + sc->curlet = update_let_with_three_slots(sc, closure_let(func), stack_protected1(sc), stack_protected2(sc), stack_protected3(sc)); + else make_let_with_three_slots(sc, func, stack_protected1(sc), stack_protected2(sc), stack_protected3(sc)); + unstack(sc); + sc->code = T_Pair(closure_body(func)); + return(true); + } + push_stack_direct(sc, OP_ANY_CLOSURE_3P_3); + set_stack_protected3_with(sc, sc->value, OP_ANY_CLOSURE_3P_3); /* arg2 == curlet stack loc */ + sc->code = car(p); + return(false); +} + +static bool op_any_closure_3p_1(s7_scheme *sc) +{ + s7_pointer p = cddr(sc->code); + sc->args = sc->value; /* (arg1 of closure) sc->value can be clobbered by fx_call? */ + if (has_fx(p)) + { + sc->value = fx_call(sc, p); + return(closure_3p_end(sc, cdr(p))); + } + push_stack_direct(sc, OP_ANY_CLOSURE_3P_2); + sc->code = car(p); + return(false); +} + +static bool op_any_closure_3p_2(s7_scheme *sc) {return(closure_3p_end(sc, cdddr(sc->code)));} + +static void op_any_closure_3p_3(s7_scheme *sc) +{ + /* display(obj) will not work here because sc->curlet is being used as arg2 of the closure3 */ + s7_pointer func = opt1_lambda(sc->code); /* incoming args (from pop_stack): sc->args, sc->curlet, and sc->value from last evaluation */ + if (is_safe_closure(func)) + sc->curlet = update_let_with_three_slots(sc, closure_let(func), sc->args, sc->curlet, sc->value); + else make_let_with_three_slots(sc, func, sc->args, sc->curlet, sc->value); + sc->code = T_Pair(closure_body(func)); +} + +static void op_any_closure_4p(s7_scheme *sc) +{ + s7_pointer p = cdr(sc->code); + check_stack_size(sc); + if (has_fx(p)) + { + gc_protect_via_stack(sc, fx_call(sc, p)); + p = cdr(p); + if (has_fx(p)) + { + set_stack_protected2(sc, fx_call(sc, p)); + p = cdr(p); + if (has_fx(p)) + { + set_stack_protected3(sc, fx_call(sc, p)); + push_stack_no_args_direct(sc, OP_ANY_CLOSURE_4P_4); + sc->code = cadr(p); + } + else + { + push_stack_no_args_direct(sc, OP_ANY_CLOSURE_4P_3); + sc->code = car(p); + }} + else + { + stack_end_args(sc) = sc->unused; /* copy_stack dangling pair */ + push_stack_no_args_direct(sc, OP_ANY_CLOSURE_4P_2); + sc->code = car(p); + }} + else + { + push_stack_no_args_direct(sc, OP_ANY_CLOSURE_4P_1); + sc->code = car(p); + } +} + +static bool closure_4p_end(s7_scheme *sc, s7_pointer p) +{ + if (has_fx(p)) + { + s7_pointer func = opt1_lambda(sc->code); + sc->args = fx_call(sc, p); + if (is_safe_closure(func)) + sc->curlet = update_let_with_four_slots(sc, closure_let(func), stack_protected1(sc), stack_protected2(sc), stack_protected3(sc), sc->args); + else make_let_with_four_slots(sc, func, stack_protected1(sc), stack_protected2(sc), stack_protected3(sc), sc->args); + sc->code = T_Pair(closure_body(func)); + unstack(sc); + return(true); + } + push_stack_no_args_direct(sc, OP_ANY_CLOSURE_4P_4); + sc->code = car(p); + return(false); +} + +static bool op_any_closure_4p_1(s7_scheme *sc) +{ + s7_pointer p = cddr(sc->code); + gc_protect_via_stack(sc, sc->value); + if (has_fx(p)) + { + set_stack_protected2(sc, fx_call(sc, p)); + p = cdr(p); + if (has_fx(p)) + { + set_stack_protected3(sc, fx_call(sc, p)); + return(closure_4p_end(sc, cdr(p))); + } + push_stack_no_args_direct(sc, OP_ANY_CLOSURE_4P_3); + sc->code = car(p); + } + else + { + push_stack_no_args_direct(sc, OP_ANY_CLOSURE_4P_2); + sc->code = car(p); + } + return(false); +} + +static bool op_any_closure_4p_2(s7_scheme *sc) +{ + s7_pointer p = cdddr(sc->code); + set_stack_protected2(sc, sc->value); + if (has_fx(p)) + { + set_stack_protected3(sc, fx_call(sc, p)); + return(closure_4p_end(sc, cdr(p))); + } + push_stack_no_args_direct(sc, OP_ANY_CLOSURE_4P_3); + sc->code = car(p); + return(false); +} + +static bool op_any_closure_4p_3(s7_scheme *sc) +{ + set_stack_protected3(sc, sc->value); + return(closure_4p_end(sc, cddddr(sc->code))); +} + +static inline void op_any_closure_4p_4(s7_scheme *sc) +{ + s7_pointer func = opt1_lambda(sc->code); + if (is_safe_closure(func)) + sc->curlet = update_let_with_four_slots(sc, closure_let(func), stack_protected1(sc), stack_protected2(sc), stack_protected3(sc), sc->value); + else make_let_with_four_slots(sc, func, stack_protected1(sc), stack_protected2(sc), stack_protected3(sc), sc->value); + sc->code = T_Pair(closure_body(func)); + unstack(sc); +} + +static void op_safe_closure_ss(s7_scheme *sc) +{ + s7_pointer f = opt1_lambda(sc->code); + sc->curlet = update_let_with_two_slots(sc, closure_let(f), lookup(sc, cadr(sc->code)), lookup(sc, opt2_sym(sc->code))); + sc->code = T_Pair(closure_body(f)); + push_stack_no_args(sc, sc->begin_op, T_Pair(cdr(sc->code))); + sc->code = car(sc->code); +} + +static void op_safe_closure_ss_o(s7_scheme *sc) +{ + s7_pointer f = opt1_lambda(sc->code); + sc->curlet = update_let_with_two_slots(sc, closure_let(f), lookup(sc, cadr(sc->code)), lookup(sc, opt2_sym(sc->code))); + sc->code = car(closure_body(f)); +} + +static inline void op_closure_ss(s7_scheme *sc) +{ + s7_pointer f = opt1_lambda(sc->code); + check_stack_size(sc); + sc->curlet = inline_make_let_with_two_slots(sc, closure_let(f), + car(closure_args(f)), lookup(sc, cadr(sc->code)), + cadr(closure_args(f)), lookup(sc, opt2_sym(sc->code))); + sc->code = T_Pair(closure_body(f)); + push_stack_no_args(sc, sc->begin_op, T_Pair(cdr(sc->code))); + sc->code = car(sc->code); +} + +static inline void op_closure_ss_o(s7_scheme *sc) +{ + s7_pointer f = opt1_lambda(sc->code); + sc->curlet = inline_make_let_with_two_slots(sc, closure_let(f), + car(closure_args(f)), lookup(sc, cadr(sc->code)), + cadr(closure_args(f)), lookup(sc, opt2_sym(sc->code))); + sc->code = car(closure_body(f)); +} + +static void op_safe_closure_sc(s7_scheme *sc) +{ + s7_pointer f = opt1_lambda(sc->code); + sc->curlet = update_let_with_two_slots(sc, closure_let(f), lookup(sc, cadr(sc->code)), opt2_con(sc->code)); + sc->code = T_Pair(closure_body(f)); + push_stack_no_args(sc, sc->begin_op, T_Pair(cdr(sc->code))); + sc->code = car(sc->code); +} + +static void op_safe_closure_sc_o(s7_scheme *sc) +{ + s7_pointer f = opt1_lambda(sc->code); + sc->curlet = update_let_with_two_slots(sc, closure_let(f), lookup(sc, cadr(sc->code)), opt2_con(sc->code)); + sc->code = car(closure_body(f)); +} + +static void op_closure_sc(s7_scheme *sc) +{ + s7_pointer f = opt1_lambda(sc->code); + check_stack_size(sc); + sc->curlet = make_let_with_two_slots(sc, closure_let(f), car(closure_args(f)), lookup(sc, cadr(sc->code)), cadr(closure_args(f)), opt2_con(sc->code)); + sc->code = T_Pair(closure_body(f)); + push_stack_no_args(sc, sc->begin_op, T_Pair(cdr(sc->code))); + sc->code = car(sc->code); +} + +static inline void op_closure_sc_o(s7_scheme *sc) +{ + s7_pointer f = opt1_lambda(sc->code); + check_stack_size(sc); + sc->curlet = inline_make_let_with_two_slots(sc, closure_let(f), car(closure_args(f)), lookup(sc, cadr(sc->code)), cadr(closure_args(f)), opt2_con(sc->code)); + sc->code = car(closure_body(f)); +} + +static void op_closure_3s(s7_scheme *sc) +{ + s7_pointer args = cdr(sc->code); + s7_pointer v1 = lookup(sc, car(args)); + s7_pointer f = opt1_lambda(sc->code); + args = cdr(args); + make_let_with_three_slots(sc, f, v1, lookup(sc, car(args)), lookup(sc, cadr(args))); /* sets sc->curlet */ + sc->code = T_Pair(closure_body(f)); + if_pair_set_up_begin(sc); +} + +static inline void op_closure_3s_o(s7_scheme *sc) +{ + s7_pointer args = cdr(sc->code); + s7_pointer v1 = lookup(sc, car(args)); + s7_pointer f = opt1_lambda(sc->code); + args = cdr(args); + make_let_with_three_slots(sc, f, v1, lookup(sc, car(args)), lookup(sc, cadr(args))); /* sets sc->curlet */ + sc->code = car(closure_body(f)); +} + +static void op_closure_4s(s7_scheme *sc) +{ + s7_pointer args = cdr(sc->code); + s7_pointer v1 = lookup(sc, car(args)), v2 = lookup(sc, cadr(args)); + s7_pointer f = opt1_lambda(sc->code); + args = cddr(args); + make_let_with_four_slots(sc, f, v1, v2, lookup(sc, car(args)), lookup(sc, cadr(args))); /* sets sc->curlet */ + sc->code = T_Pair(closure_body(f)); + if_pair_set_up_begin(sc); +} + +static inline void op_closure_4s_o(s7_scheme *sc) +{ + s7_pointer args = cdr(sc->code); + s7_pointer v1 = lookup(sc, car(args)), v2 = lookup(sc, cadr(args)); + s7_pointer f = opt1_lambda(sc->code); + args = cddr(args); + make_let_with_four_slots(sc, f, v1, v2, lookup(sc, car(args)), lookup(sc, cadr(args))); /* sets sc->curlet */ + sc->code = car(closure_body(f)); +} + +static void op_closure_5s(s7_scheme *sc) /* .1 in lg but this is marginal -- adds two ops etc */ +{ + s7_pointer args = cdr(sc->code); + s7_pointer v1 = lookup(sc, car(args)), v2 = lookup(sc, cadr(args)); + s7_pointer f = opt1_lambda(sc->code); + args = cddr(args); + make_let_with_five_slots(sc, f, v1, v2, lookup(sc, car(args)), lookup(sc, cadr(args)), lookup(sc, caddr(args))); /* sets sc->curlet */ + sc->code = T_Pair(closure_body(f)); + if_pair_set_up_begin(sc); +} + +static void op_safe_closure_aa(s7_scheme *sc) +{ + s7_pointer p = cdr(sc->code); + s7_pointer f = opt1_lambda(sc->code); + sc->code = fx_call(sc, cdr(p)); /* fx_call can affect sc->value, but not sc->code, I think */ + sc->curlet = update_let_with_two_slots(sc, closure_let(f), fx_call(sc, p), sc->code); + p = T_Pair(closure_body(f)); + /* check_stack_size(sc); */ /* pretty-print if cycles=#f? */ + push_stack_no_args(sc, sc->begin_op, T_Pair(cdr(p))); + sc->code = car(p); +} + +static inline void op_safe_closure_aa_o(s7_scheme *sc) +{ + s7_pointer p = cdr(sc->code); + s7_pointer f = opt1_lambda(sc->code); + sc->code = fx_call(sc, cdr(p)); + sc->curlet = update_let_with_two_slots(sc, closure_let(f), fx_call(sc, p), sc->code); + sc->code = car(closure_body(f)); +} + +static void op_closure_aa(s7_scheme *sc) +{ + s7_pointer p = cdr(sc->code); + s7_pointer f = opt1_lambda(sc->code); + sc->code = fx_call(sc, cdr(p)); + sc->value = fx_call(sc, p); + sc->curlet = inline_make_let_with_two_slots(sc, closure_let(f), car(closure_args(f)), sc->value, cadr(closure_args(f)), sc->code); + p = T_Pair(closure_body(f)); + check_stack_size(sc); + push_stack_no_args(sc, sc->begin_op, T_Pair(cdr(p))); + sc->code = car(p); +} + +static Inline void inline_op_closure_aa_o(s7_scheme *sc) /* called once in eval, b cb left lg list */ +{ + s7_pointer p = cdr(sc->code); + s7_pointer f = opt1_lambda(sc->code); + sc->code = fx_call(sc, cdr(p)); + sc->value = fx_call(sc, p); + sc->curlet = inline_make_let_with_two_slots(sc, closure_let(f), car(closure_args(f)), sc->value, cadr(closure_args(f)), sc->code); + sc->code = car(closure_body(f)); +} + +static /* inline */ void op_closure_fa(s7_scheme *sc) /* "inline" matters perhaps in texit.scm */ +{ + s7_pointer new_clo, code = sc->code; + s7_pointer farg = opt2_pair(code); /* cdadr(code), '((a . b) (cons a b)) for (lambda (a . b) (cons a b)) */ + s7_pointer aarg = fx_call(sc, cddr(code)); + s7_pointer func = opt1_lambda(code); /* outer func */ + s7_pointer func_args = closure_args(func); /* outer func args (not the arglist of the applied func) */ + sc->value = inline_make_let_with_two_slots(sc, closure_let(func), car(func_args), sc->F, cadr(func_args), aarg); + new_clo = make_closure_unchecked(sc, car(farg), cdr(farg), T_CLOSURE | ((!s7_is_proper_list(sc, car(farg))) ? T_COPY_ARGS : 0), CLOSURE_ARITY_NOT_SET); + /* this is checking the called closure arglist (see op_lambda), arity<0 probably not usable since "f" in "fa" is a parameter */ + slot_set_value(let_slots(sc->value), new_clo); /* this order allows us to use make_closure_unchecked */ + sc->curlet = sc->value; + sc->code = car(closure_body(func)); +} + +static void op_safe_closure_ns(s7_scheme *sc) +{ + s7_pointer args = cdr(sc->code); + s7_pointer f = opt1_lambda(sc->code); + s7_pointer let = closure_let(f); + uint64_t id = ++sc->let_number; + let_set_id(let, id); + for (s7_pointer x = let_slots(let); tis_slot(x); x = next_slot(x), args = cdr(args)) + { + slot_set_value(x, lookup(sc, car(args))); + symbol_set_local_slot(slot_symbol(x), id, x); + } + set_curlet(sc, let); + sc->code = closure_body(f); + if_pair_set_up_begin_unchecked(sc); +} + +static inline void op_safe_closure_3a(s7_scheme *sc) +{ + s7_pointer p = cdr(sc->code); + s7_pointer f = opt1_lambda(sc->code); + sc->code = fx_call(sc, cdr(p)); /* fx_call can affect sc->value, but not sc->code, I think */ + sc->args = fx_call(sc, cddr(p)); /* is sc->args safe here? */ + sc->curlet = update_let_with_three_slots(sc, closure_let(f), fx_call(sc, p), sc->code, sc->args); + sc->code = closure_body(f); + if_pair_set_up_begin_unchecked(sc); +} + +static void op_safe_closure_na(s7_scheme *sc) +{ + s7_pointer let; + uint64_t id; + + sc->args = safe_list_if_possible(sc, opt3_arglen(cdr(sc->code))); + for (s7_pointer args = cdr(sc->code), p = sc->args; is_pair(args); args = cdr(args), p = cdr(p)) + set_car(p, fx_call(sc, args)); + sc->code = opt1_lambda(sc->code); + id = ++sc->let_number; + let = closure_let(sc->code); + let_set_id(let, id); + for (s7_pointer x = let_slots(let), z = sc->args; tis_slot(x); x = next_slot(x), z = cdr(z)) + { + slot_set_value(x, car(z)); + symbol_set_local_slot(slot_symbol(x), id, x); + } + clear_list_in_use(sc->args); + set_curlet(sc, let); + sc->code = closure_body(sc->code); + if_pair_set_up_begin_unchecked(sc); +} + +static /* inline */ void op_closure_ns(s7_scheme *sc) /* called once in eval, lg? */ +{ + /* in this case, we have just lambda (not lambda*), and no dotted arglist, + * and no accessed symbols in the arglist, and we know the arglist matches the parameter list. + */ + s7_pointer args = cdr(sc->code), last_slot; + s7_pointer f = opt1_lambda(sc->code); + s7_pointer p = closure_args(f); + s7_pointer e = inline_make_let(sc, closure_let(f)); + s7_int id = let_id(e); + sc->z = e; + add_slot_unchecked(sc, e, car(p), lookup(sc, car(args)), id); + last_slot = let_slots(e); + for (p = cdr(p), args = cdr(args); is_pair(p); p = cdr(p), args = cdr(args)) + last_slot = inline_add_slot_at_end(sc, id, last_slot, car(p), lookup(sc, car(args))); /* main such call in lt (fx_s is 1/2, this is 1/5 of all calls) */ + set_curlet(sc, e); + sc->z = sc->unused; + sc->code = T_Pair(closure_body(f)); + if_pair_set_up_begin(sc); +} + +static void op_closure_ass(s7_scheme *sc) /* possibly inline b */ +{ + s7_pointer args = cdr(sc->code); + s7_pointer f = opt1_lambda(sc->code); + make_let_with_three_slots(sc, f, fx_call(sc, args), lookup(sc, cadr(args)), lookup(sc, caddr(args))); + sc->code = T_Pair(closure_body(f)); + if_pair_set_up_begin(sc); +} + +static void op_closure_aas(s7_scheme *sc) /* possibly inline b */ +{ + s7_pointer args = cdr(sc->code); + s7_pointer f = opt1_lambda(sc->code); + sc->z = fx_call(sc, args); + make_let_with_three_slots(sc, f, sc->z, fx_call(sc, cdr(args)), lookup(sc, caddr(args))); + sc->code = T_Pair(closure_body(f)); + if_pair_set_up_begin(sc); +} + +static void op_closure_saa(s7_scheme *sc) +{ + s7_pointer args = cdr(sc->code); + s7_pointer f = opt1_lambda(sc->code); + sc->z = fx_call(sc, cdr(args)); + make_let_with_three_slots(sc, f, lookup(sc, car(args)), sc->z, fx_call(sc, cddr(args))); + sc->code = T_Pair(closure_body(f)); + if_pair_set_up_begin(sc); +} + +static void op_closure_asa(s7_scheme *sc) +{ + s7_pointer args = cdr(sc->code); + s7_pointer f = opt1_lambda(sc->code); + sc->z = fx_call(sc, args); + make_let_with_three_slots(sc, f, sc->z, lookup(sc, cadr(args)), fx_call(sc, cddr(args))); + sc->code = T_Pair(closure_body(f)); + if_pair_set_up_begin(sc); +} + +static void op_closure_sas(s7_scheme *sc) +{ + s7_pointer args = cdr(sc->code); + s7_pointer f = opt1_lambda(sc->code); + make_let_with_three_slots(sc, f, lookup(sc, car(args)), fx_call(sc, cdr(args)), lookup(sc, caddr(args))); + sc->code = T_Pair(closure_body(f)); + if_pair_set_up_begin(sc); +} + +static inline void op_closure_3a(s7_scheme *sc) /* if inlined, tlist -60 */ +{ + s7_pointer args = cdr(sc->code); + s7_pointer f = opt1_lambda(sc->code); + gc_protect_2_via_stack(sc, fx_call(sc, args), fx_call(sc, cdr(args))); + make_let_with_three_slots(sc, f, stack_protected1(sc), stack_protected2(sc), fx_call(sc, cddr(args))); + unstack(sc); + sc->code = T_Pair(closure_body(f)); + if_pair_set_up_begin(sc); +} + +static void op_closure_4a(s7_scheme *sc) /* sass */ +{ + s7_pointer args = cdr(sc->code); + s7_pointer f = opt1_lambda(sc->code); + gc_protect_2_via_stack(sc, fx_call(sc, args), fx_call(sc, cddr(args))); + args = cdr(args); + set_stack_protected3(sc, fx_call(sc, args)); /* [-3]=second */ + make_let_with_four_slots(sc, f, stack_protected1(sc), stack_protected3(sc), stack_protected2(sc), fx_call(sc, cddr(args))); + unstack(sc); + sc->code = T_Pair(closure_body(f)); + if_pair_set_up_begin(sc); +} + +static void op_closure_na(s7_scheme *sc) +{ + s7_pointer exprs = cdr(sc->code); /* "n" = opt3_arglen(exprs), mostly 5 in lt, 6 in tlet */ + s7_pointer func = opt1_lambda(sc->code), slot, last_slot; + s7_int id; + s7_pointer pars = closure_args(func); + s7_pointer e = inline_make_let(sc, closure_let(func)); + sc->z = e; + sc->value = fx_call(sc, exprs); + new_cell_no_check(sc, last_slot, T_SLOT); + slot_set_symbol_and_value(last_slot, car(pars), sc->value); + slot_set_next(last_slot, let_slots(e)); /* i.e. slot_end */ + let_set_slots(e, last_slot); + for (pars = cdr(pars), exprs = cdr(exprs); is_pair(pars); pars = cdr(pars), exprs = cdr(exprs)) + { + sc->value = fx_call(sc, exprs); /* before new_cell since it might call the GC */ + new_cell(sc, slot, T_SLOT); /* args < GC_TRIGGER checked in optimizer, but we're calling fx_call? */ + slot_set_symbol_and_value(slot, car(pars), sc->value); + /* setting up the let might use unrelated-but-same-name symbols, so wait to set the symbol ids */ + slot_set_next(slot, slot_end); + slot_set_next(last_slot, slot); + last_slot = slot; + } + set_curlet(sc, e); + sc->z = sc->unused; + let_set_id(e, ++sc->let_number); + for (id = let_id(e), slot = let_slots(e); tis_slot(slot); slot = next_slot(slot)) + { + symbol_set_local_slot(slot_symbol(slot), id, slot); + set_local(slot_symbol(slot)); + } + sc->code = T_Pair(closure_body(func)); + if_pair_set_up_begin(sc); +} + +static bool check_closure_sym(s7_scheme *sc, int32_t args) +{ + /* can't use closure_is_fine -- (lambda args 1) and (lambda (name . args) 1) are both arity -1 for the internal arity checkers! */ + if ((symbol_ctr(car(sc->code)) != 1) || + (unchecked_local_value(car(sc->code)) != opt1_lambda_unchecked(sc->code))) + { + s7_pointer f = lookup_unexamined(sc, car(sc->code)); + if ((f != opt1_lambda_unchecked(sc->code)) && + ((!f) || + ((typesflag(f) & (TYPE_MASK | T_SAFE_CLOSURE)) != T_CLOSURE) || + (((args == 1) && (!is_symbol(closure_args(f)))) || + ((args == 2) && ((!is_pair(closure_args(f))) || (!is_symbol(cdr(closure_args(f))))))))) + { + sc->last_function = f; + return(false); + } + set_opt1_lambda(sc->code, f); + } + return(true); +} + +static void op_any_closure_sym(s7_scheme *sc) /* for (lambda a ...) */ +{ + s7_pointer func = opt1_lambda(sc->code), old_args = cdr(sc->code); /* args aren't evaluated yet */ + s7_int num_args = opt3_arglen(old_args); + + if (num_args == 1) + sc->curlet = inline_make_let_with_slot(sc, closure_let(func), closure_args(func), + ((is_safe_closure(func)) && (!sc->debug_or_profile)) ? + set_plist_1(sc, fx_call(sc, old_args)) : list_1(sc, sc->value = fx_call(sc, old_args))); + else + if (num_args == 2) + { + gc_protect_via_stack(sc, fx_call(sc, old_args)); /* not sc->value as GC protection! -- fx_call below can clobber it */ + sc->args = fx_call(sc, cdr(old_args)); + sc->curlet = inline_make_let_with_slot(sc, closure_let(func), closure_args(func), + ((is_safe_closure(func)) && (!sc->debug_or_profile)) ? + set_plist_2(sc, stack_protected1(sc), sc->args) : list_2(sc, stack_protected1(sc), sc->args)); + unstack(sc); + } + else + if (num_args == 0) + sc->curlet = inline_make_let_with_slot(sc, closure_let(func), closure_args(func), sc->nil); + else + { + sc->args = make_list(sc, num_args, sc->unused); + for (s7_pointer p = sc->args; is_pair(p); p = cdr(p), old_args = cdr(old_args)) + set_car(p, fx_call(sc, old_args)); + sc->curlet = make_let_with_slot(sc, closure_let(func), closure_args(func), sc->args); + } + sc->code = T_Pair(closure_body(func)); +} + +static void op_any_closure_a_sym(s7_scheme *sc) /* for (lambda (a . b) ...) */ +{ + s7_pointer func = opt1_lambda(sc->code), old_args = cdr(sc->code); + s7_int num_args = opt3_arglen(old_args); + s7_pointer func_args = closure_args(func); + + if (num_args == 1) + sc->curlet = make_let_with_two_slots(sc, closure_let(func), car(func_args), sc->value = fx_call(sc, old_args), cdr(func_args), sc->nil); + else + { + gc_protect_via_stack(sc, fx_call(sc, old_args)); /* not sc->value as GC protection! -- fx_call below can clobber it */ + if (num_args == 2) + { + sc->args = fx_call(sc, cdr(old_args)); + sc->curlet = inline_make_let_with_two_slots(sc, closure_let(func), car(func_args), stack_protected1(sc), cdr(func_args), list_1(sc, sc->args)); + } + else + { + sc->args = make_list(sc, num_args - 1, sc->unused); + old_args = cdr(old_args); + for (s7_pointer p = sc->args; is_pair(p); p = cdr(p), old_args = cdr(old_args)) + set_car(p, fx_call(sc, old_args)); + sc->curlet = make_let_with_two_slots(sc, closure_let(func), car(func_args), stack_protected1(sc), cdr(func_args), sc->args); + } + unstack(sc); + } + sc->code = T_Pair(closure_body(func)); +} + + +/* -------- */ +#if S7_DEBUGGING +#define TC_REC_SIZE NUM_OPS +#define TC_REC_LOW_OP OP_TC_AND_A_OR_A_LA + +static void init_tc_rec(s7_scheme *sc) +{ + sc->tc_rec_calls = (int *)Calloc(TC_REC_SIZE, sizeof(int)); + add_saved_pointer(sc, sc->tc_rec_calls); +} + +static s7_pointer g_report_missed_calls(s7_scheme *sc, s7_pointer args) +{ + for (int32_t i = TC_REC_LOW_OP; i < NUM_OPS; i++) + if (sc->tc_rec_calls[i] == 0) + fprintf(stderr, "%s missed\n", op_names[i]); + return(sc->F); +} + +static void tick_tc(s7_scheme *sc, int32_t op) +{ + sc->tc_rec_calls[op]++; +} +#else +#define tick_tc(Sc, Op) +#endif + +static bool op_tc_case_la(s7_scheme *sc, s7_pointer code) +{ + /* opt1_any(clause) = key, has_tc(arg) = is tc call, opt2_any(clause) = result: has_tc(la arg) has_fx(val) or ((...)...) */ + s7_pointer clauses = cddr(code), la_slot = let_slots(sc->curlet), endp, selp = cdr(code); + s7_int len = opt3_arglen(cdr(code)); + if (len == 3) + { + while (true) + { + s7_pointer selector = fx_call(sc, selp); + if (selector == opt1_any(clauses)) + endp = opt2_any(clauses); + else + { + s7_pointer p = cdr(clauses); + endp = (selector == opt1_any(p)) ? opt2_any(p) : opt2_any(cdr(p)); + } + if (has_tc(endp)) + slot_set_value(la_slot, fx_call(sc, cdr(endp))); + else break; + }} + else + while (true) + { + s7_pointer p, selector = fx_call(sc, selp); + for (p = clauses; is_pair(cdr(p)); p = cdr(p)) + if (selector == opt1_any(p)) {endp = opt2_any(p); goto CASE_ALA_END;} + endp = opt2_any(p); + CASE_ALA_END: + if (has_tc(endp)) + slot_set_value(la_slot, fx_call(sc, cdr(endp))); + else break; + } + if (has_fx(endp)) + { + sc->value = fx_call(sc, endp); + return(true); /* continue */ + } + sc->code = endp; + return(false); /* goto BEGIN (not like op_tc_z below) */ +} + +static s7_pointer fx_tc_case_la(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_CASE_LA); + op_tc_case_la(sc, arg); + return(sc->value); +} + +static bool op_tc_z(s7_scheme *sc, s7_pointer expr) +{ + if (has_fx(expr)) + { + sc->value = fx_call(sc, expr); + return(true); + } + sc->code = car(expr); + return(false); +} + +static void op_tc_and_a_or_a_la(s7_scheme *sc, s7_pointer code) +{ + s7_pointer fx_and = cdr(code), la_slot = let_slots(sc->curlet); + s7_pointer fx_or = cdadr(fx_and); + s7_pointer fx_la = cdadr(fx_or); + /* cell_optimize here is slower! */ + while (true) + { + s7_pointer p; + if (fx_call(sc, fx_and) == sc->F) {sc->value = sc->F; return;} + p = fx_call(sc, fx_or); + if (p != sc->F) {sc->value = p; return;} + slot_set_value(la_slot, fx_call(sc, fx_la)); + } +} + +static s7_pointer fx_tc_and_a_or_a_la(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_AND_A_OR_A_LA); + op_tc_and_a_or_a_la(sc, arg); + return(sc->value); +} + +static void op_tc_or_a_and_a_la(s7_scheme *sc, s7_pointer code) +{ + s7_pointer fx_or = cdr(code), la_slot = let_slots(sc->curlet); + s7_pointer fx_and = cdadr(fx_or); + s7_pointer fx_la = cdadr(fx_and); + while (true) + { + s7_pointer p = fx_call(sc, fx_or); + if (p != sc->F) {sc->value = p; return;} + if (fx_call(sc, fx_and) == sc->F) {sc->value = sc->F; return;} + slot_set_value(la_slot, fx_call(sc, fx_la)); + } +} + +static s7_pointer fx_tc_or_a_and_a_la(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_OR_A_AND_A_LA); + op_tc_or_a_and_a_la(sc, arg); + return(sc->value); +} + +static void op_tc_and_a_or_a_a_la(s7_scheme *sc, s7_pointer code) +{ + s7_pointer fx_and = cdr(code), la_slot = let_slots(sc->curlet); + s7_pointer fx_or1 = cdadr(fx_and); + s7_pointer fx_or2 = cdr(fx_or1); + s7_pointer fx_la = cdadr(fx_or2); + while (true) + { + s7_pointer p; + if (fx_call(sc, fx_and) == sc->F) {sc->value = sc->F; return;} + p = fx_call(sc, fx_or1); + if (p != sc->F) {sc->value = p; return;} + p = fx_call(sc, fx_or2); + if (p != sc->F) {sc->value = p; return;} + slot_set_value(la_slot, fx_call(sc, fx_la)); + } +} + +static s7_pointer fx_tc_and_a_or_a_a_la(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_AND_A_OR_A_A_LA); + op_tc_and_a_or_a_a_la(sc, arg); + return(sc->value); +} + +static void op_tc_or_a_and_a_a_la(s7_scheme *sc, s7_pointer code) +{ + s7_pointer fx_or = cdr(code), la_slot = let_slots(sc->curlet); + s7_pointer fx_and1 = cdadr(fx_or); + s7_pointer fx_and2 = cdr(fx_and1); + s7_pointer fx_la = cdadr(fx_and2); + while (true) + { + s7_pointer p = fx_call(sc, fx_or); + if (p != sc->F) {sc->value = p; return;} + if ((fx_call(sc, fx_and1) == sc->F) || + (fx_call(sc, fx_and2) == sc->F)) + {sc->value = sc->F; return;} + slot_set_value(la_slot, fx_call(sc, fx_la)); + } +} + +static s7_pointer fx_tc_or_a_and_a_a_la(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_OR_A_AND_A_A_LA); + op_tc_or_a_and_a_a_la(sc, arg); + return(sc->value); +} + +static void op_tc_or_a_a_and_a_a_la(s7_scheme *sc, s7_pointer code) +{ + s7_pointer fx_or1 = cdr(code), la_slot = let_slots(sc->curlet); + s7_pointer fx_or2 = cdr(fx_or1); + s7_pointer fx_and1 = cdadr(fx_or2); + s7_pointer fx_and2 = cdr(fx_and1); + s7_pointer fx_la = cdadr(fx_and2); + while (true) + { + s7_pointer p = fx_call(sc, fx_or1); + if (p != sc->F) {sc->value = p; return;} + p = fx_call(sc, fx_or2); + if (p != sc->F) {sc->value = p; return;} + if (fx_call(sc, fx_and1) == sc->F) {sc->value = sc->F; return;} + if (fx_call(sc, fx_and2) == sc->F) {sc->value = sc->F; return;} + slot_set_value(la_slot, fx_call(sc, fx_la)); + } +} + +static s7_pointer fx_tc_or_a_a_and_a_a_la(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_OR_A_A_AND_A_A_LA); + op_tc_or_a_a_and_a_a_la(sc, arg); + return(sc->value); +} + +static void op_tc_and_a_or_a_laa(s7_scheme *sc, s7_pointer code) +{ + s7_pointer fx_and = cdr(code), la_slot = let_slots(sc->curlet); + s7_pointer fx_or = cdadr(fx_and); + s7_pointer fx_la = cdadr(fx_or); + s7_pointer fx_laa = cdr(fx_la); + s7_pointer laa_slot = next_slot(la_slot); + + if ((fx_proc(fx_and) == fx_not_is_null_u) && (fx_proc(fx_or) == fx_is_null_t) && + (fx_proc(fx_la) == fx_cdr_t) && (fx_proc(fx_laa) == fx_cdr_u)) + { + s7_pointer la_val = slot_value(la_slot), laa_val = slot_value(laa_slot); + while (true) + { + if (is_null(laa_val)) {sc->value = sc->F; return;} + if (is_null(la_val)) {sc->value = sc->T; return;} + la_val = cdr(la_val); + laa_val = cdr(laa_val); + }} + while (true) + { + s7_pointer p; + if (fx_call(sc, fx_and) == sc->F) {sc->value = sc->F; return;} + p = fx_call(sc, fx_or); + if (p != sc->F) {sc->value = p; return;} + sc->rec_p1 = fx_call(sc, fx_la); + slot_set_value(laa_slot, fx_call(sc, fx_laa)); + slot_set_value(la_slot, sc->rec_p1); + } +} + +static s7_pointer fx_tc_and_a_or_a_laa(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_AND_A_OR_A_LAA); + op_tc_and_a_or_a_laa(sc, arg); + sc->rec_p1 = sc->unused; + return(sc->value); +} + +static void op_tc_or_a_and_a_laa(s7_scheme *sc, s7_pointer code) +{ + s7_pointer fx_or = cdr(code), la_slot = let_slots(sc->curlet); + s7_pointer fx_and = cdadr(fx_or); + s7_pointer fx_la = cdadr(fx_and); + s7_pointer fx_laa = cdr(fx_la); + s7_pointer laa_slot = next_slot(la_slot); + while (true) + { + s7_pointer p = fx_call(sc, fx_or); + if (p != sc->F) {sc->value = p; return;} + if (fx_call(sc, fx_and) == sc->F) {sc->value = sc->F; return;} + sc->rec_p1 = fx_call(sc, fx_la); + slot_set_value(laa_slot, fx_call(sc, fx_laa)); + slot_set_value(la_slot, sc->rec_p1); + } +} + +static s7_pointer fx_tc_or_a_and_a_laa(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_OR_A_AND_A_LAA); + op_tc_or_a_and_a_laa(sc, arg); + sc->rec_p1 = sc->unused; + return(sc->value); +} + +static void op_tc_and_a_or_a_l3a(s7_scheme *sc, s7_pointer code) +{ + s7_pointer fx_and = cdr(code), la_slot = let_slots(sc->curlet); + s7_pointer fx_or = cdadr(fx_and); + s7_pointer fx_la = cdadr(fx_or); + s7_pointer fx_laa = cdr(fx_la); + s7_pointer fx_l3a = cdr(fx_laa); + s7_pointer laa_slot = next_slot(la_slot); + s7_pointer l3a_slot = next_slot(laa_slot); + while (true) + { + s7_pointer p; + if (fx_call(sc, fx_and) == sc->F) {sc->value = sc->F; return;} + p = fx_call(sc, fx_or); + if (p != sc->F) {sc->value = p; return;} + sc->rec_p1 = fx_call(sc, fx_la); + sc->rec_p2 = fx_call(sc, fx_laa); + slot_set_value(l3a_slot, fx_call(sc, fx_l3a)); + slot_set_value(laa_slot, sc->rec_p2); + slot_set_value(la_slot, sc->rec_p1); + } +} + +static s7_pointer fx_tc_and_a_or_a_l3a(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_AND_A_OR_A_L3A); + op_tc_and_a_or_a_l3a(sc, arg); + sc->rec_p1 = sc->unused; + sc->rec_p2 = sc->unused; + return(sc->value); +} + +static void op_tc_or_a_and_a_l3a(s7_scheme *sc, s7_pointer code) +{ + s7_pointer fx_or = cdr(code), la_slot = let_slots(sc->curlet); + s7_pointer fx_and = cdadr(fx_or); + s7_pointer fx_la = cdadr(fx_and); + s7_pointer fx_laa = cdr(fx_la); + s7_pointer fx_l3a = cdr(fx_laa); + s7_pointer laa_slot = next_slot(la_slot); + s7_pointer l3a_slot = next_slot(laa_slot); + while (true) + { + s7_pointer p = fx_call(sc, fx_or); + if (p != sc->F) {sc->value = p; return;} + if (fx_call(sc, fx_and) == sc->F) {sc->value = sc->F; return;} + sc->rec_p1 = fx_call(sc, fx_la); + sc->rec_p2 = fx_call(sc, fx_laa); + slot_set_value(l3a_slot, fx_call(sc, fx_l3a)); + slot_set_value(laa_slot, sc->rec_p2); + slot_set_value(la_slot, sc->rec_p1); + } +} + +static s7_pointer fx_tc_or_a_and_a_l3a(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_OR_A_AND_A_L3A); + op_tc_or_a_and_a_l3a(sc, arg); + sc->rec_p1 = sc->unused; + sc->rec_p2 = sc->unused; + return(sc->value); +} + +static void op_tc_or_a_and_a_a_l3a(s7_scheme *sc, s7_pointer code) +{ + s7_pointer fx_or = cdr(code), la_slot = let_slots(sc->curlet); + s7_pointer fx_and1 = opt3_pair(fx_or); /* (or_case) ? cdadr(fx_or) : cdaddr(fx_or); */ + s7_pointer fx_and2 = cdr(fx_and1); + s7_pointer fx_la = cdadr(fx_and2); + s7_pointer fx_laa = cdr(fx_la); + s7_pointer laa_slot = next_slot(la_slot); + s7_pointer fx_l3a = cdr(fx_laa); + s7_pointer l3a_slot = next_slot(laa_slot); + if ((fx_proc(fx_and1) == fx_not_a) && (fx_proc(fx_and2) == fx_not_a)) + { + fx_and1 = cdar(fx_and1); + fx_and2 = cdar(fx_and2); + while (true) + { + s7_pointer p = fx_call(sc, fx_or); + if (p != sc->F) {sc->value = p; return;} + if ((fx_call(sc, fx_and1) != sc->F) || (fx_call(sc, fx_and2) != sc->F)) {sc->value = sc->F; return;} + sc->rec_p1 = fx_call(sc, fx_la); + sc->rec_p2 = fx_call(sc, fx_laa); + slot_set_value(l3a_slot, fx_call(sc, fx_l3a)); + slot_set_value(la_slot, sc->rec_p1); + slot_set_value(laa_slot, sc->rec_p2); + }} + while (true) + { + s7_pointer p = fx_call(sc, fx_or); + if (p != sc->F) {sc->value = p; return;} + if ((fx_call(sc, fx_and1) == sc->F) || (fx_call(sc, fx_and2) == sc->F)) {sc->value = sc->F; return;} + sc->rec_p1 = fx_call(sc, fx_la); + sc->rec_p2 = fx_call(sc, fx_laa); + slot_set_value(l3a_slot, fx_call(sc, fx_l3a)); + slot_set_value(la_slot, sc->rec_p1); + slot_set_value(laa_slot, sc->rec_p2); + } +} + +static s7_pointer fx_tc_or_a_and_a_a_l3a(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_OR_A_AND_A_A_L3A); + op_tc_or_a_and_a_a_l3a(sc, arg); + sc->rec_p1 = sc->unused; + sc->rec_p2 = sc->unused; + return(sc->value); +} + +static bool op_tc_if_a_z_la(s7_scheme *sc, s7_pointer code, bool cond) +{ + s7_pointer la_slot = let_slots(sc->curlet); + s7_pointer if_test = (cond) ? cadr(code) : cdr(code); + s7_pointer if_true = (cond) ? opt1_pair(cdr(code)) : opt1_pair(if_test); + s7_pointer la = (cond) ? opt3_pair(cdr(code)) : opt3_pair(if_test); + if (is_t_integer(slot_value(la_slot))) + { + sc->pc = 0; + if (bool_optimize(sc, if_test)) + { + opt_info *o = sc->opts[0], *o1 = sc->opts[sc->pc]; + if (int_optimize(sc, la)) + { + s7_pointer val = make_mutable_integer(sc, integer(slot_value(la_slot))); + slot_set_value(la_slot, val); + while (!(o->v[0].fb(o))){integer(val) = o1->v[0].fi(o1);} + return(op_tc_z(sc, if_true)); + }}} + while (fx_call(sc, if_test) == sc->F) {slot_set_value(la_slot, fx_call(sc, la));} + return(op_tc_z(sc, if_true)); +} + +static s7_pointer fx_tc_if_a_z_la(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_IF_A_Z_LA); + op_tc_if_a_z_la(sc, arg, false); + return(sc->value); +} + +static s7_pointer fx_tc_cond_a_z_la(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_COND_A_Z_LA); + op_tc_if_a_z_la(sc, arg, true); + return(sc->value); +} + +static bool op_tc_if_a_la_z(s7_scheme *sc, s7_pointer code, bool cond) +{ + s7_pointer la_slot = let_slots(sc->curlet); + s7_pointer if_test = (cond) ? cadr(code) : cdr(code); + s7_pointer if_false = (cond) ? opt1_pair(cdr(code)) : opt1_pair(if_test); + s7_pointer la = (cond) ? opt3_pair(cdr(code)) : opt3_pair(if_test); + if (is_t_integer(slot_value(la_slot))) + { + sc->pc = 0; + if (bool_optimize(sc, if_test)) + { + opt_info *o = sc->opts[0], *o1 = sc->opts[sc->pc]; + if (int_optimize(sc, la)) + { + s7_pointer val = make_mutable_integer(sc, integer(slot_value(la_slot))); + slot_set_value(la_slot, val); + while (o->v[0].fb(o)) {integer(val) = o1->v[0].fi(o1);} + return(op_tc_z(sc, if_false)); + }}} + while (fx_call(sc, if_test) != sc->F) {slot_set_value(la_slot, fx_call(sc, la));} + return(op_tc_z(sc, if_false)); +} + +static s7_pointer fx_tc_if_a_la_z(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_IF_A_LA_Z); + op_tc_if_a_la_z(sc, arg, false); + return(sc->value); +} + +static s7_pointer fx_tc_cond_a_la_z(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_COND_A_LA_Z); + op_tc_if_a_la_z(sc, arg, true); + return(sc->value); +} + +typedef enum {TC_IF, TC_COND, TC_AND} tc_choice_t; + +static bool op_tc_if_a_z_laa(s7_scheme *sc, s7_pointer code, bool z_first, tc_choice_t cond) +{ + s7_pointer if_test, if_z, la, laa, laa_slot, la_slot = let_slots(sc->curlet); + s7_function tf; + if (cond == TC_IF) + { + if_test = cdr(code); + if_z = opt1_pair(if_test); /* if_z = (z_first) ? cdr(if_test) : cddr(if_test) */ + la = opt3_pair(if_test); /* la = (z_first) ? cdaddr(if_test) : cdadr(if_test) */ + } + else + { + if_test = cadr(code); + if_z = opt1_pair(cdr(code)); /* if_z = (z_first) ? cdr(if_test) : cdr(caddr(code)) */ + la = opt3_pair(cdr(code)); /* la = (z_first) ? cdr(cadaddr(code)) : cdadr(if_test) */ + } + laa = cdr(la); + laa_slot = next_slot(la_slot); +#if (!WITH_GMP) + if (!no_bool_opt(code)) + { + sc->pc = 0; + if (bool_optimize(sc, if_test)) + { + opt_info *o = sc->opts[0], *o1 = sc->opts[sc->pc], *o2; + int32_t start_pc = sc->pc; + if ((is_t_integer(slot_value(la_slot))) && + (is_t_integer(slot_value(laa_slot)))) + { + if (int_optimize(sc, la)) + { + o2 = sc->opts[sc->pc]; + if (int_optimize(sc, laa)) + { + s7_int (*fi1)(opt_info *o) = o1->v[0].fi; + s7_int (*fi2)(opt_info *o) = o2->v[0].fi; + bool (*fb)(opt_info *o) = o->v[0].fb; + s7_pointer val1 = make_mutable_integer(sc, integer(slot_value(la_slot))); + s7_pointer val2; + slot_set_value(la_slot, val1); + slot_set_value(laa_slot, val2 = make_mutable_integer(sc, integer(slot_value(laa_slot)))); + if ((z_first) && + ((fb == opt_b_ii_sc_lt) || (fb == opt_b_ii_sc_lt_0)) && + (fi1 == opt_i_ii_sc_sub)) + { + s7_int lim = o->v[2].i, m = o1->v[2].i; + s7_pointer slot1 = o->v[1].p, slot2 = o1->v[1].p; + while (integer(slot_value(slot1)) >= lim) + { + s7_int i1 = integer(slot_value(slot2)) - m; + integer(val2) = fi2(o2); + integer(val1) = i1; + }} + else + while (fb(o) != z_first) + { + s7_int i1 = fi1(o1); + integer(val2) = fi2(o2); + integer(val1) = i1; + } + return(op_tc_z(sc, if_z)); + }}} + + if ((is_t_real(slot_value(la_slot))) && + (is_t_real(slot_value(laa_slot)))) + { + sc->pc = start_pc; + if (float_optimize(sc, la)) + { + o2 = sc->opts[sc->pc]; + if (float_optimize(sc, laa)) + { + s7_double (*fd1)(opt_info *o) = o1->v[0].fd; + s7_double (*fd2)(opt_info *o) = o2->v[0].fd; + bool (*fb)(opt_info *o) = o->v[0].fb; + s7_pointer val1 = s7_make_mutable_real(sc, real(slot_value(la_slot))); + s7_pointer val2 = s7_make_mutable_real(sc, real(slot_value(laa_slot))); + slot_set_value(la_slot, val1); + slot_set_value(laa_slot, val2); + if ((z_first) && + (fb == opt_b_dd_sc_lt) && + (fd1 == opt_d_dd_sc_sub)) + { + s7_double lim = o->v[2].x; + s7_double m = o1->v[2].x; + s7_pointer slot1 = o->v[1].p; + s7_pointer slot2 = o1->v[1].p; + while (real(slot_value(slot1)) >= lim) + { + s7_double x1 = real(slot_value(slot2)) - m; + real(val2) = fd2(o2); + real(val1) = x1; + }} + else + while (fb(o) != z_first) + { + s7_double x1 = fd1(o1); + real(val2) = fd2(o2); + real(val1) = x1; + } + return(op_tc_z(sc, if_z)); + }}}} + set_no_bool_opt(code); + } +#endif + tf = fx_proc(if_test); + if_test = car(if_test); + if (z_first) + { + if ((fx_proc(la) == fx_cdr_t) && (fx_proc(laa) == fx_subtract_u1) && + (fn_proc(if_test) == g_num_eq_xi) && /* was also (fx_proc(if_test) == fx_num_eq_ui) but we cloberred if_test above */ + (is_pair(slot_value(la_slot))) && (is_t_integer(slot_value(laa_slot)))) + { /* list-tail ferchrissake */ + s7_int end = integer(caddr(if_test)); + s7_pointer lst = slot_value(la_slot); + for (s7_int start = integer(slot_value(laa_slot)); start > end; start--) + lst = cdr(lst); + slot_set_value(la_slot, lst); + } + else + while (tf(sc, if_test) == sc->F) + { + sc->rec_p1 = fx_call(sc, la); + slot_set_value(laa_slot, fx_call(sc, laa)); + slot_set_value(la_slot, sc->rec_p1); + }} + else + while (tf(sc, if_test) != sc->F) + { + sc->rec_p1 = fx_call(sc, la); + slot_set_value(laa_slot, fx_call(sc, laa)); + slot_set_value(la_slot, sc->rec_p1); + } + return(op_tc_z(sc, if_z)); +} + +static s7_pointer fx_tc_if_a_z_laa(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_IF_A_Z_LAA); + op_tc_if_a_z_laa(sc, arg, true, TC_IF); + sc->rec_p1 = sc->unused; + return(sc->value); +} + +static s7_pointer fx_tc_cond_a_z_laa(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_COND_A_Z_LAA); + op_tc_if_a_z_laa(sc, arg, true, TC_COND); + sc->rec_p1 = sc->unused; + return(sc->value); +} + +static s7_pointer fx_tc_if_a_laa_z(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_IF_A_LAA_Z); + op_tc_if_a_z_laa(sc, arg, false, TC_IF); + sc->rec_p1 = sc->unused; + return(sc->value); +} + +static s7_pointer fx_tc_cond_a_laa_z(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_COND_A_LAA_Z); + op_tc_if_a_z_laa(sc, arg, false, TC_COND); + sc->rec_p1 = sc->unused; + return(sc->value); +} + +static void op_tc_when_la(s7_scheme *sc, s7_pointer code) +{ + s7_pointer if_test = cadr(code), body = cddr(code), la_call, la, la_slot = let_slots(sc->curlet); + s7_function tf = fx_proc(cdr(code)); + for (la_call = body; is_pair(cdr(la_call)); la_call = cdr(la_call)); + la = cdar(la_call); + while (tf(sc, if_test) != sc->F) + { + for (s7_pointer p = body; p != la_call; p = cdr(p)) fx_call(sc, p); + slot_set_value(la_slot, fx_call(sc, la)); + } + sc->value = sc->unspecified; +} + +static void op_tc_when_laa(s7_scheme *sc, s7_pointer code) +{ + s7_pointer if_test = cadr(code), body = cddr(code), la, laa, laa_slot, la_call, la_slot = let_slots(sc->curlet); + s7_function tf = fx_proc(cdr(code)); + for (la_call = body; is_pair(cdr(la_call)); la_call = cdr(la_call)); + la = cdar(la_call); + laa = cdr(la); + laa_slot = next_slot(la_slot); + while (tf(sc, if_test) != sc->F) + { + for (s7_pointer p = body; p != la_call; p = cdr(p)) fx_call(sc, p); + sc->rec_p1 = fx_call(sc, la); + slot_set_value(laa_slot, fx_call(sc, laa)); + slot_set_value(la_slot, sc->rec_p1); + } + sc->rec_p1 = sc->unused; + sc->value = sc->unspecified; +} + +static void op_tc_when_l3a(s7_scheme *sc, s7_pointer code) +{ + s7_pointer if_test = cadr(code), body = cddr(code), la, laa, l3a, laa_slot, l3a_slot, la_call, la_slot = let_slots(sc->curlet); + s7_function tf = fx_proc(cdr(code)); + for (la_call = body; is_pair(cdr(la_call)); la_call = cdr(la_call)); + la = cdar(la_call); + laa = cdr(la); + l3a = cdr(laa); + laa_slot = next_slot(la_slot); + l3a_slot = next_slot(laa_slot); + while (tf(sc, if_test) != sc->F) + { + for (s7_pointer p = body; p != la_call; p = cdr(p)) fx_call(sc, p); + sc->rec_p1 = fx_call(sc, la); + sc->rec_p2 = fx_call(sc, laa); + slot_set_value(l3a_slot, fx_call(sc, l3a)); + slot_set_value(laa_slot, sc->rec_p2); + slot_set_value(la_slot, sc->rec_p1); + } + sc->rec_p1 = sc->unused; + sc->value = sc->unspecified; +} + +static bool op_tc_if_a_z_l3a(s7_scheme *sc, s7_pointer code, bool z_first) +{ + s7_pointer if_test = cdr(code), la_slot = let_slots(sc->curlet); + s7_pointer f_z = opt1_pair(if_test); /* if_z = (z_first) ? cdr(if_test) : cddr(if_test) */ + s7_pointer la = opt3_pair(if_test); /* la = (z_first) ? cdaddr(if_test) : cdadr(if_test) */ + s7_pointer laa = cdr(la); + s7_pointer l3a = cdr(laa); + s7_pointer laa_slot = next_slot(la_slot); + s7_pointer l3a_slot = next_slot(laa_slot); + s7_function tf = fx_proc(if_test); + if_test = car(if_test); + while ((tf(sc, if_test) == sc->F) == z_first) + { + sc->rec_p1 = fx_call(sc, la); + sc->rec_p2 = fx_call(sc, laa); + slot_set_value(l3a_slot, fx_call(sc, l3a)); + slot_set_value(laa_slot, sc->rec_p2); + slot_set_value(la_slot, sc->rec_p1); + } + return(op_tc_z(sc, f_z)); +} + +static s7_pointer fx_tc_if_a_z_l3a(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_IF_A_Z_L3A); + op_tc_if_a_z_l3a(sc, arg, true); + sc->rec_p1 = sc->unused; + sc->rec_p2 = sc->unused; + return(sc->value); +} + +static s7_pointer fx_tc_if_a_l3a_z(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_IF_A_L3A_Z); + op_tc_if_a_z_l3a(sc, arg, false); + sc->rec_p1 = sc->unused; + sc->rec_p2 = sc->unused; + return(sc->value); +} + +static bool op_tc_if_a_z_if_a_z_la(s7_scheme *sc, s7_pointer code, bool z_first, tc_choice_t cond) +{ + s7_pointer if_test, if_true, if_false, f_test, f_z, la, endp, la_slot = let_slots(sc->curlet); + bool tc_and = (cond == TC_AND); + if (cond != TC_COND) + { + if_test = cdr(code); + if_true = (!tc_and) ? cdr(if_test) : sc->F; + if_false = (!tc_and) ? cadr(if_true) : cadr(if_test); + f_test = cdr(if_false); + f_z = (z_first) ? cdr(f_test) : cddr(f_test); + la = (z_first) ? cdaddr(f_test) : cdadr(f_test); + } + else + { + if_test = cadr(code); /* code: (cond (a1 z1) (a2 z2|la) (else la|z2)) */ + if_true = cdr(if_test); + if_false = caddr(code); /* (a2 z2|la) */ + f_test = if_false; + f_z = (z_first) ? cdr(f_test) : cdr(cadddr(code)); + la = (z_first) ? cdadr(cadddr(code)) : cdadr(caddr(code)); + } +#if (!WITH_GMP) + if (is_t_integer(slot_value(la_slot))) + { + opt_info *o = sc->opts[0]; + sc->pc = 0; + if (bool_optimize_nw(sc, if_test)) + { + opt_info *o1 = sc->opts[sc->pc]; + if (bool_optimize_nw(sc, f_test)) + { + opt_info *o2 = sc->opts[sc->pc]; + if (int_optimize(sc, la)) + { + s7_pointer val = make_mutable_integer(sc, integer(slot_value(la_slot))); + slot_set_value(la_slot, val); + if (tc_and) + while (true) + { + if (!o->v[0].fb(o)) {sc->value = sc->F; return(true);} + if (o1->v[0].fb(o1) == z_first) {endp = f_z; break;} + integer(val) = o2->v[0].fi(o2); + } + else + while (true) + { + if (o->v[0].fb(o)) {endp = if_true; break;} + if (o1->v[0].fb(o1) == z_first) {endp = f_z; break;} + integer(val) = o2->v[0].fi(o2); + } + return(op_tc_z(sc, endp)); + }}}} +#endif + while (true) + { + if ((fx_call(sc, if_test) == sc->F) == tc_and) {if (tc_and) {sc->value = sc->F; return(true);} else {endp = if_true; break;}} + if ((fx_call(sc, f_test) == sc->F) != z_first) {endp = f_z; break;} + slot_set_value(la_slot, fx_call(sc, la)); + } + return(op_tc_z(sc, endp)); +} + +static s7_pointer fx_tc_if_a_z_if_a_z_la(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_IF_A_Z_IF_A_Z_LA); + op_tc_if_a_z_if_a_z_la(sc, arg, true, TC_IF); + return(sc->value); +} + +static s7_pointer fx_tc_if_a_z_if_a_la_z(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_IF_A_Z_IF_A_LA_Z); + op_tc_if_a_z_if_a_z_la(sc, arg, false, TC_IF); + return(sc->value); +} + +static s7_pointer fx_tc_cond_a_z_a_z_la(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_COND_A_Z_A_Z_LA); + op_tc_if_a_z_if_a_z_la(sc, arg, true, TC_COND); + return(sc->value); +} + +static s7_pointer fx_tc_cond_a_z_a_la_z(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_COND_A_Z_A_LA_Z); + op_tc_if_a_z_if_a_z_la(sc, arg, false, TC_COND); + return(sc->value); +} + +static s7_pointer fx_tc_and_a_if_a_z_la(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_AND_A_IF_A_Z_LA); + op_tc_if_a_z_if_a_z_la(sc, arg, true, TC_AND); + return(sc->value); +} + +static s7_pointer fx_tc_and_a_if_a_la_z(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_AND_A_IF_A_LA_Z); + op_tc_if_a_z_if_a_z_la(sc, arg, false, TC_AND); + return(sc->value); +} + +static bool op_tc_if_a_z_if_a_z_laa(s7_scheme *sc, bool cond, s7_pointer code) +{ + s7_pointer if_false, f_test, f_true, la, laa, laa_slot, endp, slot1, la_slot = let_slots(sc->curlet); + s7_pointer if_test = (cond) ? cadr(code) : cdr(code); + s7_pointer if_true = cdr(if_test); + if (!cond) if_false = cadr(if_true); + f_test = (cond) ? caddr(code) : cdr(if_false); + f_true = cdr(f_test); + la = (cond) ? opt3_pair(code) : cdadr(f_true); /* cdadr(cadddr(code)) */ + laa = cdr(la); + laa_slot = next_slot(la_slot); + slot1 = (fx_proc(if_test) == fx_is_null_t) ? la_slot : ((fx_proc(if_test) == fx_is_null_u) ? laa_slot : NULL); + if (slot1) + { + if ((slot1 == laa_slot) && (fx_proc(f_test) == fx_is_null_t) && (fx_proc(la) == fx_cdr_t) && (fx_proc(laa) == fx_cdr_u) && + (is_boolean(car(if_true))) && (is_boolean(car(f_true)))) + { + s7_pointer la_val = slot_value(la_slot), laa_val = slot_value(laa_slot); + while (true) + { + if (is_null(laa_val)) {sc->value = car(if_true); return(true);} + if (is_null(la_val)) {sc->value = car(f_true); return(true);} + la_val = cdr(la_val); + laa_val = cdr(laa_val); + }} + while (true) + { + if (is_null(slot_value(slot1))) {endp = if_true; break;} + if (fx_call(sc, f_test) != sc->F) {endp = f_true; break;} + sc->rec_p1 = fx_call(sc, la); + slot_set_value(laa_slot, fx_call(sc, laa)); + slot_set_value(la_slot, sc->rec_p1); + }} + else + while (true) + { + if (fx_call(sc, if_test) != sc->F) {endp = if_true; break;} + if (fx_call(sc, f_test) != sc->F) {endp = f_true; break;} + sc->rec_p1 = fx_call(sc, la); + slot_set_value(laa_slot, fx_call(sc, laa)); + slot_set_value(la_slot, sc->rec_p1); + } + return(op_tc_z(sc, endp)); +} + +static s7_pointer fx_tc_if_a_z_if_a_z_laa(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_IF_A_Z_IF_A_Z_LAA); + op_tc_if_a_z_if_a_z_laa(sc, false, arg); + sc->rec_p1 = sc->unused; + return(sc->value); +} + +static s7_pointer fx_tc_cond_a_z_a_z_laa(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_COND_A_Z_A_Z_LAA); + op_tc_if_a_z_if_a_z_laa(sc, true, arg); + sc->rec_p1 = sc->unused; + return(sc->value); +} + +static bool op_tc_if_a_z_if_a_laa_z(s7_scheme *sc, bool cond, s7_pointer code) +{ + s7_pointer if_false, f_test, f_true, f_false, la, laa, laa_slot, endp, la_slot = let_slots(sc->curlet); + s7_pointer if_test = (cond) ? cadr(code) : cdr(code); + s7_pointer if_true = cdr(if_test); + if (!cond) if_false = cadr(if_true); + f_test = (cond) ? caddr(code) : cdr(if_false); + f_true = cdr(f_test); + f_false = (cond) ? cdr(cadddr(code)) : cdr(f_true); + la = (cond) ? opt3_pair(code) : cdar(f_true); /* cdadr(caddr(code)) */ + laa = cdr(la); + laa_slot = next_slot(la_slot); + while (true) + { + if (fx_call(sc, if_test) != sc->F) {endp = if_true; break;} + if (fx_call(sc, f_test) == sc->F) {endp = f_false; break;} + sc->rec_p1 = fx_call(sc, la); + slot_set_value(laa_slot, fx_call(sc, laa)); + slot_set_value(la_slot, sc->rec_p1); + } + return(op_tc_z(sc, endp)); +} + +static s7_pointer fx_tc_if_a_z_if_a_laa_z(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_IF_A_Z_IF_A_LAA_Z); + op_tc_if_a_z_if_a_laa_z(sc, false, arg); + sc->rec_p1 = sc->unused; + return(sc->value); +} + +static s7_pointer fx_tc_cond_a_z_a_laa_z(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_COND_A_Z_A_LAA_Z); + op_tc_if_a_z_if_a_laa_z(sc, true, arg); + sc->rec_p1 = sc->unused; + return(sc->value); +} + +static bool op_tc_if_a_z_if_a_l3a_l3a(s7_scheme *sc, s7_pointer code) +{ + s7_pointer if_test = cdr(code); + s7_pointer endp, la_slot = let_slots(sc->curlet); + s7_pointer if_true = cdr(if_test); + s7_pointer if_false = cadr(if_true); + s7_pointer f_test = cdr(if_false); + s7_pointer f_true = cdr(f_test); + s7_pointer f_false = cdr(f_true); + s7_pointer la1 = cdar(f_true); + s7_pointer la2 = cdar(f_false); + s7_pointer laa1 = cdr(la1); + s7_pointer laa2 = cdr(la2); + s7_pointer laa_slot = next_slot(la_slot); + s7_pointer l3a1 = cdr(laa1); + s7_pointer l3a2 = cdr(laa2); + s7_pointer l3a_slot = next_slot(laa_slot); + while (true) + { + if (fx_call(sc, if_test) != sc->F) {endp = if_true; break;} + if (fx_call(sc, f_test) != sc->F) + { + sc->rec_p1 = fx_call(sc, la1); + sc->rec_p2 = fx_call(sc, laa1); + slot_set_value(l3a_slot, fx_call(sc, l3a1)); + } + else + { + sc->rec_p1 = fx_call(sc, la2); + sc->rec_p2 = fx_call(sc, laa2); + slot_set_value(l3a_slot, fx_call(sc, l3a2)); + } + slot_set_value(laa_slot, sc->rec_p2); + slot_set_value(la_slot, sc->rec_p1); + } + return(op_tc_z(sc, endp)); +} + +static s7_pointer fx_tc_if_a_z_if_a_l3a_l3a(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_IF_A_Z_IF_A_L3A_L3A); + op_tc_if_a_z_if_a_l3a_l3a(sc, arg); + sc->rec_p1 = sc->unused; + sc->rec_p2 = sc->unused; + return(sc->value); +} + +static bool op_tc_let_if_a_z_la(s7_scheme *sc, s7_pointer code) +{ + s7_pointer body = caddr(code); + s7_pointer outer_let = sc->curlet; + s7_pointer la_slot = let_slots(outer_let); + s7_pointer if_test = cdr(body); + s7_pointer if_true = cddr(body); + s7_pointer if_false = cadddr(body); + s7_pointer la = cdr(if_false); + s7_pointer let_var = caadr(code); + s7_pointer inner_let = make_let_with_slot(sc, sc->curlet, car(let_var), fx_call(sc, cdr(let_var))); + s7_pointer let_slot = let_slots(inner_let); + sc->curlet = inner_let; + s7_gc_protect_via_stack(sc, inner_let); + let_var = cdr(let_var); + + while (fx_call(sc, if_test) == sc->F) + { + slot_set_value(la_slot, fx_call(sc, la)); + set_curlet(sc, outer_let); + slot_set_value(let_slot, fx_call(sc, let_var)); + set_curlet(sc, inner_let); + } + unstack(sc); + if (!op_tc_z(sc, if_true)) + return(false); + free_cell(sc, let_slots(inner_let)); + free_cell(sc, inner_let); + return(true); +} + +static s7_pointer fx_tc_let_if_a_z_la(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_LET_IF_A_Z_LA); + op_tc_let_if_a_z_la(sc, arg); + return(sc->value); +} + +static bool op_tc_let_if_a_z_laa(s7_scheme *sc, s7_pointer code) +{ + s7_pointer body = caddr(code); + s7_pointer outer_let = sc->curlet; + s7_pointer la_slot = let_slots(outer_let); + s7_pointer laa_slot = next_slot(la_slot); + s7_pointer if_test = cdr(body); + s7_pointer if_true = cddr(body); + s7_pointer if_false = cadddr(body); + s7_pointer la = cdr(if_false); + s7_pointer laa = cddr(if_false); + s7_pointer let_var = caadr(code); + s7_pointer inner_let = make_let_with_slot(sc, sc->curlet, car(let_var), fx_call(sc, cdr(let_var))); + s7_pointer let_slot = let_slots(inner_let); + sc->curlet = inner_let; + s7_gc_protect_via_stack(sc, inner_let); + let_var = cdr(let_var); +#if (!WITH_GMP) + if (!no_bool_opt(code)) + { + sc->pc = 0; + if (bool_optimize(sc, if_test)) + { + opt_info *o = sc->opts[0]; + opt_info *o1 = sc->opts[sc->pc], *o2, *o3; + if ((is_t_integer(slot_value(la_slot))) && + (is_t_integer(slot_value(laa_slot)))) + { + if (int_optimize(sc, la)) + { + o2 = sc->opts[sc->pc]; + if (int_optimize(sc, laa)) + { + o3 = sc->opts[sc->pc]; + set_curlet(sc, outer_let); + if (int_optimize(sc, let_var)) + { + s7_pointer val1 = make_mutable_integer(sc, integer(slot_value(la_slot))); + s7_pointer val2 = make_mutable_integer(sc, integer(slot_value(laa_slot))); + s7_pointer val3 = make_mutable_integer(sc, integer(slot_value(let_slot))); + set_curlet(sc, inner_let); + slot_set_value(la_slot, val1); + slot_set_value(laa_slot, val2); + slot_set_value(let_slot, val3); + while (!(o->v[0].fb(o))) + { + s7_int i1 = o1->v[0].fi(o1); + integer(val2) = o2->v[0].fi(o2); + integer(val1) = i1; + integer(val3) = o3->v[0].fi(o3); + } + unstack(sc); + if (!op_tc_z(sc, if_true)) /* sc->inner_let in effect here since it was the last set above */ + return(false); + free_cell(sc, let_slots(inner_let)); + free_cell(sc, inner_let); + return(true); + }}}}} + set_no_bool_opt(code); + } +#endif + while (fx_call(sc, if_test) == sc->F) + { + sc->rec_p1 = fx_call(sc, la); + slot_set_value(laa_slot, fx_call(sc, laa)); + slot_set_value(la_slot, sc->rec_p1); + set_curlet(sc, outer_let); + slot_set_value(let_slot, fx_call(sc, let_var)); + set_curlet(sc, inner_let); + } + unstack(sc); + if (!op_tc_z(sc, if_true)) + return(false); + free_cell(sc, let_slots(inner_let)); + free_cell(sc, inner_let); + return(true); +} + +static s7_pointer fx_tc_let_if_a_z_laa(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_LET_IF_A_Z_LAA); + op_tc_let_if_a_z_laa(sc, arg); + sc->rec_p1 = sc->unused; + return(sc->value); +} + +static void op_tc_let_when_laa(s7_scheme *sc, bool when, s7_pointer code) +{ + s7_pointer p, body = caddr(code), la, laa, let_var = caadr(code), outer_let = sc->curlet; + s7_pointer if_test = cdr(body); + s7_pointer if_true = cddr(body); + s7_pointer inner_let = make_let_with_slot(sc, sc->curlet, car(let_var), fx_call(sc, cdr(let_var))); + s7_pointer let_slot = let_slots(inner_let); + sc->curlet = inner_let; + s7_gc_protect_via_stack(sc, inner_let); + let_var = cdr(let_var); + + for (p = if_true; is_pair(cdr(p)); p = cdr(p)); + la = cdar(p); + laa = cddar(p); + if ((car(la) == slot_symbol(let_slots(outer_let))) && + (car(laa) == slot_symbol(next_slot(let_slots(outer_let))))) + { + if ((cdr(if_true) == p) && (!when)) + { + s7_pointer a1 = slot_value(let_slots(outer_let)); + s7_pointer a2 = slot_value(next_slot(let_slots(outer_let))); + if ((is_input_port(a1)) && (is_output_port(a2)) && (is_string_port(a1)) && (is_file_port(a2)) && + (!port_is_closed(a1)) && (!port_is_closed(a2)) && (fx_proc(if_true) == fx_c_tU_direct) && + (fx_proc(let_var) == fx_c_t_direct) && (((s7_p_pp_t)opt3_direct(cdar(if_true))) == write_char_p_pp) && + (((s7_p_p_t)opt2_direct(cdar(let_var))) == read_char_p_p) && (fx_proc(if_test) == fx_is_eof_t)) + { + int32_t c = (int32_t)s7_character(slot_value(let_slots(inner_let))); + a1 = slot_value(let_slots(outer_let)); + a2 = slot_value(next_slot(let_slots(outer_let))); + while (c != EOF) + { + inline_file_write_char(sc, (uint8_t)c, a2); + c = string_read_char(sc, a1); + }} + else + while (fx_call(sc, if_test) == sc->F) + { + fx_call(sc, if_true); + set_curlet(sc, outer_let); + slot_set_value(let_slot, fx_call(sc, let_var)); + set_curlet(sc, inner_let); + }} + else + while (true) + { + p = fx_call(sc, if_test); + if (when) {if (p == sc->F) break;} else {if (p != sc->F) break;} + for (p = if_true; is_pair(cdr(p)); p = cdr(p)) + fx_call(sc, p); + set_curlet(sc, outer_let); + slot_set_value(let_slot, fx_call(sc, let_var)); + set_curlet(sc, inner_let); + }} + else + { + s7_pointer la_slot = let_slots(outer_let); + s7_pointer laa_slot = next_slot(la_slot); + while (true) + { + p = fx_call(sc, if_test); + if (when) {if (p == sc->F) break;} else {if (p != sc->F) break;} + for (p = if_true; is_pair(cdr(p)); p = cdr(p)) + fx_call(sc, p); + sc->rec_p1 = fx_call(sc, la); + slot_set_value(laa_slot, fx_call(sc, laa)); + slot_set_value(la_slot, sc->rec_p1); + set_curlet(sc, outer_let); + slot_set_value(let_slot, fx_call(sc, let_var)); + set_curlet(sc, inner_let); + }} + unstack(sc); + free_cell(sc, let_slots(inner_let)); + free_cell(sc, inner_let); + sc->value = sc->unspecified; +} + +static s7_pointer fx_tc_let_when_laa(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_LET_WHEN_LAA); + op_tc_let_when_laa(sc, true, arg); + sc->rec_p1 = sc->unused; + return(sc->value); +} + +static s7_pointer fx_tc_let_unless_laa(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_LET_WHEN_LAA); + op_tc_let_when_laa(sc, false, arg); + sc->rec_p1 = sc->unused; + return(sc->value); +} + +static bool op_tc_if_a_z_let_if_a_z_laa(s7_scheme *sc, s7_pointer code) +{ + s7_pointer if1_test = cdr(code), endp, outer_let = sc->curlet, slot, var, la_slot = let_slots(sc->curlet); + s7_pointer if1_true = cdr(if1_test); /* cddr(code) */ + s7_pointer let_expr = cadr(if1_true); /* cadddr(code) */ + s7_pointer let_vars = cadr(let_expr); + s7_pointer if2 = caddr(let_expr); + s7_pointer if2_test = cdr(if2); + s7_pointer if2_true = cdr(if2_test); /* cddr(if2) */ + s7_pointer la = cdadr(if2_true); /* cdr(cadddr(if2)) */ + s7_pointer laa = cdr(la); + s7_pointer laa_slot = next_slot(la_slot); + s7_pointer inner_let = inline_make_let(sc, sc->curlet); + + s7_gc_protect_via_stack(sc, inner_let); + slot = make_slot(sc, caar(let_vars), sc->F); + slot_set_next(slot, slot_end); + let_set_slots(inner_let, slot); + symbol_set_local_slot_unincremented(caar(let_vars), let_id(inner_let), slot); + for (var = cdr(let_vars); is_pair(var); var = cdr(var)) + slot = inline_add_slot_at_end(sc, let_id(inner_let), slot, caar(var), sc->F); + + while (true) + { + if (fx_call(sc, if1_test) != sc->F) {endp = if1_true; break;} + slot = let_slots(inner_let); + slot_set_value(slot, fx_call(sc, cdar(let_vars))); + set_curlet(sc, inner_let); + for (var = cdr(let_vars), slot = next_slot(slot); is_pair(var); var = cdr(var), slot = next_slot(slot)) + slot_set_value(slot, fx_call(sc, cdar(var))); + + if (fx_call(sc, if2_test) != sc->F) {endp = if2_true; break;} + sc->rec_p1 = fx_call(sc, la); + slot_set_value(laa_slot, fx_call(sc, laa)); + slot_set_value(la_slot, sc->rec_p1); + set_curlet(sc, outer_let); + } + sc->rec_p1 = sc->unused; + unstack(sc); + if (!op_tc_z(sc, endp)) /* might refer to inner_let slots */ + return(false); + free_cell(sc, let_slots(inner_let)); /* true = has_fx, so we should be done with the let */ + free_cell(sc, inner_let); + return(true); +} + +static bool op_tc_let_cond(s7_scheme *sc, s7_pointer code) +{ + bool read_case; + s7_pointer result; + s7_pointer outer_let = sc->curlet; + s7_pointer slots = let_slots(outer_let); + s7_pointer cond_body = cdaddr(code); /* code here == body in check_tc */ + s7_pointer let_var = caadr(code); + s7_function letf = fx_proc(cdr(let_var)); + s7_pointer inner_let = make_let_with_slot(sc, sc->curlet, car(let_var), fx_call(sc, cdr(let_var))); + s7_pointer let_slot = let_slots(inner_let); + sc->curlet = inner_let; + s7_gc_protect_via_stack(sc, inner_let); + let_var = cadr(let_var); + if ((letf == fx_c_s_direct) && /* an experiment */ + (symbol_id(cadr(let_var)) != let_id(outer_let))) /* i.e. not an argument to the recursive function, and not set! (safe closure body) */ + { + letf = (s7_p_p_t)opt2_direct(cdr(let_var)); + let_var = lookup(sc, cadr(let_var)); + } + /* in the named let no-var case slots may contain the let name (it's the funclet) */ + + if (opt3_arglen(cdr(code)) == 0) /* (loop) etc -- no args */ + while (true) + { + for (s7_pointer p = cond_body; is_pair(p); p = cdr(p)) + if (fx_call(sc, car(p)) != sc->F) + { + result = cdar(p); + if (!has_tc(result)) + goto TC_LET_COND_DONE; + set_curlet(sc, outer_let); + slot_set_value(let_slot, letf(sc, let_var)); + set_curlet(sc, inner_let); + break; + }} + else + if (opt3_arglen(cdr(code)) == 1) + while (true) + for (s7_pointer p = cond_body; is_pair(p); p = cdr(p)) + if (fx_call(sc, car(p)) != sc->F) + { + result = cdar(p); + if (!has_tc(result)) + goto TC_LET_COND_DONE; + slot_set_value(slots, fx_call(sc, cdar(result))); /* arg to recursion */ + set_curlet(sc, outer_let); + slot_set_value(let_slot, letf(sc, let_var)); /* inner let var */ + set_curlet(sc, inner_let); + break; + } + + let_set_has_pending_value(outer_let); + read_case = ((letf == read_char_p_p) && (is_input_port(let_var)) && (is_string_port(let_var)) && (!port_is_closed(let_var))); + while (true) + for (s7_pointer p = cond_body; is_pair(p); p = cdr(p)) + if (fx_call(sc, car(p)) != sc->F) + { + result = cdar(p); + if (!has_tc(result)) + goto TC_LET_COND_DONE; + for (s7_pointer slot = slots, arg = cdar(result); is_pair(arg); slot = next_slot(slot), arg = cdr(arg)) + slot_simply_set_pending_value(slot, fx_call(sc, arg)); + for (s7_pointer slot = slots; tis_slot(slot); slot = next_slot(slot)) /* using two swapping lets instead is slightly slower */ + slot_set_value(slot, slot_pending_value(slot)); + + if (read_case) + slot_set_value(let_slot, chars[string_read_char(sc, let_var)]); + else + { + set_curlet(sc, outer_let); + slot_set_value(let_slot, letf(sc, let_var)); + set_curlet(sc, inner_let); + } + break; + } + let_clear_has_pending_value(outer_let); + + TC_LET_COND_DONE: + unstack(sc); + if (has_fx(result)) + { + sc->value = fx_call(sc, result); + free_cell(sc, let_slots(inner_let)); + free_cell(sc, inner_let); + return(true); + } + sc->code = car(result); + return(false); +} + +static s7_pointer fx_tc_let_cond(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_LET_COND); + op_tc_let_cond(sc, arg); + return(sc->value); +} + +static bool op_tc_cond_a_z_a_laa_laa(s7_scheme *sc, s7_pointer code) +{ + s7_pointer c1 = cadr(code), c2 = caddr(code), la_slot = let_slots(sc->curlet); + s7_pointer la1 = cdadr(c2); + s7_pointer laa1 = cddadr(c2); + s7_pointer c3 = opt3_pair(code); /* cadr(cadddr(code)) = cadr(else_clause) */ + s7_pointer la2 = cdr(c3); + s7_pointer laa2 = cddr(c3); + s7_pointer laa_slot = next_slot(la_slot); + while (true) + { + if (fx_call(sc, c1) != sc->F) {c1 = cdr(c1); break;} + if (fx_call(sc, c2) != sc->F) + { + sc->rec_p1 = fx_call(sc, la1); + slot_set_value(laa_slot, fx_call(sc, laa1)); + } + else + { + sc->rec_p1 = fx_call(sc, la2); + slot_set_value(laa_slot, fx_call(sc, laa2)); + } + slot_set_value(la_slot, sc->rec_p1); + } + return(op_tc_z(sc, c1)); +} + +static s7_pointer fx_tc_cond_a_z_a_laa_laa(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_TC_COND_A_Z_A_LAA_LAA); + op_tc_cond_a_z_a_laa_laa(sc, arg); + sc->rec_p1 = sc->unused; + return(sc->value); +} + + +#define RECUR_INITIAL_STACK_SIZE 1024 + +static void recur_resize(s7_scheme *sc) +{ + s7_pointer stack = sc->rec_stack; + block_t *ob, *nb; + vector_length(stack) = sc->rec_len * 2; + ob = vector_block(stack); + nb = reallocate(sc, ob, vector_length(stack) * sizeof(s7_pointer)); + block_info(nb) = NULL; + vector_block(stack) = nb; + vector_elements(stack) = (s7_pointer *)block_data(nb); /* GC looks only at elements within sc->rec_loc */ + sc->rec_len = vector_length(stack); + sc->rec_els = vector_elements(stack); +} + +static inline void recur_push(s7_scheme *sc, s7_pointer value) +{ + if (sc->rec_loc == sc->rec_len) + recur_resize(sc); + sc->rec_els[sc->rec_loc] = value; + sc->rec_loc++; +} + +static inline void recur_push_unchecked(s7_scheme *sc, s7_pointer value) {sc->rec_els[sc->rec_loc++] = value;} +static s7_pointer recur_pop(s7_scheme *sc) {return(sc->rec_els[--sc->rec_loc]);} +static s7_pointer recur_ref(s7_scheme *sc, s7_int loc) {return(sc->rec_els[sc->rec_loc - loc]);} + +static s7_pointer recur_pop2(s7_scheme *sc) +{ + sc->rec_loc -= 2; + return(sc->rec_els[sc->rec_loc + 1]); +} + +static s7_pointer recur_swap(s7_scheme *sc, s7_pointer value) +{ + s7_pointer res = sc->rec_els[sc->rec_loc - 1]; + sc->rec_els[sc->rec_loc - 1] = value; + return(res); +} + +static s7_pointer recur_make_stack(s7_scheme *sc) +{ + if (!sc->rec_stack) + { + sc->rec_stack = make_simple_vector(sc, RECUR_INITIAL_STACK_SIZE); + sc->rec_els = vector_elements(sc->rec_stack); + sc->rec_len = RECUR_INITIAL_STACK_SIZE; + } + sc->rec_loc = 0; + return(sc->rec_stack); +} + +static void rec_set_test(s7_scheme *sc, s7_pointer p) +{ + sc->rec_testp = p; + sc->rec_testf = fx_proc(sc->rec_testp); + sc->rec_testp = car(sc->rec_testp); +} + +static void rec_set_res(s7_scheme *sc, s7_pointer p) +{ + sc->rec_resp = p; + sc->rec_resf = fx_proc(sc->rec_resp); + sc->rec_resp = car(sc->rec_resp); +} + +static void rec_set_f1(s7_scheme *sc, s7_pointer p) +{ + sc->rec_f1p = p; + sc->rec_f1f = fx_proc(sc->rec_f1p); + sc->rec_f1p = car(sc->rec_f1p); +} + +static void rec_set_f2(s7_scheme *sc, s7_pointer p) +{ + sc->rec_f2p = p; + sc->rec_f2f = fx_proc(sc->rec_f2p); + sc->rec_f2p = car(sc->rec_f2p); +} + +static void rec_set_f3(s7_scheme *sc, s7_pointer p) +{ + sc->rec_f3p = p; + sc->rec_f3f = fx_proc(sc->rec_f3p); + sc->rec_f3p = car(sc->rec_f3p); +} + +static void rec_set_f4(s7_scheme *sc, s7_pointer p) +{ + sc->rec_f4p = p; + sc->rec_f4f = fx_proc(sc->rec_f4p); + sc->rec_f4p = car(sc->rec_f4p); +} + +static void rec_set_f5(s7_scheme *sc, s7_pointer p) +{ + sc->rec_f5p = p; + sc->rec_f5f = fx_proc(sc->rec_f5p); + sc->rec_f5p = car(sc->rec_f5p); +} + +static void rec_set_f6(s7_scheme *sc, s7_pointer p) +{ + sc->rec_f6p = p; + sc->rec_f6f = fx_proc(sc->rec_f6p); + sc->rec_f6p = car(sc->rec_f6p); +} + +/* -------- if_a_a_opa_laq and if_a_opa_laq_a -------- */ +typedef enum {OPT_PTR, OPT_INT, OPT_DBL, OPT_INT_0} opt_pid_t; + +static opt_pid_t opinit_if_a_a_opa_laq(s7_scheme *sc, bool a_op, bool la_op, s7_pointer code) +{ + s7_pointer caller = opt3_pair(code); /* false_p in check_recur */ +#if (!WITH_GMP) + s7_pointer c_op = car(caller); + if ((is_symbol(c_op)) && + ((is_global(c_op)) || + ((is_slot(global_slot(c_op))) && + (s7_slot(sc, c_op) == global_slot(c_op))))) + { + s7_pointer s_func = global_value(c_op), slot = let_slots(sc->curlet); + if (is_c_function(s_func)) + { + sc->pc = 0; + sc->rec_test_o = sc->opts[0]; + if (bool_optimize(sc, cdr(code))) + { + int32_t start_pc = sc->pc; + sc->rec_result_o = sc->opts[start_pc]; + if (is_t_integer(slot_value(slot))) + { + sc->rec_i_ii_f = s7_i_ii_function(s_func); + if ((sc->rec_i_ii_f) && + (int_optimize(sc, (a_op) ? cddr(code) : cdddr(code)))) + { + sc->rec_a1_o = sc->opts[sc->pc]; + if (int_optimize(sc, (la_op) ? cdr(caller) : cddr(caller))) /* cdadr? */ + { + sc->rec_a2_o = sc->opts[sc->pc]; + if (int_optimize(sc, cdr(opt3_pair(caller)))) + { + sc->rec_val1 = make_mutable_integer(sc, integer(slot_value(slot))); + slot_set_value(slot, sc->rec_val1); + return(OPT_INT); + }}}}}}} +#endif + rec_set_test(sc, cdr(code)); + rec_set_res(sc, (a_op) ? cddr(code) : cdddr(code)); + rec_set_f1(sc, (la_op) ? cdr(caller) : cddr(caller)); + rec_set_f2(sc, cdr(opt3_pair(caller))); + sc->rec_slot1 = let_slots(sc->curlet); + sc->rec_fn = fn_proc(caller); + return(OPT_PTR); +} + +static s7_int oprec_i_if_a_a_opa_laq(s7_scheme *sc) +{ + s7_int i1; + if (sc->rec_test_o->v[0].fb(sc->rec_test_o)) return(sc->rec_result_o->v[0].fi(sc->rec_result_o)); + i1 = sc->rec_a1_o->v[0].fi(sc->rec_a1_o); + integer(sc->rec_val1) = sc->rec_a2_o->v[0].fi(sc->rec_a2_o); + return(sc->rec_i_ii_f(i1, oprec_i_if_a_a_opa_laq(sc))); +} + +static s7_int oprec_i_if_a_opa_laq_a(s7_scheme *sc) +{ + s7_int i1; + if (!sc->rec_test_o->v[0].fb(sc->rec_test_o)) return(sc->rec_result_o->v[0].fi(sc->rec_result_o)); + i1 = sc->rec_a1_o->v[0].fi(sc->rec_a1_o); + integer(sc->rec_val1) = sc->rec_a2_o->v[0].fi(sc->rec_a2_o); + return(sc->rec_i_ii_f(i1, oprec_i_if_a_opa_laq_a(sc))); +} + +static s7_pointer oprec_if_a_a_opa_laq(s7_scheme *sc) +{ + if (sc->rec_testf(sc, sc->rec_testp) != sc->F) return(sc->rec_resf(sc, sc->rec_resp)); + recur_push(sc, sc->rec_f1f(sc, sc->rec_f1p)); + slot_set_value(sc->rec_slot1, sc->rec_f2f(sc, sc->rec_f2p)); + + if (sc->rec_testf(sc, sc->rec_testp) != sc->F) + set_car(sc->t2_2, sc->rec_resf(sc, sc->rec_resp)); + else + { + recur_push(sc, sc->rec_f1f(sc, sc->rec_f1p)); + slot_set_value(sc->rec_slot1, sc->rec_f2f(sc, sc->rec_f2p)); + set_car(sc->t2_2, oprec_if_a_a_opa_laq(sc)); + set_car(sc->t2_1, recur_pop(sc)); + set_car(sc->t2_2, sc->rec_fn(sc, sc->t2_1)); + } + set_car(sc->t2_1, recur_pop(sc)); + return(sc->rec_fn(sc, sc->t2_1)); +} + +static s7_pointer oprec_if_a_a_opla_aq(s7_scheme *sc) +{ + if (sc->rec_testf(sc, sc->rec_testp) != sc->F) return(sc->rec_resf(sc, sc->rec_resp)); + recur_push(sc, sc->rec_f1f(sc, sc->rec_f1p)); + slot_set_value(sc->rec_slot1, sc->rec_f2f(sc, sc->rec_f2p)); + set_car(sc->t2_1, oprec_if_a_a_opla_aq(sc)); + set_car(sc->t2_2, recur_pop(sc)); + return(sc->rec_fn(sc, sc->t2_1)); +} + +static s7_pointer oprec_if_a_opa_laq_a(s7_scheme *sc) +{ + if (sc->rec_testf(sc, sc->rec_testp) == sc->F) return(sc->rec_resf(sc, sc->rec_resp)); + recur_push(sc, sc->rec_f1f(sc, sc->rec_f1p)); + slot_set_value(sc->rec_slot1, sc->rec_f2f(sc, sc->rec_f2p)); + + if (sc->rec_testf(sc, sc->rec_testp) == sc->F) + set_car(sc->t2_2, sc->rec_resf(sc, sc->rec_resp)); + else + { + recur_push(sc, sc->rec_f1f(sc, sc->rec_f1p)); + slot_set_value(sc->rec_slot1, sc->rec_f2f(sc, sc->rec_f2p)); + set_car(sc->t2_2, oprec_if_a_opa_laq_a(sc)); + set_car(sc->t2_1, recur_pop(sc)); + set_car(sc->t2_2, sc->rec_fn(sc, sc->t2_1)); + } + set_car(sc->t2_1, recur_pop(sc)); + return(sc->rec_fn(sc, sc->t2_1)); +} + +static s7_pointer oprec_if_a_opla_aq_a(s7_scheme *sc) +{ + if (sc->rec_testf(sc, sc->rec_testp) == sc->F) return(sc->rec_resf(sc, sc->rec_resp)); + recur_push(sc, sc->rec_f1f(sc, sc->rec_f1p)); + slot_set_value(sc->rec_slot1, sc->rec_f2f(sc, sc->rec_f2p)); + set_car(sc->t2_1, oprec_if_a_opla_aq_a(sc)); + set_car(sc->t2_2, recur_pop(sc)); + return(sc->rec_fn(sc, sc->t2_1)); +} + +static void wrap_recur_if_a_a_opa_laq(s7_scheme *sc, bool a_op, bool la_op) +{ + opt_pid_t choice = opinit_if_a_a_opa_laq(sc, a_op, la_op, sc->code); + tick_tc(sc, sc->cur_op); + if (choice == OPT_INT) + sc->value = make_integer(sc, (a_op) ? oprec_i_if_a_a_opa_laq(sc) : oprec_i_if_a_opa_laq_a(sc)); + else + { + sc->rec_stack = recur_make_stack(sc); + if (a_op) + sc->value = (la_op) ? oprec_if_a_a_opa_laq(sc) : oprec_if_a_a_opla_aq(sc); + else sc->value = (la_op) ? oprec_if_a_opa_laq_a(sc) : oprec_if_a_opla_aq_a(sc); + sc->rec_loc = 0; + } +} + +static s7_pointer fx_recur_if_a_a_opa_laq(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_RECUR_IF_A_A_opA_LAq); + if (opinit_if_a_a_opa_laq(sc, true, true, arg) == OPT_INT) + sc->value = make_integer(sc, oprec_i_if_a_a_opa_laq(sc)); + else + { + sc->rec_stack = recur_make_stack(sc); + sc->value = oprec_if_a_a_opa_laq(sc); + sc->rec_loc = 0; + } + return(sc->value); +} + +static s7_pointer fx_recur_if_a_opa_laq_a(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_RECUR_IF_A_opA_LAq_A); + if (opinit_if_a_a_opa_laq(sc, false, true, arg) == OPT_INT) + sc->value = make_integer(sc, oprec_i_if_a_opa_laq_a(sc)); + else + { + sc->rec_stack = recur_make_stack(sc); + sc->value = oprec_if_a_opa_laq_a(sc); + sc->rec_loc = 0; + } + return(sc->value); +} + +/* -------- cond_a_a_opa_laq -------- */ +static void opinit_cond_a_a_opa_laq(s7_scheme *sc) +{ + s7_pointer caller = opt3_pair(sc->code); + rec_set_test(sc, cadr(sc->code)); + rec_set_res(sc, cdadr(sc->code)); + rec_set_f1(sc, cdr(caller)); + rec_set_f2(sc, cdr(opt3_pair(caller))); + sc->rec_slot1 = let_slots(sc->curlet); + sc->rec_fn = fn_proc(caller); +} + +static s7_pointer op_recur_cond_a_a_opa_laq(s7_scheme *sc) +{ + opinit_cond_a_a_opa_laq(sc); + return(oprec_if_a_a_opa_laq(sc)); +} + +/* -------- if_a_a_opa_laaq and if_a_opa_laaq_a and cond_a_a_opa_laaq -------- */ +enum {IF1A_LA2, IF2A_LA2, COND2A_LA2}; + +static void opinit_if_a_a_opa_laaq(s7_scheme *sc, int32_t a_op) +{ + s7_pointer caller = opt3_pair(sc->code); + rec_set_test(sc, (a_op == COND2A_LA2) ? cadr(sc->code) : cdr(sc->code)); + rec_set_res(sc, (a_op == IF2A_LA2) ? cddr(sc->code) : ((a_op == IF1A_LA2) ? cdddr(sc->code) : cdadr(sc->code))); + rec_set_f1(sc, cdr(caller)); + rec_set_f2(sc, cdr(opt3_pair(caller))); + rec_set_f3(sc, cddr(opt3_pair(caller))); + sc->rec_slot1 = let_slots(sc->curlet); + sc->rec_slot2 = next_slot(sc->rec_slot1); + sc->rec_fn = fn_proc(caller); +} + +static s7_pointer oprec_if_a_a_opa_laaq(s7_scheme *sc) +{ + if (sc->rec_testf(sc, sc->rec_testp) != sc->F) return(sc->rec_resf(sc, sc->rec_resp)); + recur_push(sc, sc->rec_f1f(sc, sc->rec_f1p)); + recur_push(sc, sc->rec_f2f(sc, sc->rec_f2p)); + slot_set_value(sc->rec_slot2, sc->rec_f3f(sc, sc->rec_f3p)); + slot_set_value(sc->rec_slot1, recur_pop(sc)); + if (sc->rec_testf(sc, sc->rec_testp) != sc->F) + set_car(sc->t2_2, sc->rec_resf(sc, sc->rec_resp)); + else + { + recur_push(sc, sc->rec_f1f(sc, sc->rec_f1p)); + recur_push(sc, sc->rec_f2f(sc, sc->rec_f2p)); + slot_set_value(sc->rec_slot2, sc->rec_f3f(sc, sc->rec_f3p)); + slot_set_value(sc->rec_slot1, recur_pop(sc)); + set_car(sc->t2_2, oprec_if_a_a_opa_laaq(sc)); + set_car(sc->t2_1, recur_pop(sc)); + set_car(sc->t2_2, sc->rec_fn(sc, sc->t2_1)); + } + set_car(sc->t2_1, recur_pop(sc)); + return(sc->rec_fn(sc, sc->t2_1)); +} + +static s7_pointer oprec_if_a_opa_laaq_a(s7_scheme *sc) +{ + if (sc->rec_testf(sc, sc->rec_testp) == sc->F) return(sc->rec_resf(sc, sc->rec_resp)); + recur_push(sc, sc->rec_f1f(sc, sc->rec_f1p)); + recur_push(sc, sc->rec_f2f(sc, sc->rec_f2p)); + slot_set_value(sc->rec_slot2, sc->rec_f3f(sc, sc->rec_f3p)); + slot_set_value(sc->rec_slot1, recur_pop(sc)); + set_car(sc->t2_2, oprec_if_a_opa_laaq_a(sc)); + set_car(sc->t2_1, recur_pop(sc)); + return(sc->rec_fn(sc, sc->t2_1)); +} + +static s7_pointer op_recur_if_a_a_opa_laaq(s7_scheme *sc) +{ + opinit_if_a_a_opa_laaq(sc, IF2A_LA2); + return(oprec_if_a_a_opa_laaq(sc)); +} + +static s7_pointer op_recur_if_a_opa_laaq_a(s7_scheme *sc) +{ + opinit_if_a_a_opa_laaq(sc, IF1A_LA2); + return(oprec_if_a_opa_laaq_a(sc)); +} + +static s7_pointer op_recur_cond_a_a_opa_laaq(s7_scheme *sc) +{ + opinit_if_a_a_opa_laaq(sc, COND2A_LA2); + return(oprec_if_a_a_opa_laaq(sc)); +} + + +/* -------- if_a_a_opa_l3aq -------- */ +static void opinit_if_a_a_opa_l3aq(s7_scheme *sc) +{ + s7_pointer caller = opt3_pair(sc->code), l3a = cdr(opt3_pair(caller)); + rec_set_test(sc, cdr(sc->code)); + rec_set_res(sc, cddr(sc->code)); + rec_set_f1(sc, cdr(caller)); + rec_set_f2(sc, l3a); + rec_set_f3(sc, cdr(l3a)); + rec_set_f4(sc, cddr(l3a)); + sc->rec_slot1 = let_slots(sc->curlet); + sc->rec_slot2 = next_slot(sc->rec_slot1); + sc->rec_slot3 = next_slot(sc->rec_slot2); + sc->rec_fn = fn_proc(caller); +} + +static s7_pointer oprec_if_a_a_opa_l3aq(s7_scheme *sc) +{ + if (sc->rec_testf(sc, sc->rec_testp) != sc->F) return(sc->rec_resf(sc, sc->rec_resp)); + recur_push(sc, sc->rec_f1f(sc, sc->rec_f1p)); + recur_push(sc, sc->rec_f2f(sc, sc->rec_f2p)); + recur_push(sc, sc->rec_f3f(sc, sc->rec_f3p)); + slot_set_value(sc->rec_slot3, sc->rec_f4f(sc, sc->rec_f4p)); + slot_set_value(sc->rec_slot2, recur_pop(sc)); + slot_set_value(sc->rec_slot1, recur_pop(sc)); + if (sc->rec_testf(sc, sc->rec_testp) != sc->F) + set_car(sc->t2_2, sc->rec_resf(sc, sc->rec_resp)); + else + { + recur_push(sc, sc->rec_f1f(sc, sc->rec_f1p)); + recur_push(sc, sc->rec_f2f(sc, sc->rec_f2p)); + recur_push(sc, sc->rec_f3f(sc, sc->rec_f3p)); + slot_set_value(sc->rec_slot3, sc->rec_f4f(sc, sc->rec_f4p)); + slot_set_value(sc->rec_slot2, recur_pop(sc)); + slot_set_value(sc->rec_slot1, recur_pop(sc)); + set_car(sc->t2_2, oprec_if_a_a_opa_l3aq(sc)); + set_car(sc->t2_1, recur_pop(sc)); + set_car(sc->t2_2, sc->rec_fn(sc, sc->t2_1)); + } + set_car(sc->t2_1, recur_pop(sc)); + return(sc->rec_fn(sc, sc->t2_1)); +} + +static s7_pointer op_recur_if_a_a_opa_l3aq(s7_scheme *sc) +{ + opinit_if_a_a_opa_l3aq(sc); + return(oprec_if_a_a_opa_l3aq(sc)); +} + +/* -------- if_a_a_opla_laq and if_a_opla_laq_a -------- */ +static opt_pid_t opinit_if_a_a_opla_laq(s7_scheme *sc, bool a_op) +{ + s7_pointer caller = opt3_pair(sc->code); +#if (!WITH_GMP) + s7_pointer c_op = car(caller); + if ((is_symbol(c_op)) && + ((is_global(c_op)) || + ((is_slot(global_slot(c_op))) && + (s7_slot(sc, c_op) == global_slot(c_op))))) + { + s7_pointer s_func = global_value(c_op); + s7_pointer slot = let_slots(sc->curlet); + if (is_c_function(s_func)) + { + sc->pc = 0; + sc->rec_test_o = sc->opts[0]; + if (bool_optimize(sc, cdr(sc->code))) + { + int32_t start_pc = sc->pc; + sc->rec_result_o = sc->opts[start_pc]; + if (is_t_integer(slot_value(slot))) + { + sc->rec_i_ii_f = s7_i_ii_function(s_func); + if ((sc->rec_i_ii_f) && + (int_optimize(sc, (a_op) ? cddr(sc->code) : cdddr(sc->code)))) + { + sc->rec_a1_o = sc->opts[sc->pc]; + if (int_optimize(sc, cdadr(caller))) + { + sc->rec_a2_o = sc->opts[sc->pc]; + if (int_optimize(sc, cdr(opt3_pair(caller)))) + { + sc->rec_val1 = make_mutable_integer(sc, integer(slot_value(slot))); + slot_set_value(slot, sc->rec_val1); + if (sc->pc != 4) + return(OPT_INT); + sc->rec_fb1 = sc->rec_test_o->v[0].fb; + sc->rec_fi1 = sc->rec_result_o->v[0].fi; + sc->rec_fi2 = sc->rec_a1_o->v[0].fi; + sc->rec_fi3 = sc->rec_a2_o->v[0].fi; + return(OPT_INT_0); + }}}} + if (is_t_real(slot_value(slot))) + { + sc->rec_d_dd_f = s7_d_dd_function(s_func); + if (sc->rec_d_dd_f) + { + sc->pc = start_pc; + sc->rec_result_o = sc->opts[start_pc]; + if (float_optimize(sc, (a_op) ? cddr(sc->code) : cdddr(sc->code))) + { + sc->rec_a1_o = sc->opts[sc->pc]; + if (float_optimize(sc, cdadr(caller))) + { + sc->rec_a2_o = sc->opts[sc->pc]; + if (float_optimize(sc, cdr(opt3_pair(caller)))) + { + sc->rec_val1 = s7_make_mutable_real(sc, real(slot_value(slot))); + slot_set_value(slot, sc->rec_val1); + return(OPT_DBL); + }}}}}}}} +#endif + rec_set_test(sc, cdr(sc->code)); + rec_set_res(sc, (a_op) ? cddr(sc->code) : cdddr(sc->code)); + rec_set_f1(sc, cdadr(caller)); + rec_set_f2(sc, cdr(opt3_pair(caller))); + sc->rec_slot1 = let_slots(sc->curlet); + sc->rec_fn = fn_proc(caller); + return(OPT_PTR); +} + +static s7_int oprec_i_if_a_a_opla_laq(s7_scheme *sc) +{ + s7_int i1, i2; + if (sc->rec_test_o->v[0].fb(sc->rec_test_o)) /* if_(A) */ + return(sc->rec_result_o->v[0].fi(sc->rec_result_o)); /* if_a_(A) */ + i1 = sc->rec_a1_o->v[0].fi(sc->rec_a1_o); /* save a1 */ + integer(sc->rec_val1) = sc->rec_a2_o->v[0].fi(sc->rec_a2_o); /* slot1 = a2 */ + i2 = oprec_i_if_a_a_opla_laq(sc); /* save la2 */ + integer(sc->rec_val1) = i1; /* slot1 = a1 */ + return(sc->rec_i_ii_f(oprec_i_if_a_a_opla_laq(sc), i2)); /* call op(la1, la2) */ +} + +static s7_int oprec_i_if_a_a_opla_laq_0(s7_scheme *sc) +{ + s7_int i1, i2; + if (sc->rec_fb1(sc->rec_test_o)) return(sc->rec_fi1(sc->rec_result_o)); + i1 = sc->rec_fi2(sc->rec_a1_o); + integer(sc->rec_val1) = sc->rec_fi3(sc->rec_a2_o); + if (sc->rec_fb1(sc->rec_test_o)) + i2 = sc->rec_fi1(sc->rec_result_o); + else + { + s7_int i3; + i2 = sc->rec_fi2(sc->rec_a1_o); + integer(sc->rec_val1) = sc->rec_fi3(sc->rec_a2_o); + i3 = oprec_i_if_a_a_opla_laq_0(sc); + integer(sc->rec_val1) = i2; + i2 = sc->rec_i_ii_f(oprec_i_if_a_a_opla_laq_0(sc), i3); + } + integer(sc->rec_val1) = i1; + return(sc->rec_i_ii_f(oprec_i_if_a_a_opla_laq_0(sc), i2)); +} + +static s7_double oprec_d_if_a_a_opla_laq(s7_scheme *sc) +{ + s7_double x1, x2; + if (sc->rec_test_o->v[0].fb(sc->rec_test_o)) return(sc->rec_result_o->v[0].fd(sc->rec_result_o)); + x1 = sc->rec_a1_o->v[0].fd(sc->rec_a1_o); + real(sc->rec_val1) = sc->rec_a2_o->v[0].fd(sc->rec_a2_o); + if (sc->rec_test_o->v[0].fb(sc->rec_test_o)) + x2 = sc->rec_result_o->v[0].fd(sc->rec_result_o); + else + { + s7_double x3; + x2 = sc->rec_a1_o->v[0].fd(sc->rec_a1_o); + real(sc->rec_val1) = sc->rec_a2_o->v[0].fd(sc->rec_a2_o); + x3 = oprec_d_if_a_a_opla_laq(sc); + real(sc->rec_val1) = x2; + x2 = sc->rec_d_dd_f(oprec_d_if_a_a_opla_laq(sc), x3); + } + real(sc->rec_val1) = x1; + return(sc->rec_d_dd_f(oprec_d_if_a_a_opla_laq(sc), x2)); +} + +static s7_pointer oprec_if_a_a_opla_laq(s7_scheme *sc) +{ + if (sc->rec_testf(sc, sc->rec_testp) != sc->F) return(sc->rec_resf(sc, sc->rec_resp)); + recur_push(sc, sc->rec_f1f(sc, sc->rec_f1p)); + slot_set_value(sc->rec_slot1, sc->rec_f2f(sc, sc->rec_f2p)); + slot_set_value(sc->rec_slot1, recur_swap(sc, oprec_if_a_a_opla_laq(sc))); + set_car(sc->t2_1, oprec_if_a_a_opla_laq(sc)); + set_car(sc->t2_2, recur_pop(sc)); + return(sc->rec_fn(sc, sc->t2_1)); +} + +static s7_int oprec_i_if_a_opla_laq_a(s7_scheme *sc) +{ + s7_int i1, i2; + if (!(sc->rec_test_o->v[0].fb(sc->rec_test_o))) return(sc->rec_result_o->v[0].fi(sc->rec_result_o)); + i1 = sc->rec_a1_o->v[0].fi(sc->rec_a1_o); + integer(sc->rec_val1) = sc->rec_a2_o->v[0].fi(sc->rec_a2_o); + i2 = oprec_i_if_a_opla_laq_a(sc); + integer(sc->rec_val1) = i1; + return(sc->rec_i_ii_f(oprec_i_if_a_opla_laq_a(sc), i2)); +} + +static s7_int oprec_i_if_a_opla_laq_a_0(s7_scheme *sc) +{ + s7_int i1, i2; + if (!sc->rec_fb1(sc->rec_test_o)) return(sc->rec_fi1(sc->rec_result_o)); + i1 = sc->rec_fi2(sc->rec_a1_o); + integer(sc->rec_val1) = sc->rec_fi3(sc->rec_a2_o); + if (!sc->rec_fb1(sc->rec_test_o)) + i2 = sc->rec_fi1(sc->rec_result_o); + else + { + s7_int i3; + i2 = sc->rec_fi2(sc->rec_a1_o); + integer(sc->rec_val1) = sc->rec_fi3(sc->rec_a2_o); + i3 = oprec_i_if_a_opla_laq_a_0(sc); + integer(sc->rec_val1) = i2; + i2 = sc->rec_i_ii_f(oprec_i_if_a_opla_laq_a_0(sc), i3); + } + integer(sc->rec_val1) = i1; + return(sc->rec_i_ii_f(oprec_i_if_a_opla_laq_a_0(sc), i2)); +} + +static s7_double oprec_d_if_a_opla_laq_a(s7_scheme *sc) +{ + s7_double x1, x2; + if (!(sc->rec_test_o->v[0].fb(sc->rec_test_o))) return(sc->rec_result_o->v[0].fd(sc->rec_result_o)); + x1 = sc->rec_a1_o->v[0].fd(sc->rec_a1_o); + real(sc->rec_val1) = sc->rec_a2_o->v[0].fd(sc->rec_a2_o); + x2 = oprec_d_if_a_opla_laq_a(sc); + real(sc->rec_val1) = x1; + return(sc->rec_d_dd_f(oprec_d_if_a_opla_laq_a(sc), x2)); +} + +static s7_pointer oprec_if_a_opla_laq_a(s7_scheme *sc) +{ + if (sc->rec_testf(sc, sc->rec_testp) == sc->F) return(sc->rec_resf(sc, sc->rec_resp)); + recur_push(sc, sc->rec_f1f(sc, sc->rec_f1p)); + slot_set_value(sc->rec_slot1, sc->rec_f2f(sc, sc->rec_f2p)); + slot_set_value(sc->rec_slot1, recur_swap(sc, oprec_if_a_opla_laq_a(sc))); + set_car(sc->t2_1, oprec_if_a_opla_laq_a(sc)); + set_car(sc->t2_2, recur_pop(sc)); + return(sc->rec_fn(sc, sc->t2_1)); +} + +static void wrap_recur_if_a_a_opla_laq(s7_scheme *sc, bool a_op) +{ + opt_pid_t choice = opinit_if_a_a_opla_laq(sc, a_op); + tick_tc(sc, sc->cur_op); + if ((choice == OPT_INT) || (choice == OPT_INT_0)) + { + if (choice == OPT_INT_0) + sc->value = make_integer(sc, (a_op) ? oprec_i_if_a_a_opla_laq_0(sc) : oprec_i_if_a_opla_laq_a_0(sc)); + else sc->value = make_integer(sc, (a_op) ? oprec_i_if_a_a_opla_laq(sc) : oprec_i_if_a_opla_laq_a(sc)); + } + else + if (choice == OPT_PTR) + { + sc->rec_stack = recur_make_stack(sc); + sc->value = (a_op) ? oprec_if_a_a_opla_laq(sc) : oprec_if_a_opla_laq_a(sc); + sc->rec_loc = 0; + } + else sc->value = make_real(sc, (a_op) ? oprec_d_if_a_a_opla_laq(sc) : oprec_d_if_a_opla_laq_a(sc)); +} + + +/* -------- if_a_a_opa_la_laq and if_a_opa_la_laq_a -------- */ +static void opinit_if_a_a_opa_la_laq(s7_scheme *sc, bool a_op) +{ + s7_pointer caller = opt3_pair(sc->code); + rec_set_test(sc, cdr(sc->code)); + rec_set_res(sc, (a_op) ? cddr(sc->code) : cdddr(sc->code)); + rec_set_f1(sc, cdr(caller)); + rec_set_f2(sc, cdaddr(caller)); + rec_set_f3(sc, cdr(opt3_pair(caller))); + sc->rec_slot1 = let_slots(sc->curlet); + sc->rec_fn = fn_proc(caller); +} + +static s7_pointer oprec_if_a_a_opa_la_laq(s7_scheme *sc) +{ + if (sc->rec_testf(sc, sc->rec_testp) != sc->F) return(sc->rec_resf(sc, sc->rec_resp)); + recur_push(sc, sc->rec_f1f(sc, sc->rec_f1p)); + recur_push(sc, sc->rec_f2f(sc, sc->rec_f2p)); + slot_set_value(sc->rec_slot1, sc->rec_f3f(sc, sc->rec_f3p)); + slot_set_value(sc->rec_slot1, recur_swap(sc, oprec_if_a_a_opa_la_laq(sc))); + set_car(sc->t3_2, oprec_if_a_a_opa_la_laq(sc)); + set_car(sc->t3_3, recur_pop(sc)); + set_car(sc->t3_1, recur_pop(sc)); + return(sc->rec_fn(sc, sc->t3_1)); +} + +static s7_pointer oprec_if_a_opa_la_laq_a(s7_scheme *sc) +{ + if (sc->rec_testf(sc, sc->rec_testp) == sc->F) return(sc->rec_resf(sc, sc->rec_resp)); + recur_push(sc, sc->rec_f1f(sc, sc->rec_f1p)); + recur_push(sc, sc->rec_f2f(sc, sc->rec_f2p)); + slot_set_value(sc->rec_slot1, sc->rec_f3f(sc, sc->rec_f3p)); + slot_set_value(sc->rec_slot1, recur_swap(sc, oprec_if_a_opa_la_laq_a(sc))); + set_car(sc->t3_2, oprec_if_a_opa_la_laq_a(sc)); + set_car(sc->t3_3, recur_pop(sc)); + set_car(sc->t3_1, recur_pop(sc)); + return(sc->rec_fn(sc, sc->t3_1)); +} + +static s7_pointer op_recur_if_a_a_opa_la_laq(s7_scheme *sc) +{ + opinit_if_a_a_opa_la_laq(sc, true); + return(oprec_if_a_a_opa_la_laq(sc)); +} + +static s7_pointer op_recur_if_a_opa_la_laq_a(s7_scheme *sc) +{ + opinit_if_a_a_opa_la_laq(sc, false); + return(oprec_if_a_opa_la_laq_a(sc)); +} + +/* -------- if_a_a_opla_la_laq -------- */ +static void opinit_if_a_a_opla_la_laq(s7_scheme *sc, bool a_op) +{ + s7_pointer caller = opt3_pair(sc->code); + rec_set_test(sc, cdr(sc->code)); + rec_set_res(sc, (a_op) ? cddr(sc->code) : cdddr(sc->code)); + rec_set_f1(sc, cdadr(caller)); + rec_set_f2(sc, cdaddr(caller)); + rec_set_f3(sc, cdr(opt3_pair(caller))); + sc->rec_slot1 = let_slots(sc->curlet); + sc->rec_fn = fn_proc(caller); +} + +static s7_pointer oprec_if_a_a_opla_la_laq(s7_scheme *sc) +{ + if (sc->rec_testf(sc, sc->rec_testp) != sc->F) return(sc->rec_resf(sc, sc->rec_resp)); + recur_push(sc, sc->rec_f1f(sc, sc->rec_f1p)); + recur_push(sc, sc->rec_f2f(sc, sc->rec_f2p)); + slot_set_value(sc->rec_slot1, sc->rec_f3f(sc, sc->rec_f3p)); + slot_set_value(sc->rec_slot1, recur_swap(sc, oprec_if_a_a_opla_la_laq(sc))); + recur_push(sc, oprec_if_a_a_opla_la_laq(sc)); + slot_set_value(sc->rec_slot1, recur_ref(sc, 3)); + set_car(sc->t3_1, oprec_if_a_a_opla_la_laq(sc)); + set_car(sc->t3_2, recur_pop(sc)); + set_car(sc->t3_3, recur_pop2(sc)); + return(sc->rec_fn(sc, sc->t3_1)); +} + +static s7_pointer op_recur_if_a_a_opla_la_laq(s7_scheme *sc) +{ + opinit_if_a_a_opla_la_laq(sc, true); + return(oprec_if_a_a_opla_la_laq(sc)); +} + +/* -------- if_a_a_lopl3a_l3a_l3aq(s7_scheme *sc) -------- + * esteemed reader, please ignore this nonsense! + * The opt_info version was not a lot faster -- ~/old/tak-st.c: say 10% faster. The current fx-based + * version has immediate lookups, and since the data is (ahem) simple, the GC is not a factor. + * The opt version has its own overheads, and has to do the same amount of stack manipulations. + */ +static s7_pointer rec_x(s7_scheme *sc, s7_pointer code) {return(slot_value(sc->rec_slot1));} +static s7_pointer rec_y(s7_scheme *sc, s7_pointer code) {return(slot_value(sc->rec_slot2));} +static s7_pointer rec_z(s7_scheme *sc, s7_pointer code) {return(slot_value(sc->rec_slot3));} +static s7_pointer rec_sub_z1(s7_scheme *sc, s7_pointer code) +{ + s7_pointer x = slot_value(sc->rec_slot3); + return((is_t_integer(x)) ? make_integer(sc, integer(x) - 1) : minus_c1(sc, x)); +} + +static void opinit_if_a_a_lopl3a_l3a_l3aq(s7_scheme *sc) +{ + s7_pointer caller = opt3_pair(sc->code); + s7_pointer la1 = cadr(caller); + s7_pointer la2 = caddr(caller); + s7_pointer la3 = opt3_pair(caller); + rec_set_test(sc, cdr(sc->code)); + rec_set_res(sc, cddr(sc->code)); + rec_set_f1(sc, cdr(la1)); + rec_set_f2(sc, cddr(la1)); + if (sc->rec_f2f == fx_u) sc->rec_f2f = rec_y; + rec_set_f3(sc, cdddr(la1)); + rec_set_f4(sc, cdr(la2)); + rec_set_f5(sc, cddr(la2)); + rec_set_f6(sc, cdddr(la2)); + if (sc->rec_f6f == fx_t) sc->rec_f6f = rec_x; + + sc->rec_f7p = cdr(la3); + sc->rec_f7f = fx_proc(sc->rec_f7p); + sc->rec_f7p = car(sc->rec_f7p); + + sc->rec_f8p = cddr(la3); + sc->rec_f8f = fx_proc(sc->rec_f8p); + if (sc->rec_f8f == fx_t) sc->rec_f8f = rec_x; + sc->rec_f8p = car(sc->rec_f8p); + + sc->rec_f9p = cdddr(la3); + sc->rec_f9f = fx_proc(sc->rec_f9p); + if (sc->rec_f9f == fx_u) sc->rec_f9f = rec_y; + sc->rec_f9p = car(sc->rec_f9p); + + sc->rec_slot1 = let_slots(sc->curlet); + sc->rec_slot2 = next_slot(sc->rec_slot1); + sc->rec_slot3 = next_slot(sc->rec_slot2); + if (cadddr(la1) == slot_symbol(sc->rec_slot3)) sc->rec_f3f = rec_z; + if (caddr(la2) == slot_symbol(sc->rec_slot3)) sc->rec_f5f = rec_z; + if ((sc->rec_f7f == fx_subtract_s1) && (cadadr(la3) == slot_symbol(sc->rec_slot3))) sc->rec_f7f = rec_sub_z1; +} + +static s7_pointer oprec_if_a_a_lopl3a_l3a_l3aq(s7_scheme *sc) +{ + if (sc->rec_testf(sc, sc->rec_testp) != sc->F) return(sc->rec_resf(sc, sc->rec_resp)); + recur_push(sc, sc->rec_f1f(sc, sc->rec_f1p)); + recur_push(sc, sc->rec_f2f(sc, sc->rec_f2p)); + recur_push(sc, sc->rec_f3f(sc, sc->rec_f3p)); + recur_push(sc, sc->rec_f4f(sc, sc->rec_f4p)); + recur_push(sc, sc->rec_f5f(sc, sc->rec_f5p)); + recur_push(sc, sc->rec_f6f(sc, sc->rec_f6p)); + recur_push(sc, sc->rec_f7f(sc, sc->rec_f7p)); + recur_push(sc, sc->rec_f8f(sc, sc->rec_f8p)); + slot_set_value(sc->rec_slot3, sc->rec_f9f(sc, sc->rec_f9p)); + slot_set_value(sc->rec_slot2, recur_pop(sc)); + slot_set_value(sc->rec_slot1, recur_pop(sc)); + recur_push(sc, oprec_if_a_a_lopl3a_l3a_l3aq(sc)); + slot_set_value(sc->rec_slot3, recur_ref(sc, 2)); + slot_set_value(sc->rec_slot2, recur_ref(sc, 3)); + slot_set_value(sc->rec_slot1, recur_ref(sc, 4)); + recur_push(sc, oprec_if_a_a_lopl3a_l3a_l3aq(sc)); + slot_set_value(sc->rec_slot3, recur_ref(sc, 6)); + slot_set_value(sc->rec_slot2, recur_ref(sc, 7)); + slot_set_value(sc->rec_slot1, recur_ref(sc, 8)); + slot_set_value(sc->rec_slot1, oprec_if_a_a_lopl3a_l3a_l3aq(sc)); + slot_set_value(sc->rec_slot2, recur_pop(sc)); + slot_set_value(sc->rec_slot3, recur_pop(sc)); + sc->rec_loc -= 6; + return(oprec_if_a_a_lopl3a_l3a_l3aq(sc)); +} + +static s7_pointer op_recur_if_a_a_lopl3a_l3a_l3aq(s7_scheme *sc) +{ + opinit_if_a_a_lopl3a_l3a_l3aq(sc); + return(oprec_if_a_a_lopl3a_l3a_l3aq(sc)); +} + +/* -------- if_a_a_and_a_laa_laa -------- */ +static void opinit_if_a_a_and_a_laa_laa(s7_scheme *sc, s7_pointer code) +{ + s7_pointer caller = opt3_pair(code); + s7_pointer la1 = caddr(caller); + s7_pointer la2 = cadddr(caller); + rec_set_test(sc, cdr(code)); + rec_set_res(sc, cddr(code)); + rec_set_f1(sc, cdr(caller)); + rec_set_f2(sc, cdr(la1)); + rec_set_f3(sc, cddr(la1)); + rec_set_f4(sc, cdr(la2)); + rec_set_f5(sc, cddr(la2)); + sc->rec_slot1 = let_slots(sc->curlet); + sc->rec_slot2 = next_slot(sc->rec_slot1); +} + +static s7_pointer oprec_if_a_a_and_a_laa_laa(s7_scheme *sc) +{ + if (sc->rec_testf(sc, sc->rec_testp) != sc->F) return(sc->rec_resf(sc, sc->rec_resp)); + if (sc->rec_f1f(sc, sc->rec_f1p) == sc->F) return(sc->F); + recur_push(sc, slot_value(sc->rec_slot1)); + recur_push(sc, slot_value(sc->rec_slot2)); + recur_push(sc, sc->rec_f2f(sc, sc->rec_f2p)); + slot_set_value(sc->rec_slot2, sc->rec_f3f(sc, sc->rec_f3p)); + slot_set_value(sc->rec_slot1, recur_pop(sc)); + if (oprec_if_a_a_and_a_laa_laa(sc) == sc->F) + { + sc->rec_loc -= 2; + return(sc->F); + } + slot_set_value(sc->rec_slot2, recur_pop(sc)); + slot_set_value(sc->rec_slot1, recur_pop(sc)); + recur_push(sc, sc->rec_f4f(sc, sc->rec_f4p)); + slot_set_value(sc->rec_slot2, sc->rec_f5f(sc, sc->rec_f5p)); + slot_set_value(sc->rec_slot1, recur_pop(sc)); + return(oprec_if_a_a_and_a_laa_laa(sc)); +} + +static s7_pointer op_recur_if_a_a_and_a_laa_laa(s7_scheme *sc) +{ + opinit_if_a_a_and_a_laa_laa(sc, sc->code); + return(oprec_if_a_a_and_a_laa_laa(sc)); +} + +static s7_pointer fx_recur_if_a_a_and_a_laa_laa(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_RECUR_IF_A_A_AND_A_LAA_LAA); + /* sc->curlet is set already and will be restored by the caller */ + sc->rec_stack = recur_make_stack(sc); + opinit_if_a_a_and_a_laa_laa(sc, arg); + sc->value = oprec_if_a_a_and_a_laa_laa(sc); + sc->rec_loc = 0; + return(sc->value); +} + +/* -------- cond_a_a_a_a_opla_laq -------- */ +static void opinit_cond_a_a_a_a_opla_laq(s7_scheme *sc, s7_pointer code, bool cond_case) +{ + s7_pointer caller = opt3_pair(code); + if (cond_case) + { + rec_set_test(sc, cadr(code)); + rec_set_res(sc, cdadr(code)); + rec_set_f1(sc, caddr(code)); + rec_set_f2(sc, cdaddr(code)); + } + else + { + rec_set_test(sc, cdr(code)); + rec_set_res(sc, cddr(code)); /* (if a b...) */ + rec_set_f1(sc, opt1_pair(code)); /* cdr(cadddr(code)), (if a b (if c d...)) */ + rec_set_f2(sc, cdr(opt1_pair(code))); + } + rec_set_f3(sc, cdadr(caller)); + rec_set_f4(sc, opt3_pair(caller)); + sc->rec_slot1 = let_slots(sc->curlet); + sc->rec_fn = fn_proc(caller); +} + +static inline s7_pointer oprec_cond_a_a_a_a_opla_laq(s7_scheme *sc) /* inline = 27 in trec */ +{ + if (sc->rec_testf(sc, sc->rec_testp) != sc->F) return(sc->rec_resf(sc, sc->rec_resp)); + if (sc->rec_f1f(sc, sc->rec_f1p) != sc->F) return(sc->rec_f2f(sc, sc->rec_f2p)); + recur_push(sc, sc->rec_f3f(sc, sc->rec_f3p)); + slot_set_value(sc->rec_slot1, sc->rec_f4f(sc, sc->rec_f4p)); + slot_set_value(sc->rec_slot1, recur_swap(sc, oprec_cond_a_a_a_a_opla_laq(sc))); + set_car(sc->t2_1, oprec_cond_a_a_a_a_opla_laq(sc)); + set_car(sc->t2_2, recur_pop(sc)); + return(sc->rec_fn(sc, sc->t2_1)); +} + +static s7_pointer op_recur_cond_a_a_a_a_opla_laq(s7_scheme *sc) +{ + opinit_cond_a_a_a_a_opla_laq(sc, sc->code, true); + return(oprec_cond_a_a_a_a_opla_laq(sc)); +} + +static s7_pointer op_recur_if_a_a_if_a_a_opla_laq(s7_scheme *sc) +{ + opinit_cond_a_a_a_a_opla_laq(sc, sc->code, false); + return(oprec_cond_a_a_a_a_opla_laq(sc)); +} + +static s7_pointer fx_recur_cond_a_a_a_a_opla_laq(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_RECUR_COND_A_A_A_A_opLA_LAq); + sc->rec_stack = recur_make_stack(sc); + opinit_cond_a_a_a_a_opla_laq(sc, arg, true); + sc->value = oprec_cond_a_a_a_a_opla_laq(sc); + sc->rec_loc = 0; + return(sc->value); +} + + +/* -------- cond_a_a_a_a_oplaa_laaq -------- */ +static void opinit_cond_a_a_a_a_oplaa_laaq(s7_scheme *sc, bool cond_case) +{ + s7_pointer caller = opt3_pair(sc->code); /* cadr(cadddr(sc->code)) = (cfunc laa laa) */ + if (cond_case) + { + rec_set_test(sc, cadr(sc->code)); + rec_set_res(sc, cdadr(sc->code)); + rec_set_f1(sc, caddr(sc->code)); + rec_set_f2(sc, cdaddr(sc->code)); + } + else + { + rec_set_test(sc, cdr(sc->code)); + rec_set_res(sc, cddr(sc->code)); /* (if a b...) */ + rec_set_f1(sc, opt1_pair(sc->code)); /* cdr(cadddr(sc->code)), (if a b (if c d...)) */ + rec_set_f2(sc, cdr(opt1_pair(sc->code))); + } + sc->rec_f3p = cdadr(caller); + rec_set_f4(sc, cdr(sc->rec_f3p)); + sc->rec_f3f = fx_proc(sc->rec_f3p); + sc->rec_f3p = car(sc->rec_f3p); + sc->rec_f5p = opt3_pair(caller); + rec_set_f6(sc, cdr(sc->rec_f5p)); + sc->rec_f5f = fx_proc(sc->rec_f5p); + sc->rec_f5p = car(sc->rec_f5p); + sc->rec_slot1 = let_slots(sc->curlet); + sc->rec_slot2 = next_slot(sc->rec_slot1); + sc->rec_fn = fn_proc(caller); +} + +static s7_pointer oprec_cond_a_a_a_a_oplaa_laaq(s7_scheme *sc) +{ + if (sc->rec_testf(sc, sc->rec_testp) != sc->F) return(sc->rec_resf(sc, sc->rec_resp)); + if (sc->rec_f1f(sc, sc->rec_f1p) != sc->F) return(sc->rec_f2f(sc, sc->rec_f2p)); + recur_push(sc, sc->rec_f3f(sc, sc->rec_f3p)); + recur_push(sc, sc->rec_f4f(sc, sc->rec_f4p)); + recur_push(sc, sc->rec_f5f(sc, sc->rec_f5p)); + slot_set_value(sc->rec_slot2, sc->rec_f6f(sc, sc->rec_f6p)); + slot_set_value(sc->rec_slot1, recur_pop(sc)); + sc->value = oprec_cond_a_a_a_a_oplaa_laaq(sc); /* second laa arg */ + slot_set_value(sc->rec_slot2, recur_pop(sc)); + slot_set_value(sc->rec_slot1, recur_pop(sc)); + recur_push_unchecked(sc, sc->value); + set_car(sc->t2_1, oprec_cond_a_a_a_a_oplaa_laaq(sc)); /* first laa arg */ + set_car(sc->t2_2, recur_pop(sc)); + return(sc->rec_fn(sc, sc->t2_1)); +} + +static s7_pointer op_recur_cond_a_a_a_a_oplaa_laaq(s7_scheme *sc) +{ + opinit_cond_a_a_a_a_oplaa_laaq(sc, true); + return(oprec_cond_a_a_a_a_oplaa_laaq(sc)); +} + +static s7_pointer op_recur_if_a_a_if_a_a_oplaa_laaq(s7_scheme *sc) +{ + opinit_cond_a_a_a_a_oplaa_laaq(sc, false); + return(oprec_cond_a_a_a_a_oplaa_laaq(sc)); +} + + +/* -------- cond_a_a_a_a_opa_laaq -------- */ +static void opinit_cond_a_a_a_a_opa_laaq(s7_scheme *sc) +{ + s7_pointer caller = opt3_pair(sc->code); + rec_set_test(sc, cadr(sc->code)); + rec_set_res(sc, cdadr(sc->code)); + sc->rec_f1p = caddr(sc->code); + rec_set_f2(sc, cdr(sc->rec_f1p)); + sc->rec_f1f = fx_proc(sc->rec_f1p); + sc->rec_f1p = car(sc->rec_f1p); + rec_set_f3(sc, cdr(caller)); + rec_set_f4(sc, opt3_pair(caller)); + rec_set_f5(sc, cdr(opt3_pair(caller))); + sc->rec_slot1 = let_slots(sc->curlet); + sc->rec_slot2 = next_slot(sc->rec_slot1); + sc->rec_fn = fn_proc(caller); +} + +static s7_pointer oprec_cond_a_a_a_a_opa_laaq(s7_scheme *sc) +{ + if (sc->rec_testf(sc, sc->rec_testp) != sc->F) return(sc->rec_resf(sc, sc->rec_resp)); + if (sc->rec_f1f(sc, sc->rec_f1p) != sc->F) return(sc->rec_f2f(sc, sc->rec_f2p)); + recur_push(sc, sc->rec_f3f(sc, sc->rec_f3p)); + recur_push(sc, sc->rec_f4f(sc, sc->rec_f4p)); + slot_set_value(sc->rec_slot2, sc->rec_f5f(sc, sc->rec_f5p)); + slot_set_value(sc->rec_slot1, recur_pop(sc)); + set_car(sc->t2_2, oprec_cond_a_a_a_a_opa_laaq(sc)); + set_car(sc->t2_1, recur_pop(sc)); + return(sc->rec_fn(sc, sc->t2_1)); +} + +static s7_pointer op_recur_cond_a_a_a_a_opa_laaq(s7_scheme *sc) +{ + opinit_cond_a_a_a_a_opa_laaq(sc); + return(oprec_cond_a_a_a_a_opa_laaq(sc)); +} + + +/* -------- cond_a_a_a_laa_opa_laaq -------- */ +static void opinit_cond_a_a_a_laa_opa_laaq(s7_scheme *sc, bool cond) +{ + s7_pointer caller = opt3_pair(sc->code); /* opA_LAA */ + rec_set_test(sc, (cond) ? cadr(sc->code) : cdr(sc->code)); + rec_set_res(sc, (cond) ? cdadr(sc->code) : cddr(sc->code)); + sc->rec_f1p = (cond) ? caddr(sc->code) : cdr(cadddr(sc->code)); + sc->rec_f2p = cdadr(sc->rec_f1p); + rec_set_f3(sc, cdr(sc->rec_f2p)); + sc->rec_f1f = fx_proc(sc->rec_f1p); + sc->rec_f1p = car(sc->rec_f1p); + sc->rec_f2f = fx_proc(sc->rec_f2p); + sc->rec_f2p = car(sc->rec_f2p); + rec_set_f4(sc, cdr(caller)); + sc->rec_f5p = cdr(opt3_pair(caller)); /* (L)AA */ + rec_set_f6(sc, cdr(sc->rec_f5p)); + sc->rec_f5f = fx_proc(sc->rec_f5p); + sc->rec_f5p = car(sc->rec_f5p); + sc->rec_fn = fn_proc(caller); + sc->rec_slot1 = let_slots(sc->curlet); + sc->rec_slot2 = next_slot(sc->rec_slot1); +} + +static s7_pointer oprec_cond_a_a_a_laa_opa_laaq(s7_scheme *sc) +{ + if (sc->rec_testf(sc, sc->rec_testp) != sc->F) return(sc->rec_resf(sc, sc->rec_resp)); + if (sc->rec_f1f(sc, sc->rec_f1p) != sc->F) + { + recur_push(sc, sc->rec_f2f(sc, sc->rec_f2p)); + slot_set_value(sc->rec_slot2, sc->rec_f3f(sc, sc->rec_f3p)); + slot_set_value(sc->rec_slot1, recur_pop(sc)); + return(oprec_cond_a_a_a_laa_opa_laaq(sc)); /* first laa above */ + } + recur_push(sc, sc->rec_f4f(sc, sc->rec_f4p)); + recur_push(sc, sc->rec_f5f(sc, sc->rec_f5p)); + slot_set_value(sc->rec_slot2, sc->rec_f6f(sc, sc->rec_f6p)); + slot_set_value(sc->rec_slot1, recur_pop(sc)); + if (sc->rec_testf(sc, sc->rec_testp) != sc->F) + set_car(sc->t2_2, sc->rec_resf(sc, sc->rec_resp)); + else + if (sc->rec_f1f(sc, sc->rec_f1p) != sc->F) + { + recur_push(sc, sc->rec_f2f(sc, sc->rec_f2p)); + slot_set_value(sc->rec_slot2, sc->rec_f3f(sc, sc->rec_f3p)); + slot_set_value(sc->rec_slot1, recur_pop(sc)); + set_car(sc->t2_2, oprec_cond_a_a_a_laa_opa_laaq(sc)); /* first laa above */ + } + else + { + recur_push(sc, sc->rec_f4f(sc, sc->rec_f4p)); + recur_push(sc, sc->rec_f5f(sc, sc->rec_f5p)); + slot_set_value(sc->rec_slot2, sc->rec_f6f(sc, sc->rec_f6p)); + slot_set_value(sc->rec_slot1, recur_pop(sc)); + set_car(sc->t2_2, oprec_cond_a_a_a_laa_opa_laaq(sc)); + set_car(sc->t2_1, recur_pop(sc)); + set_car(sc->t2_2, sc->rec_fn(sc, sc->t2_1)); + } + set_car(sc->t2_1, recur_pop(sc)); + return(sc->rec_fn(sc, sc->t2_1)); +} + +static s7_pointer op_recur_cond_a_a_a_laa_opa_laaq(s7_scheme *sc) +{ + opinit_cond_a_a_a_laa_opa_laaq(sc, true); + return(oprec_cond_a_a_a_laa_opa_laaq(sc)); +} + +static s7_pointer op_recur_if_a_a_if_a_laa_opa_laaq(s7_scheme *sc) /* if version, same logic as cond above */ +{ + opinit_cond_a_a_a_laa_opa_laaq(sc, false); + return(oprec_cond_a_a_a_laa_opa_laaq(sc)); +} + +/* -------- cond_a_a_a_laa_lopa_laaq -------- */ + +static opt_pid_t opinit_cond_a_a_a_laa_lopa_laaq(s7_scheme *sc) +{ + s7_pointer caller = opt3_pair(sc->code); + sc->rec_slot1 = let_slots(sc->curlet); + sc->rec_slot2 = next_slot(sc->rec_slot1); + +#if (!WITH_GMP) + if ((is_t_integer(slot_value(sc->rec_slot1))) && + (is_t_integer(slot_value(sc->rec_slot2)))) + { + sc->pc = 0; + sc->rec_test_o = sc->opts[0]; + if (bool_optimize(sc, cadr(sc->code))) + { + sc->rec_result_o = sc->opts[sc->pc]; + if (int_optimize(sc, cdadr(sc->code))) + { + s7_pointer laa1 = caddr(sc->code); + sc->rec_a1_o = sc->opts[sc->pc]; + if (bool_optimize(sc, laa1)) + { + sc->rec_a2_o = sc->opts[sc->pc]; + if (int_optimize(sc, cdadr(laa1))) + { + sc->rec_a3_o = sc->opts[sc->pc]; + if (int_optimize(sc, cddadr(laa1))) + { + s7_pointer laa2 = cadr(cadddr(sc->code)), laa3 = caddr(laa2); + sc->rec_a4_o = sc->opts[sc->pc]; + if (int_optimize(sc, cdr(laa2))) + { + sc->rec_a5_o = sc->opts[sc->pc]; + if (int_optimize(sc, cdr(laa3))) + { + sc->rec_a6_o = sc->opts[sc->pc]; + if (int_optimize(sc, cddr(laa3))) + { + sc->rec_val1 = make_mutable_integer(sc, integer(slot_value(sc->rec_slot1))); + slot_set_value(sc->rec_slot1, sc->rec_val1); + sc->rec_val2 = make_mutable_integer(sc, integer(slot_value(sc->rec_slot2))); + slot_set_value(sc->rec_slot2, sc->rec_val2); + if (sc->pc != 8) + return(OPT_INT); + sc->rec_fb1 = sc->rec_test_o->v[0].fb; + sc->rec_fb2 = sc->rec_a1_o->v[0].fb; + sc->rec_fi1 = sc->rec_result_o->v[0].fi; + sc->rec_fi2 = sc->rec_a2_o->v[0].fi; + sc->rec_fi3 = sc->rec_a3_o->v[0].fi; + sc->rec_fi4 = sc->rec_a4_o->v[0].fi; + sc->rec_fi5 = sc->rec_a5_o->v[0].fi; + sc->rec_fi6 = sc->rec_a6_o->v[0].fi; + return(OPT_INT_0); + }}}}}}}}} +#endif + rec_set_test(sc, cadr(sc->code)); + rec_set_res(sc, cdadr(sc->code)); + sc->rec_f1p = caddr(sc->code); + sc->rec_f2p = cdadr(sc->rec_f1p); + rec_set_f3(sc, cdr(sc->rec_f2p)); + sc->rec_f1f = fx_proc(sc->rec_f1p); + sc->rec_f1p = car(sc->rec_f1p); + sc->rec_f2f = fx_proc(sc->rec_f2p); + sc->rec_f2p = car(sc->rec_f2p); + rec_set_f4(sc, cdr(caller)); + sc->rec_f5p = opt3_pair(caller); + rec_set_f6(sc, cdr(sc->rec_f5p)); + sc->rec_f5f = fx_proc(sc->rec_f5p); + sc->rec_f5p = car(sc->rec_f5p); + return(OPT_PTR); +} + +static s7_int oprec_i_cond_a_a_a_laa_lopa_laaq(s7_scheme *sc) +{ + s7_int i1, i2; + if (sc->rec_test_o->v[0].fb(sc->rec_test_o)) return(sc->rec_result_o->v[0].fi(sc->rec_result_o)); + if (sc->rec_a1_o->v[0].fb(sc->rec_a1_o)) + { + i1 = sc->rec_a2_o->v[0].fi(sc->rec_a2_o); + integer(sc->rec_val2) = sc->rec_a3_o->v[0].fi(sc->rec_a3_o); + integer(sc->rec_val1) = i1; + return(oprec_i_cond_a_a_a_laa_lopa_laaq(sc)); + } + i1 = sc->rec_a4_o->v[0].fi(sc->rec_a4_o); + i2 = sc->rec_a5_o->v[0].fi(sc->rec_a5_o); + integer(sc->rec_val2) = sc->rec_a6_o->v[0].fi(sc->rec_a6_o); + integer(sc->rec_val1) = i2; + integer(sc->rec_val2) = oprec_i_cond_a_a_a_laa_lopa_laaq(sc); + integer(sc->rec_val1) = i1; + return(oprec_i_cond_a_a_a_laa_lopa_laaq(sc)); +} + +static s7_int oprec_i_cond_a_a_a_laa_lopa_laaq_0(s7_scheme *sc) +{ + s7_int i1, i2; + if (sc->rec_fb1(sc->rec_test_o)) return(sc->rec_fi1(sc->rec_result_o)); + if (sc->rec_fb2(sc->rec_a1_o)) + { + i1 = sc->rec_fi2(sc->rec_a2_o); + integer(sc->rec_val2) = sc->rec_fi3(sc->rec_a3_o); + integer(sc->rec_val1) = i1; + return(oprec_i_cond_a_a_a_laa_lopa_laaq_0(sc)); + } + i1 = sc->rec_fi4(sc->rec_a4_o); + i2 = sc->rec_fi5(sc->rec_a5_o); + integer(sc->rec_val2) = sc->rec_fi6(sc->rec_a6_o); + integer(sc->rec_val1) = i2; + integer(sc->rec_val2) = oprec_i_cond_a_a_a_laa_lopa_laaq_0(sc); + integer(sc->rec_val1) = i1; + return(oprec_i_cond_a_a_a_laa_lopa_laaq_0(sc)); +} + +static s7_pointer oprec_cond_a_a_a_laa_lopa_laaq(s7_scheme *sc) +{ + if (sc->rec_testf(sc, sc->rec_testp) != sc->F) return(sc->rec_resf(sc, sc->rec_resp)); + if (sc->rec_f1f(sc, sc->rec_f1p) != sc->F) + { + recur_push(sc, sc->rec_f2f(sc, sc->rec_f2p)); + slot_set_value(sc->rec_slot2, sc->rec_f3f(sc, sc->rec_f3p)); + slot_set_value(sc->rec_slot1, recur_pop(sc)); + return(oprec_cond_a_a_a_laa_lopa_laaq(sc)); + } + recur_push(sc, sc->rec_f4f(sc, sc->rec_f4p)); + recur_push(sc, sc->rec_f5f(sc, sc->rec_f5p)); + slot_set_value(sc->rec_slot2, sc->rec_f6f(sc, sc->rec_f6p)); + slot_set_value(sc->rec_slot1, recur_pop(sc)); + slot_set_value(sc->rec_slot2, oprec_cond_a_a_a_laa_lopa_laaq(sc)); + slot_set_value(sc->rec_slot1, recur_pop(sc)); + return(oprec_cond_a_a_a_laa_lopa_laaq(sc)); +} + +static void wrap_recur_cond_a_a_a_laa_lopa_laaq(s7_scheme *sc) +{ + opt_pid_t choice = opinit_cond_a_a_a_laa_lopa_laaq(sc); + tick_tc(sc, sc->cur_op); + if (choice != OPT_PTR) + sc->value = make_integer(sc, (choice == OPT_INT) ? oprec_i_cond_a_a_a_laa_lopa_laaq(sc) : oprec_i_cond_a_a_a_laa_lopa_laaq_0(sc)); + else + { + sc->rec_stack = recur_make_stack(sc); + sc->value = oprec_cond_a_a_a_laa_lopa_laaq(sc); + sc->rec_loc = 0; + } +} + +/* -------- and_a_or_a_laa_laa -------- */ +static void opinit_and_a_or_a_laa_laa(s7_scheme *sc, s7_pointer code) +{ + s7_pointer orp = cdr(opt3_pair(code)); + rec_set_test(sc, cdr(code)); + rec_set_res(sc, orp); + rec_set_f1(sc, cdr(cadr(orp))); + rec_set_f2(sc, cddr(cadr(orp))); + rec_set_f3(sc, cdr(caddr(orp))); + rec_set_f4(sc, cddr(caddr(orp))); + sc->rec_slot1 = let_slots(sc->curlet); + sc->rec_slot2 = next_slot(sc->rec_slot1); +} + +static s7_pointer oprec_and_a_or_a_laa_laa(s7_scheme *sc) +{ + s7_pointer p; + if (sc->rec_testf(sc, sc->rec_testp) == sc->F) return(sc->F); + p = sc->rec_resf(sc, sc->rec_resp); + if (p != sc->F) return(p); + recur_push(sc, slot_value(sc->rec_slot1)); + recur_push(sc, slot_value(sc->rec_slot2)); + recur_push(sc, sc->rec_f1f(sc, sc->rec_f1p)); + slot_set_value(sc->rec_slot2, sc->rec_f2f(sc, sc->rec_f2p)); + slot_set_value(sc->rec_slot1, recur_pop(sc)); + p = oprec_and_a_or_a_laa_laa(sc); + if (p != sc->F) + { + sc->rec_loc -= 2; + return(p); + } + slot_set_value(sc->rec_slot2, recur_pop(sc)); + slot_set_value(sc->rec_slot1, recur_pop(sc)); + recur_push(sc, sc->rec_f3f(sc, sc->rec_f3p)); + slot_set_value(sc->rec_slot2, sc->rec_f4f(sc, sc->rec_f4p)); + slot_set_value(sc->rec_slot1, recur_pop(sc)); + return(oprec_and_a_or_a_laa_laa(sc)); +} + +static s7_pointer op_recur_and_a_or_a_laa_laa(s7_scheme *sc) +{ + opinit_and_a_or_a_laa_laa(sc, sc->code); + return(oprec_and_a_or_a_laa_laa(sc)); +} + +static s7_pointer fx_recur_and_a_or_a_laa_laa(s7_scheme *sc, s7_pointer arg) +{ + tick_tc(sc, OP_RECUR_AND_A_OR_A_LAA_LAA); + sc->rec_stack = recur_make_stack(sc); + opinit_and_a_or_a_laa_laa(sc, arg); + sc->value = oprec_and_a_or_a_laa_laa(sc); + sc->rec_loc = 0; + return(sc->value); +} + +static void wrap_recur(s7_scheme *sc, s7_pointer (*recur)(s7_scheme *sc)) +{ + tick_tc(sc, sc->cur_op); + sc->rec_stack = recur_make_stack(sc); + sc->value = recur(sc); + sc->rec_loc = 0; +} + + +/* -------------------------------- */ +static void op_safe_c_p(s7_scheme *sc) +{ + check_stack_size(sc); + push_stack_no_args_direct(sc, OP_SAFE_C_P_1); + sc->code = T_Pair(cadr(sc->code)); +} + +static void op_safe_c_p_1(s7_scheme *sc) {sc->value = fn_proc(sc->code)(sc, with_list_t1(sc->value));} + +static void op_safe_c_ssp(s7_scheme *sc) +{ + check_stack_size(sc); + push_stack_no_args_direct(sc, OP_SAFE_C_SSP_1); + sc->code = opt3_pair(sc->code); +} + +static void op_safe_c_ssp_1(s7_scheme *sc) +{ + set_car(sc->t3_3, sc->value); + set_car(sc->t3_1, lookup(sc, cadr(sc->code))); + set_car(sc->t3_2, lookup(sc, caddr(sc->code))); + sc->value = fn_proc(sc->code)(sc, sc->t3_1); +} + +static void op_safe_c_ssp_mv(s7_scheme *sc) +{ + sc->args = cons_unchecked(sc, lookup(sc, cadr(sc->code)), cons(sc, lookup(sc, caddr(sc->code)), sc->value)); /* not ulist here */ + sc->code = c_function_base(opt1_cfunc(sc->code)); +} + +static void op_s(s7_scheme *sc) +{ + sc->code = lookup(sc, car(sc->code)); + if (!is_applicable(sc->code)) + apply_error_nr(sc, sc->code, sc->nil); + sc->args = sc->nil; /* op_s -> apply, so we'll apply sc->code to sc->args */ +} + +static bool op_s_g(s7_scheme *sc) +{ + s7_pointer code = sc->code; + sc->code = lookup_checked(sc, car(code)); + if ((is_c_function(sc->code)) && + (c_function_min_args(sc->code) == 1) && + (!needs_copied_args(sc->code))) + { + sc->value = c_function_call(sc->code)(sc, with_list_t1((is_symbol(cadr(code))) ? lookup_checked(sc, cadr(code)) : cadr(code))); + return(true); /* continue */ + } + if (!is_applicable(sc->code)) + apply_error_nr(sc, sc->code, cdr(code)); + if (dont_eval_args(sc->code)) + sc->args = cdr(code); + else + { + s7_pointer val = (is_symbol(cadr(code))) ? lookup_checked(sc, cadr(code)) : cadr(code); + sc->args = (needs_copied_args(sc->code)) ? list_1(sc, val) : set_plist_1(sc, val); + } + return(false); +} + +static bool op_x_a(s7_scheme *sc, s7_pointer f) +{ + if ((((type(f) == T_C_FUNCTION) && + (c_function_is_aritable(f, 1))) || + ((type(f) == T_C_RST_NO_REQ_FUNCTION) && + (c_function_max_args(f) >= 1) && + (f != initial_value(sc->hash_table_symbol)) && + (f != initial_value(sc->weak_hash_table_symbol)))) && + (!needs_copied_args(f))) + { + sc->value = c_function_call(f)(sc, with_list_t1(fx_call(sc, cdr(sc->code)))); + return(true); + } + if (is_any_vector(f)) + { + sc->args = set_plist_1(sc, fx_call(sc, cdr(sc->code))); + sc->code = f; + apply_vector(sc); + return(true); + } + if (!is_applicable(f)) + apply_error_nr(sc, f, cdr(sc->code)); + if (dont_eval_args(f)) + sc->args = cdr(sc->code); /* list_1(sc, cadr(sc->code)); */ + else + if (!needs_copied_args(f)) + sc->args = set_plist_1(sc, fx_call(sc, cdr(sc->code))); + else + { + sc->args = fx_call(sc, cdr(sc->code)); + sc->args = list_1(sc, sc->args); + } + sc->code = f; + return(false); /* goto APPLY */ +} + +static void op_x_aa(s7_scheme *sc, s7_pointer f) +{ + s7_pointer code = sc->code; + if (!is_applicable(f)) + apply_error_nr(sc, f, cdr(code)); + if (dont_eval_args(f)) + sc->args = list_2(sc, cadr(code), caddr(code)); + else + { + sc->args = fx_call(sc, cddr(code)); + if (!needs_copied_args(f)) + sc->args = set_plist_2(sc, fx_call(sc, cdr(code)), sc->args); + else sc->args = list_2(sc, sc->value = fx_call(sc, cdr(code)), sc->args); + } + sc->code = f; +} + +static void op_p_s_1(s7_scheme *sc) +{ + /* we get multiple values here (from op calc = "p" not "s") but don't need to handle it ourselves: + * let v be #(#_abs), so ((v 0) -2), (v 0 -2), ((values v 0) -2), and (((values v 0)) -2) are all 2 + * or: (define (f1) (values vector-ref (vector 1 2 3))) (define arg 1) (define (f2) ((f1) arg)) (f2) (f2) + * so apply calls apply_pair which handles multiple values explicitly. + */ + if (dont_eval_args(sc->value)) + sc->args = cdr(sc->code); + else + { + sc->args = lookup_checked(sc, cadr(sc->code)); + sc->args = (needs_copied_args(sc->value)) ? list_1(sc, sc->args) : set_plist_1(sc, sc->args); + } + sc->code = sc->value; /* goto APPLY */ +} + +static void op_safe_c_star_na(s7_scheme *sc) +{ + sc->args = safe_list_if_possible(sc, opt3_arglen(cdr(sc->code))); + for (s7_pointer args = cdr(sc->code), p = sc->args; is_pair(args); args = cdr(args), p = cdr(p)) + set_car(p, fx_call(sc, args)); + sc->code = opt1_cfunc(sc->code); + apply_c_function_star(sc); + clear_list_in_use(sc->args); +} + +static void op_safe_c_star(s7_scheme *sc) +{ + sc->code = opt1_cfunc(sc->code); + apply_c_function_star_fill_defaults(sc, 0); +} + +static void op_safe_c_star_a(s7_scheme *sc) +{ + sc->args = fx_call(sc, cdr(sc->code)); + if (is_symbol_and_keyword(sc->args)) /* (blocks3 (car (list :asdf))) */ + error_nr(sc, sc->syntax_error_symbol, + set_elist_4(sc, wrap_string(sc, "~A: keyword ~S, but no value: ~S", 32), car(sc->code), sc->args, sc->code)); + /* scheme-level define* here also gives "not a parameter name" */ + sc->args = list_1(sc, sc->args); + sc->code = opt1_cfunc(sc->code); + /* one arg, so it's not a keyword; all we need to do is fill in the defaults */ + apply_c_function_star_fill_defaults(sc, 1); +} + +static void op_safe_c_star_aa(s7_scheme *sc) +{ + sc->args = fx_call(sc, cdr(sc->code)); + set_car(sc->t2_2, fx_call(sc, cddr(sc->code))); + set_car(sc->t2_1, sc->args); + sc->args = sc->t2_1; + sc->code = opt1_cfunc(sc->code); + apply_c_function_star(sc); +} + + +static void op_safe_c_ps(s7_scheme *sc) +{ + push_stack_no_args_direct(sc, OP_SAFE_C_PS_1); /* got to wait in this case */ + sc->code = cadr(sc->code); +} + +static void op_safe_c_ps_1(s7_scheme *sc) +{ + set_car(sc->t2_2, lookup(sc, caddr(sc->code))); + set_car(sc->t2_1, sc->value); + sc->value = fn_proc(sc->code)(sc, sc->t2_1); +} + +static void op_safe_c_ps_mv(s7_scheme *sc) /* (define (hi a) (+ (values 1 2) a)) from safe_c_ps_1 */ +{ + sc->args = pair_append(sc, sc->value, list_1(sc, lookup(sc, caddr(sc->code)))); /* don't assume sc->value can be used as sc->args here! */ + sc->code = c_function_base(opt1_cfunc(sc->code)); + /* we know it's a c function here, but there are 3 choices (c_function, c_function_star, no_rst_no_req_function) + * sc->value = fn_proc(sc->code)(sc, sc->args) might not check argnum + */ +} + +static void op_safe_c_sp(s7_scheme *sc) +{ + s7_pointer args = cdr(sc->code); + check_stack_size(sc); + push_stack(sc, (opcode_t)T_Op(opt1_any(args)), lookup(sc, car(args)), sc->code); + sc->code = cadr(args); +} + +static void op_safe_c_sp_1(s7_scheme *sc) +{ + /* we get here from many places (op_safe_c_sp for example), but all are safe */ + sc->value = fn_proc(sc->code)(sc, with_list_t2(sc->args, sc->value)); +} + +static void op_safe_c_sp_mv(s7_scheme *sc) +{ + sc->args = cons(sc, sc->args, sc->value); /* not ulist */ + sc->code = c_function_base(opt1_cfunc(sc->code)); +} + +static void op_safe_add_sp_1(s7_scheme *sc) +{ + if ((is_t_integer(sc->args)) && (is_t_integer(sc->value))) + sc->value = add_if_overflow_to_real_or_big_integer(sc, integer(sc->args), integer(sc->value)); + else sc->value = add_p_pp(sc, sc->args, sc->value); +} + +static void op_safe_multiply_sp_1(s7_scheme *sc) +{ + if ((is_t_real(sc->args)) && (is_t_real(sc->value))) + sc->value = make_real(sc, real(sc->args) * real(sc->value)); + else sc->value = multiply_p_pp(sc, sc->args, sc->value); +} + +static void op_safe_c_pc(s7_scheme *sc) +{ + s7_pointer args = cdr(sc->code); + check_stack_size(sc); /* b dyn */ + push_stack(sc, OP_SAFE_C_PC_1, opt3_con(args), sc->code); + sc->code = car(args); +} + +static void op_safe_c_pc_mv(s7_scheme *sc) +{ + sc->args = pair_append(sc, sc->value, list_1(sc, sc->args)); /* not plist! sc->value is not reusable */ + sc->code = c_function_base(opt1_cfunc(sc->code)); +} + +static void op_safe_c_pc_1(s7_scheme *sc) {sc->value = fn_proc(sc->code)(sc, with_list_t2(sc->value, sc->args));} + +static void op_safe_c_cp(s7_scheme *sc) +{ + s7_pointer args = cdr(sc->code); + /* it's possible in a case like this to overflow the stack -- s7test has a deeply + * nested expression involving (+ c (+ c (+ ... ))) all opt'd as safe_c_cp -- if we're close + * to the stack end at the start, it runs off the end. Normally the stack increase in + * the reader protects us, but a call/cc can replace the original stack with a much smaller one. + */ + check_stack_size(sc); + push_stack(sc, (opcode_t)T_Op(opt1_any(args)), opt3_any(args), sc->code); /* to safe_add_sp_1 for example */ + sc->code = cadr(args); +} + +static Inline void inline_op_safe_c_s(s7_scheme *sc) /* called twice in eval c/cl_s many hits */ +{ + sc->value = fn_proc(sc->code)(sc, with_list_t1(lookup(sc, cadr(sc->code)))); +} +/* if op_safe_c_t added and set in fx_tree_in, we get a few hits, but nothing significant. + * if that had worked, it would be interesting to set opt1(cdr) to the fx_tree fx_proc, (init to fx_c_s), then call that here. + * opt1(cdr) is not used here, opt3_byte happens a few times, but opt2_direct clobbers opt2_fx sometimes + * (also need fx_annotate cdr(expr) in optimize_c_function_one_arg) + */ + +static Inline void inline_op_safe_c_ss(s7_scheme *sc) /* called twice in eval c/cl_ss many hits */ +{ + sc->value = fn_proc(sc->code)(sc, with_list_t2(lookup(sc, cadr(sc->code)), lookup(sc, opt2_sym(cdr(sc->code))))); +} + +static void op_safe_c_sc(s7_scheme *sc) +{ + sc->value = fn_proc(sc->code)(sc, with_list_t2(lookup(sc, cadr(sc->code)), opt2_con(cdr(sc->code)))); +} + +static void op_cl_a(s7_scheme *sc) {sc->value = fn_proc(sc->code)(sc, with_list_t1(fx_call(sc, cdr(sc->code))));} + +static inline void op_cl_aa(s7_scheme *sc) +{ + gc_protect_via_stack(sc, fx_call(sc, cdr(sc->code))); + set_car(sc->t2_2, fx_call(sc, cddr(sc->code))); + set_car(sc->t2_1, T_Ext(stack_protected1(sc))); + unstack(sc); + sc->value = fn_proc(sc->code)(sc, sc->t2_1); +} + +static void op_cl_fa(s7_scheme *sc) +{ + s7_pointer code = cdadr(sc->code); + set_car(sc->t2_2, fx_call(sc, cddr(sc->code))); + set_car(sc->t2_1, make_closure_gc_checked(sc, car(code), cdr(code), T_CLOSURE | ((!s7_is_proper_list(sc, car(sc->code))) ? T_COPY_ARGS : 0), CLOSURE_ARITY_NOT_SET)); + /* arg1 lambda can be any arity, but it must be applicable to one arg (the "a" above) */ + /* was checking is_symbol(car(sc->code) i.e. is arglist a symbol, but we need T_COPY_ARGS if arglist is '(a . b) as well (can this happen here?) */ + sc->value = fn_proc(sc->code)(sc, sc->t2_1); +} + +static inline void op_map_for_each_fa(s7_scheme *sc) +{ + s7_pointer f = cddr(sc->code), code = sc->code; + sc->value = fx_call(sc, f); + if (is_null(sc->value)) + sc->value = (fn_proc_unchecked(code)) ? sc->unspecified : sc->nil; + else + { + sc->code = opt3_pair(code); /* cdadr(code); */ + f = make_closure_gc_checked(sc, car(sc->code), cdr(sc->code), T_CLOSURE, 1); /* arity=1 checked in optimizer */ + sc->value = (fn_proc_unchecked(code)) ? g_for_each_closure(sc, f, sc->value) : g_map_closure(sc, f, sc->value); + } +} + +static void op_map_for_each_faa(s7_scheme *sc) +{ + s7_pointer f = cddr(sc->code), code = sc->code; + sc->value = fx_call(sc, f); + sc->args = fx_call(sc, cdr(f)); + if ((is_null(sc->value)) || (is_null(sc->args))) + sc->value = (fn_proc_unchecked(code)) ? sc->unspecified : sc->nil; + else + { + sc->code = opt3_pair(code); /* cdadr(code); */ + f = make_closure_gc_checked(sc, car(sc->code), cdr(sc->code), T_CLOSURE, 2); /* arity=2 checked in optimizer */ + sc->value = (fn_proc_unchecked(code)) ? g_for_each_closure_2(sc, f, sc->value, sc->args) : g_map_closure_2(sc, f, sc->value, sc->args); + } +} + +static void op_cl_na(s7_scheme *sc) +{ + s7_pointer val = safe_list_if_possible(sc, opt3_arglen(cdr(sc->code))); + if (in_heap(val)) gc_protect_via_stack(sc, val); + for (s7_pointer args = cdr(sc->code), p = val; is_pair(args); args = cdr(args), p = cdr(p)) + set_car(p, fx_call(sc, args)); + sc->value = fn_proc(sc->code)(sc, val); + if (!in_heap(val)) + clear_list_in_use(val); + else + /* the fn_proc call might push its own op (e.g. for-each/map) so we have to check for that */ + if (stack_top_op(sc) == OP_GC_PROTECT) unstack(sc); +} + +static void op_cl_sas(s7_scheme *sc) +{ + set_car(sc->t3_2, fx_call(sc, cddr(sc->code))); + set_car(sc->t3_1, lookup(sc, cadr(sc->code))); + set_car(sc->t3_3, lookup(sc, cadddr(sc->code))); + sc->value = fn_proc(sc->code)(sc, sc->t3_1); +} + +static inline void op_safe_c_pp(s7_scheme *sc) +{ + s7_pointer args = cdr(sc->code); + check_stack_size(sc); + /* has_fx check here is slower, we assume car(args) below is a pair (else cp/sp/ap?) */ + if ((has_gx(args)) && (symbol_ctr(caar(args)) == 1)) + { + sc->args = fx_proc_unchecked(args)(sc, car(args)); + push_stack_direct(sc, (opcode_t)T_Op(opt1_any(args))); /* args = first value, func(args, value) if no mv */ + sc->code = cadr(args); + } + else + { + push_stack_no_args_direct(sc, OP_SAFE_C_PP_1); /* first arg = p, if mv -> op_safe_c_pp_3 */ + sc->code = car(args); + } +} + +static void op_safe_c_pp_1(s7_scheme *sc) +{ + /* it is much slower to check has_gx here! */ + push_stack(sc, (opcode_t)T_Op(opt1_any(cdr(sc->code))), sc->value, sc->code); /* args[i.e. sc->value] = first value, func(args, value) if no mv */ + sc->code = caddr(sc->code); +} + +static void op_safe_c_pp_3_mv(s7_scheme *sc) +{ + /* we get here if the first arg returned multiple values */ + push_stack(sc, OP_SAFE_C_PP_5, copy_proper_list(sc, sc->value), sc->code); /* copy is needed here */ + sc->code = caddr(sc->code); +} + +static void op_safe_c_pp_5(s7_scheme *sc) +{ + /* 1 mv, 2 normal (else mv->6), sc->args was copied above (and this is a safe c function so its args are in no danger) */ + if (is_null(sc->args)) + sc->args = list_1(sc, sc->value); /* plist here and below, but this is almost never called */ + else + { + s7_pointer p; + for (p = sc->args; is_pair(cdr(p)); p = cdr(p)); + set_cdr(p, list_1(sc, sc->value)); + } + sc->code = c_function_base(opt1_cfunc(sc->code)); +} + +static void op_safe_c_pp_6_mv(s7_scheme *sc) /* both args mv */ +{ + s7_pointer p; + for (p = cdr(sc->args); is_pair(cdr(p)); p = cdr(p)); /* we used to copy here: sc->args = pair_append(sc, sc->args, sc->value); */ + set_cdr(p, sc->value); + /* fn_proc(sc->code) here is g_add_2, but we have any number of args from a values call + * the original (unoptimized) function is c_function_base(opt1_cfunc(sc->code)) + * (let () (define (hi) (+ (values 1 2) (values 3 4))) (hi)) -> 10 + */ + sc->code = c_function_base(opt1_cfunc(sc->code)); +} + +static void op_safe_c_3p(s7_scheme *sc) +{ + check_stack_size(sc); + push_stack_no_args_direct(sc, OP_SAFE_C_3P_1); + sc->code = cadr(sc->code); +} + +static void op_safe_c_3p_1(s7_scheme *sc) +{ + sc->args = sc->value; /* possibly fx/gx? and below */ + push_stack_direct(sc, OP_SAFE_C_3P_2); + sc->code = caddr(sc->code); +} + +static void op_safe_c_3p_1_mv(s7_scheme *sc) /* here only if sc->value is mv */ +{ + sc->args = sc->value; + push_stack_direct(sc, OP_SAFE_C_3P_2_MV); + sc->code = caddr(sc->code); +} + +static void op_safe_c_3p_2(s7_scheme *sc) +{ + gc_protect_via_stack(sc, sc->value); + check_stack_size(sc); + push_stack_direct(sc, OP_SAFE_C_3P_3); + sc->code = cadddr(sc->code); +} + +static void op_safe_c_3p_2_mv(s7_scheme *sc) /* here from 1 + 2mv, or 1_mv with 2 or 2mv */ +{ + gc_protect_via_stack(sc, sc->value); + push_stack_direct(sc, OP_SAFE_C_3P_3_MV); + sc->code = cadddr(sc->code); +} + +static void op_safe_c_3p_3(s7_scheme *sc) +{ + set_car(sc->t3_3, sc->value); + set_car(sc->t3_1, sc->args); + set_car(sc->t3_2, stack_protected1(sc)); + unstack(sc); + sc->value = fn_proc(sc->code)(sc, sc->t3_1); +} + +static void op_safe_c_3p_3_mv(s7_scheme *sc) +{ + s7_pointer p; + s7_pointer p1 = ((is_pair(sc->args)) && (car(sc->args) == sc->unused)) ? cdr(sc->args) : list_1(sc, sc->args); + s7_pointer ps1 = stack_protected1(sc); + s7_pointer p2 = ((is_pair(ps1)) && (car(ps1) == sc->unused)) ? cdr(ps1) : list_1(sc, ps1); + s7_pointer p3 = ((is_pair(sc->value)) && (car(sc->value) == sc->unused)) ? cdr(sc->value) : list_1(sc, sc->value); + unstack(sc); + for (p = p1; is_pair(cdr(p)); p = cdr(p)); + set_cdr(p, p2); + for (p = cdr(p); is_pair(cdr(p)); p = cdr(p)); + set_cdr(p, p3); + sc->args = p1; + sc->code = c_function_base(opt1_cfunc(sc->code)); +} + +static Inline bool inline_collect_np_args(s7_scheme *sc, opcode_t op, s7_pointer args) /* called (all hits:)op_any_c_np_1/mv and eval, tlet (cb/set) */ +{ + sc->args = args; + for (s7_pointer p = sc->code; is_pair(p); p = cdr(p)) + if (has_fx(p)) + sc->args = cons(sc, sc->value = fx_call(sc, p), sc->args); /* reversed before apply in OP_ANY_C_NP_1 */ + else + if ((has_gx(p)) && (symbol_ctr(caar(p)) == 1)) + sc->args = cons(sc, sc->value = fx_proc_unchecked(p)(sc, car(p)), sc->args); + else + { + push_stack(sc, op, sc->args, cdr(p)); + sc->code = T_Pair(car(p)); + return(true); + } + return(false); +} + +static bool collect_np_args(s7_scheme *sc, opcode_t op, s7_pointer args) {return(inline_collect_np_args(sc, op, args));} + +static /* inline */ bool op_any_c_np(s7_scheme *sc) /* code: (func . args) where at least one arg is not fxable */ +{ + sc->args = sc->nil; + for (s7_pointer p = cdr(sc->code); is_pair(p); p = cdr(p)) + if (has_fx(p)) + sc->args = cons(sc, sc->value = fx_call(sc, p), sc->args); /* reversed before apply in OP_ANY_C_NP_1 */ + else + if ((has_gx(p)) && (symbol_ctr(caar(p)) == 1)) + sc->args = cons(sc, sc->value = fx_proc_unchecked(p)(sc, car(p)), sc->args); + else + { + if (sc->op_stack_now >= sc->op_stack_end) + resize_op_stack(sc); + push_op_stack(sc, sc->code); + check_stack_size(sc); + push_stack(sc, ((intptr_t)((is_pair(cdr(p))) ? OP_ANY_C_NP_1 : OP_ANY_C_NP_2)), sc->args, cdr(p)); + sc->code = T_Pair(car(p)); + return(true); + } + /* here fx/gx got all the args */ + sc->args = proper_list_reverse_in_place(sc, sc->args); + sc->value = fn_proc(sc->code)(sc, sc->args); + return(false); +} + +static Inline bool inline_op_any_c_np_1(s7_scheme *sc) /* called once in eval, tlet (cb/set) */ +{ + /* in-coming sc->value has the current arg value, sc->args is all previous args, sc->code is on op-stack */ + if (inline_collect_np_args(sc, OP_ANY_C_NP_1, cons(sc, sc->value, sc->args))) + return(true); + sc->args = proper_list_reverse_in_place(sc, sc->args); + sc->code = pop_op_stack(sc); + sc->value = fn_proc(sc->code)(sc, sc->args); + return(false); +} + +static void op_any_c_np_2(s7_scheme *sc) +{ + sc->args = proper_list_reverse_in_place(sc, sc->args = cons(sc, sc->value, sc->args)); + sc->code = pop_op_stack(sc); + sc->value = fn_proc(sc->code)(sc, sc->args); +} + +static bool op_any_c_np_mv(s7_scheme *sc) +{ + /* we're looping through fp cases here, so sc->value can be non-mv after the first */ + if (collect_np_args(sc, OP_ANY_C_NP_MV, (is_multiple_value(sc->value)) ? revappend(sc, sc->value, sc->args) : cons(sc, sc->value, sc->args))) + return(true); + sc->args = proper_list_reverse_in_place(sc, sc->args); + sc->code = pop_op_stack(sc); + sc->code = c_function_base(opt1_cfunc(sc->code)); + return(false); +} + +static void op_any_closure_np(s7_scheme *sc) +{ + s7_pointer p = cdr(sc->code); + check_stack_size(sc); + if (sc->op_stack_now >= sc->op_stack_end) + resize_op_stack(sc); + push_op_stack(sc, sc->code); + if (has_fx(p)) + { + sc->args = fx_call(sc, p); + sc->args = list_1(sc, sc->args); + for (p = cdr(p); (is_pair(p)) && (has_fx(p)); p = cdr(p)) + sc->args = cons_unchecked(sc, fx_call(sc, p), sc->args); + } + else sc->args = sc->nil; + push_stack(sc, ((intptr_t)((is_pair(cdr(p))) ? OP_ANY_CLOSURE_NP_1 : OP_ANY_CLOSURE_NP_2)), sc->args, cdr(p)); + sc->code = T_Pair(car(p)); +} + +static void op_any_closure_np_end(s7_scheme *sc) +{ + s7_pointer x, z, f; + uint64_t id; + + sc->args = proper_list_reverse_in_place(sc, sc->args); /* needed in either case -- closure_args(f) is not reversed */ + sc->code = pop_op_stack(sc); + f = opt1_lambda(sc->code); + + if (is_safe_closure(f)) + { + id = ++sc->let_number; + set_curlet(sc, closure_let(f)); + let_set_id(sc->curlet, id); + for (x = let_slots(sc->curlet), z = sc->args; tis_slot(x); x = next_slot(x), z = cdr(z)) + { + slot_set_value(x, car(z)); + symbol_set_local_slot(slot_symbol(x), id, x); + /* don't free sc->args -- it might be needed in the error below */ + } + if (tis_slot(x)) + error_nr(sc, sc->wrong_number_of_args_symbol, set_elist_3(sc, not_enough_arguments_string, sc->code, sc->args)); + } + else + { + s7_pointer p = closure_args(f), last_slot; + s7_pointer e = inline_make_let(sc, closure_let(f)); + sc->z = e; + id = let_id(e); + last_slot = make_slot(sc, car(p), car(sc->args)); + slot_set_next(last_slot, slot_end); + let_set_slots(e, last_slot); + symbol_set_local_slot(car(p), id, last_slot); + for (p = cdr(p), z = cdr(sc->args); is_pair(p); p = cdr(p), z = cdr(z)) + last_slot = inline_add_slot_at_end(sc, id, last_slot, car(p), car(z)); /* sets last_slot, don't free sc->args -- used below */ + set_curlet(sc, e); + sc->z = sc->unused; + if (is_pair(p)) + error_nr(sc, sc->wrong_number_of_args_symbol, set_elist_3(sc, not_enough_arguments_string, sc->code, sc->args)); + } + if (is_pair(z)) /* these checks are needed because multiple-values might evade earlier arg num checks */ + error_nr(sc, sc->wrong_number_of_args_symbol, set_elist_3(sc, too_many_arguments_string, sc->code, sc->args)); + + sc->code = closure_body(f); + if_pair_set_up_begin(sc); +} + +static bool op_safe_c_ap(s7_scheme *sc) +{ + s7_pointer code = cdr(sc->code); + s7_pointer val = cdr(code); + if ((has_gx(val)) && (symbol_ctr(caar(val)) == 1)) + { + val = fx_proc_unchecked(val)(sc, car(val)); + gc_protect_via_stack(sc, val); + set_car(sc->t2_1, fx_call(sc, code)); + set_car(sc->t2_2, val); + unstack(sc); + sc->value = fn_proc(sc->code)(sc, sc->t2_1); + return(false); + } + check_stack_size(sc); + sc->args = fx_call(sc, code); + push_stack_direct(sc, (opcode_t)T_Op(opt1_any(code))); /* safe_c_sp cases, mv->safe_c_sp_mv */ + sc->code = car(val); + return(true); +} + +static bool op_safe_c_pa(s7_scheme *sc) +{ + s7_pointer args = cdr(sc->code); + if ((has_gx(args)) && (symbol_ctr(caar(args)) == 1)) + { + gc_protect_via_stack(sc, fx_proc_unchecked(args)(sc, car(args))); + set_car(sc->t2_2, fx_call(sc, cdr(args))); + set_car(sc->t2_1, stack_protected1(sc)); + unstack(sc); + sc->value = fn_proc(sc->code)(sc, sc->t2_1); + return(false); + } + check_stack_size(sc); + push_stack_no_args_direct(sc, OP_SAFE_C_PA_1); + sc->code = car(args); + return(true); +} + +static void op_safe_c_pa_1(s7_scheme *sc) +{ + sc->args = sc->value; /* fx* might change sc->value? */ + set_car(sc->t2_2, fx_call(sc, cddr(sc->code))); + set_car(sc->t2_1, sc->args); + sc->value = fn_proc(sc->code)(sc, sc->t2_1); +} + +static void op_safe_c_pa_mv(s7_scheme *sc) +{ + s7_pointer p, val = copy_proper_list(sc, sc->value); /* this is necessary since the fx_proc below can clobber sc->value */ + gc_protect_via_stack(sc, val); + for (p = val; is_pair(cdr(p)); p = cdr(p)); /* must be more than 1 member of list or it's not mv */ + sc->args = fx_call(sc, cddr(sc->code)); + cdr(p) = set_plist_1(sc, sc->args); /* do we need to copy sc->args if it is immutable (i.e. plist)? */ + sc->args = val; + unstack(sc); + sc->code = c_function_base(opt1_cfunc(sc->code)); +} + +static void op_c_na(s7_scheme *sc) /* (set-cdr! lst ()) */ +{ + s7_pointer new_args = make_list(sc, opt3_arglen(cdr(sc->code)), sc->unused); + gc_protect_via_stack(sc, new_args); + for (s7_pointer args = cdr(sc->code), p = new_args; is_pair(args); args = cdr(args), p = cdr(p)) + set_car(p, fx_call(sc, args)); + unstack(sc); + sc->temp3 = new_args; /* desperation? */ + sc->value = fn_proc(sc->code)(sc, new_args); + sc->temp3 = sc->unused; +} + +static void op_c_a(s7_scheme *sc) +{ + sc->value = fx_call(sc, cdr(sc->code)); /* gc protect result before list_1 */ + sc->args = list_1(sc, sc->value); + sc->value = fn_proc(sc->code)(sc, sc->args); +} + +static void op_c_p(s7_scheme *sc) +{ + push_stack_no_args_direct(sc, OP_C_P_1); + sc->code = T_Pair(cadr(sc->code)); +} + +static void op_c_p_mv(s7_scheme *sc) /* op_c_p_1 -> mv case: (define (hi) (format (values #f "~A ~D" 1 2))) */ +{ + sc->code = c_function_base(opt1_cfunc(sc->code)); /* see comment above */ + sc->args = copy_proper_list(sc, sc->value); +} + +static inline void op_c_ss(s7_scheme *sc) +{ + sc->args = list_2(sc, lookup(sc, cadr(sc->code)), lookup(sc, caddr(sc->code))); + sc->value = fn_proc(sc->code)(sc, sc->args); +} + +static void op_c_ap(s7_scheme *sc) +{ + sc->args = fx_call(sc, cdr(sc->code)); + push_stack_direct(sc, OP_C_AP_1); /* op_c_ap_1 sends us to apply which calls check_stack_size I think */ + sc->code = caddr(sc->code); +} + +static void op_c_ap_mv(s7_scheme *sc) +{ + clear_multiple_value(sc->value); + sc->args = cons(sc, sc->args, sc->value); + sc->code = c_function_base(opt1_cfunc(sc->code)); +} + +static void op_c_aa(s7_scheme *sc) +{ + gc_protect_via_stack(sc, fx_call(sc, cdr(sc->code))); + set_stack_protected2(sc, fx_call(sc, cddr(sc->code))); + sc->value = list_2(sc, stack_protected1(sc), stack_protected2(sc)); + unstack(sc); /* fn_proc here is unsafe so clear stack first */ + sc->value = fn_proc(sc->code)(sc, sc->value); +} + +static inline void op_c_s(s7_scheme *sc) +{ + sc->args = list_1(sc, lookup(sc, cadr(sc->code))); + sc->value = fn_proc(sc->code)(sc, sc->args); +} + +static Inline void inline_op_apply_ss(s7_scheme *sc) /* called once in eval, sg: all time spent in proper_list check */ +{ + sc->args = lookup(sc, opt2_sym(sc->code)); + if (!s7_is_proper_list(sc, sc->args)) + error_nr(sc, sc->syntax_error_symbol, set_elist_2(sc, wrap_string(sc, "apply: improper list of arguments: ~S", 37), sc->args)); + sc->code = lookup(sc, cadr(sc->code)); /* global search here was slower (e.g. tauto) */ + if (needs_copied_args(sc->code)) + sc->args = copy_proper_list(sc, sc->args); +} + +static void op_apply_sa(s7_scheme *sc) +{ + s7_pointer p = cdr(sc->code); + sc->args = fx_call(sc, cdr(p)); + if (!s7_is_proper_list(sc, sc->args)) + error_nr(sc, sc->syntax_error_symbol, set_elist_2(sc, wrap_string(sc, "apply: improper list of arguments: ~S", 37), sc->args)); + sc->code = lookup_global(sc, car(p)); + if (needs_copied_args(sc->code)) + sc->args = copy_proper_list(sc, sc->args); +} + +static void op_apply_sl(s7_scheme *sc) +{ + s7_pointer p = cdr(sc->code); + sc->args = fx_call(sc, cdr(p)); + sc->code = lookup_global(sc, car(p)); +} + +static bool op_pair_pair(s7_scheme *sc) +{ + if (!is_pair(car(sc->code))) + { + clear_optimize_op(sc->code); + return(false); + } + if (sc->stack_end >= (sc->stack_resize_trigger - 8)) + check_for_cyclic_code(sc, sc->code); /* calls resize_stack */ + push_stack_no_args_direct(sc, OP_EVAL_ARGS); /* eval args goes immediately to cdr(sc->code) */ + /* don't put check_stack_size here! */ + push_stack_no_args(sc, OP_EVAL_ARGS, car(sc->code)); + sc->code = caar(sc->code); + return(true); +} + +static bool op_pair_sym(s7_scheme *sc) +{ + if (!is_symbol(car(sc->code))) + { + clear_optimize_op(sc->code); + return(false); + } + sc->value = lookup_global(sc, car(sc->code)); + return(true); +} + +static void op_eval_args2(s7_scheme *sc) +{ + sc->code = pop_op_stack(sc); + sc->args = (is_null(sc->args)) ? list_1(sc, sc->value) : proper_list_reverse_in_place(sc, cons(sc, sc->value, sc->args)); +} + +static void op_eval_args3(s7_scheme *sc) +{ + s7_pointer val = sc->code; + if (is_symbol(val)) + val = lookup_checked(sc, val); + sc->args = proper_list_reverse_in_place(sc, cons_unchecked(sc, val, cons(sc, sc->value, sc->args))); + sc->code = pop_op_stack(sc); +} + +static void op_eval_args5(s7_scheme *sc) /* sc->value is the last arg, sc->code is the previous */ +{ + sc->args = proper_list_reverse_in_place(sc, cons_unchecked(sc, sc->value, cons(sc, sc->code, sc->args))); + sc->code = pop_op_stack(sc); +} + +static bool eval_args_no_eval_args(s7_scheme *sc) +{ + if (is_any_macro(sc->value)) + { + if (!s7_is_proper_list(sc, cdr(sc->code))) + error_nr(sc, sc->syntax_error_symbol, set_elist_2(sc, wrap_string(sc, "improper list of arguments: ~S", 30), sc->code)); + sc->args = cdr(sc->code); + if (is_symbol(car(sc->code))) /* not ((f p) args...) where (f p) has returned a macro, op_macro_d assumes car is a symbol */ + { + if (is_macro(sc->value)) + set_optimize_op(sc->code, fixup_macro_d(sc, OP_MACRO_D, sc->value)); + else + if (is_macro_star(sc->value)) + set_optimize_op(sc->code, fixup_macro_d(sc, OP_MACRO_STAR_D, sc->value)); + } + sc->code = sc->value; + return(true); + } + if (is_syntactic_pair(sc->code)) /* (define progn begin) (progn (display "hi") (+ 1 23)) */ + sc->cur_op = optimize_op(sc->code); + else + { + sc->cur_op = syntax_opcode(sc->value); + if ((is_symbol(car(sc->code))) && /* don't opt pair to syntax op if sc->value is actually an arg not the op! ((write and)) should not be op_and */ + ((car(sc->code) == syntax_symbol(sc->value)) || (lookup_global(sc, car(sc->code)) == sc->value))) + pair_set_syntax_op(sc->code, sc->cur_op); + /* weird that sc->cur_op setting above seems ok, but OP_PAIR_PAIR hangs?? */ + } + return(false); +} + +static inline void eval_last_arg(s7_scheme *sc, s7_pointer car_code) +{ + /* here we've reached the last arg (sc->code == nil), it is not a pair */ + if (!is_null(cdr(sc->code))) + improper_arglist_error_nr(sc); + sc->code = (is_symbol(car_code)) ? lookup_checked(sc, car_code) : car_code; /* this has to precede the set_type below */ + sc->args = (is_null(sc->args)) ? list_1(sc, sc->code) : proper_list_reverse_in_place(sc, cons(sc, sc->code, sc->args)); + sc->code = pop_op_stack(sc); +} + +static inline void eval_args_pair_car(s7_scheme *sc) +{ + s7_pointer code = cdr(sc->code); + if (sc->stack_end >= sc->stack_resize_trigger) + check_for_cyclic_code(sc, sc->code); /* calls resize_stack */ + if (is_null(code)) + push_stack_no_code(sc, OP_EVAL_ARGS2, sc->args); + else + { + if (!is_pair(code)) /* (= 0 '(1 . 2) . 3) */ + improper_arglist_error_nr(sc); + if ((is_null(cdr(code))) && + (!is_pair(car(code)))) + push_stack(sc, OP_EVAL_ARGS3, sc->args, car(code)); + else push_stack(sc, OP_EVAL_ARGS4, sc->args, code); + } + sc->code = car(sc->code); +} + +static bool eval_car_pair(s7_scheme *sc) +{ + s7_pointer code = sc->code, carc = car(sc->code); + + /* evaluate the inner list but that list can be circular: carc: #1=(#1# #1#)! and the cycle can be well-hidden -- #1=((#1 2) . 2) and other such stuff */ + if (sc->stack_end >= sc->stack_resize_trigger) + check_for_cyclic_code(sc, code); + + if (is_symbol_and_syntactic(car(carc))) + /* was checking for is_syntactic (pair or symbol) here but that can be confused by successive optimizer passes: (define (hi) (((lambda () list)) 1 2 3)) etc */ + { + if (!no_int_opt(code)) + { + if ((car(carc) == sc->lambda_symbol) && /* ((lambda ...) expr) */ + (is_pair(cddr(carc))) && (s7_is_proper_list(sc, cddr(carc)))) /* not dotted! */ + { + set_opt3_pair(code, cddr(carc)); + if ((is_null(cadr(carc))) && (is_null(cdr(code)))) + { + set_optimize_op(code, OP_F); /* ((lambda () ...)) */ + return(false); + } + if (is_pair(cadr(carc))) + { + if ((is_normal_symbol(caadr(carc))) && (!is_constant(sc, caadr(carc))) && + (is_pair(cdr(code))) && (is_fxable(sc, cadr(code)))) + { + set_opt3_sym(cdr(code), caadr(carc)); + if ((is_null(cdadr(carc))) && (is_null(cddr(code)))) + { + fx_annotate_args(sc, cdr(code), sc->curlet); /* ((lambda (x) ...) expr) */ + set_optimize_op(code, OP_F_A); + return(false); + } + if ((is_pair(cdadr(carc))) && (is_pair(cddr(code))) && (is_fxable(sc, caddr(code))) && + (is_null(cddadr(carc))) && (is_null(cdddr(code))) && + (is_normal_symbol(cadadr(carc))) && (!is_constant(sc, cadadr(carc))) && (caadr(carc) != cadadr(carc))) + { + fx_annotate_args(sc, cdr(code), sc->curlet); + set_optimize_op(code, OP_F_AA); /* ((lambda (x y) ...) expr exor) */ + return(false); + }} + set_optimize_op(code, OP_F_NP); + }} + set_no_int_opt(code); + } + /* ((if op1 op2) args...) is another somewhat common case */ + if ((car(carc) == sc->quote_symbol) && /* ('and #f) */ + ((!is_pair(cdr(carc))) || /* ((quote . #\h) (2 . #\i)) ! */ + (is_symbol_and_syntactic(cadr(carc))))) /* ('or #f) but not ('#_or #f) */ + apply_error_nr(sc, (is_pair(cdr(carc))) ? cadr(carc) : carc, cdr(code)); + + push_stack_no_args(sc, OP_EVAL_ARGS, code); + sc->code = carc; + if (!no_cell_opt(carc)) + { + if ((car(carc) == sc->if_symbol) && + (is_pair(cdr(code))) && /* check that we got one or two args */ + ((is_null(cddr(code))) || + ((is_pair(cddr(code))) && (is_null(cdddr(code)))))) + { + check_if(sc, carc); + if ((fx_function[optimize_op(carc)]) && + (is_fxable(sc, cadr(code))) && + ((is_null(cddr(code))) || (is_fxable(sc, caddr(code))))) /* checked cdddr above */ + { + fx_annotate_args(sc, cdr(code), sc->curlet); + set_fx_direct(code, fx_function[optimize_op(carc)]); + set_optimize_op(code, (is_null(cddr(code))) ? OP_A_A : OP_A_AA); + return(false); /* goto eval in trailers */ + }} + set_no_cell_opt(carc); + } + sc->cur_op = (opcode_t)symbol_syntax_op_checked(sc->code); + pair_set_syntax_op(sc->code, sc->cur_op); + return(true); + } + push_stack_no_args(sc, OP_EVAL_ARGS, code); + if ((is_pair(cdr(code))) && (is_optimized(carc))) + { + if ((fx_function[optimize_op(carc)]) && + (is_fxable(sc, cadr(code))) && + ((is_null(cddr(code))) || + ((is_fxable(sc, caddr(code))) && (is_null(cdddr(code)))))) + { + fx_annotate_args(sc, cdr(code), sc->curlet); + set_fx_direct(code, fx_function[optimize_op(carc)]); + set_optimize_op(code, (is_null(cddr(code))) ? OP_A_A : OP_A_AA); + sc->code = carc; + return(false); /* goto eval in trailers */ + } + if ((is_null(cddr(code))) && (is_symbol(cadr(code)))) /* ((x 'f82) x) in tstar for example */ + { + set_optimize_op(code, OP_P_S); + set_opt3_sym(code, cadr(code)); + } + /* possible op OP_P_ALL_A runs into opt2 fx overwrites in a case like ((values set!) x 32) */ + else set_optimize_op(code, OP_PAIR_PAIR); + } + else set_optimize_op(code, OP_PAIR_PAIR); + push_stack_no_args(sc, OP_EVAL_ARGS, carc); + sc->code = car(carc); + return(false); +} + +static /* inline */ bool eval_args_last_arg(s7_scheme *sc) /* inline: no diff tmisc, small diff tmac (3) */ +{ + s7_pointer car_code = car(sc->code); /* we're at the last arg, sc->value is the previous one, not yet saved in the args list */ + if (is_pair(car_code)) + { + if (sc->stack_end >= sc->stack_resize_trigger) + check_for_cyclic_code(sc, sc->code); + push_stack(sc, OP_EVAL_ARGS5, sc->args, sc->value); + sc->code = car_code; + return(true); + } + /* get the last arg */ + sc->code = (is_symbol(car_code)) ? lookup_checked(sc, car_code) : car_code; + /* get the current arg, which is not a list */ + sc->args = proper_list_reverse_in_place(sc, cons_unchecked(sc, sc->code, cons(sc, sc->value, sc->args))); + sc->code = pop_op_stack(sc); + return(false); +} + +static goto_t trailers(s7_scheme *sc) +{ + s7_pointer code = sc->code; + set_current_code(sc, code); + if (is_pair(code)) + { + s7_pointer carc = car(code); + if (is_symbol(carc)) + { + /* car is a symbol, sc->code a list */ + if (is_syntactic_symbol(carc)) + { + sc->cur_op = (opcode_t)symbol_syntax_op_checked(code); + pair_set_syntax_op(sc->code, sc->cur_op); + return(goto_top_no_pop); + } + sc->value = lookup_global(sc, carc); + set_optimize_op(code, OP_PAIR_SYM); /* mostly stuff outside functions (unopt) */ + return(goto_eval_args_top); + } + if (is_pair(carc)) /* ((if x y z) a b) etc */ + return((eval_car_pair(sc)) ? goto_top_no_pop : goto_eval); + + /* here we can get syntax objects like quote */ + if (is_syntax(carc)) + { + sc->cur_op = syntax_opcode(carc); + pair_set_syntax_op(sc->code, sc->cur_op); + return(goto_top_no_pop); + } + /* car must be the function to be applied, or (for example) a syntax variable like quote that has been used locally */ + set_optimize_op(code, OP_PAIR_ANY); /* usually an error: (#\a) etc, might be (#(0) 0) */ + sc->value = T_Ext(carc); + return(goto_eval_args_top); + } + if (is_symbol(code)) + { + sc->value = lookup_checked(sc, code); + set_optimize_op(code, (is_keyword(code)) ? OP_CONSTANT : OP_SYMBOL); + } + else + { + sc->value = T_Ext(code); + set_optimize_op(code, OP_CONSTANT); + } + return(goto_start); +} + + +/* ---------------- reader funcs for eval ---------------- */ +static void back_up_stack(s7_scheme *sc) +{ + opcode_t top_op = stack_top_op(sc); + if (top_op == OP_READ_DOT) + { + pop_stack(sc); + top_op = stack_top_op(sc); + } + if ((top_op == OP_READ_VECTOR) || + (top_op == OP_READ_BYTE_VECTOR) || + (top_op == OP_READ_INT_VECTOR) || + (top_op == OP_READ_FLOAT_VECTOR)) + { + pop_stack(sc); + top_op = stack_top_op(sc); + } + if (top_op == OP_READ_QUOTE) + pop_stack(sc); +} + +static token_t read_block_comment(s7_scheme *sc, s7_pointer pt) +{ + /* block comments in #| ... |# + * since we ignore everything until the |#, internal semicolon comments are ignored, + * meaning that ;|# is as effective as |# + */ + const char *str, *orig_str, *p, *pend; + if (is_file_port(pt)) + { + char last_char = ' '; + while (true) + { + int32_t c = fgetc(port_file(pt)); + if (c == EOF) + error_nr(sc, sc->read_error_symbol, + set_elist_1(sc, wrap_string(sc, "unexpected end of input while reading #|", 40))); + if ((c == '#') && + (last_char == '|')) + break; + last_char = c; + if (c == '\n') + port_line_number(pt)++; + } + return(token(sc)); + } + orig_str = (const char *)(port_data(pt) + port_position(pt)); + pend = (const char *)(port_data(pt) + port_data_size(pt)); + str = orig_str; + while (true) + { + p = strchr(str, (int)'|'); + if ((!p) || (p >= pend)) + { + port_position(pt) = port_data_size(pt); + error_nr(sc, sc->read_error_symbol, + set_elist_1(sc, wrap_string(sc, "unexpected end of input while reading #|", 40))); + } + if (p[1] == '#') + break; + str = (const char *)(p + 1); + } + port_position(pt) += (p - orig_str + 2); + /* now count newlines inside the comment */ + str = (const char *)orig_str; + pend = p; + while (true) + { + p = strchr(str, (int)'\n'); + if ((p) && (p < pend)) + { + port_line_number(pt)++; + str = (const char *)(p + 1); + } + else break; + } + return(token(sc)); +} + +static token_t read_excl_comment(s7_scheme *sc, s7_pointer pt) +{ + /* block comments in #! ... !# + * this is needed when an input file is treated as a script: + #!/home/bil/cl/snd + !# + (format #t "a test~%") + (exit) + * but very often the closing !# is omitted which is too bad + */ + int32_t c; + char last_char = ' '; + /* make it possible to override #! handling */ + for (s7_pointer reader = slot_value(sc->sharp_readers); is_pair(reader); reader = cdr(reader)) + if (s7_character(caar(reader)) == '!') + { + sc->strbuf[0] = (unsigned char)'!'; + return(TOKEN_SHARP_CONST); /* next stage notices any errors */ + } + /* not #! as block comment (for Guile I guess) */ + while ((c = inchar(pt)) != EOF) + { + if ((c == '#') && + (last_char == '!')) + break; + last_char = c; + } + if (c == EOF) + error_nr(sc, sc->read_error_symbol, + set_elist_1(sc, wrap_string(sc, "unexpected end of input while reading #!", 40))); + return(token(sc)); +} + +static token_t read_sharp(s7_scheme *sc, s7_pointer pt) +{ + int32_t c = inchar(pt); /* inchar can return EOF, so it can't be used directly as an index into the digits array */ + switch (c) + { + case EOF: + error_nr(sc, sc->read_error_symbol, set_elist_1(sc, wrap_string(sc, "unexpected '#' at end of input", 30))); + break; + + case '(': /* #(...) */ + sc->w = int_one; + return(TOKEN_VECTOR); + + case 'i': /* #i(...) */ + if (read_sharp(sc, pt) == TOKEN_VECTOR) + return(TOKEN_INT_VECTOR); + backchar('i', pt); + break; + + case 'r': /* #r(...) */ + if (read_sharp(sc, pt) == TOKEN_VECTOR) + return(TOKEN_FLOAT_VECTOR); + backchar('r', pt); + break; + + case 'u': /* #u(...) or #u8(...) */ + if (s7_peek_char(sc, pt) == chars[(int32_t)('8')]) /* backwards compatibility: #u8(...) == #u(...) */ + { + int32_t bc = inchar(pt); + if (s7_peek_char(sc, pt) == chars[(int32_t)('(')]) + { + inchar(pt); + sc->w = int_one; + return(TOKEN_BYTE_VECTOR); + } + backchar(bc, pt); + } + if (read_sharp(sc, pt) == TOKEN_VECTOR) + return(TOKEN_BYTE_VECTOR); + backchar('u', pt); + break; + + case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': + { + /* here we can get an overflow: #1231231231231232131D() */ + s7_int dims = digits[c]; + int32_t d = 0, loc = 0; + + sc->strbuf[loc++] = (unsigned char)c; + while (true) + { + s7_int dig; + d = inchar(pt); + if (d == EOF) + error_nr(sc, sc->read_error_symbol, + set_elist_1(sc, wrap_string(sc, "unexpected end of input while reading #n...", 43))); + dig = digits[d]; + if (dig >= 10) break; + dims = dig + (dims * 10); + if (dims <= 0) + { + sc->strbuf[loc++] = (unsigned char)d; + error_nr(sc, sc->read_error_symbol, + set_elist_3(sc, wrap_string(sc, "reading #~A...: ~A must be a positive integer", 45), + wrap_string(sc, sc->strbuf, loc), + wrap_integer(sc, dims))); + } + if (dims > sc->max_vector_dimensions) + { + sc->strbuf[loc++] = (unsigned char)d; + sc->strbuf[loc + 1] = '\0'; + error_nr(sc, sc->read_error_symbol, + set_elist_4(sc, wrap_string(sc, "reading #~A...: ~A is too large, (*s7* 'max-vector-dimensions): ~A", 66), + wrap_string(sc, sc->strbuf, loc), + wrap_integer(sc, dims), + wrap_integer(sc, sc->max_vector_dimensions))); + } + sc->strbuf[loc++] = (unsigned char)d; + } + sc->strbuf[loc++] = d; + if ((d == 'd') || (d == 'i') || (d == 'r') || (d == 'u')) + { + int32_t e = inchar(pt); + if (e == EOF) + error_nr(sc, sc->read_error_symbol, + set_elist_1(sc, wrap_string(sc, "unexpected end of input while reading #n()", 42))); + sc->strbuf[loc++] = (unsigned char)e; + if (e == '(') + { + sc->w = make_integer(sc, dims); + if (d == 'd') return(TOKEN_VECTOR); + if (d == 'r') return(TOKEN_FLOAT_VECTOR); + return((d == 'u') ? TOKEN_BYTE_VECTOR : TOKEN_INT_VECTOR); + }} + /* try to back out */ + for (d = loc - 1; d > 0; d--) + backchar(sc->strbuf[d], pt); + } + break; + + case ':': /* turn #: into : -- this is for compatibility with Guile, sigh. I just noticed that Rick is using this -- + * I'll just leave it alone, but that means : readers need to handle this case specially. + */ + sc->strbuf[0] = ':'; + return(TOKEN_ATOM); + + case '!': /* I don't think #! is special anymore -- maybe remove this code? */ + return(read_excl_comment(sc, pt)); + + case '|': + return(read_block_comment(sc, pt)); + } + sc->strbuf[0] = (unsigned char)c; + return(TOKEN_SHARP_CONST); /* next stage notices any errors */ +} + +static token_t read_comma(s7_scheme *sc, s7_pointer pt) +{ + /* here we probably should check for symbol names that start with "@": + (define-macro (hi @foo) `(+ ,@foo 1)): (hi 2) -> ;foo: unbound variable + but (define-macro (hi .foo) `(+ ,.foo 1)): (hi 2) -> 3 + and ambiguous: (define-macro (hi @foo . foo) `(list ,@foo)) + what about , @foo -- is the space significant? We accept ,@ foo. (Currently , @ says unbound variable @foo). + */ + int32_t c = inchar(pt); + if (c == '@') + return(TOKEN_AT_MARK); + if (c == EOF) + { + sc->strbuf[0] = ','; /* was '@' which doesn't make any sense */ + return(TOKEN_COMMA); /* was TOKEN_ATOM, which also doesn't seem sensible */ + } + backchar(c, pt); + return(TOKEN_COMMA); +} + +static token_t read_dot(s7_scheme *sc, s7_pointer pt) +{ + int32_t c = inchar(pt); + if (c != EOF) + { + backchar(c, pt); + if ((!char_ok_in_a_name[c]) && (c != 0)) + return(TOKEN_DOT); + } + else + { + sc->strbuf[0] = '.'; + return(TOKEN_DOT); + } + sc->strbuf[0] = '.'; + return(TOKEN_ATOM); /* i.e. something that can start with a dot like a number */ +} + +static token_t token(s7_scheme *sc) /* inline here is slower */ +{ + int32_t c = port_read_white_space(current_input_port(sc))(sc, current_input_port(sc)); + switch (c) + { + case '(': return(TOKEN_LEFT_PAREN); + case ')': return(TOKEN_RIGHT_PAREN); + case '.': return(read_dot(sc, current_input_port(sc))); + case '\'': return(TOKEN_QUOTE); + case ';': return(port_read_semicolon(current_input_port(sc))(sc, current_input_port(sc))); + case '"': return(TOKEN_DOUBLE_QUOTE); + case '`': return(TOKEN_BACK_QUOTE); + case ',': return(read_comma(sc, current_input_port(sc))); + case '#': return(read_sharp(sc, current_input_port(sc))); + case '\0': + case EOF: return(TOKEN_EOF); + default: + sc->strbuf[0] = (unsigned char)c; /* every TOKEN_ATOM return goes to port_read_name, so we save a backchar/inchar shuffle by starting the read here */ + return(TOKEN_ATOM); + } +} + +static int32_t read_x_char(s7_scheme *sc, int32_t i, s7_pointer pt) +{ + /* possible "\xn...;" char (write creates these things, so we have to read them) + * but we could have crazy input like "\x -- with no trailing double quote + */ + for (int32_t c_ctr = 0; ; c_ctr++) + { + int32_t d1, d2, c = inchar(pt); + if (c == '"') /* "\x" -> error, "\x44" or "\x44;" -> #\D */ + { + if (c_ctr == 0) /* "\x" */ + read_error_nr(sc, "unknown backslash usage -- perhaps you meant two backslashes?"); + backchar(c, pt); /* "\x44" I think -- not sure about this -- Guile is happy but I think it contradicts r7rs.pdf */ + return(i); + } + if (c == ';') + { + if (c_ctr == 0) /* "\x;" */ + read_error_nr(sc, "unknown backslash usage -- perhaps you meant two backslashes?"); + return(i); /* "\x44;" */ + } + if (c == EOF) /* "\x */ + { + read_error_nr(sc, "# in midst of hex-char"); + return(i); + } + d1 = digits[c]; + if (d1 >= 16) /* "\x4H", also "\x44H" which Guile thinks is ok -- it apparently reads 2 digits and quits? */ + { + if (c_ctr == 0) + read_error_nr(sc, "unknown backslash usage -- perhaps you meant two backslashes?"); + backchar(c, pt); + return(i); + } + /* perhaps if c_ctr==0 error else backchar + return(i??) */ + + c = inchar(pt); + if (c == '"') /* "\x4" */ + { + sc->strbuf[i++] = (unsigned char)d1; + backchar((char)c, pt); + return(i); + } + if (c == ';') /* "\x4;" */ + { + sc->strbuf[i++] = (unsigned char)d1; + return(i); + } + if (c == EOF) /* "\x4 in midst of hex-char"); + return(i); + } + d2 = digits[c]; + if (d2 >= 16) + { + if (c_ctr == 0) + read_error_nr(sc, "unknown backslash usage -- perhaps you meant two backslashes?"); + backchar(c, pt); + return(i); + } + sc->strbuf[i++] = (unsigned char)(16 * d1 + d2); + } + return(i); +} + +static s7_pointer unknown_string_constant(s7_scheme *sc, int32_t c) +{ + /* check *read-error-hook* */ + if (hook_has_functions(sc->read_error_hook)) + { + s7_pointer result = s7_call(sc, sc->read_error_hook, set_plist_2(sc, sc->F, chars[(uint8_t)c])); + if (is_character(result)) + return(result); + } + return(sc->T); +} + +static s7_pointer read_string_constant(s7_scheme *sc, s7_pointer pt) +{ + /* sc->F => error, no check needed here for bad input port and so on */ + s7_int i = 0; + + if (is_string_port(pt)) + { + /* try the most common case first */ + char *s, *end, *start = (char *)(port_data(pt) + port_position(pt)); + if (*start == '"') + { + port_position(pt)++; + return(nil_string); + } + end = (char *)(port_data(pt) + port_data_size(pt)); + s = strpbrk(start, "\"\n\\"); + if ((!s) || (s >= end)) /* can this read a huge string constant from a file? */ + { + if (start == end) + sc->strbuf[0] = '\0'; + else memcpy((void *)(sc->strbuf), (void *)start, (end - start > 8) ? 8 : (end - start)); + sc->strbuf[8] = '\0'; + return(sc->F); + } + if (*s == '"') + { + s7_int len = s - start; + port_position(pt) += (len + 1); + return(make_string_with_length(sc, start, len)); + } + for (; s < end; s++) + { + if (*s == '"') /* switch here no faster */ + { + s7_int len = s - start; + port_position(pt) += (len + 1); + return(make_string_with_length(sc, start, len)); + } + if (*s == '\\') + { + /* all kinds of special cases here (resultant string is not the current string), so drop to loop below (setting "i") */ + s7_int len = (s7_int)(s - start); + if (len > 0) + { + if (len >= sc->strbuf_size) + resize_strbuf(sc, len); + memcpy((void *)(sc->strbuf), (void *)(port_data(pt) + port_position(pt)), len); + port_position(pt) += len; + } + i = len; + break; + } + else + if (*s == '\n') + port_line_number(pt)++; + }} + + while (true) + { + /* splitting this check out and duplicating the loop was slower?!? */ + int32_t c = port_read_character(pt)(sc, pt); + switch (c) + { + case '\n': + port_line_number(pt)++; + sc->strbuf[i++] = (unsigned char)c; + break; + + case EOF: + sc->strbuf[(i > 8) ? 8 : i] = '\0'; + return(sc->F); + + case '"': + return(make_string_with_length(sc, sc->strbuf, i)); + + case '\\': + c = inchar(pt); + switch (c) + { + case EOF: + sc->strbuf[(i > 8) ? 8 : i] = '\0'; + return(sc->F); + + case '\\': case '"': case '|': + sc->strbuf[i++] = (unsigned char)c; + break; + + case 'n': sc->strbuf[i++] = '\n'; break; + case 't': sc->strbuf[i++] = '\t'; break; + case 'r': sc->strbuf[i++] = '\r'; break; + case '/': sc->strbuf[i++] = '/'; break; + case 'b': sc->strbuf[i++] = (unsigned char)8; break; + case 'f': sc->strbuf[i++] = (unsigned char)12; break; + + case 'x': + i = read_x_char(sc, i, pt); + break; + + default: /* if (!is_white_space(c)) */ /* changed 8-Apr-12 */ + if ((c != '\n') && (c != '\r')) /* i.e. line continuation via #\\ at end of line */ + { + s7_pointer result = unknown_string_constant(sc, c); + if (!is_character(result)) return(result); + sc->strbuf[i++] = character(result); + } + /* #f here would give confusing error message "end of input", so return #t=bad backslash. + * this is not optimal. It's easy to forget that backslash needs to be backslashed. + * the white_space business half-implements Scheme's \...... or \...... + * feature -- the characters after \ are flushed if they're all white space and include a newline. + * (string->number "1\ 2") is 12?? Too bizarre. + */ + } + break; + + default: + sc->strbuf[i++] = (unsigned char)c; + break; + } + if (i >= sc->strbuf_size) + resize_strbuf(sc, i); + } +} + +static void read_double_quote(s7_scheme *sc) +{ + sc->value = read_string_constant(sc, current_input_port(sc)); + if (sc->value == sc->F) /* can happen if input code ends in the middle of a string */ + string_read_error_nr(sc, "end of input encountered while in a string"); + if (sc->value == sc->T) + read_error_nr(sc, "unknown backslash usage -- perhaps you meant two backslashes?"); + if (sc->safety > IMMUTABLE_VECTOR_SAFETY) set_immutable(sc->value); +} + +static /* inline */ bool read_sharp_const(s7_scheme *sc) /* tread but inline makes no difference? (it's currently inlined anyway) */ +{ + sc->value = port_read_sharp(current_input_port(sc))(sc, current_input_port(sc)); + if (sc->value == sc->no_value) + { + /* (set! *#readers* (cons (cons #\; (lambda (s) (read) (values))) *#readers*)) + * (+ 1 #;(* 2 3) 4) + * so we need to get the next token, act on it without any assumptions about read list + */ + sc->tok = token(sc); + return(true); + } + return(false); +} + +static noreturn void read_expression_read_error_nr(s7_scheme *sc) +{ + s7_pointer pt = current_input_port(sc); + pop_stack(sc); + if ((is_input_port(pt)) && + (!port_is_closed(pt)) && + (port_data(pt)) && + (port_position(pt) > 0)) + { + s7_pointer p = make_empty_string(sc, 128, '\0'); + char *msg = string_value(p); + s7_int pos = port_position(pt); + s7_int start = pos - 40; + if (start < 0) start = 0; + memcpy((void *)msg, (const void *)"at \"...", 7); + memcpy((void *)(msg + 7), (void *)(port_data(pt) + start), pos - start); + memcpy((void *)(msg + 7 + pos - start), (const void *)"...", 3); + string_length(p) = 7 + pos - start + 3; + error_nr(sc, sc->read_error_symbol, set_elist_1(sc, p)); + } + read_error_nr(sc, "stray comma before ')'?"); /* '("a" "b",) */ +} + +static s7_pointer read_expression(s7_scheme *sc) +{ + while (true) + { + switch (sc->tok) + { + case TOKEN_EOF: + return(eof_object); + + case TOKEN_BYTE_VECTOR: + push_stack_no_let_no_code(sc, OP_READ_BYTE_VECTOR, sc->w); + sc->tok = TOKEN_LEFT_PAREN; + break; + + case TOKEN_INT_VECTOR: + push_stack_no_let_no_code(sc, OP_READ_INT_VECTOR, sc->w); + sc->tok = TOKEN_LEFT_PAREN; + break; + + case TOKEN_FLOAT_VECTOR: + push_stack_no_let_no_code(sc, OP_READ_FLOAT_VECTOR, sc->w); + sc->tok = TOKEN_LEFT_PAREN; + break; + + case TOKEN_VECTOR: /* already read #( -- TOKEN_VECTOR is triggered by #( */ + push_stack_no_let_no_code(sc, OP_READ_VECTOR, sc->w); /* sc->w is the dimensions */ + /* fall through */ + + case TOKEN_LEFT_PAREN: + sc->tok = token(sc); + if (sc->tok == TOKEN_RIGHT_PAREN) + return(sc->nil); + if (sc->tok == TOKEN_DOT) + { + int32_t c; + back_up_stack(sc); + do {c = inchar(current_input_port(sc));} while ((c != ')') && (c != EOF)); + read_error_nr(sc, "stray dot after '('?"); /* (car '( . )) */ + } + if (sc->tok == TOKEN_EOF) + missing_close_paren_error_nr(sc); + push_stack_no_let_no_code(sc, OP_READ_LIST, sc->nil); /* here we need to clear args, but code is ignored */ + check_stack_size(sc); /* s7test */ + break; + + case TOKEN_QUOTE: + check_stack_size(sc); /* no speed diff in tload.scm which looks like the worst case */ + push_stack_no_let_no_code(sc, OP_READ_QUOTE, sc->nil); + sc->tok = token(sc); + break; + + case TOKEN_BACK_QUOTE: + sc->tok = token(sc); + push_stack_no_let_no_code(sc, OP_READ_QUASIQUOTE, sc->nil); + break; + + case TOKEN_COMMA: + push_stack_no_let_no_code(sc, OP_READ_UNQUOTE, sc->nil); + sc->tok = token(sc); + if (sc->tok == TOKEN_RIGHT_PAREN) + read_expression_read_error_nr(sc); + if (sc->tok == TOKEN_EOF) + { + pop_stack(sc); + read_error_nr(sc, "stray comma at the end of the input?"); + } + break; + + case TOKEN_AT_MARK: + push_stack_no_let_no_code(sc, OP_READ_APPLY_VALUES, sc->nil); + sc->tok = token(sc); + break; + + case TOKEN_ATOM: + return(port_read_name(current_input_port(sc))(sc, current_input_port(sc))); + /* If reading list (from lparen), this will finally get us to op_read_list */ + + case TOKEN_DOUBLE_QUOTE: + read_double_quote(sc); + return(sc->value); + + case TOKEN_SHARP_CONST: + return(port_read_sharp(current_input_port(sc))(sc, current_input_port(sc))); + + case TOKEN_DOT: /* (catch #t (lambda () (+ 1 . . )) (lambda args 'hiho)) */ + back_up_stack(sc); + {int32_t c; do {c = inchar(current_input_port(sc));} while ((c != ')') && (c != EOF));} + read_error_nr(sc, "stray dot in list?"); /* (+ 1 . . ) */ + + case TOKEN_RIGHT_PAREN: /* (catch #t (lambda () '(1 2 . )) (lambda args 'hiho)) */ + back_up_stack(sc); + read_error_nr(sc, "unexpected close paren"); /* (+ 1 2)) or (+ 1 . ) */ + }} + /* we never get here */ + return(sc->nil); +} + +static void read_dot_and_expression(s7_scheme *sc) +{ + push_stack_no_let_no_code(sc, OP_READ_DOT, sc->args); + sc->tok = token(sc); + sc->value = read_expression(sc); +} + +static void read_tok_default(s7_scheme *sc) +{ + /* by far the main case here is TOKEN_LEFT_PAREN, but it doesn't save anything to move it to this level */ + push_stack_no_let_no_code(sc, OP_READ_LIST, sc->args); + sc->value = read_expression(sc); + /* check for op_read_list here and explicit pop_stack are slower */ +} + +static int32_t read_atom(s7_scheme *sc, s7_pointer pt) +{ + push_stack_no_let_no_code(sc, OP_READ_LIST, sc->args); + /* check_stack_size(sc); */ + sc->value = port_read_name(pt)(sc, pt); + sc->args = list_1(sc, sc->value); + pair_set_current_input_location(sc, sc->args); + return(port_read_white_space(pt)(sc, pt)); +} + +static /* inline */ int32_t read_start_list(s7_scheme *sc, s7_pointer pt, int32_t c) +{ + sc->strbuf[0] = (unsigned char)c; + push_stack_no_let_no_code(sc, OP_READ_LIST, sc->args); + check_stack_size(sc); /* s7test */ + sc->value = port_read_name(pt)(sc, pt); + sc->args = list_1(sc, sc->value); + pair_set_current_input_location(sc, sc->args); + return(port_read_white_space(pt)(sc, pt)); +} + +static void op_read_internal(s7_scheme *sc) +{ + /* if we're loading a file, and in the file we evaluate (at top-level) something like: + * (set-current-input-port (open-input-file "tmp2.r5rs")) + * (close-input-port (current-input-port)) + * ... (with no reset of input port to its original value) + * the load process tries to read the loaded string, but the current-input-port is now closed, + * and the original is inaccessible! So we get a segfault in token. We don't want to put + * a port_is_closed check there because token only rarely is in this danger. I think this + * is the only place where we can be about to call token, and someone has screwed up our port. + */ + if (port_is_closed(current_input_port(sc))) + error_nr(sc, sc->read_error_symbol, /* not read_error here because it paws through the port string which doesn't exist here */ + set_elist_1(sc, wrap_string(sc, (is_loader_port(current_input_port(sc))) ? "load input port is closed!" : "read input port is closed!", 26))); + + sc->tok = token(sc); + switch (sc->tok) + { + case TOKEN_EOF: break; + case TOKEN_RIGHT_PAREN: read_error_nr(sc, "unexpected close paren"); + case TOKEN_COMMA: read_error_nr(sc, "unexpected comma"); + default: + sc->value = read_expression(sc); + sc->current_line = port_line_number(current_input_port(sc)); /* this info is used to track down missing close parens */ + sc->current_file = port_filename(current_input_port(sc)); + break; + } +} + +static void op_read_done(s7_scheme *sc) +{ + pop_input_port(sc); + if (sc->tok == TOKEN_EOF) + sc->value = eof_object; + sc->current_file = NULL; /* this is for error handling */ +} + +static void op_read_s(s7_scheme *sc) +{ + s7_pointer port = lookup(sc, cadr(sc->code)); + if (!is_input_port(port)) /* was also not stdin */ + { + sc->value = g_read(sc, set_plist_1(sc, port)); + return; + } + if (port_is_closed(port)) /* I guess the port_is_closed check is needed because we're going down a level below */ + sole_arg_wrong_type_error_nr(sc, sc->read_symbol, port, an_open_input_port_string); + + if (is_function_port(port)) + { + sc->value = (*(port_input_function(port)))(sc, S7_READ, port); + if (is_multiple_value(sc->value)) + { + clear_multiple_value(sc->value); + error_nr(sc, sc->bad_result_symbol, set_elist_2(sc, wrap_string(sc, "input-function-port read returned: ~S", 37), sc->value)); + }} + else /* we used to check for string port at end here, but that is rarely true so checking takes up more time than it saves */ + { + push_input_port(sc, port); + push_stack_op(sc, OP_READ_DONE); /* this stops the internal read process so we only get one form */ + sc->tok = token(sc); + switch (sc->tok) + { + case TOKEN_EOF: return; + case TOKEN_RIGHT_PAREN: read_error_nr(sc, "unexpected close paren"); + case TOKEN_COMMA: read_error_nr(sc, "unexpected comma"); + default: + sc->value = read_expression(sc); + sc->current_line = port_line_number(current_input_port(sc)); /* this info is used to track down missing close parens */ + sc->current_file = port_filename(current_input_port(sc)); + }} +} + +static bool op_read_quasiquote(s7_scheme *sc) +{ + /* this was pushed when the backquote was seen, then eventually we popped back to it */ + sc->value = g_quasiquote_1(sc, sc->value, false); + /* doing quasiquote at read time means there are minor inconsistencies in various combinations or quote/' and quasiquote/`. + * A quoted ` will expand but quoted quasiquote will not (` can't be redefined, but quasiquote can). see s7test.scm for examples. + */ + return(stack_top_op(sc) != OP_READ_LIST); +} + +static bool pop_read_list(s7_scheme *sc) +{ + /* push-stack OP_READ_LIST is always no_code and op is always OP_READ_LIST (and not used), sc->curlet is apparently not needed here */ + unstack_with(sc, OP_READ_LIST); + sc->args = stack_end_args(sc); + if (!is_null(sc->args)) return(false); /* fall into read_list where sc->args is placed at end of on-going list, sc->value */ + sc->args = list_1(sc, sc->value); + pair_set_current_input_location(sc, sc->args); /* uses port_location */ + return(true); +} + +static bool op_load_return_if_eof(s7_scheme *sc) +{ + if (SHOW_EVAL_OPS) fprintf(stderr, "op_load_return_if_eof: value: %s\n", display(sc->value)); + if (sc->tok != TOKEN_EOF) + { + push_stack_op_let(sc, OP_LOAD_RETURN_IF_EOF); + push_stack_op_let(sc, OP_READ_INTERNAL); + sc->code = sc->value; + return(true); /* we read an expression, now evaluate it, and return to read the next */ + } + sc->current_file = NULL; + return(false); +} + +static bool op_load_close_and_pop_if_eof(s7_scheme *sc) +{ + /* (load "file") in scheme: read and evaluate all exprs, then upon EOF, close current and pop input port stack */ + if (sc->tok != TOKEN_EOF) + { + push_stack_op_let(sc, OP_LOAD_CLOSE_AND_POP_IF_EOF); /* was push args, code */ + if ((!is_string_port(current_input_port(sc))) || + (port_position(current_input_port(sc)) < port_data_size(current_input_port(sc)))) + push_stack_op_let(sc, OP_READ_INTERNAL); + else sc->tok = TOKEN_EOF; + sc->code = sc->value; + return(true); /* we read an expression, now evaluate it, and return to read the next */ + } + if ((S7_DEBUGGING) && (!is_loader_port(current_input_port(sc)))) fprintf(stderr, "%s not loading?\n", display(current_input_port(sc))); + /* if *#readers* func hits error, clear_loader_port might not be undone? */ + s7_close_input_port(sc, current_input_port(sc)); + pop_input_port(sc); + sc->current_file = NULL; + if (is_multiple_value(sc->value)) /* (load (file)) where file returns (values "a-file" an-environment)? */ + sc->value = splice_in_values(sc, multiple_value(sc->value)); + return(false); +} + +static bool op_read_apply_values(s7_scheme *sc) +{ + sc->value = list_2_unchecked(sc, sc->unquote_symbol, list_2(sc, sc->apply_values_symbol, sc->value)); + return(stack_top_op(sc) != OP_READ_LIST); +} + +static goto_t op_read_dot(s7_scheme *sc) +{ + token_t c = token(sc); + if (c != TOKEN_RIGHT_PAREN) /* '(1 . (2) 3) -> '(1 2 3), Guile says "missing close paren" */ + { + if (is_pair(sc->value)) + { + for (s7_pointer p = sc->value; is_pair(p); p = cdr(p)) + sc->args = cons(sc, car(p), sc->args); + sc->tok = c; + return(goto_read_tok); + } + back_up_stack(sc); + read_error_nr(sc, "stray dot?"); /* (+ 1 . 2 3) or (list . ) */ + } + /* args = previously read stuff, value = thing just after the dot and before the ')': + * (list 1 2 . 3) -> value: 3, args: (2 1 list), '(1 . 2) -> value: 2, args: (1) + * but we also get here in a lambda arg list: (lambda (a b . c) #f) -> value: c, args: (b a) + */ + sc->value = any_list_reverse_in_place(sc, sc->value, sc->args); + return((stack_top_op(sc) == OP_READ_LIST) ? goto_pop_read_list : goto_start); +} + +static bool op_read_quote(s7_scheme *sc) +{ + /* can't check for sc->value = sc->nil here because we want ''() to be different from '() */ + if ((sc->safety > IMMUTABLE_VECTOR_SAFETY) && + ((is_pair(sc->value)) || (is_any_vector(sc->value)) || (is_string(sc->value)))) + set_immutable(sc->value); + sc->value = list_2(sc, sc->quote_symbol, sc->value); + return(stack_top_op(sc) != OP_READ_LIST); +} + +static bool op_read_unquote(s7_scheme *sc) +{ + /* here if sc->value is a constant, the unquote is pointless (should we complain?) + * also currently stray "," can be ignored: (abs , 1) -- scanning the stack for quasiquote or quote seems to be unreliable + */ + if ((is_pair(sc->value)) || + (is_symbol(sc->value))) + sc->value = list_2(sc, sc->unquote_symbol, sc->value); + return(stack_top_op(sc) != OP_READ_LIST); +} + +/* safety check is at read time, so (immutable? (let-temporarily (((*s7* 'safety) 2)) #(1 2 3))) is #f + * but (immutable? (let-temporarily (((*s7* 'safety) 2)) (eval-string "#(1 2 3)"))) is #t + */ +static bool op_read_vector(s7_scheme *sc) +{ + sc->value = (sc->args == int_one) ? g_vector(sc, sc->value) : g_multivector(sc, integer(sc->args), sc->value); /* sc->args was sc->w earlier from read_sharp */ + /* here and below all of the sc->value list can be freed, but my tests showed no speed up even in large cases */ + if (sc->safety > IMMUTABLE_VECTOR_SAFETY) set_immutable(sc->value); + return(stack_top_op(sc) != OP_READ_LIST); +} + +static bool op_read_int_vector(s7_scheme *sc) +{ + sc->value = (sc->args == int_one) ? g_int_vector(sc, sc->value) : g_int_multivector(sc, integer(sc->args), sc->value); + if (sc->safety > IMMUTABLE_VECTOR_SAFETY) set_immutable(sc->value); + return(stack_top_op(sc) != OP_READ_LIST); +} + +static bool op_read_float_vector(s7_scheme *sc) +{ + sc->value = (sc->args == int_one) ? g_float_vector(sc, sc->value) : g_float_multivector(sc, integer(sc->args), sc->value); + if (sc->safety > IMMUTABLE_VECTOR_SAFETY) set_immutable(sc->value); + return(stack_top_op(sc) != OP_READ_LIST); +} + +static bool op_read_byte_vector(s7_scheme *sc) +{ + sc->value = (sc->args == int_one) ? g_byte_vector(sc, sc->value) : g_byte_multivector(sc, integer(sc->args), sc->value); + if (sc->safety > IMMUTABLE_VECTOR_SAFETY) set_immutable(sc->value); + return(stack_top_op(sc) != OP_READ_LIST); +} + + +/* ---------------- unknown ops ---------------- */ +static bool fixup_unknown_op(s7_scheme *sc, s7_pointer code, s7_pointer func, opcode_t op) +{ + set_optimize_op(code, op); + if (is_any_closure(func)) + set_opt1_lambda_add(code, func); /* perhaps set_opt1_lambda_add here and throughout op_unknown* */ + return(true); +} + +static bool unknown_unknown(s7_scheme *sc, s7_pointer code, opcode_t op) +{ + if ((is_symbol(car(code))) && + (!is_slot(s7_slot(sc, car(code))))) + unbound_variable_error_nr(sc, car(code)); + set_optimize_op(code, op); + return(true); +} + +static bool is_immutable_and_stable(s7_scheme *sc, s7_pointer func) +{ + if (symbol_ctr(func) != 1) /* protect against (define-constant (p) (define-constant (p) ...)) */ + return(false); + if ((is_global(func)) && (is_immutable_slot(global_slot(func)))) + return(true); + for (s7_pointer p = sc->curlet; is_let(p); p = let_outlet(p)) + if ((is_funclet(p)) && (funclet_function(p) != func)) + return(false); + return(is_immutable_slot(s7_slot(sc, func))); +} + +static bool op_unknown(s7_scheme *sc) +{ + s7_pointer code = sc->code, f = sc->last_function; + if (!f) /* can be NULL if unbound variable */ + unbound_variable_error_nr(sc, car(sc->code)); + if (SHOW_EVAL_OPS) fprintf(stderr, "%s %s %s\n", __func__, display(f), s7_type_names[type(f)]); + + switch (type(f)) + { + case T_CLOSURE: + case T_CLOSURE_STAR: + if (!has_methods(f)) + { + int32_t hop = (is_immutable_and_stable(sc, car(code))) ? 1 : 0; + if (is_null(closure_args(f))) + { + s7_pointer body = closure_body(f); + bool one_form = is_null(cdr(body)); + bool safe_case = is_safe_closure(f); + set_opt1_lambda_add(code, f); + if (one_form) + { + if ((safe_case) && (is_fxable(sc, car(body)))) + { + set_safe_closure(f); /* safe because no args so no reference to funclet? needed because op_safe_thunk_a will check for it */ + fx_annotate_arg(sc, body, sc->curlet); + set_safe_optimize_op(code, hop + OP_SAFE_THUNK_A); + set_closure_one_form_fx_arg(f); + sc->value = fx_safe_thunk_a(sc, sc->code); + return(false); + } + clear_has_fx(code); + } + set_safe_optimize_op(code, hop + ((safe_case) ? OP_SAFE_THUNK : ((one_form) ? OP_THUNK_O : OP_THUNK))); + return(true); + } + if (is_closure_star(f)) + { + set_safe_optimize_op(code, hop + ((is_safe_closure(f)) ? OP_SAFE_CLOSURE_STAR_NA_0 : OP_CLOSURE_STAR_NA)); + set_opt1_lambda_add(code, f); + return(true); + }} + break; + + case T_GOTO: return(fixup_unknown_op(sc, code, f, OP_IMPLICIT_GOTO)); + case T_ITERATOR: return(fixup_unknown_op(sc, code, f, OP_IMPLICIT_ITERATE)); + case T_MACRO: return(fixup_unknown_op(sc, code, f, fixup_macro_d(sc, OP_MACRO_D, f))); + case T_MACRO_STAR: return(fixup_unknown_op(sc, code, f, fixup_macro_d(sc, OP_MACRO_STAR_D, f))); + + default: + if ((is_symbol(car(code))) && + (!is_slot(s7_slot(sc, car(code))))) + unbound_variable_error_nr(sc, car(code)); + } + return(fixup_unknown_op(sc, code, f, OP_S)); +} + +static bool fxify_closure_star_g(s7_scheme *sc, s7_pointer f, s7_pointer code) +{ + if ((!has_methods(f)) && + (closure_star_arity_to_int(sc, f) != 0)) + { + int32_t hop = (is_immutable_and_stable(sc, car(code))) ? 1 : 0; + bool safe_case = is_safe_closure(f); + fx_annotate_arg(sc, cdr(code), sc->curlet); + set_opt3_arglen(cdr(code), 1); + if ((safe_case) && (is_null(cdr(closure_args(f))))) + set_optimize_op(code, hop + OP_SAFE_CLOSURE_STAR_A1); + else + if (lambda_has_simple_defaults(f)) + { + if (arglist_has_rest(sc, closure_args(f))) + fixup_unknown_op(sc, code, f, hop + ((safe_case) ? OP_SAFE_CLOSURE_STAR_NA_1 : OP_CLOSURE_STAR_NA)); + else fixup_unknown_op(sc, code, f, hop + ((safe_case) ? + ((is_null(cdr(closure_args(f)))) ? OP_SAFE_CLOSURE_STAR_A1 : OP_SAFE_CLOSURE_STAR_A) : OP_CLOSURE_STAR_A)); + return(true); + } + fixup_unknown_op(sc, code, f, hop + ((safe_case) ? OP_SAFE_CLOSURE_STAR_NA_1 : OP_CLOSURE_STAR_NA)); + return(true); + } + return(false); +} + +static bool op_unknown_closure_s(s7_scheme *sc, s7_pointer f, s7_pointer code) +{ + s7_pointer body = closure_body(f); + bool one_form = is_null(cdr(body)); + int32_t hop = (is_immutable_and_stable(sc, car(code))) ? 1 : 0; + set_opt2_sym(code, cadr(code)); + + /* code here might be (f x) where f is passed elsewhere as a function parameter, + * first time through we look it up, find a safe-closure and optimize as (say) safe_closure_s_a, + * next time it is something else, etc. Rather than keep optimizing it locally, we need to + * back out: safe_closure_s_* -> safe_closure_s -> closure_s -> op_s_g. Ideally we'd know + * this was a parameter or whatever. The tricky case is local letrec(f) calling f which initially + * thinks it is not safe, then later is set safe correctly, now outer func is called again, + * this time f is safe, and we're ok from then on. + */ + if (is_unknopt(code)) + { + switch (op_no_hop(code)) + { + case OP_CLOSURE_S: + set_optimize_op(code, (is_safe_closure(f)) ? ((one_form) ? OP_SAFE_CLOSURE_S_O : OP_SAFE_CLOSURE_S) : OP_S_G); break; + case OP_CLOSURE_S_O: + case OP_SAFE_CLOSURE_S: + set_optimize_op(code, ((one_form) ? OP_CLOSURE_S_O : OP_CLOSURE_S)); break; + case OP_SAFE_CLOSURE_S_O: + case OP_SAFE_CLOSURE_S_A: + case OP_SAFE_CLOSURE_S_TO_S: + case OP_SAFE_CLOSURE_S_TO_SC: + set_optimize_op(code, (is_safe_closure(f)) ? + ((one_form) ? OP_SAFE_CLOSURE_S_O : OP_SAFE_CLOSURE_S) : + ((one_form) ? OP_CLOSURE_S_O : OP_CLOSURE_S)); + break; + default: + set_optimize_op(code, OP_S_G); break; + } + set_opt1_lambda_add(code, f); + return(true); + } + if (!is_safe_closure(f)) + set_optimize_op(code, hop + ((one_form) ? OP_CLOSURE_S_O : OP_CLOSURE_S)); + else + if (!is_null(cdr(body))) + set_safe_optimize_op(code, hop + OP_SAFE_CLOSURE_S); + else + if (is_fxable(sc, car(body))) + fxify_closure_s(sc, f, code, sc->curlet, hop); + else set_safe_optimize_op(code, hop + OP_SAFE_CLOSURE_S_O); + /* hop if is_constant(sc, car(code)) is not foolproof here (see t967.scm): + * (define (f) (define-constant (f1) ... (f1))...) where each call on f makes a different f1 + */ + set_is_unknopt(code); + set_opt1_lambda_add(code, f); + return(true); +} + +static bool op_unknown_s(s7_scheme *sc) +{ + s7_pointer code = sc->code, f = sc->last_function; + + if (!f) unbound_variable_error_nr(sc, car(sc->code)); + if (SHOW_EVAL_OPS) fprintf(stderr, "%s %s\n", __func__, display(f)); + + if ((S7_DEBUGGING) && (!is_normal_symbol(cadr(code)))) fprintf(stderr, "%s[%d]: not a symbol: %s\n", __func__, __LINE__, display(code)); + if ((!is_any_macro(f)) && /* if f is a macro, its argument can be unbound legitimately */ + (!is_slot(s7_slot(sc, cadr(code))))) + return(unknown_unknown(sc, sc->code, (is_normal_symbol(cadr(sc->code))) ? OP_CLEAR_OPTS : OP_S_G)); + + if ((is_unknopt(code)) && (!is_closure(f))) + return(fixup_unknown_op(sc, code, f, OP_S_G)); + + switch (type(f)) + { + case T_C_FUNCTION: + if (!(c_function_is_aritable(f, 1))) break; + case T_C_RST_NO_REQ_FUNCTION: + set_c_function(code, f); + if (is_safe_procedure(f)) + { + set_optimize_op(code, OP_SAFE_C_S); + sc->value = fx_c_s(sc, sc->code); + } + else + { + set_optimize_op(code, OP_C_S); + op_c_s(sc); + } + return(false); + + case T_CLOSURE: + if ((!has_methods(f)) && (closure_arity_to_int(sc, f) == 1)) + return(op_unknown_closure_s(sc, f, code)); + break; + + case T_CLOSURE_STAR: + if (fxify_closure_star_g(sc, f, code)) return(true); + break; + + case T_GOTO: + fx_annotate_arg(sc, cdr(code), sc->curlet); + set_opt3_arglen(cdr(code), 1); + return(fixup_unknown_op(sc, code, f, OP_IMPLICIT_GOTO_A)); + + case T_INT_VECTOR: case T_FLOAT_VECTOR: case T_VECTOR: case T_BYTE_VECTOR: + fx_annotate_arg(sc, cdr(code), sc->curlet); + return(fixup_unknown_op(sc, code, f, OP_IMPLICIT_VECTOR_REF_A)); + + case T_STRING: + fx_annotate_arg(sc, cdr(code), sc->curlet); + return(fixup_unknown_op(sc, code, f, OP_IMPLICIT_STRING_REF_A)); + + case T_PAIR: + fx_annotate_arg(sc, cdr(code), sc->curlet); + return(fixup_unknown_op(sc, code, f, OP_IMPLICIT_PAIR_REF_A)); + + case T_C_OBJECT: + if (s7_is_aritable(sc, f, 1)) + { + fx_annotate_arg(sc, cdr(code), sc->curlet); + return(fixup_unknown_op(sc, code, f, OP_IMPLICIT_C_OBJECT_REF_A)); + } + break; + + case T_LET: + fx_annotate_arg(sc, cdr(code), sc->curlet); + return(fixup_unknown_op(sc, code, f, OP_IMPLICIT_LET_REF_A)); + + case T_HASH_TABLE: + fx_annotate_arg(sc, cdr(code), sc->curlet); + return(fixup_unknown_op(sc, code, f, OP_IMPLICIT_HASH_TABLE_REF_A)); + + case T_CONTINUATION: + fx_annotate_arg(sc, cdr(code), sc->curlet); + return(fixup_unknown_op(sc, code, f, OP_IMPLICIT_CONTINUATION_A)); + + case T_MACRO: return(fixup_unknown_op(sc, code, f, fixup_macro_d(sc, OP_MACRO_D, f))); + case T_MACRO_STAR: return(fixup_unknown_op(sc, code, f, fixup_macro_d(sc, OP_MACRO_STAR_D, f))); + + default: break; + } + if ((is_symbol(car(code))) && + (!is_slot(s7_slot(sc, car(code))))) + unbound_variable_error_nr(sc, car(code)); + return(fixup_unknown_op(sc, code, f, OP_S_G)); +} + +static bool op_unknown_a(s7_scheme *sc) +{ + s7_pointer code = sc->code, f = sc->last_function; + if (!f) unbound_variable_error_nr(sc, car(sc->code)); + if (SHOW_EVAL_OPS) fprintf(stderr, "%s %s\n", __func__, display(f)); + + switch (type(f)) + { + case T_C_FUNCTION: + if (!(c_function_is_aritable(f, 1))) break; + case T_C_RST_NO_REQ_FUNCTION: + clear_has_fx(code); + set_c_function(code, f); + if (is_safe_procedure(f)) + { + set_optimize_op(code, OP_SAFE_C_A); + sc->value = fx_c_a(sc, code); + } + else + { + set_optimize_op(code, OP_C_A); + op_c_a(sc); + } + return(false); + + case T_CLOSURE: + if ((!has_methods(f)) && + (closure_arity_to_int(sc, f) == 1)) + { + s7_pointer body = closure_body(f); + bool safe_case = is_safe_closure(f); + int32_t hop = (is_immutable_and_stable(sc, car(code))) ? 1 : 0; + bool one_form = is_null(cdr(body)); + + fxify_closure_a(sc, f, one_form, safe_case, hop, code, sc->curlet); + set_opt1_lambda_add(code, f); + return(true); + } + break; + + case T_CLOSURE_STAR: + if (fxify_closure_star_g(sc, f, code)) return(true); + break; + + case T_INT_VECTOR: case T_FLOAT_VECTOR: case T_VECTOR: case T_BYTE_VECTOR: + return(fixup_unknown_op(sc, code, f, OP_IMPLICIT_VECTOR_REF_A)); + + case T_STRING: return(fixup_unknown_op(sc, code, f, OP_IMPLICIT_STRING_REF_A)); + case T_PAIR: return(fixup_unknown_op(sc, code, f, OP_IMPLICIT_PAIR_REF_A)); + case T_C_OBJECT: return(fixup_unknown_op(sc, code, f, OP_IMPLICIT_C_OBJECT_REF_A)); + case T_HASH_TABLE: return(fixup_unknown_op(sc, code, f, OP_IMPLICIT_HASH_TABLE_REF_A)); + case T_GOTO: return(fixup_unknown_op(sc, code, f, OP_IMPLICIT_GOTO_A)); + case T_CONTINUATION: return(fixup_unknown_op(sc, code, f, OP_IMPLICIT_CONTINUATION_A)); + case T_MACRO: return(fixup_unknown_op(sc, code, f, fixup_macro_d(sc, OP_MACRO_D, f))); + case T_MACRO_STAR: return(fixup_unknown_op(sc, code, f, fixup_macro_d(sc, OP_MACRO_STAR_D, f))); + + case T_LET: + { + s7_pointer arg1 = cadr(code); + if ((is_pair(arg1)) && (car(arg1) == sc->quote_symbol)) + { + set_opt3_con(code, cadadr(code)); + return(fixup_unknown_op(sc, code, f, OP_IMPLICIT_LET_REF_C)); + } + set_opt3_any(code, cadr(code)); + return(fixup_unknown_op(sc, code, f, OP_IMPLICIT_LET_REF_A)); + } + + default: break; + } + if ((is_symbol(car(code))) && + (!is_slot(s7_slot(sc, car(code))))) + unbound_variable_error_nr(sc, car(code)); + return(fixup_unknown_op(sc, code, f, OP_S_A)); /* closure with methods etc */ +} + +static bool op_unknown_gg(s7_scheme *sc) +{ + bool s1, s2; + s7_pointer code = sc->code, f = sc->last_function; + if (!f) unbound_variable_error_nr(sc, car(sc->code)); + if (SHOW_EVAL_OPS) fprintf(stderr, "%s %s\n", __func__, display(f)); + + s1 = is_normal_symbol(cadr(code)); + s2 = is_normal_symbol(caddr(code)); + + if ((s1) && + (!is_slot(s7_slot(sc, cadr(code))))) + return(unknown_unknown(sc, sc->code, OP_CLEAR_OPTS)); + if ((s2) && + (!is_slot(s7_slot(sc, caddr(code))))) + return(unknown_unknown(sc, sc->code, OP_CLEAR_OPTS)); + + switch (type(f)) + { + case T_C_FUNCTION: + if (!(c_function_is_aritable(f, 2))) break; + case T_C_RST_NO_REQ_FUNCTION: + if (is_safe_procedure(f)) + { + if (s1) + { + set_optimize_op(code, (s2) ? OP_SAFE_C_SS : OP_SAFE_C_SC); + if (s2) + set_opt2_sym(cdr(code), caddr(code)); + else set_opt2_con(cdr(code), caddr(code)); + } + else + { + set_optimize_op(code, (s2) ? OP_SAFE_C_CS : OP_SAFE_C_NC); + if (s2) + { + set_opt1_con(cdr(code), (is_pair(cadr(code))) ? cadadr(code) : cadr(code)); + set_opt2_sym(cdr(code), caddr(code)); + }}} + else + { + set_optimize_op(code, (is_semisafe(f)) ? OP_CL_NA : OP_C_NA); + fx_annotate_args(sc, cdr(code), sc->curlet); + } + set_opt3_arglen(cdr(code), 2); + set_c_function(code, f); + return(true); + + case T_CLOSURE: + if (has_methods(f)) break; + if (closure_arity_to_int(sc, f) == 2) + { + s7_pointer body = closure_body(f); + bool safe_case = is_safe_closure(f); + int32_t hop = (is_immutable_and_stable(sc, car(code))) ? 1 : 0; + bool one_form = is_null(cdr(body)); + + if ((s1) && (s2)) + { + set_opt2_sym(code, caddr(code)); + if (!one_form) + set_optimize_op(code, hop + ((safe_case) ? OP_SAFE_CLOSURE_SS : OP_CLOSURE_SS)); + else + if (!safe_case) + set_optimize_op(code, hop + OP_CLOSURE_SS_O); + else + if (!is_fxable(sc, car(body))) + set_safe_optimize_op(code, hop + OP_SAFE_CLOSURE_SS_O); + else + { + fx_annotate_arg(sc, body, sc->curlet); + fx_tree(sc, body, car(closure_args(f)), cadr(closure_args(f)), NULL, false); + set_safe_optimize_op(code, hop + OP_SAFE_CLOSURE_SS_A); + set_closure_one_form_fx_arg(f); + }} + else + if (s1) + { + set_opt2_con(code, caddr(code)); + if (one_form) + set_safe_optimize_op(code, hop + ((safe_case) ? OP_SAFE_CLOSURE_SC_O : OP_CLOSURE_SC_O)); + else set_optimize_op(code, hop + ((safe_case) ? OP_SAFE_CLOSURE_SC : OP_CLOSURE_SC)); + } + else + { + set_opt3_arglen(cdr(code), 2); + fx_annotate_args(sc, cdr(code), sc->curlet); + if (safe_case) + set_safe_optimize_op(code, hop + ((one_form) ? OP_SAFE_CLOSURE_AA_O : OP_SAFE_CLOSURE_AA)); + else set_safe_optimize_op(code, hop + ((one_form) ? OP_CLOSURE_AA_O : OP_CLOSURE_AA)); + } + set_opt1_lambda_add(code, f); + return(true); + } + break; + + case T_CLOSURE_STAR: + if ((closure_star_arity_to_int(sc, f) != 0) && + (closure_star_arity_to_int(sc, f) != 1)) + { + fx_annotate_args(sc, cdr(code), sc->curlet); + if (!has_methods(f)) + { + fixup_closure_star_aa(sc, f, code, (is_immutable_and_stable(sc, car(code))) ? 1 : 0); + set_opt1_lambda_add(code, f); + } + else set_optimize_op(code, OP_S_AA); + return(true); + } + break; + + case T_INT_VECTOR: case T_FLOAT_VECTOR: case T_VECTOR: case T_BYTE_VECTOR: case T_PAIR: + set_opt3_arglen(cdr(code), 2); + fx_annotate_args(sc, cdr(code), sc->curlet); + if ((!is_pair(f)) && (vector_rank(f) != 2)) + return(fixup_unknown_op(sc, code, f, OP_S_AA)); + return(fixup_unknown_op(sc, code, f, (is_pair(f)) ? OP_IMPLICIT_PAIR_REF_AA : OP_IMPLICIT_VECTOR_REF_AA)); + + case T_HASH_TABLE: + fx_annotate_args(sc, cdr(code), sc->curlet); + return(fixup_unknown_op(sc, code, f, OP_IMPLICIT_HASH_TABLE_REF_AA)); + + case T_MACRO: return(fixup_unknown_op(sc, code, f, fixup_macro_d(sc, OP_MACRO_D, f))); + case T_MACRO_STAR: return(fixup_unknown_op(sc, code, f, fixup_macro_d(sc, OP_MACRO_STAR_D, f))); + + default: break; + } + if ((is_symbol(car(code))) && + (!is_slot(s7_slot(sc, car(code))))) + unbound_variable_error_nr(sc, car(code)); + fx_annotate_args(sc, cdr(code), sc->curlet); + return(fixup_unknown_op(sc, code, f, OP_S_AA)); +} + +static bool op_unknown_ns(s7_scheme *sc) +{ + s7_pointer code = sc->code, f = sc->last_function; + int32_t num_args = opt3_arglen(cdr(code)); + + if (!f) unbound_variable_error_nr(sc, car(sc->code)); + if (SHOW_EVAL_OPS) fprintf(stderr, "%s %s\n", __func__, display(f)); + + for (s7_pointer arg = cdr(code); is_pair(arg); arg = cdr(arg)) + if (!is_slot(s7_slot(sc, car(arg)))) + unbound_variable_error_nr(sc, car(arg)); + + switch (type(f)) + { + case T_C_FUNCTION: + if (!(c_function_is_aritable(f, num_args))) break; + case T_C_RST_NO_REQ_FUNCTION: + if (is_safe_procedure(f)) + { + if (num_args == 3) + { + set_safe_optimize_op(code, OP_SAFE_C_SSS); + set_opt1_sym(cdr(code), caddr(code)); + set_opt2_sym(cdr(code), cadddr(code)); + } + else set_safe_optimize_op(code, OP_SAFE_C_NS); + } + else + { + set_optimize_op(code, (is_semisafe(f)) ? OP_CL_NA : OP_C_NA); + fx_annotate_args(sc, cdr(code), sc->curlet); + } + set_c_function(code, f); + return(true); + + case T_CLOSURE: + if ((!has_methods(f)) && + (closure_arity_to_int(sc, f) == num_args)) + { + int32_t hop = (is_immutable_and_stable(sc, car(code))) ? 1 : 0; + bool one_form = is_null(cdr(closure_body(f))); + fx_annotate_args(sc, cdr(code), sc->curlet); + if (num_args == 3) + return(fixup_unknown_op(sc, code, f, hop + ((is_safe_closure(f)) ? OP_SAFE_CLOSURE_3S : ((one_form) ? OP_CLOSURE_3S_O : OP_CLOSURE_3S)))); + if (num_args == 4) + return(fixup_unknown_op(sc, code, f, hop + ((is_safe_closure(f)) ? OP_SAFE_CLOSURE_NS : ((one_form) ? OP_CLOSURE_4S_O : OP_CLOSURE_4S)))); + return(fixup_unknown_op(sc, code, f, hop + ((is_safe_closure(f)) ? OP_SAFE_CLOSURE_NS : ((num_args == 5) ? OP_CLOSURE_5S : OP_CLOSURE_NS)))); + } + break; + + case T_CLOSURE_STAR: + if ((!has_methods(f)) && + ((closure_star_arity_to_int(sc, f) < 0) || ((closure_star_arity_to_int(sc, f) * 2) >= num_args))) + { + int32_t hop = (is_immutable_and_stable(sc, car(code))) ? 1 : 0; + fx_annotate_args(sc, cdr(code), sc->curlet); + if ((is_safe_closure(f)) && (num_args == 3) && (closure_star_arity_to_int(sc, f) == 3)) + return(fixup_unknown_op(sc, code, f, OP_SAFE_CLOSURE_STAR_3A)); + return(fixup_unknown_op(sc, code, f, hop + ((is_safe_closure(f)) ? OP_SAFE_CLOSURE_STAR_NA : OP_CLOSURE_STAR_NA))); + } + break; + + case T_MACRO: return(fixup_unknown_op(sc, code, f, fixup_macro_d(sc, OP_MACRO_D, f))); + case T_MACRO_STAR: return(fixup_unknown_op(sc, code, f, fixup_macro_d(sc, OP_MACRO_STAR_D, f))); + + /* PERHAPS: vector, but need op_implicit_vector_ns? */ + default: break; + } + return(unknown_unknown(sc, sc->code, OP_CLEAR_OPTS)); +} + +static bool op_unknown_aa(s7_scheme *sc) +{ + s7_pointer code = sc->code, f = sc->last_function; + + if (!f) unbound_variable_error_nr(sc, car(sc->code)); + if (SHOW_EVAL_OPS) fprintf(stderr, "%s %s\n", __func__, display(f)); + + switch (type(f)) + { + case T_C_FUNCTION: + if (!(c_function_is_aritable(f, 2))) break; + case T_C_RST_NO_REQ_FUNCTION: + if (is_safe_procedure(f)) /* why is this different from unknown_a and unknown_na? */ + { + if (!safe_c_aa_to_ag_ga(sc, code, 0)) + { + set_safe_optimize_op(code, OP_SAFE_C_AA); + set_opt3_pair(code, cddr(code)); + }} + else set_optimize_op(code, (is_semisafe(f)) ? OP_CL_NA : OP_C_NA); + set_c_function(code, f); + return(true); + + case T_CLOSURE: + if ((!has_methods(f)) && + (closure_arity_to_int(sc, f) == 2)) + { + s7_pointer body = closure_body(f); + bool safe_case = is_safe_closure(f); + int32_t hop = (is_immutable_and_stable(sc, car(code))) ? 1 : 0; + bool one_form = is_null(cdr(body)); + if (!one_form) + set_safe_optimize_op(code, hop + ((safe_case) ? OP_SAFE_CLOSURE_AA : OP_CLOSURE_AA)); + else + if (!safe_case) + set_optimize_op(code, hop + OP_CLOSURE_AA_O); + else + if (!is_fxable(sc, car(body))) + set_safe_optimize_op(code, hop + OP_SAFE_CLOSURE_AA_O); + else + { + fx_annotate_arg(sc, body, sc->curlet); + set_safe_optimize_op(code, hop + OP_SAFE_CLOSURE_AA_A); + set_closure_one_form_fx_arg(f); + } + if ((is_fx_treeable(cdr(code))) && (tis_slot(let_slots(sc->curlet)))) fx_curlet_tree(sc, cdr(code)); + set_opt1_lambda_add(code, f); + return(true); + } + break; + + case T_CLOSURE_STAR: + if (!has_methods(f)) + { + fixup_closure_star_aa(sc, f, code, (is_immutable_and_stable(sc, car(code))) ? 1 : 0); + set_opt1_lambda_add(code, f); + } + else set_optimize_op(code, OP_S_AA); + return(true); + + case T_INT_VECTOR: case T_FLOAT_VECTOR: case T_VECTOR: case T_BYTE_VECTOR: + if (vector_rank(f) != 2) + return(fixup_unknown_op(sc, code, f, OP_S_AA)); + return(fixup_unknown_op(sc, code, f, OP_IMPLICIT_VECTOR_REF_AA)); + + case T_PAIR: return(fixup_unknown_op(sc, code, f, OP_IMPLICIT_PAIR_REF_AA)); + case T_HASH_TABLE: return(fixup_unknown_op(sc, code, f, OP_IMPLICIT_HASH_TABLE_REF_AA)); + case T_MACRO: return(fixup_unknown_op(sc, code, f, fixup_macro_d(sc, OP_MACRO_D, f))); + case T_MACRO_STAR: return(fixup_unknown_op(sc, code, f, fixup_macro_d(sc, OP_MACRO_STAR_D, f))); + + default: break; + } + if ((is_symbol(car(code))) && + (!is_slot(s7_slot(sc, car(code))))) + unbound_variable_error_nr(sc, car(code)); + return(fixup_unknown_op(sc, code, f, OP_S_AA)); +} + +static bool is_normal_happy_symbol(s7_scheme *sc, s7_pointer sym) +{ + if (!is_normal_symbol(sym)) + return(false); + if (!is_slot(s7_slot(sc, sym))) + unbound_variable_error_nr(sc, sym); + return(true); +} + +static bool op_unknown_na(s7_scheme *sc) +{ + s7_pointer code = sc->code, f = sc->last_function; + int32_t num_args = (is_pair(cdr(code))) ? opt3_arglen(cdr(code)) : 0; + + if (!f) unbound_variable_error_nr(sc, car(sc->code)); + if (SHOW_EVAL_OPS) fprintf(stderr, "%s[%d]: %s %s\n", __func__, __LINE__, display(f), display(sc->code)); + if (num_args == 0) return(fixup_unknown_op(sc, code, f, OP_S)); /* via op_closure*-fx where original had 0 args, safe case -> op_safe_closure*_0 */ + + switch (type(f)) + { + case T_C_FUNCTION: + if (!(c_function_is_aritable(f, num_args))) break; + case T_C_RST_NO_REQ_FUNCTION: + if (is_safe_procedure(f)) + { + if (num_args == 3) + { + int32_t pairs = 0, symbols = 0, quotes = 0; /* specialize aaa->ssc etc, this makes less difference than I expected */ + for (s7_pointer p = cdr(code); is_pair(p); p = cdr(p)) + { + s7_pointer car_p = car(p); + if (is_normal_happy_symbol(sc, car_p)) + symbols++; + else + if (is_pair(car_p)) + { + pairs++; + if (is_proper_quote(sc, car_p)) + quotes++; + }} + if (optimize_safe_c_func_three_args(sc, code, f, 0 /* hop */, pairs, symbols, quotes, sc->curlet) == OPT_T) + return(true); + set_opt3_pair(cdr(code), cdddr(code)); + set_opt3_pair(code, cddr(code)); + set_safe_optimize_op(code, OP_SAFE_C_AAA); + } + else set_safe_optimize_op(code, (num_args == 4) ? OP_SAFE_C_4A : OP_SAFE_C_NA); + } + else set_safe_optimize_op(code, (is_semisafe(f)) ? OP_CL_NA : OP_C_NA); + fx_annotate_args(sc, cdr(code), sc->curlet); + set_c_function(code, f); + return(true); + + case T_CLOSURE: + if ((!has_methods(f)) && + (closure_arity_to_int(sc, f) == num_args)) + { + int32_t hop = (is_immutable_and_stable(sc, car(code))) ? 1 : 0; + fx_annotate_args(sc, cdr(code), sc->curlet); + if ((is_fx_treeable(cdr(code))) && (tis_slot(let_slots(sc->curlet)))) fx_curlet_tree(sc, cdr(code)); + if (is_safe_closure(f)) + { + if (num_args != 3) + set_safe_optimize_op(code, hop + OP_SAFE_CLOSURE_NA); + else + if (is_normal_happy_symbol(sc, cadr(code))) + set_safe_optimize_op(code, hop + ((is_normal_happy_symbol(sc, caddr(code))) ? OP_SAFE_CLOSURE_SSA : OP_SAFE_CLOSURE_SAA)); + else set_safe_optimize_op(code, hop + (((!is_pair(caddr(code))) && (!is_pair(cadddr(code)))) ? OP_SAFE_CLOSURE_AGG : OP_SAFE_CLOSURE_3A)); + } + else + if (num_args != 3) + set_safe_optimize_op(code, hop + ((num_args == 4) ? OP_CLOSURE_4A : OP_CLOSURE_NA)); + else + if ((is_normal_happy_symbol(sc, caddr(code))) && (is_normal_happy_symbol(sc, cadddr(code)))) + set_safe_optimize_op(code, hop + OP_CLOSURE_ASS); + else + if (is_normal_happy_symbol(sc, cadr(code))) + set_safe_optimize_op(code, hop + ((is_normal_happy_symbol(sc, cadddr(code))) ? OP_CLOSURE_SAS : OP_CLOSURE_SAA)); + else + if (is_normal_happy_symbol(sc, caddr(code))) + set_safe_optimize_op(code, hop + OP_CLOSURE_ASA); + else set_safe_optimize_op(code, hop + ((is_normal_happy_symbol(sc, cadddr(code))) ? OP_CLOSURE_AAS : OP_CLOSURE_3A)); + set_opt1_lambda_add(code, f); + return(true); + } + if (is_symbol(closure_args(f))) + { + optimize_closure_sym(sc, code, f, 0, num_args, sc->curlet); + if (optimize_op(code) == OP_ANY_CLOSURE_SYM) return(true); + } + break; + + case T_CLOSURE_STAR: + if ((!has_methods(f)) && + ((closure_star_arity_to_int(sc, f) < 0) || ((closure_star_arity_to_int(sc, f) * 2) >= num_args))) + { + int32_t hop = (is_immutable_and_stable(sc, car(code))) ? 1 : 0; + if (num_args > 0) + { + set_opt3_arglen(cdr(code), num_args); + fx_annotate_args(sc, cdr(code), sc->curlet); + if ((is_fx_treeable(cdr(code))) && (tis_slot(let_slots(sc->curlet)))) fx_curlet_tree(sc, cdr(code)); + } + if (is_safe_closure(f)) + switch (num_args) + { + case 0: return(fixup_unknown_op(sc, code, f, hop + OP_SAFE_CLOSURE_STAR_NA_0)); + case 1: return(fixup_unknown_op(sc, code, f, hop + OP_SAFE_CLOSURE_STAR_NA_1)); + case 2: return(fixup_unknown_op(sc, code, f, hop + OP_SAFE_CLOSURE_STAR_NA_2)); + case 3: if (closure_star_arity_to_int(sc, f) == 3) return(fixup_unknown_op(sc, code, f, OP_SAFE_CLOSURE_STAR_3A)); + default: return(fixup_unknown_op(sc, code, f, hop + OP_SAFE_CLOSURE_STAR_NA)); + } + return(fixup_unknown_op(sc, code, f, hop + OP_CLOSURE_STAR_NA)); + } + break; + + case T_MACRO: return(fixup_unknown_op(sc, code, f, fixup_macro_d(sc, OP_MACRO_D, f))); + case T_MACRO_STAR: return(fixup_unknown_op(sc, code, f, fixup_macro_d(sc, OP_MACRO_STAR_D, f))); + /* implicit vector doesn't happen */ + + default: break; + } + /* closure happens if wrong-number-of-args passed -- probably no need for op_s_na */ + /* PERHAPS: vector */ + return(unknown_unknown(sc, sc->code, OP_CLEAR_OPTS)); +} + +static bool op_unknown_np(s7_scheme *sc) +{ + s7_pointer code = sc->code, f = sc->last_function; + int32_t num_args = (is_pair(cdr(code))) ? opt3_arglen(cdr(code)) : 0; + + if (!f) unbound_variable_error_nr(sc, car(sc->code)); + if (SHOW_EVAL_OPS) fprintf(stderr, "%s[%d]: %s %s %s\n", __func__, __LINE__, display(f), type_name(sc, f, NO_ARTICLE), display(sc->code)); + + switch (type(f)) + { + case T_C_FUNCTION: + if (!(c_function_is_aritable(f, num_args))) break; + case T_C_RST_NO_REQ_FUNCTION: + if (num_args == 1) + set_any_c_np(sc, f, code, sc->curlet, num_args, (is_safe_procedure(f)) ? OP_SAFE_C_P : OP_C_P); + else + if ((num_args == 2) && (is_safe_procedure(f))) + { + set_any_c_np(sc, f, code, sc->curlet, 2, OP_SAFE_C_PP); + opt_sp_1(sc, c_function_call(f), code); + gx_annotate_args(sc, cdr(code), sc->curlet); + } + else + if ((num_args == 3) && + ((is_safe_procedure(f)) || + ((is_semisafe(f)) && + (((car(code) != sc->assoc_symbol) && (car(code) != sc->member_symbol)) || + (unsafe_is_safe(sc, cadddr(code), sc->curlet)))))) + set_any_c_np(sc, f, code, sc->curlet, 3, OP_SAFE_C_3P); + else set_any_c_np(sc, f, code, sc->curlet, num_args, OP_ANY_C_NP); + return(true); + + case T_CLOSURE: + if ((!has_methods(f)) && + (closure_arity_to_int(sc, f) == num_args)) + { + int32_t hop = (is_immutable_and_stable(sc, car(code))) ? 1 : 0; + switch (num_args) + { + case 1: + if (is_safe_closure(f)) + { + s7_pointer body = closure_body(f); + if ((is_null(cdr(body))) && (is_fxable(sc, car(body)))) + { + set_optimize_op(code, hop + OP_SAFE_CLOSURE_P_A); + fx_annotate_arg(sc, body, sc->curlet); + } + else set_optimize_op(code, hop + OP_SAFE_CLOSURE_P); + } + else set_optimize_op(code, hop + OP_CLOSURE_P); + set_opt1_lambda_add(code, f); /* added 8-Jun-22 */ + set_opt3_arglen(cdr(code), 1); + set_unsafely_optimized(code); + break; + + case 2: + if (is_fxable(sc, cadr(code))) + { + fx_annotate_arg(sc, cdr(code), sc->curlet); + set_optimize_op(code, hop + ((is_safe_closure(f)) ? OP_SAFE_CLOSURE_AP : OP_CLOSURE_AP)); + } + else + if (is_fxable(sc, caddr(code))) + { + fx_annotate_arg(sc, cddr(code), sc->curlet); + set_optimize_op(code, hop + ((is_safe_closure(f)) ? OP_SAFE_CLOSURE_PA : OP_CLOSURE_PA)); + } + else set_optimize_op(code, hop + ((is_safe_closure(f)) ? OP_SAFE_CLOSURE_PP : OP_CLOSURE_PP)); + set_opt1_lambda_add(code, f); /* added 8-Jun-22 */ + set_opt3_arglen(cdr(code), 2); /* for later op_unknown_np */ + set_unsafely_optimized(code); + break; + + case 3: set_any_closure_np(sc, f, code, sc->curlet, 3, hop + OP_ANY_CLOSURE_3P); break; + case 4: set_any_closure_np(sc, f, code, sc->curlet, 4, hop + OP_ANY_CLOSURE_4P); break; + default: set_any_closure_np(sc, f, code, sc->curlet, num_args, hop + OP_ANY_CLOSURE_NP); break; + } + return(true); + } + break; + + case T_MACRO: return(fixup_unknown_op(sc, code, f, fixup_macro_d(sc, OP_MACRO_D, f))); + case T_MACRO_STAR: return(fixup_unknown_op(sc, code, f, fixup_macro_d(sc, OP_MACRO_STAR_D, f))); + } + return(unknown_unknown(sc, sc->code, OP_CLEAR_OPTS)); +} + +static bool unknown_any(s7_scheme *sc, s7_pointer f, s7_pointer code) +{ + sc->last_function = f; + if (is_null(cdr(code))) return(op_unknown(sc)); + if ((is_null(cddr(code))) && (is_normal_symbol(cadr(code)))) return(op_unknown_s(sc)); + set_opt3_arglen(cdr(code), proper_list_length(cdr(code))); + return(op_unknown_np(sc)); +} + + +/* ---------------- eval type checkers ---------------- */ +#if WITH_GCC +#define h_c_function_is_ok(Sc, P) ({s7_pointer _P_; _P_ = P; ((op_has_hop(_P_)) || (c_function_is_ok(Sc, _P_)));}) +#else +#define h_c_function_is_ok(Sc, P) ((op_has_hop(P)) || (c_function_is_ok(Sc, P))) +#endif + +#define c_function_is_ok_cadr(Sc, P) ((c_function_is_ok(Sc, P)) && (h_c_function_is_ok(Sc, cadr(P)))) +#define c_function_is_ok_caddr(Sc, P) ((c_function_is_ok(Sc, P)) && (h_c_function_is_ok(Sc, caddr(P)))) + +static bool c_function_is_ok_cadr_caddr(s7_scheme *sc, s7_pointer p) +{ + return((c_function_is_ok(sc, p)) && (h_c_function_is_ok(sc, cadr(p))) && (h_c_function_is_ok(sc, caddr(p)))); +} + +static bool c_function_is_ok_cadr_cadadr(s7_scheme *sc, s7_pointer p) +{ + return((c_function_is_ok(sc, p)) && (h_c_function_is_ok(sc, cadr(p))) && (h_c_function_is_ok(sc, opt3_pair(p)))); /* cadadr(P) */ +} + +static bool c_function_is_ok_cadr_caddadr(s7_scheme *sc, s7_pointer p) +{ + return((c_function_is_ok(sc, p)) && (h_c_function_is_ok(sc, cadr(p))) && (h_c_function_is_ok(sc, opt3_pair(p)))); /* caddadr(p) */ +} + +/* closure_is_ok_1 checks the type and the body length indications + * closure_is_fine_1 just checks the type (safe or unsafe closure) + * closure_is_ok calls _ok_1, closure_is_fine calls _fine_1 + * closure_np_is_ok accepts safe/unsafe etc + */ + +static /* inline */ bool closure_is_ok_1(s7_scheme *sc, s7_pointer code, uint16_t type, int32_t args) +{ + s7_pointer f = lookup_unexamined(sc, car(code)); + if ((f == opt1_lambda_unchecked(code)) || + ((f) && /* this fixup check does save time (e.g. cb) */ + (typesflag(f) == type) && + ((closure_arity(f) == args) || (closure_arity_to_int(sc, f) == args)) && /* 3 type bits to replace this but not hit enough to warrant them */ + (set_opt1_lambda(code, f)))) + return(true); + sc->last_function = f; + return(false); +} + +static /* inline */ bool closure_is_fine_1(s7_scheme *sc, s7_pointer code, uint16_t type, int32_t args) +{ + s7_pointer f = lookup_unexamined(sc, car(code)); + if ((f == opt1_lambda_unchecked(code)) || + ((f) && + ((typesflag(f) & (TYPE_MASK | T_SAFE_CLOSURE)) == type) && + ((closure_arity(f) == args) || (closure_arity_to_int(sc, f) == args)) && + (set_opt1_lambda(code, f)))) + return(true); + sc->last_function = f; + return(false); +} + +static bool closure_np_is_ok_1(s7_scheme *sc, s7_pointer code) +{ + s7_pointer f = lookup_unexamined(sc, car(code)); + if ((f == opt1_lambda_unchecked(code)) || + ((f) && + (is_closure(f)) && + (set_opt1_lambda(code, f)))) + return(true); + sc->last_function = f; + return(false); +} + +#define closure_is_ok(Sc, Code, Type, Args) ((symbol_ctr(car(Code)) == 1) || (closure_is_ok_1(Sc, Code, Type, Args))) +#define closure_np_is_ok(Sc, Code) ((symbol_ctr(car(Code)) == 1) || (closure_np_is_ok_1(Sc, Code))) +#define closure_is_fine(Sc, Code, Type, Args) ((symbol_ctr(car(Code)) == 1) || (closure_is_fine_1(Sc, Code, Type, Args))) +#define closure_star_is_fine(Sc, Code, Type, Args) ((symbol_ctr(car(Code)) == 1) || (closure_star_is_fine_1(Sc, Code, Type, Args))) + +static /* inline */ bool closure_is_eq(s7_scheme *sc) +{ + sc->last_function = lookup_unexamined(sc, car(sc->code)); + return(sc->last_function == opt1_lambda_unchecked(sc->code)); +} + +static bool star_arity_is_ok(s7_scheme *sc, s7_pointer val, int32_t args) +{ + int32_t arity = closure_star_arity_to_int(sc, val); + return((arity < 0) || ((arity * 2) >= args)); +} + +static bool closure_star_is_fine_1(s7_scheme *sc, s7_pointer code, uint16_t type, int32_t args) +{ + s7_pointer val = lookup_unexamined(sc, car(code)); + if ((val == opt1_lambda_unchecked(code)) || + ((val) && + ((typesflag(val) & (T_SAFE_CLOSURE | TYPE_MASK)) == type) && + (star_arity_is_ok(sc, val, args)) && + (set_opt1_lambda(code, val)))) + return(true); + sc->last_function = val; + return(false); +} + +/* closure_is_fine: */ +#define FINE_UNSAFE_CLOSURE (T_CLOSURE) +#define FINE_SAFE_CLOSURE (T_CLOSURE | T_SAFE_CLOSURE) + +/* closure_star_is_fine: */ +#define FINE_UNSAFE_CLOSURE_STAR (T_CLOSURE_STAR) +#define FINE_SAFE_CLOSURE_STAR (T_CLOSURE_STAR | T_SAFE_CLOSURE) + +/* closure_is_ok: */ +#define OK_UNSAFE_CLOSURE_P (T_CLOSURE | T_ONE_FORM) +#define OK_SAFE_CLOSURE_P (T_CLOSURE | T_SAFE_CLOSURE | T_ONE_FORM) +#define OK_UNSAFE_CLOSURE_M (T_CLOSURE | T_MULTIFORM) +#define OK_SAFE_CLOSURE_M (T_CLOSURE | T_SAFE_CLOSURE | T_MULTIFORM) +#define OK_SAFE_CLOSURE_A (T_CLOSURE | T_SAFE_CLOSURE | T_ONE_FORM_FX_ARG) +/* since T_HAS_METHODS is on if there might be methods, this can protect us from that case */ + + +/* ---------------- eval ---------------- */ +static s7_pointer eval(s7_scheme *sc, opcode_t first_op) +{ + if (SHOW_EVAL_OPS) safe_print(fprintf(stderr, "eval[%d]:, %s %s %s\n", __LINE__, op_names[first_op], display_80(sc->code), display_80(sc->args))); + sc->cur_op = first_op; + goto TOP_NO_POP; + + while (true) /* "continue" in this procedure refers to this loop */ + { + pop_stack(sc); + goto TOP_NO_POP; + + BEGIN: + if (is_pair(cdr(sc->code))) + { + set_current_code(sc, sc->code); + push_stack_no_args(sc, sc->begin_op, cdr(sc->code)); + } + sc->code = car(sc->code); + + EVAL: + sc->cur_op = optimize_op(sc->code); /* sc->code can be anything, optimize_op examines a type field (opt_choice) */ + + TOP_NO_POP: + if (SHOW_EVAL_OPS) safe_print(fprintf(stderr, " %s (%d), code: %s\n", op_names[sc->cur_op], (int)(sc->cur_op), display_80(sc->code))); + + /* it is only slightly faster to use labels as values (computed gotos) here. In my timing tests (June-2018), the best case speedup was in titer.scm + * callgrind numbers 4808 to 4669; another good case was tread.scm: 2410 to 2386. Most timings were a draw. computed-gotos-s7.c has the code, + * macroized so it will work if such gotos aren't available. I think I'll stick with a switch statement. + * Another idea is to put the function in the tree, not an index to it (the optimize_op business above), + * then the switch below is not needed, and we free up 16 type bits. C does not guarantee tail calls (I think) + * so we'd have each function return the next, and eval would be [while (true) f = f(sc)] but would the function + * call overhead be less expensive than the switch? (We get most functions inlined in the current code). + * with some fake fx_calls for the P cases, many of these could be [sc->value = fx_function[sc->cur_op](sc, sc->code); continue;] + * so the switch statement is unnecessary -- maybe a table eval_functions[cur_op] eventually + */ + switch (sc->cur_op) + { + /* safe c_functions */ + case OP_SAFE_C_NC: if (!c_function_is_ok(sc, sc->code)) break; /* break refers to the switch statement */ + case HOP_SAFE_C_NC: sc->value = fc_call(sc, sc->code); continue; /* continue refers to the outer while loop -- unfortunate C ambiguity */ + + case OP_SAFE_C_S: if (!c_function_is_ok(sc, sc->code)) {if (op_unknown_s(sc)) goto EVAL; continue;} /* checking symbol_ctr(car(sc->code)) == 1 just slows us down */ + case HOP_SAFE_C_S: inline_op_safe_c_s(sc); continue; + + case OP_SAFE_C_SS: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_SS: inline_op_safe_c_ss(sc); continue; + + case OP_SAFE_C_NS: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_NS: sc->value = fx_c_ns(sc, sc->code); continue; + + case OP_SAFE_C_SC: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_SC: op_safe_c_sc(sc); continue; + + case OP_SAFE_C_CS: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_CS: sc->value = fx_c_cs(sc, sc->code); continue; + + case OP_SAFE_C_CQ: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_CQ: sc->value = fx_c_cq(sc, sc->code); continue; + + case OP_SAFE_C_FF: if (!c_function_is_ok(sc, sc->code)) {if (op_unknown_np(sc)) goto EVAL; continue;} + case HOP_SAFE_C_FF: sc->value = fx_c_ff(sc, sc->code); continue; + + case OP_SAFE_C_P: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_P: op_safe_c_p(sc); goto EVAL; + case OP_SAFE_C_P_1: op_safe_c_p_1(sc); continue; + + case OP_ANY_C_NP: if (!c_function_is_ok(sc, sc->code)) {if (op_unknown_np(sc)) goto EVAL; continue;} + case HOP_ANY_C_NP: if (op_any_c_np(sc)) goto EVAL; continue; + case OP_ANY_C_NP_1: if (inline_op_any_c_np_1(sc)) goto EVAL; continue; + case OP_ANY_C_NP_2: op_any_c_np_2(sc); continue; + case OP_ANY_C_NP_MV: if (op_any_c_np_mv(sc)) goto EVAL; goto APPLY; + + case OP_SAFE_C_SSP: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_SSP: op_safe_c_ssp(sc); goto EVAL; + case OP_SAFE_C_SSP_1: op_safe_c_ssp_1(sc); continue; + case OP_SAFE_C_SSP_MV: op_safe_c_ssp_mv(sc); goto APPLY; + + case OP_SAFE_C_A: if (!c_function_is_ok(sc, sc->code)) {if (op_unknown_a(sc)) goto EVAL; continue;} + case HOP_SAFE_C_A: sc->value = fx_c_a(sc, sc->code); continue; + + case OP_SAFE_C_opAq: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_opAq: sc->value = fx_c_opaq(sc, sc->code); continue; + + case OP_SAFE_C_opAAq: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_opAAq: sc->value = fx_c_opaaq(sc, sc->code); continue; + + case OP_SAFE_C_opAAAq: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_opAAAq: sc->value = fx_c_opaaaq(sc, sc->code); continue; + + case OP_SAFE_C_S_opAq: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_S_opAq: sc->value = fx_c_s_opaq(sc, sc->code); continue; + + case OP_SAFE_C_opAq_S: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_opAq_S: sc->value = fx_c_opaq_s(sc, sc->code); continue; + + case OP_SAFE_C_S_opAAq: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_S_opAAq: sc->value = fx_c_s_opaaq(sc, sc->code); continue; + + case OP_SAFE_C_AA: if (!c_function_is_ok(sc, sc->code)) {if (op_unknown_aa(sc)) goto EVAL; continue;} + case HOP_SAFE_C_AA: sc->value = fx_c_aa(sc, sc->code); continue; + + case OP_SAFE_C_SA: if (!c_function_is_ok(sc, sc->code)) {if (op_unknown_aa(sc)) goto EVAL; continue;} + case HOP_SAFE_C_SA: sc->value = fx_c_sa(sc, sc->code); continue; + + case OP_SAFE_C_AS: if (!c_function_is_ok(sc, sc->code)) {if (op_unknown_aa(sc)) goto EVAL; continue;} + case HOP_SAFE_C_AS: sc->value = fx_c_as(sc, sc->code); continue; + + case OP_SAFE_C_CA: if (!c_function_is_ok(sc, sc->code)) {if (op_unknown_aa(sc)) goto EVAL; continue;} + case HOP_SAFE_C_CA: sc->value = fx_c_ca(sc, sc->code); continue; + + case OP_SAFE_C_AC: if (!c_function_is_ok(sc, sc->code)) {if (op_unknown_aa(sc)) goto EVAL; continue;} + case HOP_SAFE_C_AC: sc->value = fx_c_ac(sc, sc->code); continue; + + case OP_SAFE_C_AAA: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_AAA: sc->value = fx_c_aaa(sc, sc->code); continue; + + case OP_SAFE_C_SAA: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_SAA: sc->value = fx_c_saa(sc, sc->code); continue; + + case OP_SAFE_C_SSA: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_SSA: sc->value = fx_c_ssa(sc, sc->code); continue; + case HOP_HASH_TABLE_INCREMENT: sc->value = fx_hash_table_increment(sc, sc->code); continue; /* a placeholder, almost never called */ + + case OP_SAFE_C_SAS: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_SAS: sc->value = fx_c_sas(sc, sc->code); continue; + + case OP_SAFE_C_ASS: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_ASS: sc->value = fx_c_ass(sc, sc->code); continue; + + case OP_SAFE_C_AGG: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_AGG: sc->value = fx_c_agg(sc, sc->code); continue; + + case OP_SAFE_C_CAC: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_CAC: sc->value = fx_c_cac(sc, sc->code); continue; + + case OP_SAFE_C_CSA: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_CSA: sc->value = fx_c_csa(sc, sc->code); continue; + + case OP_SAFE_C_SCA: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_SCA: sc->value = fx_c_sca(sc, sc->code); continue; + + case OP_SAFE_C_4A: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_4A: sc->value = fx_c_4a(sc, sc->code); continue; + + case OP_SAFE_C_NA: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_NA: sc->value = fx_c_na(sc, sc->code); continue; + + case OP_SAFE_C_ALL_CA: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_ALL_CA: sc->value = fx_c_all_ca(sc, sc->code); continue; + + case OP_SAFE_C_SCS: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_SCS: sc->value = fx_c_scs(sc, sc->code); continue; + + case OP_SAFE_C_SSC: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_SSC: sc->value = fx_c_ssc(sc, sc->code); continue; + + case OP_SAFE_C_SCC: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_SCC: sc->value = fx_c_scc(sc, sc->code); continue; + + case OP_SAFE_C_CSC: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_CSC: sc->value = fx_c_csc(sc, sc->code); continue; + + case OP_SAFE_C_CCS: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_CCS: sc->value = fx_c_ccs(sc, sc->code); continue; + + case OP_SAFE_C_CSS: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_CSS: sc->value = fx_c_css(sc, sc->code); continue; + + case OP_SAFE_C_SSS: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_SSS: sc->value = fx_c_sss(sc, sc->code); continue; + + case OP_SAFE_C_opNCq: if (!c_function_is_ok_cadr(sc, sc->code)) break; + case HOP_SAFE_C_opNCq: sc->value = fx_c_opncq(sc, sc->code); continue; + + case OP_SAFE_C_opSq: if (!c_function_is_ok_cadr(sc, sc->code)) break; + case HOP_SAFE_C_opSq: sc->value = fx_c_opsq(sc, sc->code); continue; + + case OP_SAFE_C_op_opSqq: if (!c_function_is_ok_cadr_cadadr(sc, sc->code)) break; + case HOP_SAFE_C_op_opSqq: sc->value = fx_c_op_opsqq(sc, sc->code); continue; /* lg cb (splits to not) */ + + case OP_SAFE_C_op_S_opSqq: if (!c_function_is_ok_cadr_caddadr(sc, sc->code)) break; + case HOP_SAFE_C_op_S_opSqq: sc->value = fx_c_op_s_opsqq(sc, sc->code); continue; /* tlet sg (splits to not) */ + + case OP_SAFE_C_op_opSq_Sq: if (!c_function_is_ok_cadr_cadadr(sc, sc->code)) break; + case HOP_SAFE_C_op_opSq_Sq: sc->value = fx_c_op_opsq_sq(sc, sc->code); continue; /* lg cb (splits to not etc) */ + + case OP_SAFE_C_PS: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_PS: op_safe_c_ps(sc); goto EVAL; + case OP_SAFE_C_PS_1: op_safe_c_ps_1(sc); continue; + case OP_SAFE_C_PS_MV: op_safe_c_ps_mv(sc); goto APPLY; + + case OP_SAFE_C_PC: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_PC: op_safe_c_pc(sc); goto EVAL; + case OP_SAFE_C_PC_1: op_safe_c_pc_1(sc); continue; + case OP_SAFE_C_PC_MV: op_safe_c_pc_mv(sc); goto APPLY; + + case OP_SAFE_C_SP: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_SP: op_safe_c_sp(sc); goto EVAL; + case OP_SAFE_C_SP_1: op_safe_c_sp_1(sc); continue; + case OP_SAFE_C_SP_MV: op_safe_c_sp_mv(sc); goto APPLY; + + case OP_SAFE_CONS_SP_1: sc->value = cons(sc, sc->args, sc->value); continue; + case OP_SAFE_ADD_SP_1: op_safe_add_sp_1(sc); continue; + case OP_SAFE_MULTIPLY_SP_1: op_safe_multiply_sp_1(sc); continue; + + case OP_SAFE_C_AP: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_AP: if (op_safe_c_ap(sc)) goto EVAL; continue; + + case OP_SAFE_C_PA: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_PA: if (op_safe_c_pa(sc)) goto EVAL; continue; + case OP_SAFE_C_PA_1: op_safe_c_pa_1(sc); continue; + case OP_SAFE_C_PA_MV: op_safe_c_pa_mv(sc); goto APPLY; + + case OP_SAFE_C_CP: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_CP: op_safe_c_cp(sc); goto EVAL; + + case OP_SAFE_C_PP: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_PP: op_safe_c_pp(sc); goto EVAL; + case OP_SAFE_C_PP_1: op_safe_c_pp_1(sc); goto EVAL; + case OP_SAFE_C_PP_3_MV: op_safe_c_pp_3_mv(sc); goto EVAL; + case OP_SAFE_C_PP_5: op_safe_c_pp_5(sc); goto APPLY; + case OP_SAFE_C_PP_6_MV: op_safe_c_pp_6_mv(sc); goto APPLY; + + case OP_SAFE_C_3P: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_3P: op_safe_c_3p(sc); goto EVAL; + case OP_SAFE_C_3P_1: op_safe_c_3p_1(sc); goto EVAL; + case OP_SAFE_C_3P_2: op_safe_c_3p_2(sc); goto EVAL; + case OP_SAFE_C_3P_3: op_safe_c_3p_3(sc); continue; + case OP_SAFE_C_3P_1_MV: op_safe_c_3p_1_mv(sc); goto EVAL; + case OP_SAFE_C_3P_2_MV: op_safe_c_3p_2_mv(sc); goto EVAL; + case OP_SAFE_C_3P_3_MV: op_safe_c_3p_3_mv(sc); goto APPLY; + + case OP_SAFE_C_opSSq: if (!c_function_is_ok_cadr(sc, sc->code)) break; + case HOP_SAFE_C_opSSq: sc->value = fx_c_opssq(sc, sc->code); continue; + + case OP_SAFE_C_opSCq: if (!c_function_is_ok_cadr(sc, sc->code)) break; + case HOP_SAFE_C_opSCq: sc->value = fx_c_opscq(sc, sc->code); continue; + + case OP_SAFE_C_opCSq: if (!c_function_is_ok_cadr(sc, sc->code)) break; + case HOP_SAFE_C_opCSq: sc->value = fx_c_opcsq(sc, sc->code); continue; + + case OP_SAFE_C_S_opSq: if (!c_function_is_ok_caddr(sc, sc->code)) break; + case HOP_SAFE_C_S_opSq: sc->value = fx_c_s_opsq(sc, sc->code); continue; + + case OP_SAFE_C_C_opSq: if (!c_function_is_ok_caddr(sc, sc->code)) break; + case HOP_SAFE_C_C_opSq: sc->value = fx_c_c_opsq(sc, sc->code); continue; + + case OP_SAFE_C_C_opSSq: if (!c_function_is_ok_caddr(sc, sc->code)) break; + case HOP_SAFE_C_C_opSSq: sc->value = fx_c_c_opssq(sc, sc->code); continue; + + case OP_SAFE_C_opCSq_C: if (!c_function_is_ok_cadr(sc, sc->code)) break; + case HOP_SAFE_C_opCSq_C: sc->value = fx_c_opcsq_c(sc, sc->code); continue; + + case OP_SAFE_C_opSSq_C: if (!c_function_is_ok_cadr(sc, sc->code)) break; + case HOP_SAFE_C_opSSq_C: sc->value = fx_c_opssq_c(sc, sc->code); continue; + + case OP_SAFE_C_opSSq_S: if (!c_function_is_ok_cadr(sc, sc->code)) break; + case HOP_SAFE_C_opSSq_S: sc->value = fx_c_opssq_s(sc, sc->code); continue; + + case OP_SAFE_C_op_opSSqq_S: if (!c_function_is_ok_cadr_cadadr(sc, sc->code)) break; + case HOP_SAFE_C_op_opSSqq_S: sc->value = fx_c_op_opssqq_s(sc, sc->code); continue; + + case OP_SAFE_C_opSCq_C: if (!c_function_is_ok_cadr(sc, sc->code)) break; + case HOP_SAFE_C_opSCq_C: sc->value = fx_c_opscq_c(sc, sc->code); continue; + + case OP_SAFE_C_opCSq_S: if (!c_function_is_ok_cadr(sc, sc->code)) break; + case HOP_SAFE_C_opCSq_S: sc->value = fx_c_opcsq_s(sc, sc->code); continue; + + case OP_SAFE_C_S_opSCq: if (!c_function_is_ok_caddr(sc, sc->code)) break; + case HOP_SAFE_C_S_opSCq: sc->value = fx_c_s_opscq(sc, sc->code); continue; + + case OP_SAFE_C_C_opSCq: if (!c_function_is_ok_caddr(sc, sc->code)) break; + case HOP_SAFE_C_C_opSCq: sc->value = fx_c_c_opscq(sc, sc->code); continue; + + case OP_SAFE_C_S_opSSq: if (!c_function_is_ok_caddr(sc, sc->code)) break; + case HOP_SAFE_C_S_opSSq: sc->value = fx_c_s_opssq(sc, sc->code); continue; + + case OP_SAFE_C_S_opCSq: if (!c_function_is_ok_caddr(sc, sc->code)) break; + case HOP_SAFE_C_S_opCSq: sc->value = fx_c_s_opcsq(sc, sc->code); continue; + + case OP_SAFE_C_opSq_S: if (!c_function_is_ok_cadr(sc, sc->code)) break; + case HOP_SAFE_C_opSq_S: sc->value = fx_c_opsq_s(sc, sc->code); continue; + + case OP_SAFE_C_opSq_CS: if (!c_function_is_ok_cadr(sc, sc->code)) break; + case HOP_SAFE_C_opSq_CS: sc->value = fx_c_opsq_cs(sc, sc->code); continue; + + case OP_SAFE_C_opSq_C: if (!c_function_is_ok_cadr(sc, sc->code)) break; + case HOP_SAFE_C_opSq_C: sc->value = fx_c_opsq_c(sc, sc->code); continue; + + case OP_SAFE_C_opSq_opSq: if (!c_function_is_ok_cadr_caddr(sc, sc->code)) break; + case HOP_SAFE_C_opSq_opSq: sc->value = fx_c_opsq_opsq(sc, sc->code); continue; + + case OP_SAFE_C_opSSq_opSSq: if (!c_function_is_ok_cadr_caddr(sc, sc->code)) break; + case HOP_SAFE_C_opSSq_opSSq: sc->value = fx_c_opssq_opssq(sc, sc->code); continue; + + case OP_SAFE_C_opSSq_opSq: if (!c_function_is_ok_cadr_caddr(sc, sc->code)) break; + case HOP_SAFE_C_opSSq_opSq: sc->value = fx_c_opssq_opsq(sc, sc->code); continue; + + case OP_SAFE_C_opSq_opSSq: if (!c_function_is_ok_cadr_caddr(sc, sc->code)) break; + case HOP_SAFE_C_opSq_opSSq: sc->value = fx_c_opsq_opssq(sc, sc->code); continue; + + + /* semisafe c_functions */ + case OP_CL_S: if (!cl_function_is_ok(sc, sc->code)) break; + case HOP_CL_S: inline_op_safe_c_s(sc); continue; + + case OP_CL_SS: if (!cl_function_is_ok(sc, sc->code)) break; + case HOP_CL_SS: inline_op_safe_c_ss(sc); continue; /* safe_c case has the code we want */ + + case OP_CL_A: if (!cl_function_is_ok(sc, sc->code)) {set_optimize_op(sc->code, OP_S_A); goto EVAL;} + case HOP_CL_A: op_cl_a(sc); continue; + + case OP_CL_AA: if (!cl_function_is_ok(sc, sc->code)) break; + case HOP_CL_AA: op_cl_aa(sc); continue; + + case OP_CL_SAS: if (!cl_function_is_ok(sc, sc->code)) break; + case HOP_CL_SAS: op_cl_sas(sc); continue; + + case OP_CL_NA: if (!cl_function_is_ok(sc, sc->code)) break; + case HOP_CL_NA: op_cl_na(sc); continue; + + case OP_CL_FA: if (!cl_function_is_ok(sc, sc->code)) break; + case HOP_CL_FA: op_cl_fa(sc); continue; /* op_c_fs was not faster if fx_s below */ + case OP_MAP_FOR_EACH_FA: op_map_for_each_fa(sc); continue; /* here only if for-each or map + one seq */ + case OP_MAP_FOR_EACH_FAA: op_map_for_each_faa(sc); continue; /* here only if for-each or map + twp seqs */ + + + /* unsafe c_functions */ + case OP_C: if (!c_function_is_ok(sc, sc->code)) {set_optimize_op(sc->code, OP_S); goto EVAL;} + case HOP_C: sc->value = fn_proc(sc->code)(sc, sc->nil); continue; + + case OP_C_S: if (!c_function_is_ok(sc, sc->code)) {set_optimize_op(sc->code, OP_S_G); goto EVAL;} + case HOP_C_S: op_c_s(sc); continue; + + case OP_READ_S: if (!c_function_is_ok(sc, sc->code)) {set_optimize_op(sc->code, OP_S_G); goto EVAL;} + case HOP_READ_S: op_read_s(sc); continue; + + case OP_C_A: if (!c_function_is_ok(sc, sc->code)) {set_optimize_op(sc->code, OP_S_A); goto EVAL;} + case HOP_C_A: op_c_a(sc); continue; + + case OP_C_P: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_C_P: op_c_p(sc); goto EVAL; + case OP_C_P_1: sc->value = fn_proc(sc->code)(sc, list_1(sc, sc->value)); continue; + case OP_C_P_MV: op_c_p_mv(sc); goto APPLY; + + case OP_C_SS: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_C_SS: op_c_ss(sc); continue; + + case OP_C_AP: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_C_AP: op_c_ap(sc); goto EVAL; + case OP_C_AP_1: sc->value = fn_proc(sc->code)(sc, sc->args = list_2(sc, sc->args, sc->value)); continue; + case OP_C_AP_MV: op_c_ap_mv(sc); goto APPLY; + + case OP_C_AA: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_C_AA: op_c_aa(sc); continue; + + case OP_C_NA: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_C_NA: op_c_na(sc); continue; + + case OP_APPLY_SS: inline_op_apply_ss(sc); goto APPLY; + case OP_APPLY_SA: op_apply_sa(sc); goto APPLY; + case OP_APPLY_SL: op_apply_sl(sc); goto APPLY; + + case OP_CALL_WITH_EXIT: op_call_with_exit(sc); goto BEGIN; + case OP_CALL_CC: op_call_cc(sc); goto BEGIN; + case OP_CALL_WITH_EXIT_O: op_call_with_exit_o(sc); goto EVAL; + case OP_C_CATCH: op_c_catch(sc); goto BEGIN; + case OP_C_CATCH_ALL: op_c_catch_all(sc); goto BEGIN; + case OP_C_CATCH_ALL_O: op_c_catch_all(sc); goto EVAL; + case OP_C_CATCH_ALL_A: op_c_catch_all_a(sc); continue; + + case OP_WITH_IO: if (op_with_io_op(sc)) goto EVAL; goto BEGIN; + case OP_WITH_IO_1: + if (!is_string(sc->value)) {op_with_io_1_method(sc); continue;} + sc->code = op_with_io_1(sc); + goto BEGIN; + + case OP_WITH_IO_C: sc->value = cadr(sc->code); sc->code = op_with_io_1(sc); goto BEGIN; + case OP_WITH_OUTPUT_TO_STRING: op_with_output_to_string(sc); goto BEGIN; + case OP_CALL_WITH_OUTPUT_STRING: op_call_with_output_string(sc); goto BEGIN; + + + case OP_F: op_f(sc); goto BEGIN; + case OP_F_A: op_f_a(sc); goto BEGIN; + case OP_F_AA: op_f_aa(sc); goto BEGIN; + case OP_F_NP: op_f_np(sc); goto EVAL; + case OP_F_NP_1: if (op_f_np_1(sc)) goto EVAL; goto BEGIN; + + case OP_S: op_s(sc); goto APPLY; + case OP_S_G: if (op_s_g(sc)) continue; goto APPLY; + case OP_S_A: if (op_x_a(sc, lookup_checked(sc, car(sc->code)))) continue; goto APPLY; + case OP_A_A: if (op_x_a(sc, fx_call(sc, sc->code))) continue; goto APPLY; + case OP_S_AA: op_x_aa(sc, lookup_checked(sc, car(sc->code))); goto APPLY; + case OP_A_AA: op_x_aa(sc, fx_call(sc, sc->code)); goto APPLY; + case OP_P_S: push_stack_no_args_direct(sc, OP_P_S_1); sc->code = car(sc->code); goto EVAL; + case OP_P_S_1: op_p_s_1(sc); goto APPLY; + + case OP_SAFE_C_STAR: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_STAR: op_safe_c_star(sc); continue; + + case OP_SAFE_C_STAR_A: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_STAR_A: op_safe_c_star_a(sc); continue; + + case OP_SAFE_C_STAR_AA: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_STAR_AA: op_safe_c_star_aa(sc); continue; + + case OP_SAFE_C_STAR_NA: if (!c_function_is_ok(sc, sc->code)) break; + case HOP_SAFE_C_STAR_NA: op_safe_c_star_na(sc); continue; + + + case OP_THUNK: if (!closure_is_ok(sc, sc->code, FINE_UNSAFE_CLOSURE, 0)) {if (op_unknown(sc)) goto EVAL; continue;} + case HOP_THUNK: op_thunk(sc); goto EVAL; + + case OP_THUNK_O: if (!closure_is_ok(sc, sc->code, OK_UNSAFE_CLOSURE_P, 0)) {if (op_unknown(sc)) goto EVAL; continue;} + case HOP_THUNK_O: op_thunk_o(sc); goto EVAL; + + case OP_SAFE_THUNK: if (!closure_is_fine(sc, sc->code, FINE_SAFE_CLOSURE, 0)) {if (op_unknown(sc)) goto EVAL; continue;} + case HOP_SAFE_THUNK: op_safe_thunk(sc); goto EVAL; + + case OP_THUNK_ANY: if (!closure_is_fine(sc, sc->code, FINE_UNSAFE_CLOSURE, 1)) break; /* symbol as arglist */ + case HOP_THUNK_ANY: op_thunk_any(sc); goto BEGIN; + + case OP_SAFE_THUNK_ANY: if (!closure_is_fine(sc, sc->code, FINE_SAFE_CLOSURE, 1)) break; /* symbol as arglist */ + case HOP_SAFE_THUNK_ANY: op_safe_thunk_any(sc); goto EVAL; + + case OP_SAFE_THUNK_A: if (!closure_is_ok(sc, sc->code, OK_SAFE_CLOSURE_A, 0)) {if (op_unknown(sc)) goto EVAL; continue;} + case HOP_SAFE_THUNK_A: sc->value = op_safe_thunk_a(sc, sc->code); continue; + + case OP_CLOSURE_S: if (!closure_is_fine(sc, sc->code, FINE_UNSAFE_CLOSURE, 1)) {if (op_unknown_s(sc)) goto EVAL; continue;} + case HOP_CLOSURE_S: op_closure_s(sc); goto EVAL; + + case OP_CLOSURE_S_O: if (!closure_is_ok(sc, sc->code, OK_UNSAFE_CLOSURE_P, 1)) {if (op_unknown_s(sc)) goto EVAL; continue;} + case HOP_CLOSURE_S_O: op_closure_s_o(sc); goto EVAL; + + case OP_SAFE_CLOSURE_S: if (!closure_is_fine(sc, sc->code, FINE_SAFE_CLOSURE, 1)) {if (op_unknown_s(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_S: op_safe_closure_s(sc); goto EVAL; + + case OP_SAFE_CLOSURE_S_O: if (!closure_is_ok(sc, sc->code, OK_SAFE_CLOSURE_P, 1)) {if (op_unknown_s(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_S_O: op_safe_closure_s_o(sc); goto EVAL; + + case OP_SAFE_CLOSURE_S_A: if (!closure_is_ok(sc, sc->code, OK_SAFE_CLOSURE_A, 1)) {if (op_unknown_s(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_S_A: sc->value = op_safe_closure_s_a(sc, sc->code); continue; + + case OP_SAFE_CLOSURE_S_TO_S: if ((symbol_ctr(car(sc->code)) > 1) && (!closure_is_eq(sc))) {if (op_unknown_s(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_S_TO_S: sc->value = fx_safe_closure_s_to_s(sc, sc->code); continue; + + case OP_SAFE_CLOSURE_S_TO_SC: if ((symbol_ctr(car(sc->code)) > 1) && (!closure_is_eq(sc))) {if (op_unknown_s(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_S_TO_SC: sc->value = fx_proc(cdr(sc->code))(sc, sc->code); continue; + + case OP_SAFE_CLOSURE_A_TO_SC: if ((symbol_ctr(car(sc->code)) > 1) && (!closure_is_eq(sc))) {if (op_unknown_a(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_A_TO_SC: sc->value = fx_proc(sc->code)(sc, sc->code); continue; + + case OP_CLOSURE_P: if (!closure_is_fine(sc, sc->code, FINE_UNSAFE_CLOSURE, 1)) {if (op_unknown_np(sc)) goto EVAL; continue;} + case HOP_CLOSURE_P: op_closure_p(sc); goto EVAL; + case OP_CLOSURE_P_1: op_closure_p_1(sc); goto BEGIN; + + case OP_SAFE_CLOSURE_P: if (!closure_is_fine(sc, sc->code, FINE_SAFE_CLOSURE, 1)) {if (op_unknown_np(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_P: op_safe_closure_p(sc); goto EVAL; + case OP_SAFE_CLOSURE_P_1: op_safe_closure_p_1(sc); goto BEGIN; + + case OP_SAFE_CLOSURE_P_A: if (!closure_is_fine(sc, sc->code, FINE_SAFE_CLOSURE, 1)) {if (op_unknown_np(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_P_A: op_safe_closure_p_a(sc); goto EVAL; + case OP_SAFE_CLOSURE_P_A_1: op_safe_closure_p_a_1(sc); continue; + + case OP_CLOSURE_A: if (!closure_is_ok(sc, sc->code, OK_UNSAFE_CLOSURE_M, 1)) {if (op_unknown_a(sc)) goto EVAL; continue;} + case HOP_CLOSURE_A: inline_op_closure_a(sc); push_stack_no_args(sc, sc->begin_op, T_Pair(cdr(sc->code))); sc->code = car(sc->code); goto EVAL; + + case OP_CLOSURE_A_O: if (!closure_is_ok(sc, sc->code, OK_UNSAFE_CLOSURE_P, 1)) {if (op_unknown_a(sc)) goto EVAL; continue;} + case HOP_CLOSURE_A_O: inline_op_closure_a(sc); sc->code = car(sc->code); goto EVAL; + + case OP_SAFE_CLOSURE_A: if (!closure_is_ok(sc, sc->code, OK_SAFE_CLOSURE_M, 1)) {if (op_unknown_a(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_A: op_safe_closure_a(sc); goto EVAL; + + case OP_SAFE_CLOSURE_A_O: if (!closure_is_ok(sc, sc->code, OK_SAFE_CLOSURE_P, 1)) {if (op_unknown_a(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_A_O: op_safe_closure_a_o(sc); goto EVAL; + + case OP_SAFE_CLOSURE_A_A: if (!closure_is_ok(sc, sc->code, OK_SAFE_CLOSURE_A, 1)) {if (op_unknown_a(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_A_A: sc->value = op_safe_closure_a_a(sc, sc->code); continue; + + case OP_CLOSURE_AP: if (!closure_is_fine(sc, sc->code, FINE_UNSAFE_CLOSURE, 2)) {if (op_unknown_np(sc)) goto EVAL; continue;} + case HOP_CLOSURE_AP: op_closure_ap(sc); goto EVAL; + case OP_CLOSURE_AP_1: op_closure_ap_1(sc); goto BEGIN; + + case OP_CLOSURE_PA: if (!closure_is_fine(sc, sc->code, FINE_UNSAFE_CLOSURE, 2)) {if (op_unknown_np(sc)) goto EVAL; continue;} + case HOP_CLOSURE_PA: op_closure_pa(sc); goto EVAL; + case OP_CLOSURE_PA_1: op_closure_pa_1(sc); goto BEGIN; + + case OP_CLOSURE_PP: if (!closure_is_fine(sc, sc->code, FINE_UNSAFE_CLOSURE, 2)) {if (op_unknown_np(sc)) goto EVAL; continue;} + case HOP_CLOSURE_PP: op_closure_pp(sc); goto EVAL; + case OP_CLOSURE_PP_1: op_closure_pp_1(sc); goto EVAL; + + case OP_SAFE_CLOSURE_AP: if (!closure_is_fine(sc, sc->code, FINE_SAFE_CLOSURE, 2)) {if (op_unknown_np(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_AP: op_safe_closure_ap(sc); goto EVAL; + case OP_SAFE_CLOSURE_AP_1: op_safe_closure_ap_1(sc); goto BEGIN; + + case OP_SAFE_CLOSURE_PA: if (!closure_is_fine(sc, sc->code, FINE_SAFE_CLOSURE, 2)) {if (op_unknown_np(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_PA: op_safe_closure_pa(sc); goto EVAL; + case OP_SAFE_CLOSURE_PA_1: op_safe_closure_pa_1(sc); goto BEGIN; + + case OP_SAFE_CLOSURE_PP: if (!closure_is_fine(sc, sc->code, FINE_SAFE_CLOSURE, 2)) {if (op_unknown_np(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_PP: op_safe_closure_pp(sc); goto EVAL; + case OP_SAFE_CLOSURE_PP_1: op_safe_closure_pp_1(sc); goto EVAL; + + case OP_ANY_CLOSURE_3P: if (!closure_is_fine(sc, sc->code, FINE_SAFE_CLOSURE, 3)) {if (op_unknown_np(sc)) goto EVAL; continue;} + case HOP_ANY_CLOSURE_3P: op_any_closure_3p(sc); goto EVAL; + case OP_ANY_CLOSURE_3P_1: if (!op_any_closure_3p_1(sc)) goto EVAL; goto BEGIN; + case OP_ANY_CLOSURE_3P_2: if (!op_any_closure_3p_2(sc)) goto EVAL; goto BEGIN; + case OP_ANY_CLOSURE_3P_3: op_any_closure_3p_3(sc); goto BEGIN; + + case OP_ANY_CLOSURE_4P: if (!closure_is_fine(sc, sc->code, FINE_SAFE_CLOSURE, 4)) {if (op_unknown_np(sc)) goto EVAL; continue;} + case HOP_ANY_CLOSURE_4P: op_any_closure_4p(sc); goto EVAL; + case OP_ANY_CLOSURE_4P_1: if (!op_any_closure_4p_1(sc)) goto EVAL; goto BEGIN; + case OP_ANY_CLOSURE_4P_2: if (!op_any_closure_4p_2(sc)) goto EVAL; goto BEGIN; + case OP_ANY_CLOSURE_4P_3: if (!op_any_closure_4p_3(sc)) goto EVAL; goto BEGIN; + case OP_ANY_CLOSURE_4P_4: op_any_closure_4p_4(sc); goto BEGIN; + + case OP_CLOSURE_FA: if (!closure_is_fine(sc, sc->code, FINE_UNSAFE_CLOSURE, 2)) break; + case HOP_CLOSURE_FA: op_closure_fa(sc); goto EVAL; + + case OP_CLOSURE_SS: if (!closure_is_ok(sc, sc->code, OK_UNSAFE_CLOSURE_M, 2)) {if (op_unknown_gg(sc)) goto EVAL; continue;} + case HOP_CLOSURE_SS: op_closure_ss(sc); goto EVAL; + + case OP_CLOSURE_SS_O: if (!closure_is_ok(sc, sc->code, OK_UNSAFE_CLOSURE_P, 2)) {if (op_unknown_gg(sc)) goto EVAL; continue;} + case HOP_CLOSURE_SS_O: op_closure_ss_o(sc); goto EVAL; + + case OP_SAFE_CLOSURE_SS: if (!closure_is_ok(sc, sc->code, OK_SAFE_CLOSURE_M, 2)) {if (op_unknown_gg(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_SS: op_safe_closure_ss(sc); goto EVAL; + + case OP_SAFE_CLOSURE_SS_O: if (!closure_is_ok(sc, sc->code, OK_SAFE_CLOSURE_P, 2)) {if (op_unknown_gg(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_SS_O: op_safe_closure_ss_o(sc); goto EVAL; + + case OP_SAFE_CLOSURE_SS_A: if (!closure_is_ok(sc, sc->code, OK_SAFE_CLOSURE_A, 2)) {if (op_unknown_gg(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_SS_A: sc->value = op_safe_closure_ss_a(sc, sc->code); continue; + + case OP_CLOSURE_3S: if (!closure_is_fine(sc, sc->code, FINE_UNSAFE_CLOSURE, 3)) {if (op_unknown_ns(sc)) goto EVAL; continue;} + case HOP_CLOSURE_3S: op_closure_3s(sc); goto EVAL; /* "fine" here means changing func (as arg) does not constantly call op_unknown_ns */ + + case OP_CLOSURE_3S_O: if (!closure_is_ok(sc, sc->code, OK_UNSAFE_CLOSURE_P, 3)) {if (op_unknown_ns(sc)) goto EVAL; continue;} + case HOP_CLOSURE_3S_O: op_closure_3s_o(sc); goto EVAL; + + case OP_CLOSURE_4S: if (!closure_is_fine(sc, sc->code, FINE_UNSAFE_CLOSURE, 4)) {if (op_unknown_ns(sc)) goto EVAL; continue;} + case HOP_CLOSURE_4S: op_closure_4s(sc); goto EVAL; + + case OP_CLOSURE_4S_O: if (!closure_is_ok(sc, sc->code, OK_UNSAFE_CLOSURE_P, 4)) {if (op_unknown_ns(sc)) goto EVAL; continue;} + case HOP_CLOSURE_4S_O: op_closure_4s_o(sc); goto EVAL; + + case OP_CLOSURE_5S: if (!closure_is_fine(sc, sc->code, FINE_UNSAFE_CLOSURE, 5)) {if (op_unknown_ns(sc)) goto EVAL; continue;} + case HOP_CLOSURE_5S: op_closure_5s(sc); goto EVAL; + + case OP_CLOSURE_SC: if (!closure_is_ok(sc, sc->code, OK_UNSAFE_CLOSURE_M, 2)) {if (op_unknown_gg(sc)) goto EVAL; continue;} + case HOP_CLOSURE_SC: op_closure_sc(sc); goto EVAL; + + case OP_CLOSURE_SC_O: if (!closure_is_ok(sc, sc->code, OK_UNSAFE_CLOSURE_P, 2)) {if (op_unknown_gg(sc)) goto EVAL; continue;} + case HOP_CLOSURE_SC_O: op_closure_sc_o(sc); goto EVAL; + + case OP_SAFE_CLOSURE_SC: if (!closure_is_ok(sc, sc->code, OK_SAFE_CLOSURE_M, 2)) {if (op_unknown_gg(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_SC: op_safe_closure_sc(sc); goto EVAL; + + case OP_SAFE_CLOSURE_SC_O: if (!closure_is_ok(sc, sc->code, OK_SAFE_CLOSURE_P, 2)) {if (op_unknown_gg(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_SC_O: op_safe_closure_sc_o(sc); goto EVAL; + + case OP_CLOSURE_AA: if (!closure_is_ok(sc, sc->code, OK_UNSAFE_CLOSURE_M, 2)) {if (op_unknown_aa(sc)) goto EVAL; continue;} + case HOP_CLOSURE_AA: op_closure_aa(sc); goto EVAL; + + case OP_CLOSURE_AA_O: if (!closure_is_ok(sc, sc->code, OK_UNSAFE_CLOSURE_P, 2)) {if (op_unknown_aa(sc)) goto EVAL; continue;} + case HOP_CLOSURE_AA_O: inline_op_closure_aa_o(sc); goto EVAL; + + case OP_SAFE_CLOSURE_AA: if (!closure_is_ok(sc, sc->code, OK_SAFE_CLOSURE_M, 2)) {if (op_unknown_aa(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_AA: op_safe_closure_aa(sc); goto EVAL; + + case OP_SAFE_CLOSURE_AA_O: if (!closure_is_ok(sc, sc->code, OK_SAFE_CLOSURE_P, 2)) {if (op_unknown_aa(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_AA_O: op_safe_closure_aa_o(sc); goto EVAL; + + case OP_SAFE_CLOSURE_AA_A: if (!closure_is_ok(sc, sc->code, OK_SAFE_CLOSURE_A, 2)) {if (op_unknown_aa(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_AA_A: sc->value = fx_safe_closure_aa_a(sc, sc->code); continue; + + case OP_SAFE_CLOSURE_SSA: if (!closure_is_fine(sc, sc->code, FINE_SAFE_CLOSURE, 3)) {if (op_unknown_na(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_SSA: op_safe_closure_ssa(sc); goto EVAL; + + case OP_SAFE_CLOSURE_SAA: if (!closure_is_fine(sc, sc->code, FINE_SAFE_CLOSURE, 3)) {if (op_unknown_na(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_SAA: op_safe_closure_saa(sc); goto EVAL; + + case OP_SAFE_CLOSURE_AGG: if (!closure_is_fine(sc, sc->code, FINE_SAFE_CLOSURE, 3)) {if (op_unknown_na(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_AGG: op_safe_closure_agg(sc); goto EVAL; + + case OP_SAFE_CLOSURE_3A: if (!closure_is_ok(sc, sc->code, FINE_SAFE_CLOSURE, 3)) {if (op_unknown_na(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_3A: op_safe_closure_3a(sc); goto EVAL; + + case OP_SAFE_CLOSURE_NS: if (!closure_is_fine(sc, sc->code, FINE_SAFE_CLOSURE, opt3_arglen(cdr(sc->code)))) {if (op_unknown_ns(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_NS: op_safe_closure_ns(sc); goto EVAL; + + case OP_SAFE_CLOSURE_NA: if (!closure_is_fine(sc, sc->code, FINE_SAFE_CLOSURE, opt3_arglen(cdr(sc->code)))) {if (op_unknown_na(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_NA: op_safe_closure_na(sc); goto EVAL; + + case OP_SAFE_CLOSURE_3S: if (!closure_is_fine(sc, sc->code, FINE_SAFE_CLOSURE, 3)) {if (op_unknown_ns(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_3S: op_safe_closure_3s(sc); goto EVAL; + + case OP_SAFE_CLOSURE_3S_A: if (!closure_is_ok(sc, sc->code, OK_SAFE_CLOSURE_A, 3)) {if (op_unknown_ns(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_3S_A: sc->value = op_safe_closure_3s_a(sc, sc->code); continue; + + case OP_CLOSURE_NS: if (!closure_is_fine(sc, sc->code, FINE_UNSAFE_CLOSURE, opt3_arglen(cdr(sc->code)))) {if (op_unknown_ns(sc)) goto EVAL; continue;} + case HOP_CLOSURE_NS: op_closure_ns(sc); goto EVAL; + + case OP_CLOSURE_ASS: if (!closure_is_fine(sc, sc->code, FINE_UNSAFE_CLOSURE, 3)) {if (op_unknown_na(sc)) goto EVAL; continue;} + case HOP_CLOSURE_ASS: op_closure_ass(sc); goto EVAL; + + case OP_CLOSURE_AAS: if (!closure_is_fine(sc, sc->code, FINE_UNSAFE_CLOSURE, 3)) {if (op_unknown_na(sc)) goto EVAL; continue;} + case HOP_CLOSURE_AAS: op_closure_aas(sc); goto EVAL; + + case OP_CLOSURE_SAA: if (!closure_is_fine(sc, sc->code, FINE_UNSAFE_CLOSURE, 3)) {if (op_unknown_na(sc)) goto EVAL; continue;} + case HOP_CLOSURE_SAA: op_closure_saa(sc); goto EVAL; + + case OP_CLOSURE_ASA: if (!closure_is_fine(sc, sc->code, FINE_UNSAFE_CLOSURE, 3)) {if (op_unknown_na(sc)) goto EVAL; continue;} + case HOP_CLOSURE_ASA: op_closure_asa(sc); goto EVAL; + + case OP_CLOSURE_SAS: if (!closure_is_fine(sc, sc->code, FINE_UNSAFE_CLOSURE, 3)) {if (op_unknown_na(sc)) goto EVAL; continue;} + case HOP_CLOSURE_SAS: op_closure_sas(sc); goto EVAL; + + case OP_CLOSURE_3A: if (!closure_is_fine(sc, sc->code, FINE_UNSAFE_CLOSURE, 3)) {if (op_unknown_na(sc)) goto EVAL; continue;} + case HOP_CLOSURE_3A: op_closure_3a(sc); goto EVAL; + + case OP_CLOSURE_4A: if (!closure_is_fine(sc, sc->code, FINE_UNSAFE_CLOSURE, 4)) {if (op_unknown_na(sc)) goto EVAL; continue;} + case HOP_CLOSURE_4A: op_closure_4a(sc); goto EVAL; + + case OP_CLOSURE_NA: if (!closure_is_fine(sc, sc->code, FINE_UNSAFE_CLOSURE, opt3_arglen(cdr(sc->code)))) {if (op_unknown_na(sc)) goto EVAL; continue;} + case HOP_CLOSURE_NA: op_closure_na(sc); goto EVAL; + + case OP_ANY_CLOSURE_NP: if (!closure_np_is_ok(sc, sc->code)) {if (op_unknown_np(sc)) goto EVAL; continue;} + case HOP_ANY_CLOSURE_NP: op_any_closure_np(sc); goto EVAL; + case OP_ANY_CLOSURE_NP_1: + if (!(inline_collect_np_args(sc, OP_ANY_CLOSURE_NP_1, cons(sc, sc->value, sc->args)))) + op_any_closure_np_end(sc); + goto EVAL; + case OP_ANY_CLOSURE_NP_2: + sc->args = cons(sc, sc->value, sc->args); + op_any_closure_np_end(sc); + goto EVAL; + case OP_ANY_CLOSURE_NP_MV: /* this is an error -- a values call confusing the optimizer's arg count */ + if (!(collect_np_args(sc, OP_ANY_CLOSURE_NP_MV, (is_multiple_value(sc->value)) ? revappend(sc, sc->value, sc->args) : cons(sc, sc->value, sc->args)))) + op_any_closure_np_end(sc); + goto EVAL; + + case OP_ANY_CLOSURE_SYM: if (!check_closure_sym(sc, 1)) break; /* (lambda args ...) */ + case HOP_ANY_CLOSURE_SYM: op_any_closure_sym(sc); goto BEGIN; + case OP_ANY_CLOSURE_A_SYM: if (!check_closure_sym(sc, 2)) break; /* (lambda (a . args) ...) */ + case HOP_ANY_CLOSURE_A_SYM: op_any_closure_a_sym(sc); goto BEGIN; + + + case OP_TC_AND_A_OR_A_LA: tick_tc(sc, sc->cur_op); op_tc_and_a_or_a_la(sc, sc->code); continue; + case OP_TC_OR_A_AND_A_LA: tick_tc(sc, sc->cur_op); op_tc_or_a_and_a_la(sc, sc->code); continue; + case OP_TC_AND_A_OR_A_LAA: tick_tc(sc, sc->cur_op); op_tc_and_a_or_a_laa(sc, sc->code); continue; + case OP_TC_OR_A_AND_A_LAA: tick_tc(sc, sc->cur_op); op_tc_or_a_and_a_laa(sc, sc->code); continue; + case OP_TC_AND_A_OR_A_L3A: tick_tc(sc, sc->cur_op); op_tc_and_a_or_a_l3a(sc, sc->code); continue; + case OP_TC_OR_A_AND_A_L3A: tick_tc(sc, sc->cur_op); op_tc_or_a_and_a_l3a(sc, sc->code); continue; + case OP_TC_AND_A_OR_A_A_LA: tick_tc(sc, sc->cur_op); op_tc_and_a_or_a_a_la(sc, sc->code); continue; + case OP_TC_OR_A_AND_A_A_LA: tick_tc(sc, sc->cur_op); op_tc_or_a_and_a_a_la(sc, sc->code); continue; + case OP_TC_OR_A_A_AND_A_A_LA: tick_tc(sc, sc->cur_op); op_tc_or_a_a_and_a_a_la(sc, sc->code); continue; + case OP_TC_OR_A_AND_A_A_L3A: tick_tc(sc, sc->cur_op); op_tc_or_a_and_a_a_l3a(sc, sc->code); continue; + + case OP_TC_IF_A_Z_LA: tick_tc(sc, sc->cur_op); if (op_tc_if_a_z_la(sc, sc->code, false)) continue; goto EVAL; + case OP_TC_IF_A_LA_Z: tick_tc(sc, sc->cur_op); if (op_tc_if_a_la_z(sc, sc->code, false)) continue; goto EVAL; + case OP_TC_COND_A_Z_LA: tick_tc(sc, sc->cur_op); if (op_tc_if_a_z_la(sc, sc->code, true)) continue; goto EVAL; + case OP_TC_COND_A_LA_Z: tick_tc(sc, sc->cur_op); if (op_tc_if_a_la_z(sc, sc->code, true)) continue; goto EVAL; + + case OP_TC_IF_A_LAA_Z: tick_tc(sc, sc->cur_op); if (op_tc_if_a_z_laa(sc, sc->code, false, TC_IF)) continue; goto EVAL; + case OP_TC_IF_A_Z_LAA: tick_tc(sc, sc->cur_op); if (op_tc_if_a_z_laa(sc, sc->code, true, TC_IF)) continue; goto EVAL; + case OP_TC_COND_A_Z_LAA: tick_tc(sc, sc->cur_op); if (op_tc_if_a_z_laa(sc, sc->code, true, TC_COND)) continue; goto EVAL; + case OP_TC_COND_A_LAA_Z: tick_tc(sc, sc->cur_op); if (op_tc_if_a_z_laa(sc, sc->code, false, TC_COND)) continue; goto EVAL; + case OP_TC_WHEN_LA: tick_tc(sc, sc->cur_op); op_tc_when_la(sc, sc->code); continue; + case OP_TC_WHEN_LAA: tick_tc(sc, sc->cur_op); op_tc_when_laa(sc, sc->code); continue; + case OP_TC_WHEN_L3A: tick_tc(sc, sc->cur_op); op_tc_when_l3a(sc, sc->code); continue; + + case OP_TC_IF_A_Z_L3A: tick_tc(sc, sc->cur_op); if (op_tc_if_a_z_l3a(sc, sc->code, true)) continue; goto EVAL; + case OP_TC_IF_A_L3A_Z: tick_tc(sc, sc->cur_op); if (op_tc_if_a_z_l3a(sc, sc->code, false)) continue; goto EVAL; + + case OP_TC_IF_A_Z_IF_A_Z_LA: tick_tc(sc, sc->cur_op); if (op_tc_if_a_z_if_a_z_la(sc, sc->code, true, TC_IF)) continue; goto EVAL; + case OP_TC_IF_A_Z_IF_A_LA_Z: tick_tc(sc, sc->cur_op); if (op_tc_if_a_z_if_a_z_la(sc, sc->code, false, TC_IF)) continue; goto EVAL; + case OP_TC_COND_A_Z_A_Z_LA: tick_tc(sc, sc->cur_op); if (op_tc_if_a_z_if_a_z_la(sc, sc->code, true, TC_COND)) continue; goto EVAL; + case OP_TC_COND_A_Z_A_LA_Z: tick_tc(sc, sc->cur_op); if (op_tc_if_a_z_if_a_z_la(sc, sc->code, false, TC_COND))continue; goto EVAL; + case OP_TC_AND_A_IF_A_LA_Z: tick_tc(sc, sc->cur_op); if (op_tc_if_a_z_if_a_z_la(sc, sc->code, false, TC_AND)) continue; goto EVAL; + case OP_TC_AND_A_IF_A_Z_LA: tick_tc(sc, sc->cur_op); if (op_tc_if_a_z_if_a_z_la(sc, sc->code, true, TC_AND)) continue; goto EVAL; + + case OP_TC_IF_A_Z_IF_A_Z_LAA: tick_tc(sc, sc->cur_op); if (op_tc_if_a_z_if_a_z_laa(sc, false, sc->code)) continue; goto EVAL; + case OP_TC_IF_A_Z_IF_A_LAA_Z: tick_tc(sc, sc->cur_op); if (op_tc_if_a_z_if_a_laa_z(sc, false, sc->code)) continue; goto EVAL; + case OP_TC_COND_A_Z_A_Z_LAA: tick_tc(sc, sc->cur_op); if (op_tc_if_a_z_if_a_z_laa(sc, true, sc->code)) continue; goto EVAL; + case OP_TC_COND_A_Z_A_LAA_Z: tick_tc(sc, sc->cur_op); if (op_tc_if_a_z_if_a_laa_z(sc, true, sc->code)) continue; goto EVAL; + + case OP_TC_LET_IF_A_Z_LA: tick_tc(sc, sc->cur_op); if (op_tc_let_if_a_z_la(sc, sc->code)) continue; goto EVAL; + case OP_TC_LET_IF_A_Z_LAA: tick_tc(sc, sc->cur_op); if (op_tc_let_if_a_z_laa(sc, sc->code)) continue; goto EVAL; + case OP_TC_LET_WHEN_LAA: tick_tc(sc, sc->cur_op); op_tc_let_when_laa(sc, true, sc->code); continue; + case OP_TC_LET_UNLESS_LAA: tick_tc(sc, sc->cur_op); op_tc_let_when_laa(sc, false, sc->code); continue; + + case OP_TC_COND_A_Z_A_LAA_LAA: tick_tc(sc, sc->cur_op); if (op_tc_cond_a_z_a_laa_laa(sc, sc->code)) continue; goto EVAL; + case OP_TC_IF_A_Z_IF_A_L3A_L3A: tick_tc(sc, sc->cur_op); if (op_tc_if_a_z_if_a_l3a_l3a(sc, sc->code)) continue; goto EVAL; + case OP_TC_IF_A_Z_LET_IF_A_Z_LAA: tick_tc(sc, sc->cur_op); if (op_tc_if_a_z_let_if_a_z_laa(sc, sc->code)) continue; goto EVAL; + case OP_TC_CASE_LA: tick_tc(sc, sc->cur_op); if (op_tc_case_la(sc, sc->code)) continue; goto BEGIN; + case OP_TC_LET_COND: tick_tc(sc, sc->cur_op); if (op_tc_let_cond(sc, sc->code)) continue; goto EVAL; + + case OP_RECUR_IF_A_A_opA_LAq: wrap_recur_if_a_a_opa_laq(sc, true, true); continue; + case OP_RECUR_IF_A_A_opLA_Aq: wrap_recur_if_a_a_opa_laq(sc, true, false); continue; + case OP_RECUR_IF_A_opA_LAq_A: wrap_recur_if_a_a_opa_laq(sc, false, true); continue; + case OP_RECUR_IF_A_opLA_Aq_A: wrap_recur_if_a_a_opa_laq(sc, false, false); continue; + case OP_RECUR_IF_A_A_opA_LAAq: wrap_recur(sc, op_recur_if_a_a_opa_laaq); continue; + case OP_RECUR_IF_A_A_opA_L3Aq: wrap_recur(sc, op_recur_if_a_a_opa_l3aq); continue; + case OP_RECUR_IF_A_opA_LAAq_A: wrap_recur(sc, op_recur_if_a_opa_laaq_a); continue; + case OP_RECUR_IF_A_A_opLA_LAq: wrap_recur_if_a_a_opla_laq(sc, true); continue; + case OP_RECUR_IF_A_opLA_LAq_A: wrap_recur_if_a_a_opla_laq(sc, false); continue; + case OP_RECUR_IF_A_A_opA_LA_LAq: wrap_recur(sc, op_recur_if_a_a_opa_la_laq); continue; + case OP_RECUR_IF_A_opA_LA_LAq_A: wrap_recur(sc, op_recur_if_a_opa_la_laq_a); continue; + case OP_RECUR_IF_A_A_LopL3A_L3A_L3Aq: wrap_recur(sc, op_recur_if_a_a_lopl3a_l3a_l3aq); continue; + case OP_RECUR_IF_A_A_AND_A_LAA_LAA: wrap_recur(sc, op_recur_if_a_a_and_a_laa_laa); continue; + case OP_RECUR_IF_A_A_opLA_LA_LAq: wrap_recur(sc, op_recur_if_a_a_opla_la_laq); continue; + case OP_RECUR_IF_A_A_IF_A_LAA_opA_LAAq: wrap_recur(sc, op_recur_if_a_a_if_a_laa_opa_laaq); continue; + case OP_RECUR_IF_A_A_IF_A_A_opLA_LAq: wrap_recur(sc, op_recur_if_a_a_if_a_a_opla_laq); continue; + case OP_RECUR_IF_A_A_IF_A_A_opLAA_LAAq: wrap_recur(sc, op_recur_if_a_a_if_a_a_oplaa_laaq); continue; + case OP_RECUR_COND_A_A_opA_LAq: wrap_recur(sc, op_recur_cond_a_a_opa_laq); continue; + case OP_RECUR_COND_A_A_opA_LAAq: wrap_recur(sc, op_recur_cond_a_a_opa_laaq); continue; + case OP_RECUR_COND_A_A_A_A_opLA_LAq: wrap_recur(sc, op_recur_cond_a_a_a_a_opla_laq); continue; + case OP_RECUR_COND_A_A_A_A_opA_LAAq: wrap_recur(sc, op_recur_cond_a_a_a_a_opa_laaq); continue; + case OP_RECUR_COND_A_A_A_A_opLAA_LAAq: wrap_recur(sc, op_recur_cond_a_a_a_a_oplaa_laaq); continue; + case OP_RECUR_COND_A_A_A_LAA_opA_LAAq: wrap_recur(sc, op_recur_cond_a_a_a_laa_opa_laaq); continue; + case OP_RECUR_COND_A_A_A_LAA_LopA_LAAq: wrap_recur_cond_a_a_a_laa_lopa_laaq(sc); continue; + case OP_RECUR_AND_A_OR_A_LAA_LAA: wrap_recur(sc, op_recur_and_a_or_a_laa_laa); continue; + + + case OP_SAFE_CLOSURE_STAR_A: if (!closure_star_is_fine(sc, sc->code, FINE_SAFE_CLOSURE_STAR, 1)) {if (op_unknown_a(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_STAR_A: op_safe_closure_star_a(sc, sc->code); goto BEGIN; + + case OP_SAFE_CLOSURE_STAR_A1: if (!closure_star_is_fine(sc, sc->code, FINE_SAFE_CLOSURE_STAR, 1)) {if (op_unknown_a(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_STAR_A1: op_safe_closure_star_a1(sc, sc->code); goto BEGIN; + + case OP_SAFE_CLOSURE_STAR_KA: if (!closure_star_is_fine(sc, sc->code, FINE_SAFE_CLOSURE_STAR, 1)) {if (op_unknown_a(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_STAR_KA: op_safe_closure_star_ka(sc, sc->code); goto BEGIN; + + case OP_SAFE_CLOSURE_STAR_AA: if (!closure_star_is_fine(sc, sc->code, FINE_SAFE_CLOSURE_STAR, 2)) {if (op_unknown_aa(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_STAR_AA: op_safe_closure_star_aa(sc, sc->code); goto BEGIN; + + case OP_SAFE_CLOSURE_STAR_AA_O: if (!closure_star_is_fine(sc, sc->code, FINE_SAFE_CLOSURE_STAR, 2)) {if (op_unknown_aa(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_STAR_AA_O: op_safe_closure_star_aa(sc, sc->code); sc->code = car(sc->code); goto EVAL; + + case OP_SAFE_CLOSURE_STAR_3A: if (!closure_star_is_fine(sc, sc->code, FINE_UNSAFE_CLOSURE_STAR, 3)) {if (op_unknown_na(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_STAR_3A: if (op_safe_closure_star_aaa(sc, sc->code)) goto EVAL; goto BEGIN; + + case OP_SAFE_CLOSURE_STAR_NA: + if (!closure_star_is_fine(sc, sc->code, FINE_SAFE_CLOSURE_STAR, (is_pair(cdr(sc->code))) ? opt3_arglen(cdr(sc->code)) : 0)) + {if (op_unknown_na(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_STAR_NA: if (op_safe_closure_star_na(sc, sc->code)) goto EVAL; goto BEGIN; + + case OP_SAFE_CLOSURE_STAR_NA_0: if (!closure_star_is_fine(sc, sc->code, FINE_SAFE_CLOSURE_STAR, 0)) {if (op_unknown(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_STAR_NA_0: if (op_safe_closure_star_na_0(sc, sc->code)) goto EVAL; goto BEGIN; + + case OP_SAFE_CLOSURE_STAR_NA_1: if (!closure_star_is_fine(sc, sc->code, FINE_SAFE_CLOSURE_STAR, 1)) {if (op_unknown_a(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_STAR_NA_1: if (op_safe_closure_star_na_1(sc, sc->code)) goto EVAL; goto BEGIN; + + case OP_SAFE_CLOSURE_STAR_NA_2: if (!closure_star_is_fine(sc, sc->code, FINE_SAFE_CLOSURE_STAR, 2)) {if (op_unknown_aa(sc)) goto EVAL; continue;} + case HOP_SAFE_CLOSURE_STAR_NA_2: if (op_safe_closure_star_na_2(sc, sc->code)) goto EVAL; goto BEGIN; + + + case OP_CLOSURE_STAR_A: if (!closure_star_is_fine(sc, sc->code, FINE_UNSAFE_CLOSURE_STAR, 1)) {if (op_unknown_a(sc)) goto EVAL; continue;} + case HOP_CLOSURE_STAR_A: op_closure_star_a(sc, sc->code); goto BEGIN; + + case OP_CLOSURE_STAR_KA: if (!closure_star_is_fine(sc, sc->code, FINE_UNSAFE_CLOSURE_STAR, 1)) {if (op_unknown_aa(sc)) goto EVAL; continue;} + case HOP_CLOSURE_STAR_KA: op_closure_star_ka(sc, sc->code); goto BEGIN; + + case OP_CLOSURE_STAR_NA: + if (!closure_star_is_fine(sc, sc->code, FINE_UNSAFE_CLOSURE_STAR, (is_pair(cdr(sc->code))) ? opt3_arglen(cdr(sc->code)) : 0)) + {if (op_unknown_na(sc)) goto EVAL; continue;} + case HOP_CLOSURE_STAR_NA: if (op_closure_star_na(sc, sc->code)) goto EVAL; goto BEGIN; + + + case OP_UNKNOWN: sc->last_function = lookup_checked(sc, car(sc->code)); if (op_unknown(sc)) goto EVAL; continue; + case OP_UNKNOWN_NS: sc->last_function = lookup_checked(sc, car(sc->code)); if (op_unknown_ns(sc)) goto EVAL; continue; + case OP_UNKNOWN_S: sc->last_function = lookup_checked(sc, car(sc->code)); if (op_unknown_s(sc)) goto EVAL; continue; + case OP_UNKNOWN_GG: sc->last_function = lookup_checked(sc, car(sc->code)); if (op_unknown_gg(sc)) goto EVAL; continue; + case OP_UNKNOWN_A: sc->last_function = lookup_checked(sc, car(sc->code)); if (op_unknown_a(sc)) goto EVAL; continue; + case OP_UNKNOWN_AA: sc->last_function = lookup_checked(sc, car(sc->code)); if (op_unknown_aa(sc)) goto EVAL; continue; + case OP_UNKNOWN_NA: sc->last_function = lookup_checked(sc, car(sc->code)); if (op_unknown_na(sc)) goto EVAL; continue; + case OP_UNKNOWN_NP: sc->last_function = lookup_checked(sc, car(sc->code)); if (op_unknown_np(sc)) goto EVAL; continue; + + + case OP_IMPLICIT_VECTOR_REF_A: if (!inline_op_implicit_vector_ref_a(sc)) {if (op_unknown_a(sc)) goto EVAL;} continue; + case OP_IMPLICIT_VECTOR_REF_AA: if (!op_implicit_vector_ref_aa(sc)) {if (op_unknown_aa(sc)) goto EVAL;} continue; + case OP_IMPLICIT_STRING_REF_A: if (!op_implicit_string_ref_a(sc)) {if (op_unknown_a(sc)) goto EVAL;} continue; + case OP_IMPLICIT_HASH_TABLE_REF_A: if (!op_implicit_hash_table_ref_a(sc)) {if (op_unknown_a(sc)) goto EVAL;} continue; + case OP_IMPLICIT_HASH_TABLE_REF_AA: if (!op_implicit_hash_table_ref_aa(sc)) {if (op_unknown_a(sc)) goto EVAL;} continue; + case OP_IMPLICIT_CONTINUATION_A: if (!op_implicit_continuation_a(sc)) {if (op_unknown_a(sc)) goto EVAL;} continue; + case OP_IMPLICIT_ITERATE: if (!op_implicit_iterate(sc)) {if (op_unknown(sc)) goto EVAL;} continue; + case OP_IMPLICIT_LET_REF_C: if (!op_implicit_let_ref_c(sc)) {if ((has_fx(cdr(sc->code))) && (op_unknown_a(sc))) goto EVAL;} continue; + case OP_IMPLICIT_LET_REF_A: if (!op_implicit_let_ref_a(sc)) {if (op_unknown_a(sc)) goto EVAL;} continue; + case OP_IMPLICIT_PAIR_REF_A: if (!op_implicit_pair_ref_a(sc)) {if (op_unknown_a(sc)) goto EVAL;} continue; + case OP_IMPLICIT_PAIR_REF_AA: if (!op_implicit_pair_ref_aa(sc)) {if (op_unknown_aa(sc)) goto EVAL;} continue; + case OP_IMPLICIT_C_OBJECT_REF_A: if (!op_implicit_c_object_ref_a(sc)) {if (op_unknown_a(sc)) goto EVAL;} continue; + case OP_IMPLICIT_GOTO: if (!op_implicit_goto(sc)) {if (op_unknown(sc)) goto EVAL;} continue; + case OP_IMPLICIT_GOTO_A: if (!op_implicit_goto_a(sc)) {if (op_unknown_a(sc)) goto EVAL;} continue; + case OP_IMPLICIT_VECTOR_SET_3: if (op_implicit_vector_set_3(sc)) goto EVAL; continue; + case OP_IMPLICIT_VECTOR_SET_4: if (op_implicit_vector_set_4(sc)) goto EVAL; continue; + case OP_IMPLICIT_S7_STARLET_REF_S: sc->value = s7_starlet(sc, opt3_int(sc->code)); continue; + case OP_IMPLICIT_S7_STARLET_SET: + sc->value = s7_starlet_set_1(sc, opt3_sym(sc->code), fx_call(sc, cddr(sc->code))); + continue; + + case OP_UNOPT: goto UNOPT; + case OP_SYMBOL: sc->value = lookup_checked(sc, sc->code); continue; + case OP_CONSTANT: sc->value = sc->code; continue; + case OP_PAIR_PAIR: if (op_pair_pair(sc)) goto EVAL; continue; /* car is pair ((if x car cadr) ...) */ + case OP_PAIR_ANY: sc->value = car(sc->code); goto EVAL_ARGS_TOP; + case OP_PAIR_SYM: if (op_pair_sym(sc)) goto EVAL_ARGS_TOP; continue; + + case OP_EVAL_ARGS1: sc->args = cons(sc, sc->value, sc->args); goto EVAL_ARGS; + case OP_EVAL_ARGS2: op_eval_args2(sc); goto APPLY; /* sc->value is the last arg, [so if is_null(cdr(sc->code) and current is pair, push args2] */ + case OP_EVAL_ARGS3: op_eval_args3(sc); goto APPLY; /* sc->value is the next-to-last arg, and the last arg is not a list (so values can't mess us up!) */ + case OP_EVAL_ARGS4: sc->args = cons(sc, sc->value, sc->args); goto EVAL_ARGS_PAIR; + case OP_EVAL_ARGS5: op_eval_args5(sc); goto APPLY; + + EVAL_ARGS_TOP: + case OP_EVAL_ARGS: + if (dont_eval_args(sc->value)) + { + if (eval_args_no_eval_args(sc)) goto APPLY; + goto TOP_NO_POP; + } + sc->code = cdr(sc->code); + /* sc->value is the func (but can be anything if the code is messed up: (#\a 3)) + * we don't have to delay lookup of the func because arg evaluation order is not specified, so + * (let ((func +)) (func (let () (set! func -) 3) 2)) + * can return 5. + */ + push_op_stack(sc, sc->value); + if (sc->op_stack_now >= sc->op_stack_end) + resize_op_stack(sc); + sc->args = sc->nil; + + EVAL_ARGS: /* first time, value = op, args = nil, code is args */ + if (is_pair(sc->code)) /* evaluate current arg -- must check for pair here, not sc->nil (improper list as args) */ + { + if ((sc->safety > NO_SAFETY) && + (!is_safety_checked(sc->code))) + { + if (tree_is_cyclic(sc, sc->code)) + syntax_error_nr(sc, "attempt to evaluate a circular list: ~A", 39, sc->code); + set_safety_checked(sc->code); + } + EVAL_ARGS_PAIR: + if (is_pair(car(sc->code))) + { + eval_args_pair_car(sc); + goto EVAL; + } + if (is_pair(cdr(sc->code))) + { + s7_pointer car_code = car(sc->code); /* not a pair */ + sc->code = cdr(sc->code); + sc->value = (is_symbol(car_code)) ? lookup_checked(sc, car_code) : T_Ext(car_code); + /* sc->value is the current arg's value, sc->code is pointing to the next */ + + /* cdr(sc->code) might not be a pair or nil here! (eq? #f . 1) -> sc->code is 1 */ + if (is_null(cdr(sc->code))) + { + if (eval_args_last_arg(sc)) goto EVAL; + /* drop into APPLY */ + } + else + { + /* here we know sc->code is a pair, cdr(sc->code) is not null, sc->value is the previous arg's value */ + sc->args = cons(sc, sc->value, sc->args); + goto EVAL_ARGS_PAIR; + }} + else eval_last_arg(sc, car(sc->code)); + /* drop into APPLY */ + } + else /* got all args -- go to apply */ + { + /* *(--sc->op_stack_now) is the "function" (sc->value perhaps), sc->code is the arglist end, sc->args might be the preceding args reversed? */ + if (is_not_null(sc->code)) + improper_arglist_error_nr(sc); + sc->code = pop_op_stack(sc); + sc->args = proper_list_reverse_in_place(sc, sc->args); + } + /* turning this into a call on an array of functions was not a complete disaster, but tauto.scm was ~1.5% slower. + * the array-index overhead is the same as the current switch statement's, but there was also the boolean+jump overhead, + * and the function-local overhead currently otherwise 0 if inlined. + */ + APPLY: + case OP_APPLY: + if (SHOW_EVAL_OPS) safe_print(fprintf(stderr, " %s[%d]: op_apply %s (%s) to %s\n", __func__, __LINE__, + display_80(sc->code), s7_type_names[type(sc->code)], display_80(sc->args))); + switch (type(sc->code)) + { + case T_C_FUNCTION: sc->value = apply_c_function(sc, sc->code, sc->args); continue; + case T_C_RST_NO_REQ_FUNCTION: apply_c_rst_no_req_function(sc); continue; + case T_C_FUNCTION_STAR: apply_c_function_star(sc); continue; + case T_CONTINUATION: call_with_current_continuation(sc); continue; + case T_GOTO: call_with_exit(sc); continue; + case T_C_OBJECT: apply_c_object(sc); continue; + case T_STRING: apply_string(sc); continue; + case T_HASH_TABLE: apply_hash_table(sc); continue; + case T_ITERATOR: apply_iterator(sc); continue; + case T_LET: apply_let(sc); continue; + case T_INT_VECTOR: case T_BYTE_VECTOR: case T_FLOAT_VECTOR: + case T_VECTOR: apply_vector(sc); continue; + case T_SYNTAX: apply_syntax(sc); goto TOP_NO_POP; + case T_PAIR: if (apply_pair(sc)) continue; goto APPLY; + case T_CLOSURE: apply_closure(sc); goto APPLY_LAMBDA; + case T_CLOSURE_STAR: if (apply_closure_star(sc)) goto EVAL; goto BEGIN; + case T_C_MACRO: apply_c_macro(sc); goto EVAL; + case T_MACRO: apply_macro(sc); goto APPLY_LAMBDA; + case T_BACRO: apply_bacro(sc); goto APPLY_LAMBDA; + case T_MACRO_STAR: apply_macro_star(sc); goto BEGIN; + case T_BACRO_STAR: apply_bacro_star(sc); goto BEGIN; + default: apply_error_nr(sc, sc->code, sc->args); + } + + case OP_MACRO_STAR_D: if (op_macro_star_d(sc)) goto EVAL_ARGS_TOP; goto BEGIN; + case OP_MACRO_D: if (op_macro_d(sc, T_MACRO)) goto EVAL_ARGS_TOP; /* fall through presumably */ + + APPLY_LAMBDA: + case OP_APPLY_LAMBDA: + inline_apply_lambda(sc); + goto BEGIN; + + case OP_LAMBDA_STAR_DEFAULT: if (op_lambda_star_default(sc)) goto EVAL; goto BEGIN; + + case OP_MACROEXPAND_1: + switch (op_macroexpand_1(sc)) {case goto_begin: goto BEGIN; case goto_eval: goto EVAL; case goto_start: continue; default: goto APPLY_LAMBDA;} + case OP_MACROEXPAND: + switch (op_macroexpand(sc)) {case goto_begin: goto BEGIN; case goto_eval: goto EVAL; case goto_start: continue; default: goto APPLY_LAMBDA;} + + + HEAPSORT: if (op_heapsort(sc)) continue; if (sc->value != sc->F) goto APPLY; + case OP_SORT1: op_sort1(sc); goto APPLY; + case OP_SORT2: if (op_sort2(sc)) continue; goto HEAPSORT; + case OP_SORT: if (!op_sort(sc)) goto HEAPSORT; + case OP_SORT3: if (op_sort3(sc)) continue; goto HEAPSORT; + case OP_SORT_PAIR_END: sc->value = vector_into_list(sc, sc->value, car(sc->args)); continue; + case OP_SORT_VECTOR_END: sc->value = vector_into_fi_vector(sc->value, car(sc->args)); continue; + case OP_SORT_STRING_END: sc->value = vector_into_string(sc->value, car(sc->args)); continue; + +#if S7_DEBUGGING + case OP_MAP_UNWIND: /* this probably can't happen -- left on stack only if opt succeeds then func called */ + fprintf(stderr, "%s[%d]: op_map_unwind %" ld64 "\n", __func__, __LINE__, sc->map_call_ctr); + sc->map_call_ctr--; + if (sc->map_call_ctr < 0) {fprintf(stderr, "%s[%d]: map ctr: %" ld64 "\n", __func__, __LINE__, sc->map_call_ctr); sc->map_call_ctr = 0;} + continue; +#endif + case OP_MAP_GATHER: inline_op_map_gather(sc); + case OP_MAP: if (op_map(sc)) continue; goto APPLY; + + case OP_MAP_GATHER_1: inline_op_map_gather(sc); + case OP_MAP_1: if (op_map_1(sc)) continue; goto BEGIN; + + case OP_MAP_GATHER_2: + case OP_MAP_GATHER_3: inline_op_map_gather(sc); + case OP_MAP_2: if (op_map_2(sc)) continue; goto EVAL; + + case OP_FOR_EACH: if (op_for_each(sc)) continue; goto APPLY; + case OP_FOR_EACH_1: if (inline_op_for_each_1(sc)) continue; goto BEGIN; + + case OP_FOR_EACH_2: + case OP_FOR_EACH_3: if (inline_op_for_each_2(sc)) continue; goto EVAL; + + case OP_MEMBER_IF: + case OP_MEMBER_IF1: if (op_member_if(sc)) continue; goto APPLY; + + case OP_ASSOC_IF: + case OP_ASSOC_IF1: if (op_assoc_if(sc)) continue; goto APPLY; + + + case OP_SAFE_DOTIMES: /* gen form */ + SAFE_DOTIMES: /* check_do */ + switch (op_safe_dotimes(sc)) + { + case goto_safe_do_end_clauses: if (is_null(sc->code)) continue; goto DO_END_CODE; + case goto_do_end_clauses: goto DO_END_CLAUSES; + case goto_eval: goto EVAL; + case goto_top_no_pop: goto TOP_NO_POP; + default: goto BEGIN; + } + + case OP_SAFE_DO: + SAFE_DO: /* from check_do */ + switch (op_safe_do(sc)) /* mat */ + { + case goto_safe_do_end_clauses: + if (is_null(sc->code)) continue; /* multiple values (as test result) can't happen -- safe do loops involve counters by 1 to some integer end */ + goto DO_END_CODE; + case goto_do_unchecked: goto DO_UNCHECKED; + default: goto BEGIN; + } + + case OP_DOTIMES_P: + DOTIMES_P: /* from check_do */ + switch (op_dotimes_p(sc)) + { + case goto_do_end_clauses: goto DO_END_CLAUSES; + case goto_do_unchecked: goto DO_UNCHECKED; + default: goto EVAL; + } + + case OP_DOX: + DOX: /* from check_do */ + switch (op_dox(sc)) /* lg fft exit */ + { + case goto_do_end_clauses: goto DO_END_CLAUSES; + case goto_start: continue; + case goto_top_no_pop: goto TOP_NO_POP; /* includes dox_step_o */ + default: goto BEGIN; + } + + DO_NO_BODY: + case OP_DO_NO_BODY_NA_VARS: op_do_no_body_na_vars(sc); goto EVAL; + case OP_DO_NO_BODY_NA_VARS_STEP: if (op_do_no_body_na_vars_step(sc)) goto DO_END_CLAUSES; goto EVAL; + case OP_DO_NO_BODY_NA_VARS_STEP_1: if (op_do_no_body_na_vars_step_1(sc)) goto DO_END_CLAUSES; goto EVAL; + + case OP_DO_NO_VARS_NO_OPT: op_do_no_vars_no_opt(sc); /* fall through */ + case OP_DO_NO_VARS_NO_OPT_1: if (op_do_no_vars_no_opt_1(sc)) goto DO_END_CLAUSES; goto BEGIN; + case OP_DO_NO_VARS: if (op_do_no_vars(sc)) goto DO_END_CLAUSES; goto BEGIN; + case OP_SAFE_DOTIMES_STEP_O: if (op_safe_dotimes_step_o(sc)) goto DO_END_CLAUSES; goto EVAL; + case OP_SAFE_DOTIMES_STEP: if (op_safe_dotimes_step(sc)) goto DO_END_CLAUSES; goto EVAL; + case OP_SAFE_DO_STEP: if (op_safe_do_step(sc)) goto DO_END_CLAUSES; goto BEGIN; + case OP_SIMPLE_DO: if (op_simple_do(sc)) goto DO_END_CLAUSES; goto BEGIN; + case OP_SIMPLE_DO_STEP: if (op_simple_do_step(sc)) goto DO_END_CLAUSES; goto BEGIN; + case OP_DOTIMES_STEP_O: if (op_dotimes_step_o(sc)) goto DO_END_CLAUSES; goto EVAL; + case OP_DOX_INIT: if (op_dox_init(sc)) goto DO_END_CLAUSES; goto BEGIN; + case OP_DOX_STEP: if (op_dox_step_1(sc)) goto DO_END_CLAUSES; op_dox_step(sc); goto BEGIN; + case OP_DOX_STEP_O: if (op_dox_step_1(sc)) goto DO_END_CLAUSES; op_dox_step_o(sc); goto EVAL; + case OP_DOX_NO_BODY: op_dox_no_body(sc); continue; + case OP_DOX_PENDING_NO_BODY: op_dox_pending_no_body(sc); goto DO_END_CLAUSES; + + case OP_DO_INIT: if (op_do_init(sc)) goto DO_END; goto EVAL; + + case OP_DO: + if (is_null(check_do(sc))) + switch (optimize_op(sc->code)) + { + case OP_DOX: goto DOX; + case OP_SAFE_DOTIMES: goto SAFE_DOTIMES; + case OP_DOTIMES_P: goto DOTIMES_P; + case OP_SAFE_DO: goto SAFE_DO; + case OP_DO_NO_BODY_NA_VARS: goto DO_NO_BODY; + case OP_DO_NO_VARS: if (op_do_no_vars(sc)) goto DO_END_CLAUSES; goto BEGIN; + case OP_DOX_NO_BODY: op_dox_no_body(sc); continue; + case OP_DOX_PENDING_NO_BODY: op_dox_pending_no_body(sc); goto DO_END_CLAUSES; + default: if (op_simple_do(sc)) goto DO_END_CLAUSES; goto BEGIN; + } + + case OP_DO_UNCHECKED: + op_do_unchecked(sc); + DO_UNCHECKED: + if (do_unchecked(sc)) goto EVAL; + + DO_END: + case OP_DO_END: + if (op_do_end(sc)) goto EVAL; + + case OP_DO_END1: + if (is_true(sc, sc->value)) + { + goto_t next = op_do_end_true(sc); + if (next == goto_start) continue; + if (next == goto_eval) goto EVAL; + goto FEED_TO; + } + else + { + goto_t next = op_do_end_false(sc); + if (next == goto_begin) goto BEGIN; + if (next == goto_do_end) goto DO_END; + /* fall through */ + } + + case OP_DO_STEP: if (op_do_step(sc)) goto DO_END; goto EVAL; + case OP_DO_STEP2: if (op_do_step2(sc)) goto DO_END; goto EVAL; + + DO_END_CLAUSES: + if (do_end_clauses(sc)) continue; + DO_END_CODE: + { + goto_t next = do_end_code(sc); + if (next == goto_eval) goto EVAL; + if (next == goto_start) continue; + goto FEED_TO; + } + + + case OP_BEGIN_UNCHECKED: + set_current_code(sc, sc->code); + sc->code = T_Pair(cdr(sc->code)); + goto BEGIN; + + case OP_BEGIN: + if (op_begin(sc, sc->code)) continue; + sc->code = T_Pair(cdr(sc->code)); + + case OP_BEGIN_HOOK: + if (sc->begin_hook) + { + /* call_begin_hook might clobber sc->code? via s7_eval_string probably yes */ + set_current_code(sc, sc->code); + if (call_begin_hook(sc)) + return(sc->F); + } + case OP_BEGIN_NO_HOOK: + goto BEGIN; + + case OP_BEGIN_2_UNCHECKED: + push_stack_no_args(sc, OP_EVAL, caddr(sc->code)); + sc->code = cadr(sc->code); + goto EVAL; + + case OP_BEGIN_AA: sc->value = fx_begin_aa(sc, sc->code); continue; + case OP_BEGIN_NA: sc->value = fx_begin_na(sc, sc->code); continue; + + + case OP_EVAL: goto EVAL; + case OP_EVAL_STRING: op_eval_string(sc); goto EVAL; + + case OP_QUOTE: sc->value = check_quote(sc, sc->code); continue; + case OP_QUOTE_UNCHECKED: sc->value = cadr(sc->code); continue; + + case OP_DEFINE_FUNCHECKED: define_funchecked(sc); continue; + case OP_DEFINE_CONSTANT1: op_define_constant1(sc); continue; + + case OP_DEFINE_CONSTANT_UNCHECKED: + push_stack_no_args(sc, OP_DEFINE_CONSTANT1, cadr(sc->code)); + goto DEFCONS; + + case OP_DEFINE_CONSTANT: + if (op_define_constant(sc)) continue; + + case OP_DEFINE_STAR: case OP_DEFINE: + check_define(sc); + + DEFCONS: + case OP_DEFINE_STAR_UNCHECKED: + case OP_DEFINE_UNCHECKED: + if (op_define_unchecked(sc)) goto TOP_NO_POP; + + case OP_DEFINE1: if (op_define1(sc)) goto APPLY; + case OP_DEFINE_WITH_SETTER: op_define_with_setter(sc); continue; + + case OP_SET_opSq_A: if (op_set_opsq_a(sc)) goto APPLY; continue; + case OP_SET_opSAq_A: if (op_set_opsaq_a(sc)) goto APPLY; continue; + case OP_SET_opSAq_P: if (op_set_opsaq_p(sc)) goto APPLY; goto EVAL; + case OP_SET_opSAq_P_1: if (op_set_opsaq_p_1(sc)) goto APPLY; continue; + case OP_SET_opSAAq_A: if (op_set_opsaaq_a(sc)) goto APPLY; continue; + case OP_SET_opSAAq_P: if (op_set_opsaaq_p(sc)) goto APPLY; goto EVAL; + case OP_SET_opSAAq_P_1: if (op_set_opsaaq_p_1(sc)) goto APPLY; continue; + + case OP_INCREMENT_BY_1: inline_op_increment_by_1(sc); continue; + case OP_DECREMENT_BY_1: op_decrement_by_1(sc); continue; + case OP_INCREMENT_SA: op_increment_sa(sc); continue; + case OP_INCREMENT_SAA: op_increment_saa(sc); continue; + + case OP_SET_S_C: op_set_s_c(sc); continue; + case OP_SET_S_S: op_set_s_s(sc); continue; + case OP_SET_S_A: op_set_s_a(sc); continue; + case OP_SET_S_P: op_set_s_p(sc); goto EVAL; + case OP_SET_CONS: op_set_cons(sc); continue; + case OP_SET_SAFE: op_set_safe(sc); continue; + + case OP_SET_FROM_SETTER: slot_set_value(sc->code, sc->value); continue; /* mv caught in splice_in_values */ + case OP_SET_FROM_LET_TEMP: op_set_from_let_temp(sc); continue; + + case OP_SET2: + switch (op_set2(sc)) /* imp */ + { + case goto_eval: goto EVAL; + case goto_top_no_pop: goto TOP_NO_POP; + case goto_start: continue; + case goto_apply: goto APPLY; + case goto_unopt: goto UNOPT; + default: goto EVAL_ARGS; /* goto_eval_args in funcs called by op_set2, unopt */ + } + + case OP_SET: check_set(sc); + case OP_SET_UNCHECKED: + SET_UNCHECKED: + if (is_pair(cadr(sc->code))) /* has setter */ + switch (set_implicit(sc)) + { + case goto_top_no_pop: goto TOP_NO_POP; + case goto_start: continue; + case goto_apply: goto APPLY; + case goto_unopt: goto UNOPT; + default: goto EVAL_ARGS; /* very common, op_unopt at this point */ + } + case OP_SET_NORMAL: if (op_set_normal(sc)) goto EVAL; + case OP_SET1: if (op_set1(sc)) continue; goto APPLY; + + case OP_SET_WITH_LET_1: if (op_set_with_let_1(sc)) goto TOP_NO_POP; goto SET_WITH_LET; + case OP_SET_WITH_LET_2: if (op_set_with_let_2(sc)) continue; + SET_WITH_LET: + activate_with_let(sc, sc->value); /* this activates sc->value, so the set! will happen in that environment */ + if (is_pair(cadr(sc->code))) + switch (set_implicit(sc)) /* imp misc */ + { + case goto_top_no_pop: goto TOP_NO_POP; + case goto_start: continue; + case goto_apply: goto APPLY; + case goto_unopt: goto UNOPT; + default: goto EVAL_ARGS; /* unopt */ + } + error_nr(sc, sc->no_setter_symbol, + set_elist_3(sc, wrap_string(sc, "can't set ~A in ~S", 18), cadr(sc->code), + list_3(sc, sc->set_symbol, + (is_pair(cadr(sc->code))) ? copy_proper_list(sc, cadr(sc->code)) : cadr(sc->code), + (is_pair(caddr(sc->code))) ? copy_proper_list(sc, caddr(sc->code)) : caddr(sc->code)))); + + + case OP_IF: op_if(sc); goto EVAL; + case OP_IF_UNCHECKED: op_if_unchecked(sc); goto EVAL; + case OP_IF1: if (op_if1(sc)) goto EVAL; continue; + + #define if_a_p(sc) if (is_true(sc, fx_call(sc, cdr(sc->code)))) + #define if_not_a_p(sc) if (is_false(sc, fx_call(sc, opt3_pair(sc->code)))) /* cdadr(sc->code) */ + + case OP_IF_A_C_C: sc->value = (is_true(sc, fx_call(sc, cdr(sc->code)))) ? opt1_con(sc->code) : opt2_con(sc->code); continue; + case OP_IF_A_A: sc->value = (is_true(sc, fx_call(sc, cdr(sc->code)))) ? fx_call(sc, opt1_pair(sc->code)) : sc->unspecified; continue; + case OP_IF_S_A_A: sc->value = (is_true(sc, lookup(sc, cadr(sc->code)))) ? fx_call(sc, opt1_pair(sc->code)) : fx_call(sc, opt2_pair(sc->code)); continue; + case OP_IF_A_A_A: sc->value = (is_true(sc, fx_call(sc, cdr(sc->code)))) ? fx_call(sc, opt1_pair(sc->code)) : fx_call(sc, opt2_pair(sc->code)); continue; + case OP_IF_A_A_P: if_a_p(sc) {sc->value = fx_call(sc, opt1_pair(sc->code)); continue;} sc->code = opt2_any(sc->code); goto EVAL; + case OP_IF_A_P_A: if_a_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->value = fx_call(sc, opt2_pair(sc->code)); continue; + case OP_IF_NOT_A_A: sc->value = (is_false(sc, fx_call(sc, opt1_pair(sc->code)))) ? fx_call(sc, opt2_pair(sc->code)) : sc->unspecified; continue; + case OP_IF_NOT_A_A_A: sc->value = (is_false(sc, fx_call(sc, opt1_pair(sc->code)))) ? fx_call(sc, opt2_pair(sc->code)) : fx_call(sc, opt3_pair(sc->code)); continue; + case OP_IF_AND2_S_A: sc->value = fx_if_and2_s_a(sc, sc->code); continue; + + #define call_bfunc(Sc, Expr) ((s7_bfunc)opt3_any(cdr(Sc->code)))(Sc, Expr) + case OP_IF_B_A: sc->value = (call_bfunc(sc, cadr(sc->code))) ? fx_call(sc, opt1_pair(sc->code)) : sc->unspecified; continue; + case OP_IF_B_A_P: if (call_bfunc(sc, cadr(sc->code))) {sc->value = fx_call(sc, opt1_pair(sc->code)); continue;} sc->code = opt2_any(sc->code); goto EVAL; + case OP_IF_B_P_A: if (call_bfunc(sc, cadr(sc->code))) {sc->code = opt1_any(sc->code); goto EVAL;} sc->value = fx_call(sc, opt2_pair(sc->code)); continue; + case OP_IF_B_P_P: if (call_bfunc(sc, cadr(sc->code))) {sc->code = opt1_any(sc->code); goto EVAL;} sc->code = opt2_any(sc->code); goto EVAL; + + #define if_s_p(sc) if (is_true(sc, lookup(sc, cadr(sc->code)))) + #define if_not_s_p(sc) if (is_false(sc, lookup(sc, opt1_sym(cdr(sc->code))))) /* cadadr(sc->code) */ + + case OP_IF_S_P: if_s_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->value = sc->unspecified; continue; + case OP_IF_S_R: if_s_p(sc) {sc->value = sc->unspecified; continue;} sc->code = opt1_any(sc->code); goto EVAL; + case OP_IF_S_P_P: if_s_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->code = opt2_any(sc->code); goto EVAL; + case OP_IF_S_N: if_not_s_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->value = sc->unspecified; continue; + case OP_IF_S_N_N: if_not_s_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->code = opt2_any(sc->code); goto EVAL; + case OP_IF_S_P_A: if_s_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->value = fx_call(sc, opt2_pair(sc->code)); continue; + case OP_IF_S_A_P: if_s_p(sc) {sc->value = fx_call(sc, opt1_pair(sc->code)); continue;} sc->code = opt2_any(sc->code); goto EVAL; + + case OP_IF_A_P: if_a_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->value = sc->unspecified; continue; + case OP_IF_A_R: if_a_p(sc) {sc->value = sc->unspecified; continue;} sc->code = opt1_any(sc->code); goto EVAL; + case OP_IF_A_P_P: if_a_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->code = opt2_any(sc->code); goto EVAL; + case OP_IF_A_N: if_not_a_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->value = sc->unspecified; continue; + case OP_IF_A_N_N: if_not_a_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->code = opt2_any(sc->code); goto EVAL; + + case OP_IF_B_P: if (call_bfunc(sc, cadr(sc->code))) {sc->code = opt1_any(sc->code); goto EVAL;} sc->value = sc->unspecified; continue; + case OP_IF_B_R: if (call_bfunc(sc, cadr(sc->code))) {sc->value = sc->unspecified; continue;} sc->code = opt1_any(sc->code); goto EVAL; + case OP_IF_B_N_N: if (call_bfunc(sc, car(opt3_pair(sc->code)))) {sc->code = opt2_any(sc->code); goto EVAL;} sc->code = opt1_any(sc->code); goto EVAL; + + #define if_is_type_s_p(sc) if (gen_type_match(sc, lookup(sc, opt2_sym(cdr(sc->code))), opt3_byte(cdr(sc->code)))) + #define if_is_not_type_s_p(sc) if (!gen_type_match(sc, lookup(sc, opt2_sym(cdr(sc->code))), opt3_byte(cdr(sc->code)))) + + case OP_IF_IS_TYPE_S_P: if_is_type_s_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->value = sc->unspecified; continue; + case OP_IF_IS_TYPE_S_R: if_is_type_s_p(sc) {sc->value = sc->unspecified; continue;} sc->code = opt1_any(sc->code); goto EVAL; + case OP_IF_IS_TYPE_S_P_P: if_is_type_s_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->code = opt2_any(sc->code); goto EVAL; + case OP_IF_IS_TYPE_S_N: if_is_not_type_s_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->value = sc->unspecified; continue; + case OP_IF_IS_TYPE_S_N_N: if_is_not_type_s_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->code = opt2_any(sc->code); goto EVAL; + case OP_IF_IS_TYPE_S_A_A: if_is_type_s_p(sc) sc->value = fx_call(sc, opt1_pair(sc->code)); else sc->value = fx_call(sc, opt2_pair(sc->code)); continue; + case OP_IF_IS_TYPE_S_P_A: if_is_type_s_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->value = fx_call(sc, opt2_pair(sc->code)); continue; + case OP_IF_IS_TYPE_S_A_P: if_is_type_s_p(sc) {sc->value = fx_call(sc, opt1_pair(sc->code)); continue;} sc->code = opt2_any(sc->code); goto EVAL; + + #define if_opsq_p(sc) set_car(sc->t1_1, lookup(sc, opt2_sym(cdr(sc->code)))); if (is_true(sc, fn_proc(cadr(sc->code))(sc, sc->t1_1))) + #define if_not_opsq_p(sc) set_car(sc->t1_1, lookup(sc, opt2_sym(cdr(sc->code)))); if (is_false(sc, fn_proc(opt1_pair(cdr(sc->code)))(sc, sc->t1_1))) /* cadadr */ + + case OP_IF_opSq_P: if_opsq_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->value = sc->unspecified; continue; + case OP_IF_opSq_R: if_opsq_p(sc) {sc->value = sc->unspecified; continue;} sc->code = opt1_any(sc->code); goto EVAL; + case OP_IF_opSq_P_P: if_opsq_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->code = opt2_any(sc->code); goto EVAL; + case OP_IF_opSq_N: if_not_opsq_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->value = sc->unspecified; continue; + case OP_IF_opSq_N_N: if_not_opsq_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->code = opt2_any(sc->code); goto EVAL; + + #define if_and2_p(sc) if ((is_true(sc, fx_call(sc, opt2_pair(cdr(sc->code))))) && (is_true(sc, fx_call(sc, opt3_pair(cdr(sc->code)))))) + #define if_not_and2_p(sc) if ((is_false(sc, fx_call(sc, opt2_pair(cdr(sc->code))))) || (is_false(sc, fx_call(sc, opt3_pair(cdr(sc->code)))))) + + case OP_IF_AND2_P: if_and2_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->value = sc->unspecified; continue; + case OP_IF_AND2_R: if_and2_p(sc) {sc->value = sc->unspecified; continue;} sc->code = opt1_any(sc->code); goto EVAL; + case OP_IF_AND2_P_P: if_and2_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->code = opt2_any(sc->code); goto EVAL; + case OP_IF_AND2_N: if_not_and2_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->value = sc->unspecified; continue; + case OP_IF_AND2_N_N: if_not_and2_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->code = opt2_any(sc->code); goto EVAL; + + #define if_or2_p(sc) if ((is_true(sc, fx_call(sc, opt2_pair(cdr(sc->code))))) || (is_true(sc, fx_call(sc, opt3_pair(cdr(sc->code)))))) + #define if_not_or2_p(sc) if ((is_false(sc, fx_call(sc, opt2_pair(cdr(sc->code))))) && (is_false(sc, fx_call(sc, opt3_pair(cdr(sc->code)))))) + + case OP_IF_OR2_P: if_or2_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->value = sc->unspecified; continue; + case OP_IF_OR2_R: if_or2_p(sc) {sc->value = sc->unspecified; continue;} sc->code = opt1_any(sc->code); goto EVAL; + case OP_IF_OR2_P_P: if_or2_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->code = opt2_any(sc->code); goto EVAL; + case OP_IF_OR2_N: if_not_or2_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->value = sc->unspecified; continue; + case OP_IF_OR2_N_N: if_not_or2_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->code = opt2_any(sc->code); goto EVAL; + + #define if_and3_p(sc) if ((is_true(sc, fx_call(sc, opt2_pair(cdr(sc->code))))) && \ + (is_true(sc, fx_call(sc, opt3_pair(cdr(sc->code))))) && (is_true(sc, fx_call(sc, opt1_pair(cdr(sc->code)))))) + #define if_not_and3_p(sc) if ((is_false(sc, fx_call(sc, opt2_pair(cdr(sc->code))))) || \ + (is_false(sc, fx_call(sc, opt3_pair(cdr(sc->code))))) || (is_false(sc, fx_call(sc, opt1_pair(cdr(sc->code)))))) + + case OP_IF_AND3_P: if_and3_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->value = sc->unspecified; continue; + case OP_IF_AND3_R: if_and3_p(sc) {sc->value = sc->unspecified; continue;} sc->code = opt1_any(sc->code); goto EVAL; + case OP_IF_AND3_P_P: if_and3_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->code = opt2_any(sc->code); goto EVAL; + case OP_IF_AND3_N: if_not_and3_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->value = sc->unspecified; continue; + case OP_IF_AND3_N_N: if_not_and3_p(sc) {sc->code = opt1_any(sc->code); goto EVAL;} sc->code = opt2_any(sc->code); goto EVAL; + + #define if_p_push(op) do {push_stack_no_args(sc, op, opt2_any(cdr(sc->code))); sc->code = opt3_any(cdr(sc->code));} while (0) + case OP_IF_P_P: if_p_push(OP_IF_PP); goto EVAL; + case OP_IF_P_N: if_p_push(OP_IF_PN); goto EVAL; + case OP_IF_P_P_P: if_p_push(OP_IF_PPP); goto EVAL; + case OP_IF_P_R: if_p_push(OP_IF_PR); goto EVAL; + case OP_IF_P_N_N: if_p_push(OP_IF_PRR); goto EVAL; + + #define if_bp_push(op) do {push_stack_no_args(sc, op, opt2_any(cdr(sc->code))); sc->code = opt3_pair(cdr(sc->code));} while (0) + case OP_IF_ANDP_P: if_bp_push(OP_IF_PP); goto AND_P; + case OP_IF_ANDP_R: if_bp_push(OP_IF_PR); goto AND_P; + case OP_IF_ANDP_P_P: if_bp_push(OP_IF_PPP); goto AND_P; + case OP_IF_ANDP_N: if_bp_push(OP_IF_PR); goto AND_P; + case OP_IF_ANDP_N_N: if_bp_push(OP_IF_PRR); goto AND_P; + + case OP_IF_ORP_P: if_bp_push(OP_IF_PP); goto OR_P; + case OP_IF_ORP_R: if_bp_push(OP_IF_PR); goto OR_P; + case OP_IF_ORP_P_P: if_bp_push(OP_IF_PPP); goto OR_P; + case OP_IF_ORP_N: if_bp_push(OP_IF_PR); goto OR_P; + case OP_IF_ORP_N_N: if_bp_push(OP_IF_PRR); goto OR_P; + + case OP_IF_PP: if (sc->value != sc->F) goto EVAL; sc->value = sc->unspecified; continue; + case OP_IF_PN: + case OP_IF_PR: if (sc->value == sc->F) goto EVAL; sc->value = sc->unspecified; continue; + case OP_IF_PPP: sc->code = (sc->value != sc->F) ? car(sc->code) : cadr(sc->code); goto EVAL; + case OP_IF_PRR: sc->code = (sc->value == sc->F) ? car(sc->code) : cadr(sc->code); goto EVAL; + + case OP_WHEN: check_when(sc); goto EVAL; + case OP_WHEN_S: if (op_when_s(sc)) continue; goto EVAL; + case OP_WHEN_A: if (op_when_a(sc)) continue; goto EVAL; + case OP_WHEN_P: op_when_p(sc); goto EVAL; + case OP_WHEN_AND_2A: if (op_when_and_2a(sc)) continue; goto EVAL; + case OP_WHEN_AND_3A: if (op_when_and_3a(sc)) continue; goto EVAL; + case OP_WHEN_AND_AP: if (op_when_and_ap(sc)) continue; goto EVAL; + case OP_WHEN_PP: if (op_when_pp(sc)) continue; goto EVAL; + + case OP_UNLESS: check_unless(sc); goto EVAL; + case OP_UNLESS_S: if (op_unless_s(sc)) continue; goto EVAL; + case OP_UNLESS_A: if (op_unless_a(sc)) continue; goto EVAL; + case OP_UNLESS_P: op_unless_p(sc); goto EVAL; + case OP_UNLESS_PP: if (op_unless_pp(sc)) continue; goto EVAL; + + + case OP_COND_FEED: if (op_cond_feed(sc)) goto EVAL; /* else fall through */ + case OP_COND_FEED_1: if (is_true(sc, sc->value)) {op_cond_feed_1(sc); goto EVAL;} sc->value = sc->unspecified; continue; + + case OP_COND: check_cond(sc); + case OP_COND_UNCHECKED: if (op_cond_unchecked(sc)) goto EVAL; + case OP_COND1: if (op_cond1(sc)) goto TOP_NO_POP; /* else fall through */ + FEED_TO: + if (feed_to(sc)) goto APPLY; + goto EVAL; + case OP_FEED_TO_1: sc->code = sc->value; goto APPLY; /* sc->args saved in feed_to via push_stack */ + + case OP_COND_SIMPLE: if (op_cond_simple(sc)) goto EVAL; + case OP_COND1_SIMPLE: if (op_cond1_simple(sc)) goto TOP_NO_POP; goto BEGIN; + case OP_COND_SIMPLE_O: if (op_cond_simple_o(sc)) goto EVAL; + case OP_COND1_SIMPLE_O: if (op_cond1_simple_o(sc)) continue; goto EVAL; + + case OP_COND_NA_NA: sc->value = fx_cond_na_na(sc, sc->code); continue; + case OP_COND_NA_NP: if (op_cond_na_np(sc)) continue; goto EVAL; + case OP_COND_NA_NP_1: if (op_cond_na_np_1(sc)) continue; goto EVAL; + case OP_COND_NA_NP_O: if (inline_op_cond_na_np_o(sc)) continue; goto EVAL; + case OP_COND_NA_2E: if (op_cond_na_2e(sc)) continue; goto EVAL; + case OP_COND_NA_3E: if (op_cond_na_3e(sc)) continue; goto EVAL; + + + case OP_AND: + if (check_and(sc, sc->code)) continue; + case OP_AND_P: + sc->code = cdr(sc->code); + AND_P: /* this code (and OR_P below) is ugly, but the pretty version (procedurized) is much slower */ + if (has_fx(sc->code)) /* all fx_proc's are set via fx_choose which can return nil, but it is not cleared when type is */ + { /* so, if (fx_proc(sc->code)) here and in OR_P is not safe */ + sc->value = fx_call(sc, sc->code); + if (is_false(sc, sc->value)) continue; + sc->code = cdr(sc->code); + if (is_null(sc->code)) continue; /* this order of checks appears to be faster than any of the alternatives */ + goto AND_P; + } + if (is_pair(cdr(sc->code))) /* apparently exactly as fast as is_not_null */ + push_stack_no_args(sc, OP_AND_P1, cdr(sc->code)); + sc->code = car(sc->code); + goto EVAL; + + case OP_AND_P1: + if ((is_false(sc, sc->value)) || + (is_null(sc->code))) + continue; + goto AND_P; + + case OP_AND_AP: if (op_and_ap(sc)) continue; goto EVAL; + case OP_AND_2A: sc->value = fx_and_2a(sc, sc->code); continue; + case OP_AND_3A: sc->value = fx_and_3a(sc, sc->code); continue; + case OP_AND_N: sc->value = fx_and_n(sc, sc->code); continue; + case OP_AND_S_2: sc->value = fx_and_s_2(sc, sc->code); continue; + case OP_AND_PAIR_P: if (op_and_pair_p(sc)) continue; goto EVAL; + case OP_AND_SAFE_P1: op_and_safe_p1(sc); goto EVAL; + case OP_AND_SAFE_P2: if (op_and_safe_p2(sc)) continue; goto EVAL; + case OP_AND_SAFE_P3: if (op_and_safe_p3(sc)) continue; goto EVAL; + case OP_AND_SAFE_P_REST: if (is_true(sc, sc->value)) sc->value = fx_and_n(sc, sc->code); continue; + + + case OP_OR: + if (check_or(sc, sc->code)) continue; + case OP_OR_P: + sc->code = cdr(sc->code); + OR_P: + if (has_fx(sc->code)) + { + sc->value = fx_call(sc, sc->code); + if (is_true(sc, sc->value)) continue; + sc->code = cdr(sc->code); + if (is_null(sc->code)) continue; + goto OR_P; + } + if (is_pair(cdr(sc->code))) + push_stack_no_args(sc, OP_OR_P1, cdr(sc->code)); + sc->code = car(sc->code); + goto EVAL; + + case OP_OR_P1: + if ((is_true(sc, sc->value)) || + (is_null(sc->code))) + continue; + goto OR_P; + + case OP_OR_AP: if (op_or_ap(sc)) continue; goto EVAL; + case OP_OR_2A: sc->value = fx_or_2a(sc, sc->code); continue; + case OP_OR_S_2: sc->value = fx_or_s_2(sc, sc->code); continue; + case OP_OR_S_TYPE_2: sc->value = fx_or_s_type_2(sc, sc->code); continue; + case OP_OR_3A: sc->value = fx_or_3a(sc, sc->code); continue; + case OP_OR_N: sc->value = fx_or_n(sc, sc->code); continue; + + + case OP_NAMED_LET_NO_VARS: op_named_let_no_vars(sc); goto BEGIN; + case OP_NAMED_LET: if (op_named_let(sc)) goto BEGIN; goto EVAL; + case OP_NAMED_LET_A: op_named_let_a(sc); goto BEGIN; + case OP_NAMED_LET_AA: op_named_let_aa(sc); goto BEGIN; + case OP_NAMED_LET_NA: if (op_named_let_na(sc)) goto BEGIN; goto EVAL; + + case OP_LET: if (op_let(sc)) goto BEGIN; goto EVAL; + case OP_LET_UNCHECKED: if (op_let_unchecked(sc)) goto BEGIN; goto EVAL; + case OP_LET1: if (op_let1(sc)) goto BEGIN; goto EVAL; + case OP_LET_NO_VARS: op_let_no_vars(sc); goto BEGIN; + + case OP_LET_A_A_OLD: op_let_a_a_old(sc); continue; + case OP_LET_A_A_NEW: op_let_a_a_new(sc); continue; + case OP_LET_A_NA_OLD: op_let_a_na_old(sc); continue; + case OP_LET_A_NA_NEW: op_let_a_na_new(sc); continue; + case OP_LET_NA_OLD: op_let_na_old(sc); goto BEGIN; + case OP_LET_NA_NEW: inline_op_let_na_new(sc); goto BEGIN; + case OP_LET_2A_OLD: op_let_2a_old(sc); goto EVAL; + case OP_LET_2A_NEW: op_let_2a_new(sc); goto EVAL; + case OP_LET_3A_OLD: op_let_3a_old(sc); goto EVAL; + case OP_LET_3A_NEW: op_let_3a_new(sc); goto EVAL; + case OP_LET_ONE_OLD: op_let_one_old(sc); goto EVAL; + case OP_LET_ONE_NEW: op_let_one_new(sc); goto EVAL; + case OP_LET_ONE_P_OLD: op_let_one_p_old(sc); goto EVAL; + case OP_LET_ONE_P_NEW: op_let_one_p_new(sc); goto EVAL; + + case OP_LET_A_OLD: op_let_a_old(sc); sc->code = cdr(sc->code); goto BEGIN; + case OP_LET_A_NEW: inline_op_let_a_new(sc); sc->code = cdr(sc->code); goto BEGIN; + case OP_LET_A_OLD_2: inline_op_let_a_old(sc); push_stack_no_args(sc, OP_EVAL, caddr(sc->code)); sc->code = cadr(sc->code); goto EVAL; + case OP_LET_A_NEW_2: inline_op_let_a_new(sc); push_stack_no_args(sc, OP_EVAL, caddr(sc->code)); sc->code = cadr(sc->code); goto EVAL; + case OP_LET_A_P_OLD: inline_op_let_a_old(sc); sc->code = cadr(sc->code); goto EVAL; + case OP_LET_A_P_NEW: inline_op_let_a_new(sc); sc->code = cadr(sc->code); goto EVAL; + case OP_LET_ONE_OLD_1: op_let_one_old_1(sc); goto BEGIN; + case OP_LET_ONE_P_OLD_1: op_let_one_p_old_1(sc); goto EVAL; + case OP_LET_ONE_NEW_1: sc->curlet = inline_make_let_with_slot(sc, sc->curlet, opt2_sym(sc->code), sc->value); goto BEGIN; + case OP_LET_ONE_P_NEW_1: sc->curlet = inline_make_let_with_slot(sc, sc->curlet, opt2_sym(sc->code), sc->value); sc->code = car(sc->code); goto EVAL; + case OP_LET_opaSSq_OLD: op_let_opassq_old(sc); goto BEGIN; + case OP_LET_opaSSq_NEW: op_let_opassq_new(sc); goto BEGIN; + + case OP_LET_STAR_NA: op_let_star_na(sc); goto BEGIN; + case OP_LET_STAR_NA_A: op_let_star_na_a(sc); continue; + + case OP_NAMED_LET_STAR: op_named_let_star(sc); goto EVAL; + case OP_LET_STAR2: op_let_star2(sc); goto EVAL; + case OP_LET_STAR: if (check_let_star(sc)) goto EVAL; goto BEGIN; + case OP_LET_STAR1: if (op_let_star1(sc)) goto EVAL; goto BEGIN; + case OP_LET_STAR_SHADOWED: if (op_let_star_shadowed(sc)) goto EVAL; goto BEGIN; + + case OP_LETREC: check_letrec(sc, true); + case OP_LETREC_UNCHECKED: if (op_letrec_unchecked(sc)) goto EVAL; goto BEGIN; + case OP_LETREC1: if (op_letrec1(sc)) goto EVAL; goto BEGIN; + + case OP_LETREC_STAR: check_letrec(sc, false); + case OP_LETREC_STAR_UNCHECKED: if (op_letrec_star_unchecked(sc)) goto EVAL; goto BEGIN; + case OP_LETREC_STAR1: if (op_letrec_star1(sc)) goto EVAL; goto BEGIN; + + + case OP_LET_TEMPORARILY: check_let_temporarily(sc); + case OP_LET_TEMP_UNCHECKED: op_let_temp_unchecked(sc); goto LET_TEMP_INIT1; + + case OP_LET_TEMP_INIT1: + op_let_temp_init1_1(sc); + LET_TEMP_INIT1: + if (op_let_temp_init1(sc)) goto EVAL; + case OP_LET_TEMP_INIT2: + switch (op_let_temp_init2(sc)) /* let misc obj */ + { + case goto_begin: goto BEGIN; + case goto_eval: goto EVAL; + case goto_set_unchecked: goto SET_UNCHECKED; + case fall_through: + default: break; + } + + case OP_LET_TEMP_DONE: + sc->code = sc->value; + push_stack(sc, OP_GC_PROTECT, sc->args, sc->value); /* save let-temp body val as sc->code */ + case OP_LET_TEMP_DONE1: + if (op_let_temp_done1(sc)) continue; + goto SET_UNCHECKED; + + + case OP_LET_TEMP_S7: if(op_let_temp_s7(sc)) goto BEGIN; sc->value = sc->nil; continue; + case OP_LET_TEMP_S7_DIRECT: if (op_let_temp_s7_direct(sc)) goto BEGIN; sc->value = sc->nil; continue; + + case OP_LET_TEMP_NA: if (op_let_temp_na(sc)) goto BEGIN; sc->value = sc->nil; continue; + case OP_LET_TEMP_A: if (op_let_temp_a(sc)) goto BEGIN; sc->value = sc->nil; continue; + case OP_LET_TEMP_SETTER: if (op_let_temp_setter(sc)) goto BEGIN; sc->value = sc->nil; continue; + case OP_LET_TEMP_A_A: sc->value = fx_let_temp_a_a(sc, sc->code); continue; + + case OP_LET_TEMP_UNWIND: op_let_temp_unwind(sc); continue; + case OP_LET_TEMP_S7_UNWIND: op_let_temp_s7_unwind(sc); continue; + case OP_LET_TEMP_S7_DIRECT_UNWIND: op_let_temp_s7_direct_unwind(sc); continue; + case OP_LET_TEMP_SETTER_UNWIND: op_let_temp_setter_unwind(sc); continue; + + + case OP_EVAL_MACRO: op_eval_macro(sc); goto EVAL; + case OP_EVAL_MACRO_MV: if (op_eval_macro_mv(sc)) continue; goto EVAL; + case OP_EXPANSION: op_finish_expansion(sc); continue; + + case OP_DEFINE_BACRO: case OP_DEFINE_BACRO_STAR: + case OP_DEFINE_EXPANSION: case OP_DEFINE_EXPANSION_STAR: + case OP_DEFINE_MACRO: case OP_DEFINE_MACRO_STAR: + op_define_macro(sc); + continue; + + case OP_MACRO: case OP_BACRO: case OP_MACRO_STAR: case OP_BACRO_STAR: + op_macro(sc); + continue; + + case OP_LAMBDA: sc->value = op_lambda(sc, sc->code); continue; + case OP_LAMBDA_UNCHECKED: sc->value = op_lambda_unchecked(sc, sc->code); continue; + case OP_LAMBDA_STAR: op_lambda_star(sc); continue; + case OP_LAMBDA_STAR_UNCHECKED: op_lambda_star_unchecked(sc); continue; + + + case OP_CASE: /* car(sc->code) is the selector */ + /* selector A, key type: E=eq (symbol/char), I=integer, G=any, S=single keys and single bodies */ + if (check_case(sc)) goto EVAL; else goto G_G; /* selector is a symbol or constant, stupid "else" to shut up the compiler */ + + case OP_CASE_A_G_G: sc->value = fx_call(sc, cdr(sc->code)); + G_G: case OP_CASE_G_G: if (op_case_g_g(sc)) goto TOP_NO_POP; goto FEED_TO; + case OP_CASE_A_E_S: sc->value = fx_call(sc, cdr(sc->code)); + case OP_CASE_E_S: op_case_e_s(sc); goto EVAL; +#if (!WITH_GMP) + case OP_CASE_P_I_S: push_stack_no_args_direct(sc, OP_CASE_I_S); sc->code = cadr(sc->code); goto EVAL; + case OP_CASE_A_I_S: sc->value = fx_call(sc, cdr(sc->code)); + case OP_CASE_I_S: if (op_case_i_s(sc)) continue; goto EVAL; +#endif + case OP_CASE_A_G_S: sc->value = fx_call(sc, cdr(sc->code)); /* this almost never happens? */ + case OP_CASE_G_S: op_case_g_s(sc); goto EVAL; + + case OP_CASE_A_E_G: sc->value = fx_call(sc, cdr(sc->code)); + case OP_CASE_E_G: if (op_case_e_g_1(sc, sc->value, is_simple(sc->value))) goto TOP_NO_POP; goto FEED_TO; + case OP_CASE_A_S_G: /* splitting this case out matters in lint */ + sc->value = fx_call(sc, cdr(sc->code)); + if (op_case_e_g_1(sc, sc->value, is_case_key(sc->value))) goto TOP_NO_POP; else goto FEED_TO; + + case OP_CASE_P_G_G: push_stack_no_args_direct(sc, OP_CASE_G_G); sc->code = cadr(sc->code); goto EVAL; + case OP_CASE_P_E_S: push_stack_no_args_direct(sc, OP_CASE_E_S); sc->code = cadr(sc->code); goto EVAL; + case OP_CASE_P_G_S: push_stack_no_args_direct(sc, OP_CASE_G_S); sc->code = cadr(sc->code); goto EVAL; + case OP_CASE_P_E_G: push_stack_no_args_direct(sc, OP_CASE_E_G); sc->code = cadr(sc->code); goto EVAL; +#if (!WITH_GMP) + case OP_CASE_A_I_S_A: sc->value = fx_case_a_i_s_a(sc, sc->code); continue; +#endif + case OP_CASE_A_E_S_A: sc->value = fx_case_a_e_s_a(sc, sc->code); continue; + case OP_CASE_A_G_S_A: sc->value = fx_case_a_g_s_a(sc, sc->code); continue; + case OP_CASE_A_S_G_A: sc->value = fx_case_a_s_g_a(sc, sc->code); continue; + + + case OP_ERROR_QUIT: + if (sc->stack_end <= sc->stack_start) stack_reset(sc); /* sets stack_end to stack_start, then pushes op_eval_done, (can <= be F); + + case OP_ERROR_HOOK_QUIT: + op_error_hook_quit(sc); + + case OP_EVAL_DONE: + return(sc->F); + + case OP_SPLICE_VALUES: /* if splice_in_values hits eval_done, it needs to continue the splice after returning, so we get here */ + sc->value = splice_in_values(sc, sc->args); + continue; + + case OP_GC_PROTECT: case OP_BARRIER: case OP_NO_VALUES: + case OP_CATCH_ALL: case OP_CATCH: case OP_CATCH_1: case OP_CATCH_2: + if (SHOW_EVAL_OPS) fprintf(stderr, " flush %s\n", op_names[sc->cur_op]); + continue; + + case OP_GET_OUTPUT_STRING: op_get_output_string(sc); /* from call-with-output-string|with-output-to-string; return the port string directly *//* fall through */ + case OP_UNWIND_OUTPUT: op_unwind_output(sc); continue; + case OP_UNWIND_INPUT: op_unwind_input(sc); continue; + case OP_DYNAMIC_UNWIND: dynamic_unwind(sc, sc->code, sc->args); continue; + case OP_PROFILE_IN: g_profile_in(sc, set_plist_2(sc, cadr(sc->code), sc->curlet)); continue; + case OP_DYNAMIC_UNWIND_PROFILE: g_profile_out(sc, set_plist_1(sc, sc->args)); continue; + case OP_DYNAMIC_WIND: if (op_dynamic_wind(sc)) goto APPLY; continue; + case OP_DEACTIVATE_GOTO: call_exit_active(sc->args) = false; continue; /* deactivate the exiter */ + + case OP_WITH_LET_S: sc->value = fx_with_let_s(sc, sc->code); continue; + case OP_WITH_LET: check_with_let(sc); + case OP_WITH_LET_UNCHECKED: if (op_with_let_unchecked(sc)) goto EVAL; + case OP_WITH_LET1: if (sc->value != sc->curlet) activate_with_let(sc, sc->value); goto BEGIN; + + case OP_WITH_BAFFLE: check_with_baffle(sc); + case OP_WITH_BAFFLE_UNCHECKED: if (op_with_baffle_unchecked(sc)) continue; goto BEGIN; + + + case OP_READ_INTERNAL: op_read_internal(sc); continue; + case OP_READ_DONE: op_read_done(sc); continue; + case OP_LOAD_RETURN_IF_EOF: if (op_load_return_if_eof(sc)) goto EVAL; return(sc->F); + case OP_LOAD_CLOSE_AND_POP_IF_EOF: if (op_load_close_and_pop_if_eof(sc)) goto EVAL; continue; + + POP_READ_LIST: + if (pop_read_list(sc)) goto READ_NEXT; + + READ_LIST: + case OP_READ_LIST: /* sc->args is sc->nil at first */ + sc->args = cons(sc, sc->value, sc->args); + + READ_NEXT: + case OP_READ_NEXT: /* this is 75% of the token calls, so expanding it saves lots of time */ + { + int32_t c; + s7_pointer pt = current_input_port(sc); + c = port_read_white_space(pt)(sc, pt); + + READ_C: + switch (c) + { + case '(': + c = port_read_white_space(pt)(sc, pt); /* sc->tok = token(sc) */ + switch (c) + { + case '(': sc->tok = TOKEN_LEFT_PAREN; break; + case ')': sc->value = sc->nil; goto READ_LIST; /* was tok = TOKEN_RIGHT_PAREN */ + case '.': sc->tok = read_dot(sc, pt); break; + case '\'': sc->tok = TOKEN_QUOTE; break; + case ';': sc->tok = port_read_semicolon(pt)(sc, pt); break; + case '"': sc->tok = TOKEN_DOUBLE_QUOTE; break; + case '`': sc->tok = TOKEN_BACK_QUOTE; break; + case ',': sc->tok = read_comma(sc, pt); break; + case '#': sc->tok = read_sharp(sc, pt); break; + case '\0': case EOF: sc->tok = TOKEN_EOF; break; + + default: /* read first element of list (ignore callgrind confusion -- this happens a lot) */ + c = read_start_list(sc, pt, c); + goto READ_C; + } + if (sc->tok == TOKEN_ATOM) + { + c = read_atom(sc, pt); + goto READ_C; + } + if (sc->tok == TOKEN_RIGHT_PAREN) + { + sc->value = sc->nil; + goto READ_LIST; + } + if (sc->tok == TOKEN_DOT) + { + do {c = inchar(pt);} while ((c != ')') && (c != EOF)); + read_error_nr(sc, "stray dot after '('?"); /* (car '( . )) */ + } + if (sc->tok == TOKEN_EOF) + missing_close_paren_error_nr(sc); + + push_stack_no_let_no_code(sc, OP_READ_LIST, sc->args); + push_stack_no_let_no_code(sc, OP_READ_LIST, sc->nil); + /* check_stack_size(sc); */ + sc->value = read_expression(sc); + if (stack_top_op(sc) == OP_READ_LIST) goto POP_READ_LIST; + continue; + + case ')': + sc->tok = TOKEN_RIGHT_PAREN; + break; + + case '.': + sc->tok = read_dot(sc, pt); /* dot or atom */ + break; + + case '\'': + sc->tok = TOKEN_QUOTE; + /* might need check_stack_size(sc) here */ + push_stack_no_let_no_code(sc, OP_READ_LIST, sc->args); + sc->value = read_expression(sc); + continue; + + case ';': + sc->tok = port_read_semicolon(pt)(sc, pt); + break; + + case '"': + sc->tok = TOKEN_DOUBLE_QUOTE; + read_double_quote(sc); + goto READ_LIST; + + case '`': + sc->tok = TOKEN_BACK_QUOTE; + push_stack_no_let_no_code(sc, OP_READ_LIST, sc->args); + sc->value = read_expression(sc); + if (stack_top_op(sc) == OP_READ_LIST) goto POP_READ_LIST; + continue; + + case ',': + sc->tok = read_comma(sc, pt); /* at_mark or comma */ + push_stack_no_let_no_code(sc, OP_READ_LIST, sc->args); + sc->value = read_expression(sc); + continue; + + case '#': + sc->tok = read_sharp(sc, pt); + break; + + case '\0': + case EOF: + missing_close_paren_error_nr(sc); + + default: + sc->strbuf[0] = (unsigned char)c; + sc->value = port_read_name(pt)(sc, pt); + goto READ_LIST; + }} + + READ_TOK: + switch (sc->tok) + { + case TOKEN_RIGHT_PAREN: /* sc->args can't be null here */ + sc->value = proper_list_reverse_in_place(sc, sc->args); + if ((is_expansion(car(sc->value))) && + (sc->is_expanding)) + switch (op_expansion(sc)) + { + case goto_begin: goto BEGIN; + case goto_apply_lambda: goto APPLY_LAMBDA; + default: break; + } + break; + + case TOKEN_EOF: missing_close_paren_error_nr(sc); /* can't happen, I believe */ + case TOKEN_ATOM: sc->value = port_read_name(current_input_port(sc))(sc, current_input_port(sc)); goto READ_LIST; + case TOKEN_SHARP_CONST: if (read_sharp_const(sc)) goto READ_TOK; goto READ_LIST; + case TOKEN_DOUBLE_QUOTE: read_double_quote(sc); goto READ_LIST; + case TOKEN_DOT: read_dot_and_expression(sc); break; + default: read_tok_default(sc); break; + } + if (stack_top_op(sc) == OP_READ_LIST) goto POP_READ_LIST; + continue; + + case OP_READ_DOT: + switch (op_read_dot(sc)) + { + case goto_start: continue; + case goto_pop_read_list: goto POP_READ_LIST; + default: goto READ_TOK; + } + case OP_READ_QUOTE: if (op_read_quote(sc)) continue; goto POP_READ_LIST; + case OP_READ_QUASIQUOTE: if (op_read_quasiquote(sc)) continue; goto POP_READ_LIST; + case OP_READ_UNQUOTE: if (op_read_unquote(sc)) continue; goto POP_READ_LIST; + case OP_READ_APPLY_VALUES: if (op_read_apply_values(sc)) continue; goto POP_READ_LIST; + case OP_READ_VECTOR: if (op_read_vector(sc)) continue; goto POP_READ_LIST; + case OP_READ_INT_VECTOR: if (op_read_int_vector(sc)) continue; goto POP_READ_LIST; + case OP_READ_FLOAT_VECTOR: if (op_read_float_vector(sc)) continue; goto POP_READ_LIST; + case OP_READ_BYTE_VECTOR: if (op_read_byte_vector(sc)) continue; goto POP_READ_LIST; + + case OP_CLEAR_OPTS: + break; + + default: + return(sc->F); + } + + /* this code is reached from OP_CLEAR_OPTS and many others where the optimization has turned out to be incorrect, OP_CLOSURE_SYM for example; search for break */ + if (!tree_is_cyclic(sc, sc->code)) + clear_all_optimizations(sc, sc->code); + UNOPT: + switch (trailers(sc)) + { + case goto_top_no_pop: goto TOP_NO_POP; + case goto_eval_args_top: goto EVAL_ARGS_TOP; + case goto_eval: goto EVAL; + case goto_start: continue; /* sc->value has been set, this is OP_SYMBOL|CONSTANT on the next pass */ + default: + if (S7_DEBUGGING) fprintf(stderr, "%s[%d]: unexpected switch default: %s\n", __func__, __LINE__, display(sc->code)); + break; + }} + return(sc->F); /* this never happens (make the compiler happy) */ +} + + +/* -------------------------------- s7_heap_scan -------------------------------- */ +#if S7_DEBUGGING +static void mark_holdee(s7_pointer holder, s7_pointer holdee, const char *root) +{ + holdee->holders++; + holdee->holder = holder; + holdee->root = root; +} + +static void mark_stack_holdees(s7_scheme *sc, s7_pointer p, s7_int top) +{ + if (stack_elements(p)) + { + s7_pointer heap0 = *(sc->heap); + s7_pointer heap1 = (s7_pointer)(heap0 + sc->heap_size); + for (s7_pointer *tp = (s7_pointer *)(stack_elements(p)), *tend = (s7_pointer *)(tp + top); (tp < tend); tp++) + { + s7_pointer x = *tp++; + if ((x >= heap0) && (x < heap1)) mark_holdee(p, x, NULL); + x = *tp++; + if ((x >= heap0) && (x < heap1)) mark_holdee(p, x, NULL); + x = *tp++; + if ((x >= heap0) && (x < heap1)) mark_holdee(p, x, NULL); + }} +} + +static void save_holder_data(s7_scheme *sc, s7_pointer p) +{ + switch (unchecked_type(p)) + { + case T_PAIR: mark_holdee(p, car(p), NULL); mark_holdee(p, cdr(p), NULL); break; + case T_CATCH: mark_holdee(p, catch_tag(p), NULL); mark_holdee(p, catch_handler(p), NULL); break; + case T_DYNAMIC_WIND: mark_holdee(p, dynamic_wind_in(p), NULL); mark_holdee(p, dynamic_wind_out(p), NULL); mark_holdee(p, dynamic_wind_body(p), NULL); break; + case T_INPUT_PORT: mark_holdee(p, port_string_or_function(p), NULL); break; + case T_C_POINTER: mark_holdee(p, c_pointer_type(p), NULL); mark_holdee(p, c_pointer_info(p), NULL); break; + case T_COUNTER: mark_holdee(p, counter_result(p), NULL); mark_holdee(p, counter_list(p), NULL); mark_holdee(p, counter_let(p), NULL); break; + case T_STACK: mark_stack_holdees(sc, p, (p == sc->stack) ? stack_top(sc) : temp_stack_top(p)); break; + case T_OUTPUT_PORT: if (is_function_port(p)) mark_holdee(p, port_string_or_function(p), NULL); break; + + case T_ITERATOR: + mark_holdee(p, iterator_sequence(p), NULL); + if (is_mark_seq(p)) mark_holdee(p, iterator_current(p), NULL); + break; + + case T_SLOT: + mark_holdee(p, slot_value(p), NULL); + mark_holdee(p, slot_symbol(p), NULL); + if (slot_has_setter(p)) mark_holdee(p, slot_setter(p), NULL); + if (slot_has_pending_value(p)) mark_holdee(p, slot_pending_value(p), NULL); + break; + + case T_VECTOR: + if (is_subvector(p)) mark_holdee(p, subvector_vector(p), NULL); + for (s7_int i = 0, len = vector_length(p); i < len; i++) + if (vector_element(p, i)) mark_holdee(p, vector_element(p, i), NULL); + break; + + case T_INT_VECTOR: case T_FLOAT_VECTOR: case T_BYTE_VECTOR: + if (is_subvector(p)) mark_holdee(p, subvector_vector(p), NULL); + break; + + case T_LET: + if (p != sc->rootlet) /* do rootlet later? */ + { + for (s7_pointer slot = let_slots(p); tis_slot(slot); slot = next_slot(slot)) mark_holdee(p, slot, NULL); + if (has_dox_slot1(p)) mark_holdee(p, let_dox_slot1(p), NULL); + if ((has_dox_slot2(p)) && (is_slot(let_dox_slot2(p)))) mark_holdee(p, let_dox_slot2(p), NULL); + } + break; + + case T_C_FUNCTION_STAR: + if ((!c_func_has_simple_defaults(p)) && (c_function_call_args(p))) + for (s7_pointer arg = c_function_call_args(p); is_pair(arg); arg = cdr(arg)) + mark_holdee(p, car(arg), NULL); + break; + + case T_CLOSURE: case T_CLOSURE_STAR: + case T_MACRO: case T_MACRO_STAR: + case T_BACRO: case T_BACRO_STAR: + mark_holdee(p, closure_args(p), NULL); + mark_holdee(p, closure_body(p), NULL); + mark_holdee(p, closure_let(p), NULL); + mark_holdee(p, closure_setter_or_map_list(p), NULL); + break; + + case T_HASH_TABLE: + mark_holdee(p, hash_table_procedures(p), NULL); + if (is_pair(hash_table_procedures(p))) + { + mark_holdee(p, hash_table_key_typer_unchecked(p), NULL); + mark_holdee(p, hash_table_value_typer_unchecked(p), NULL); + } + if (hash_table_entries(p) > 0) + { + s7_int len = hash_table_mask(p) + 1; + hash_entry_t **entries = hash_table_elements(p); + hash_entry_t **last = (hash_entry_t **)(entries + len); + if ((is_weak_hash_table(p)) && (weak_hash_iters(p) == 0)) + while (entries < last) + { + for (hash_entry_t *xp = *entries++; xp; xp = hash_entry_next(xp)) + mark_holdee(p, hash_entry_value(xp), NULL); + } + else + while (entries < last) + for (hash_entry_t *xp = *entries++; xp; xp = hash_entry_next(xp)) + { + mark_holdee(p, hash_entry_key(xp), NULL); + mark_holdee(p, hash_entry_value(xp), NULL); + }} + break; + + case T_CONTINUATION: + mark_holdee(p, continuation_op_stack(p), NULL); + mark_stack_holdees(sc, continuation_stack(p), continuation_stack_top(p)); + break; + + default: break; /* includes T_C_OBJECT */ + } +} + +void s7_heap_analyze(s7_scheme *sc); +void s7_heap_analyze(s7_scheme *sc) +{ + /* clear possible previous data */ + for (s7_int k = 0; k < sc->heap_size; k++) + { + s7_pointer obj = sc->heap[k]; + obj->root = NULL; + obj->holders = 0; + obj->holder = NULL; + } + /* now parcel out all the holdings */ + for (s7_int k = 0; k < sc->heap_size; k++) + save_holder_data(sc, sc->heap[k]); + + { + s7_pointer *tmps = sc->free_heap_top; + s7_pointer *tmps_top = tmps + sc->gc_temps_size; + if (tmps_top > sc->previous_free_heap_top) tmps_top = sc->previous_free_heap_top; + while (tmps < tmps_top) + { + s7_pointer p = *tmps++; + mark_holdee(NULL, p, "gc temp"); + }} + + mark_holdee(NULL, sc->w, "sc->w"); + mark_holdee(NULL, sc->x, "sc->x"); + mark_holdee(NULL, sc->y, "sc->y"); + mark_holdee(NULL, sc->z, "sc->z"); + mark_holdee(NULL, sc->temp1, "sc->temp1"); + mark_holdee(NULL, sc->temp2, "sc->temp2"); + mark_holdee(NULL, sc->temp3, "sc->temp3"); + mark_holdee(NULL, sc->temp4, "sc->temp4"); + mark_holdee(NULL, sc->temp5, "sc->temp5"); + mark_holdee(NULL, sc->temp7, "sc->temp7"); + mark_holdee(NULL, sc->temp8, "sc->temp8"); + mark_holdee(NULL, sc->temp9, "sc->temp9"); + mark_holdee(NULL, sc->temp10, "sc->temp10"); + mark_holdee(NULL, sc->rec_p1, "sc->rec_p1"); + mark_holdee(NULL, sc->rec_p2, "sc->rec_p2"); + + mark_holdee(NULL, car(sc->t1_1), "car(sc->t1_1)"); + mark_holdee(NULL, car(sc->t2_1), "car(sc->t2_1)"); + mark_holdee(NULL, car(sc->t2_2), "car(sc->t2_2)"); + mark_holdee(NULL, car(sc->t3_1), "car(sc->t3_1)"); + mark_holdee(NULL, car(sc->t3_2), "car(sc->t3_2)"); + mark_holdee(NULL, car(sc->t3_3), "car(sc->t3_3)"); + mark_holdee(NULL, car(sc->t4_1), "car(sc->t4_1)"); + mark_holdee(NULL, car(sc->u1_1), "car(sc->u1_1)"); + mark_holdee(NULL, car(sc->plist_1), "car(sc->plist_1)"); + mark_holdee(NULL, car(sc->plist_2), "car(sc->plist_2)"); + mark_holdee(NULL, car(sc->plist_3), "car(sc->plist_3)"); + mark_holdee(NULL, car(sc->qlist_2), "car(sc->qlist_2)"); + mark_holdee(NULL, car(sc->qlist_3), "car(sc->qlist_3)"); + mark_holdee(NULL, car(sc->elist_1), "car(sc->elist_1)"); + mark_holdee(NULL, car(sc->elist_2), "car(sc->elist_2)"); + mark_holdee(NULL, car(sc->elist_3), "car(sc->elist_3)"); + mark_holdee(NULL, car(sc->elist_4), "car(sc->elist_4)"); + mark_holdee(NULL, car(sc->elist_5), "car(sc->elist_5)"); + mark_holdee(NULL, car(sc->elist_6), "car(sc->elist_6)"); + mark_holdee(NULL, car(sc->elist_7), "car(sc->elist_7)"); + mark_holdee(NULL, cadr(sc->plist_2), "cadr(sc->plist_2)"); + mark_holdee(NULL, cadr(sc->plist_3), "cadr(sc->plist_3)"); + mark_holdee(NULL, cadr(sc->elist_2), "cadr(sc->elist_2)"); + mark_holdee(NULL, cadr(sc->elist_3), "cadr(sc->elist_3)"); + mark_holdee(NULL, cadr(sc->qlist_2), "cadr(sc->qlist_2)"); + mark_holdee(NULL, caddr(sc->plist_3), "caddr(sc->plist_3)"); + mark_holdee(NULL, caddr(sc->elist_3), "caddr(sc->elist_3)"); + + mark_holdee(NULL, sc->code, "sc->code"); + mark_holdee(NULL, sc->value, "sc->value"); + mark_holdee(NULL, sc->args, "sc->args"); + mark_holdee(NULL, sc->curlet, "sc->curlet"); + mark_holdee(NULL, sc->stack, "sc->stack"); + mark_holdee(NULL, sc->default_random_state, "sc->default_random_state"); + mark_holdee(NULL, sc->let_temp_hook, "sc->let_temp_hook"); + mark_holdee(NULL, sc->stacktrace_defaults, "sc->stacktrace_defaults"); + mark_holdee(NULL, sc->protected_objects, "sc->protected_objects"); + mark_holdee(NULL, sc->protected_setters, "sc->protected_setters"); + mark_holdee(NULL, sc->protected_setter_symbols, "sc->protected_setter_symbols"); + mark_holdee(NULL, sc->error_type, "sc->error_type"); + mark_holdee(NULL, sc->error_data, "sc->error_data"); + mark_holdee(NULL, sc->error_code, "sc->error_code"); + mark_holdee(NULL, sc->error_line, "sc->error_line"); + mark_holdee(NULL, sc->error_file, "sc->error_file"); + mark_holdee(NULL, sc->error_position, "sc->error_position"); +#if WITH_HISTORY + mark_holdee(NULL, sc->error_history, "sc->error_history"); +#endif + + for (gc_obj_t *g = sc->semipermanent_objects; g; g = (gc_obj_t *)(g->nxt)) + mark_holdee(NULL, g->p, "permanent object"); + + for (s7_int i = 0; i < sc->protected_objects_size; i++) + mark_holdee(NULL, vector_element(sc->protected_objects, i), "gc protected object"); + + for (s7_int i = 0; i < sc->protected_setters_loc; i++) + mark_holdee(NULL, vector_element(sc->protected_setters, i), "gc protected setter"); + + for (s7_int i = 0; i < sc->setters_loc; i++) + mark_holdee(NULL, cdr(sc->setters[i]), "setter"); + + for (s7_int i = 0; i <= sc->format_depth; i++) + if (sc->fdats[i]) + mark_holdee(NULL, sc->fdats[i]->curly_arg, "fdat curly_arg"); + + { + s7_pointer *tp = (s7_pointer *)(sc->input_port_stack + sc->input_port_stack_loc); + for (s7_pointer *p = sc->input_port_stack; p < tp; p++) + mark_holdee(NULL, *p, "input stack"); + } + { + s7_pointer *p = sc->op_stack; + s7_pointer *tp = sc->op_stack_now; + while (p < tp) {s7_pointer x = *p++; mark_holdee(NULL, x, "op stack");} + } + + if (sc->rec_stack) + for (s7_int i = 0; i < sc->rec_loc; i++) + mark_holdee(NULL, sc->rec_els[i], "sc->rec_els"); + + { + gc_list_t *gp = sc->opt1_funcs; + for (s7_int i = 0; i < gp->loc; i++) + { + s7_pointer s1 = T_Pair(gp->list[i]); + mark_holdee(NULL, opt1_any(s1), "opt1_funcs"); + }} + + for (int32_t i = 1; i < NUM_SAFE_LISTS; i++) + if ((is_pair(sc->safe_lists[i])) && + (list_is_in_use(sc->safe_lists[i]))) + for (s7_pointer p = sc->safe_lists[i]; is_pair(p); p = cdr(p)) + mark_holdee(NULL, car(p), "safe_lists"); + + for (s7_pointer p = sc->wrong_type_arg_info; is_pair(p); p = cdr(p)) mark_holdee(NULL, car(p), "wrong-type-arg"); + for (s7_pointer p = sc->sole_arg_wrong_type_info; is_pair(p); p = cdr(p)) mark_holdee(NULL, car(p), "simple wrong-type-arg"); + for (s7_pointer p = sc->out_of_range_info; is_pair(p); p = cdr(p)) mark_holdee(NULL, car(p), "out-of-range"); + for (s7_pointer p = sc->sole_arg_out_of_range_info; is_pair(p); p = cdr(p)) mark_holdee(NULL, car(p), "simple out-of-range"); + + { + s7_pointer *tmp = rootlet_elements(sc->rootlet); + s7_pointer *top = (s7_pointer *)(tmp + sc->rootlet_entries); + while (tmp < top) {s7_pointer slot = *tmp++; mark_holdee(NULL, slot_value(slot), "rootlet");} + } +#if WITH_HISTORY + for (s7_pointer p1 = sc->eval_history1, p2 = sc->eval_history2, p3 = sc->history_pairs; ; p2 = cdr(p2), p3 = cdr(p3)) + { + mark_holdee(NULL, car(p1), "eval history1"); + mark_holdee(NULL, car(p2), "eval history2"); + mark_holdee(NULL, car(p3), "eval history3"); + p1 = cdr(p1); + if (p1 == sc->eval_history1) break; + } +#else + mark_holdee(NULL, sc->cur_code, "current code"); +#endif +} + +void s7_heap_scan(s7_scheme *sc, int32_t typ); +void s7_heap_scan(s7_scheme *sc, int32_t typ) +{ + bool found_one = false; + for (s7_int k = 0; k < sc->heap_size; k++) + { + s7_pointer obj = sc->heap[k]; + if (unchecked_type(obj) == typ) + { + found_one = true; + if (obj->holders == 0) + fprintf(stderr, "%s has no holder (alloc: %d)\n", display_80(obj), obj->alloc_line); + else + if (obj->root) + fprintf(stderr, "%s from %s (%d holder%s)\n", display_80(obj), obj->root, + obj->holders, (obj->holders != 1) ? "s" : ""); + else fprintf(stderr, "%s from %s (%s, %p, alloc: %d, holder%s: %d)\n", + display_80(obj), display_80(obj->holder), + s7_type_names[unchecked_type(obj->holder)], obj->holder, obj->alloc_line, + (obj->holders != 1) ? "s" : "", obj->holders); + }} + if (!found_one) + fprintf(stderr, "heap-scan: no %s found\n", s7_type_names[typ]); +} + +static s7_pointer g_heap_scan(s7_scheme *sc, s7_pointer args) +{ + #define H_heap_scan "(heap-scan type) scans the heap for objects of type and reports info about them" + #define Q_heap_scan s7_make_signature(sc, 2, sc->not_symbol, sc->is_integer_symbol) + s7_pointer p = car(args); + if (!s7_is_integer(p)) + sole_arg_wrong_type_error_nr(sc, make_symbol(sc, "heap-scan", 9), p, sc->type_names[T_INTEGER]); + if ((s7_integer(p) <= 0) || (s7_integer(p) >= NUM_TYPES)) + sole_arg_out_of_range_error_nr(sc, make_symbol(sc, "heap-scan", 9), p, wrap_string(sc, "0 < type < 48", 13)); + s7_heap_scan(sc, (int32_t)s7_integer(p)); /* 0..48 currently */ + return(sc->F); +} + +static s7_pointer g_heap_analyze(s7_scheme *sc, s7_pointer args) +{ + #define H_heap_analyze "(heap-analyze type) gets heap data for subsequent heap-scan" + #define Q_heap_analyze s7_make_signature(sc, 1, sc->not_symbol) + s7_heap_analyze(sc); + return(sc->F); +} + +static s7_pointer g_heap_holder(s7_scheme *sc, s7_pointer args) +{ + #define H_heap_holder "(heap-holder obj) returns the object pointing to obj" + #define Q_heap_holder s7_make_signature(sc, 2, sc->T, sc->T) + s7_pointer p = car(args); + if ((p->holders == 0) || ((!(p->holder)) && (!(p->root)))) return(sc->F); + return((p->holder) ? p->holder : s7_make_string(sc, p->root)); +} + +static s7_pointer g_heap_holders(s7_scheme *sc, s7_pointer args) +{ + #define H_heap_holders "(heap-holders obj) returns the number of objects pointing to obj" + #define Q_heap_holders s7_make_signature(sc, 2, sc->is_integer_symbol, sc->T) + return(make_integer(sc, car(args)->holders)); +} + +/* random debugging stuff */ +static s7_pointer g_show_stack(s7_scheme *sc, s7_pointer args) +{ + #define H_show_stack "no help" + #define Q_show_stack s7_make_signature(sc, 1, sc->not_symbol) + s7_show_stack(sc); + return(sc->F); +} + +void s7_show_op_stack(s7_scheme *sc); +void s7_show_op_stack(s7_scheme *sc) +{ + fprintf(stderr, "op_stack:\n"); + for (s7_pointer *p = sc->op_stack, *tp = sc->op_stack_now; (p < tp); p++) + fprintf(stderr, " %s\n", display(*p)); +} + +static s7_pointer g_show_op_stack(s7_scheme *sc, s7_pointer args) +{ + #define H_show_op_stack "no help" + #define Q_show_op_stack s7_make_signature(sc, 1, sc->not_symbol) + s7_show_op_stack(sc); + return(sc->F); +} + +static s7_pointer g_is_op_stack(s7_scheme *sc, s7_pointer args) +{ + #define H_is_op_stack "no help" + #define Q_is_op_stack s7_make_signature(sc, 1, sc->is_boolean_symbol) + return(make_boolean(sc, (sc->op_stack < sc->op_stack_now))); +} +#endif + + +/* -------------------------------- *s7* let -------------------------------- */ +/* maybe *features* field in *s7*, others are *libraries*, *load-path*, *cload-directory*, *autoload*, *#readers* */ + +static noreturn void s7_starlet_wrong_type_error_nr(s7_scheme *sc, s7_pointer caller, s7_pointer arg, s7_pointer typ) +{ + error_nr(sc, sc->wrong_type_arg_symbol, + set_elist_5(sc, wrap_string(sc, "(set! (*s7* '~A) ~S): new value is ~A but should be ~A", 54), + caller, arg, object_type_name(sc, arg), typ)); +} + +static noreturn void sl_stacktrace_wrong_type_error_nr(s7_scheme *sc, s7_pointer caller, s7_int num, s7_pointer arg, s7_pointer typ, s7_pointer val) +{ + set_elist_7(sc, wrap_string(sc, "(set! (*s7* '~A) '~S): the ~:D list element ~S is ~A but should be ~A", 69), + caller, val, wrap_integer(sc, num), arg, object_type_name(sc, arg), typ); + error_nr(sc, sc->wrong_type_arg_symbol, sc->elist_7); +} + +static noreturn void s7_starlet_out_of_range_error_nr(s7_scheme *sc, s7_pointer caller, s7_pointer arg, s7_pointer descr) +{ + error_nr(sc, sc->out_of_range_symbol, + set_elist_4(sc, wrap_string(sc, "(set! (*s7* '~A) ~S): new value is out of range (~A)", 52), caller, arg, descr)); +} + +static s7_int s7_starlet_length(void) {return(SL_NUM_FIELDS - 1);} + +static s7_pointer g_s7_starlet_set_fallback(s7_scheme *sc, s7_pointer args) +{ + s7_pointer sym = cadr(args); + if (!is_symbol(sym)) + sole_arg_wrong_type_error_nr(sc, sc->let_set_symbol, sym, sc->type_names[T_SYMBOL]); + return(s7_starlet_set_1(sc, sym, caddr(args))); +} + +static s7_pointer g_s7_starlet_ref_fallback(s7_scheme *sc, s7_pointer args); + +static s7_pointer make_s7_starlet(s7_scheme *sc) /* *s7* is semipermanent -- 20-May-21 */ +{ + s7_pointer slot1 = make_semipermanent_slot(sc, sc->let_set_fallback_symbol, s7_make_function(sc, "s7-let-set", g_s7_starlet_set_fallback, 3, 0, false, "*s7* writer")); + s7_pointer slot2 = make_semipermanent_slot(sc, sc->let_ref_fallback_symbol, s7_make_function(sc, "s7-let-ref", g_s7_starlet_ref_fallback, 2, 0, false, "*s7* reader")); + s7_pointer x = alloc_pointer(sc); + set_full_type(x, T_LET | T_SAFE_PROCEDURE | T_UNHEAP | T_HAS_METHODS | T_HAS_LET_REF_FALLBACK | T_HAS_LET_SET_FALLBACK); + let_set_id(x, ++sc->let_number); + let_set_outlet(x, sc->nil); + symbol_set_local_slot(sc->let_set_fallback_symbol, sc->let_number, slot1); + slot_set_next(slot1, slot_end); + symbol_set_local_slot(sc->let_ref_fallback_symbol, sc->let_number, slot2); + slot_set_next(slot2, slot1); + let_set_slots(x, slot2); + set_immutable_slot(slot1); /* make the *s7* let-ref|set! fallbacks immutable */ + set_immutable_slot(slot2); + set_immutable_let(x); + sc->s7_starlet_symbol = s7_define_constant(sc, "*s7*", s7_openlet(sc, x)); /* define_constant returns the symbol */ + for (int32_t i = SL_STACK_TOP; i < SL_NUM_FIELDS; i++) + { + s7_pointer sym = make_symbol_with_strlen(sc, s7_starlet_names[i]); + s7_starlet_symbol_set(sym, (s7_starlet_t)i); /* evaluates sym twice */ + } + return(x); +} + +static void add_symbol_table(s7_scheme *sc, s7_pointer mu_let) +{ + /* check the symbol table, counting gensyms etc */ + s7_int syms = 0, gens = 0, keys = 0, mx_list = 0; + s7_pointer *els = vector_elements(sc->symbol_table); + for (s7_int i = 0; i < SYMBOL_TABLE_SIZE; i++) + { + s7_pointer x; + s7_int k = 0; + for (x = els[i]; is_not_null(x); x = cdr(x), k++) + { + syms++; + if (is_gensym(car(x))) gens++; + if (is_keyword(car(x))) keys++; + } + if (k > mx_list) mx_list = k; + } + add_slot_unchecked_with_id(sc, mu_let, sc->symbol_table_symbol, + s7_list(sc, 9, + make_integer(sc, SYMBOL_TABLE_SIZE), + make_symbol(sc, "max-bin", 7), make_integer(sc, mx_list), + make_symbol(sc, "symbols", 7), cons(sc, make_integer(sc, syms), make_integer(sc, syms - gens - keys)), + make_symbol(sc, "gensyms", 7), make_integer(sc, gens), + make_symbol(sc, "keys", 4), make_integer(sc, keys))); +} + +static s7_pointer kmg(s7_scheme *sc, s7_int bytes) +{ + block_t *b = mallocate(sc, 128); + int32_t len = 0; + if (bytes < 1000) + len = snprintf((char *)block_data(b), 128, "%" ld64, bytes); + else + if (bytes < 1000000) + len = snprintf((char *)block_data(b), 128, "%.1fk", bytes / 1000.0); + else + if (bytes < 1000000000) + len = snprintf((char *)block_data(b), 128, "%.1fM", bytes / 1000000.0); + else len = snprintf((char *)block_data(b), 128, "%.1fG", bytes / 1000000000.0); + return(cons(sc, make_integer(sc, bytes), block_to_string(sc, b, len))); +} + +static void add_gc_list_sizes(s7_scheme *sc, s7_pointer mu_let) +{ + /* check the gc lists (finalizations), at startup there are strings/input-strings from the s7_eval_c_string calls for make-polar et el */ + s7_int len = sc->strings->size + sc->vectors->size + sc->input_ports->size + sc->output_ports->size + sc->input_string_ports->size + + sc->continuations->size + sc->c_objects->size + sc->hash_tables->size + sc->gensyms->size + sc->undefineds->size + + sc->multivectors->size + sc->weak_refs->size + sc->weak_hash_iterators->size + sc->opt1_funcs->size; + + int32_t loc = sc->strings->loc + sc->vectors->loc + sc->input_ports->loc + sc->output_ports->loc + sc->input_string_ports->loc + + sc->continuations->loc + sc->c_objects->loc + sc->hash_tables->loc + sc->gensyms->loc + sc->undefineds->loc + + sc->multivectors->loc + sc->weak_refs->loc + sc->weak_hash_iterators->loc + sc->opt1_funcs->loc; + + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "gc-lists", 8), + s7_list(sc, 4, make_integer(sc, loc), make_integer(sc, len), kmg(sc, len * sizeof(s7_pointer)), /* active, total, space allocated */ + s7_list(sc, 14, + list_3(sc, sc->string_symbol, make_integer(sc, sc->strings->loc), make_integer(sc, sc->strings->size)), + list_3(sc, sc->vector_symbol, make_integer(sc, sc->vectors->loc), make_integer(sc, sc->vectors->size)), + list_3(sc, sc->hash_table_symbol, make_integer(sc, sc->hash_tables->loc), make_integer(sc, sc->hash_tables->size)), + list_3(sc, make_symbol(sc, "multivector", 11), make_integer(sc, sc->multivectors->loc), make_integer(sc, sc->multivectors->size)), + list_3(sc, make_symbol(sc, "input", 5), make_integer(sc, sc->input_ports->loc), make_integer(sc, sc->input_ports->size)), + list_3(sc, make_symbol(sc, "output", 6), make_integer(sc, sc->output_ports->loc), make_integer(sc, sc->output_ports->size)), + list_3(sc, make_symbol(sc, "input-string", 12), make_integer(sc, sc->input_string_ports->loc), make_integer(sc, sc->input_string_ports->size)), + list_3(sc, make_symbol(sc, "continuation", 12), make_integer(sc, sc->continuations->loc), make_integer(sc, sc->continuations->size)), + list_3(sc, make_symbol(sc, "c-object", 8), make_integer(sc, sc->c_objects->loc), make_integer(sc, sc->c_objects->size)), + list_3(sc, sc->gensym_symbol, make_integer(sc, sc->gensyms->loc), make_integer(sc, sc->gensyms->size)), + list_3(sc, make_symbol(sc, "undefined", 9), make_integer(sc, sc->undefineds->loc), make_integer(sc, sc->undefineds->size)), + list_3(sc, make_symbol(sc, "weak-ref", 8), make_integer(sc, sc->weak_refs->loc), make_integer(sc, sc->weak_refs->size)), + list_3(sc, make_symbol(sc, "weak-hash-iter", 14),make_integer(sc, sc->weak_hash_iterators->loc), make_integer(sc, sc->weak_hash_iterators->size)), + list_3(sc, make_symbol(sc, "opt1-func", 9), make_integer(sc, sc->opt1_funcs->loc), make_integer(sc, sc->opt1_funcs->size))))); +} + +/* handling all *s7* fields via fallbacks lets us use direct field accesses in the rest of s7, and avoids + * using ca 100 cells for the let slots/values. We would need the fallbacks anyway for 'files et al. + * Since most of the fields need special setters, it's actually less code this way. See old/s7-let-s7.c. + */ + +#if (!_WIN32) /* (!MS_WINDOWS) */ + #include +#endif + +static s7_pointer memory_usage(s7_scheme *sc) +{ + s7_int i, k, len, in_use = 0, vlen = 0, flen = 0, ilen = 0, blen = 0, hlen = 0; + gc_list_t *gp; + s7_int ts[NUM_TYPES]; + +#if (!_WIN32) /* (!MS_WINDOWS) */ + struct rusage info; + struct timeval ut; +#endif + + s7_pointer mu_let = s7_inlet(sc, sc->nil); + s7_int gc_loc = gc_protect_1(sc, mu_let); + +#if (!_WIN32) /* (!MS_WINDOWS) */ + getrusage(RUSAGE_SELF, &info); + ut = info.ru_utime; + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "process-time", 12), make_real(sc, ut.tv_sec + (floor(ut.tv_usec / 1000.0) / 1000.0))); +#ifdef __APPLE__ + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "process-resident-size", 21), kmg(sc, info.ru_maxrss)); + /* apple docs say this is in kilobytes, but apparently that is an error */ +#else + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "process-resident-size", 21), kmg(sc, info.ru_maxrss * 1024)); + /* why does this number sometimes have no relation to RES in top? */ +#endif + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "IO", 2), cons(sc, make_integer(sc, info.ru_inblock), make_integer(sc, info.ru_oublock))); +#endif + + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "rootlet-size", 12), make_integer(sc, sc->rootlet_entries)); + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "heap-size", 9), + cons(sc, make_integer(sc, sc->heap_size), kmg(sc, sc->heap_size * (sizeof(s7_cell) + 2 * sizeof(s7_pointer))))); + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "cell-size", 9), make_integer(sc, sizeof(s7_cell))); + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "gc-total-freed", 14), make_integer(sc, sc->gc_total_freed)); + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "gc-total-time", 13), make_real(sc, (double)(sc->gc_total_time) / ticks_per_second())); + + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "small_ints", 10), + cons(sc, make_integer(sc, NUM_SMALL_INTS), kmg(sc, NUM_SMALL_INTS * sizeof(s7_cell)))); + + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "permanent-cells", 15), + cons(sc, make_integer(sc, sc->semipermanent_cells), kmg(sc, sc->semipermanent_cells * sizeof(s7_cell)))); + i = 0; + for (gc_obj_t *g = sc->semipermanent_objects; g; i++, g = (gc_obj_t *)(g->nxt)); + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "permanent_objects", 17), make_integer(sc, i)); + i = 0; + for (gc_obj_t *g = sc->semipermanent_lets; g; i++, g = (gc_obj_t *)(g->nxt)); + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "permanent_lets", 14), make_integer(sc, i)); + + /* show how many active cells there are of each type (this is where all the memory_usage cpu time goes) */ + for (i = 0; i < NUM_TYPES; i++) ts[i] = 0; + for (k = 0; k < sc->heap_size; k++) + ts[unchecked_type(sc->heap[k])]++; + sc->w = sc->nil; + for (i = 0; i < NUM_TYPES; i++) + { + if (i > 0) in_use += ts[i]; + if (ts[i] > 50) + sc->w = cons_unchecked(sc, cons(sc, make_symbol_with_strlen(sc, (i == 0) ? "free" : type_name_from_type(i, NO_ARTICLE)), + make_integer(sc, ts[i])), sc->w); + } + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "cells-in-use/free", 17), + cons(sc, make_integer(sc, in_use), make_integer(sc, sc->free_heap_top - sc->free_heap))); + if (is_pair(sc->w)) + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "types", 5), proper_list_reverse_in_place(sc, sc->w)); + sc->w = sc->unused; + /* same for semipermanent cells requires traversing saved_pointers and the alloc and big_alloc blocks up to alloc_k, or keeping explicit counts */ + + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "gc-protected-objects", 20), + cons(sc, make_integer(sc, sc->protected_objects_size - sc->protected_objects_free_list_loc), + make_integer(sc, sc->protected_objects_size))); + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "setters", 7), make_integer(sc, sc->protected_setters_loc)); + + add_symbol_table(sc, mu_let); + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "stack", 5), cons(sc, make_integer(sc, stack_top(sc)), make_integer(sc, sc->stack_size))); + + len = sc->autoload_names_top * (sizeof(const char **) + sizeof(s7_int) + sizeof(bool)); + for (i = 0; i < sc->autoload_names_loc; i++) len += sc->autoload_names_sizes[i]; + add_slot_unchecked_with_id(sc, mu_let, sc->autoload_symbol, make_integer(sc, len)); + + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "circle_info", 11), + make_integer(sc, sc->circle_info->size * (sizeof(s7_pointer) + sizeof(int32_t) + sizeof(bool)))); + + add_gc_list_sizes(sc, mu_let); + + /* strings */ + gp = sc->strings; + for (len = 0, i = 0; i < (int32_t)(gp->loc); i++) + len += string_length(gp->list[i]); + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "strings", 7), cons(sc, make_integer(sc, gp->loc), make_integer(sc, len))); + + /* vectors */ + for (k = 0, gp = sc->vectors; k < 2; k++, gp = sc->multivectors) + for (i = 0; i < gp->loc; i++) + { + s7_pointer v = gp->list[i]; + if (is_float_vector(v)) + flen += vector_length(v); + else + if (is_int_vector(v)) + ilen += vector_length(v); + else + if (is_byte_vector(v)) + blen += vector_length(v); + else vlen += vector_length(v); + } + add_slot_unchecked_with_id(sc, mu_let, + make_symbol(sc, "vectors", 7), + s7_list(sc, 9, + make_integer(sc, sc->vectors->loc + sc->multivectors->loc), + make_symbol(sc, "vlen", 4), make_integer(sc, vlen), + make_symbol(sc, "fvlen", 5), make_integer(sc, flen), + make_symbol(sc, "ivlen", 5), make_integer(sc, ilen), + make_symbol(sc, "bvlen", 5), make_integer(sc, blen))); + /* hash-tables */ + for (i = 0, gp = sc->hash_tables; i < gp->loc; i++) + { + s7_pointer v = gp->list[i]; + hlen += ((hash_table_mask(v) + 1) * sizeof(hash_entry_t *)); + hlen += (hash_table_entries(v) * sizeof(hash_entry_t)); + } + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "hash-tables", 11), + cons(sc, make_integer(sc, sc->hash_tables->loc), make_integer(sc, hlen))); + + /* ports */ + gp = sc->input_ports; + for (i = 0, len = 0; i < gp->loc; i++) + { + s7_pointer v = gp->list[i]; + if (port_data(v)) len += port_data_size(v); + } + gp = sc->input_string_ports; + for (i = 0, len = 0; i < gp->loc; i++) + { + s7_pointer v = gp->list[i]; + if (port_data(v)) len += port_data_size(v); + } + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "input-ports", 11), + cons(sc, make_integer(sc, sc->input_ports->loc + sc->input_string_ports->loc), make_integer(sc, len))); + gp = sc->output_ports; + for (i = 0, len = 0; i < gp->loc; i++) + { + s7_pointer v = gp->list[i]; + if (port_data(v)) len += port_data_size(v); + } + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "output-ports", 12), + cons(sc, make_integer(sc, sc->output_ports->loc), make_integer(sc, len))); + + i = 0; + for (s7_pointer p = sc->format_ports; p; i++, p = (s7_pointer)port_next(p)); + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "format-ports", 12), make_integer(sc, i)); + + /* continuations (sketchy!) */ + gp = sc->continuations; + for (i = 0, len = 0; i < gp->loc; i++) + if (is_continuation(gp->list[i])) + len += continuation_stack_size(gp->list[i]); + if (len > 0) + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "continuations", 13), + cons(sc, make_integer(sc, sc->continuations->loc), make_integer(sc, len * sizeof(s7_pointer)))); + /* c-objects */ + if (sc->c_objects->loc > 0) + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "c-objects", 9), make_integer(sc, sc->c_objects->loc)); + if (sc->num_c_object_types > 0) + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "c-types", 7), + cons(sc, make_integer(sc, sc->num_c_object_types), + make_integer(sc, (sc->c_object_types_size * sizeof(c_object_t *)) + (sc->num_c_object_types * sizeof(c_object_t))))); + /* we're ignoring c_type->scheme_name: make_permanent_string(sc, name) */ +#if WITH_GMP + add_slot_unchecked_with_id(sc, mu_let, + make_symbol(sc, "bignums", 7), + s7_list(sc, 5, make_integer(sc, sc->big_integers->loc), make_integer(sc, sc->big_ratios->loc), + make_integer(sc, sc->big_reals->loc), make_integer(sc, sc->big_complexes->loc), + make_integer(sc, sc->big_random_states->loc))); +#endif + /* free-lists (mallocate) */ + { + block_t *b; + for (i = 0, len = 0, sc->w = sc->nil; i < TOP_BLOCK_LIST; i++) + { + for (b = sc->block_lists[i], k = 0; b; b = block_next(b), k++); + sc->w = cons(sc, make_integer(sc, k), sc->w); + len += ((sizeof(block_t) + (1LL << i)) * k); + } + for (b = sc->block_lists[TOP_BLOCK_LIST], k = 0; b; b = block_next(b), k++) + len += (sizeof(block_t) + block_size(b)); + sc->w = cons(sc, make_integer(sc, k), sc->w); + add_slot_unchecked_with_id(sc, mu_let, make_symbol(sc, "free-lists", 10), + list_2(sc, cons(sc, make_symbol(sc, "bytes", 5), kmg(sc, len)), + cons(sc, make_symbol(sc, "bins", 4), proper_list_reverse_in_place(sc, sc->w)))); + sc->w = sc->unused; + add_slot_unchecked_with_id(sc, mu_let, + make_symbol(sc, "approximate-s7-size", 19), + kmg(sc, ((sc->semipermanent_cells + NUM_SMALL_INTS + sc->heap_size) * sizeof(s7_cell)) + + ((2 * sc->heap_size + SYMBOL_TABLE_SIZE + sc->stack_size) * sizeof(s7_pointer)) + + len + hlen + (vlen * sizeof(s7_pointer)) + (flen * sizeof(s7_double)) + (ilen * sizeof(s7_int)) + blen)); + } + s7_gc_unprotect_at(sc, gc_loc); + return(mu_let); +} + +static s7_pointer sl_c_types(s7_scheme *sc) +{ + s7_pointer res; + sc->w = sc->nil; + for (int32_t i = 0; i < sc->num_c_object_types; i++) /* c-object type (tag) is i */ + sc->w = cons(sc, sc->c_object_types[i]->scheme_name, sc->w); + res = proper_list_reverse_in_place(sc, sc->w); /* so car(types) has tag 0 */ + sc->w = sc->unused; + return(res); +} + +static s7_pointer sl_file_names(s7_scheme *sc) +{ + s7_pointer p; + sc->w = sc->nil; + for (int32_t i = 0; i <= sc->file_names_top; i++) + sc->w = cons(sc, sc->file_names[i], sc->w); + p = proper_list_reverse_in_place(sc, sc->w); + sc->w = sc->unused; + return(p); +} + +static s7_pointer sl_int_fixup(s7_scheme *sc, s7_pointer val) +{ +#if WITH_GMP + return(s7_int_to_big_integer(sc, s7_integer_clamped_if_gmp(sc, val))); +#else + return(val); +#endif +} + +static s7_pointer sl_history(s7_scheme *sc) +{ +#if WITH_HISTORY + return(cull_history(sc, (sc->cur_code == sc->history_sink) ? sc->old_cur_code : sc->cur_code)); +#else + return(sc->cur_code); +#endif +} + +static s7_pointer sl_active_catches(s7_scheme *sc) +{ + s7_pointer lst = sc->nil; + for (int64_t i = stack_top(sc) - 1; i >= 3; i -= 4) + switch (stack_op(sc->stack, i)) + { + case OP_CATCH_ALL: + lst = cons(sc, sc->T, lst); + break; + case OP_CATCH_2: case OP_CATCH_1: case OP_CATCH: + lst = cons(sc, catch_tag(T_Cat(stack_code(sc->stack, i))), lst); + break; + } + return(reverse_in_place_unchecked(sc, sc->nil, lst)); +} + +static s7_pointer sl_stack_entries(s7_scheme *sc, s7_pointer stack, int64_t top) +{ + s7_pointer lst = sc->nil; /* the stack can contain anything (like #): this is a dangerous function */ + for (int64_t i = top - 1; i >= 3; i -= 4) + { + s7_pointer func = stack_code(stack, i), args = stack_args(stack, i), e = stack_let(stack, i); + opcode_t op = stack_op(stack, i); + s7_pointer entry = sc->nil; + if (s7_is_valid(sc, e)) entry = cons(sc, e, entry); + if (s7_is_valid(sc, args)) entry = cons_unchecked(sc, args, entry); + if (s7_is_valid(sc, func)) entry = cons_unchecked(sc, func, entry); + if ((op >= 0) && (op < NUM_OPS)) entry = cons_unchecked(sc, make_symbol_with_strlen(sc, op_names[op]), entry); + lst = cons_unchecked(sc, entry, lst); + sc->w = lst; + } + sc->w = sc->unused; + return(reverse_in_place_unchecked(sc, sc->nil, lst)); +} + +static s7_pointer sl_protected_objects(s7_scheme *sc) +{ + s7_pointer nv = s7_vector_copy(sc, sc->protected_objects); + s7_pointer *vals = vector_elements(nv); + s7_int len = vector_length(nv); + for (s7_int i = 0; i < len; i++) + if (vals[i] == sc->unused) + vals[i] = sc->F; + return(nv); +} + +static s7_pointer s7_starlet(s7_scheme *sc, s7_int choice) +{ + switch (choice) + { + case SL_ACCEPT_ALL_KEYWORD_ARGUMENTS: return(make_boolean(sc, sc->accept_all_keyword_arguments)); + case SL_AUTOLOADING: return(s7_make_boolean(sc, sc->is_autoloading)); + case SL_BIGNUM_PRECISION: return(make_integer(sc, sc->bignum_precision)); + case SL_CATCHES: return(sl_active_catches(sc)); + case SL_CPU_TIME: return(s7_make_real(sc, (double)clock() / (double)CLOCKS_PER_SEC)); /* cpu, not wall-clock time */ + case SL_C_TYPES: return(sl_c_types(sc)); + case SL_DEBUG: return(make_integer(sc, sc->debug)); + case SL_DEFAULT_HASH_TABLE_LENGTH: return(make_integer(sc, sc->default_hash_table_length)); + case SL_DEFAULT_RANDOM_STATE: return(sc->default_random_state); + case SL_DEFAULT_RATIONALIZE_ERROR: return(make_real(sc, sc->default_rationalize_error)); + case SL_EQUIVALENT_FLOAT_EPSILON: return(s7_make_real(sc, sc->equivalent_float_epsilon)); + case SL_EXPANSIONS: return(s7_make_boolean(sc, sc->is_expanding)); + case SL_FILE_NAMES: case SL_FILENAMES: return(sl_file_names(sc)); + case SL_FLOAT_FORMAT_PRECISION: return(make_integer(sc, sc->float_format_precision)); + case SL_FREE_HEAP_SIZE: return(make_integer(sc, sc->free_heap_top - sc->free_heap)); + case SL_GC_FREED: return(make_integer(sc, sc->gc_freed)); + case SL_GC_INFO: return(list_3(sc, make_integer(sc, sc->gc_calls), make_integer(sc, sc->gc_total_time), make_integer(sc, ticks_per_second()))); + case SL_GC_PROTECTED_OBJECTS: return(sl_protected_objects(sc)); + case SL_GC_RESIZE_HEAP_BY_4_FRACTION: return(make_real(sc, sc->gc_resize_heap_by_4_fraction)); + case SL_GC_RESIZE_HEAP_FRACTION: return(make_real(sc, sc->gc_resize_heap_fraction)); + case SL_GC_STATS: return(make_integer(sc, sc->gc_stats)); + case SL_GC_TEMPS_SIZE: return(make_integer(sc, sc->gc_temps_size)); + case SL_GC_TOTAL_FREED: return(make_integer(sc, sc->gc_total_freed)); + case SL_HASH_TABLE_FLOAT_EPSILON: return(s7_make_real(sc, sc->hash_table_float_epsilon)); + case SL_HEAP_SIZE: return(make_integer(sc, sc->heap_size)); + case SL_HISTORY: return(sl_history(sc)); + case SL_HISTORY_ENABLED: return(s7_make_boolean(sc, s7_history_enabled(sc))); + case SL_HISTORY_SIZE: return(make_integer(sc, sc->history_size)); + case SL_INITIAL_STRING_PORT_LENGTH: return(make_integer(sc, sc->initial_string_port_length)); + case SL_MAJOR_VERSION: return(make_integer(sc, S7_MAJOR_VERSION)); + case SL_MINOR_VERSION: return(make_integer(sc, S7_MINOR_VERSION)); + case SL_MAX_FORMAT_LENGTH: return(make_integer(sc, sc->max_format_length)); + case SL_MAX_HEAP_SIZE: return(make_integer(sc, sc->max_heap_size)); + case SL_MAX_LIST_LENGTH: return(make_integer(sc, sc->max_list_length)); + case SL_MAX_PORT_DATA_SIZE: return(make_integer(sc, sc->max_port_data_size)); + case SL_MAX_STACK_SIZE: return(make_integer(sc, sc->max_stack_size)); + case SL_MAX_STRING_LENGTH: return(make_integer(sc, sc->max_string_length)); + case SL_MAX_VECTOR_DIMENSIONS: return(make_integer(sc, sc->max_vector_dimensions)); + case SL_MAX_VECTOR_LENGTH: return(make_integer(sc, sc->max_vector_length)); + case SL_MEMORY_USAGE: return(memory_usage(sc)); + case SL_MOST_NEGATIVE_FIXNUM: return(sl_int_fixup(sc, leastfix)); + case SL_MOST_POSITIVE_FIXNUM: return(sl_int_fixup(sc, mostfix)); + case SL_MUFFLE_WARNINGS: return(s7_make_boolean(sc, sc->muffle_warnings)); + case SL_NUMBER_SEPARATOR: return(chars[(int)(sc->number_separator)]); + case SL_OPENLETS: return(s7_make_boolean(sc, sc->has_openlets)); + case SL_OUTPUT_PORT_DATA_SIZE: return(make_integer(sc, sc->output_port_data_size)); + case SL_PRINT_LENGTH: return(make_integer(sc, sc->print_length)); + case SL_PROFILE: return(make_integer(sc, sc->profile)); + case SL_PROFILE_INFO: return(profile_info_out(sc)); + case SL_PROFILE_PREFIX: return(sc->profile_prefix); + case SL_ROOTLET_SIZE: return(make_integer(sc, sc->rootlet_entries)); + case SL_SAFETY: return(make_integer(sc, sc->safety)); + case SL_STACK: return(sl_stack_entries(sc, sc->stack, stack_top(sc))); + case SL_STACKTRACE_DEFAULTS: return(copy_proper_list(sc, sc->stacktrace_defaults)); /* if not copied, we can set! entries directly to garbage */ + case SL_STACK_SIZE: return(make_integer(sc, sc->stack_size)); + case SL_STACK_TOP: return(make_integer(sc, (sc->stack_end - sc->stack_start) / 4)); + case SL_UNDEFINED_CONSTANT_WARNINGS: return(s7_make_boolean(sc, sc->undefined_constant_warnings)); + case SL_UNDEFINED_IDENTIFIER_WARNINGS: return(s7_make_boolean(sc, sc->undefined_identifier_warnings)); + case SL_VERSION: return(s7_make_string(sc, "s7 " S7_VERSION ", " S7_DATE)); + } + return(sc->undefined); +} + +s7_pointer s7_starlet_ref(s7_scheme *sc, s7_pointer sym) /* s7.h, not used here */ +{ + if (is_symbol(sym)) + { + if (is_keyword(sym)) + sym = keyword_symbol(sym); + if (s7_starlet_symbol(sym) != SL_NO_FIELD) + return(s7_starlet(sc, s7_starlet_symbol(sym))); + } + return(sc->undefined); +} + +s7_pointer s7_let_field_ref(s7_scheme *sc, s7_pointer sym) {return(s7_starlet_ref(sc, sym));} + +static s7_pointer g_s7_starlet_ref_fallback(s7_scheme *sc, s7_pointer args) +{ + s7_pointer sym = cadr(args); + if (!is_symbol(sym)) + sole_arg_wrong_type_error_nr(sc, sc->let_ref_symbol, sym, sc->type_names[T_SYMBOL]); + if (is_keyword(sym)) + sym = keyword_symbol(sym); + return(s7_starlet(sc, s7_starlet_symbol(sym))); +} + +static s7_pointer s7_starlet_iterate(s7_scheme *sc, s7_pointer iterator) +{ + s7_pointer symbol, value; + iterator_position(iterator)++; + if (iterator_position(iterator) >= SL_NUM_FIELDS) + return(iterator_quit(iterator)); + symbol = make_symbol_with_strlen(sc, s7_starlet_names[iterator_position(iterator)]); + + if ((iterator_position(iterator) == SL_STACK) || + (iterator_position(iterator) == SL_GC_PROTECTED_OBJECTS) || + (iterator_position(iterator) == SL_MEMORY_USAGE)) + value = sc->F; /* (format #f "~W" (inlet *s7*)) or (let->list *s7*) etc */ + else + { + s7_pointer osw = sc->w; /* protect against s7_starlet list making */ + value = s7_starlet(sc, s7_starlet_symbol(symbol)); + sc->w = osw; + } + if (iterator_let_cons(iterator)) + { + s7_pointer p = iterator_let_cons(iterator); + set_car(p, symbol); + set_cdr(p, value); + return(p); + } + return(cons(sc, symbol, value)); +} + +static s7_pointer s7_starlet_make_iterator(s7_scheme *sc, s7_pointer iter) +{ + iterator_position(iter) = SL_NO_FIELD; + iterator_next(iter) = s7_starlet_iterate; + iterator_let_cons(iter) = NULL; + return(iter); +} + +static s7_pointer sl_real_geq_0(s7_scheme *sc, s7_pointer sym, s7_pointer val) +{ + if (!is_real(val)) s7_starlet_wrong_type_error_nr(sc, sym, val, sc->type_names[T_REAL]); + if (s7_real(val) < 0.0) s7_starlet_out_of_range_error_nr(sc, sym, val, wrap_string(sc, "it should not be negative", 25)); + return(val); +} + +static s7_pointer sl_integer_gt_0(s7_scheme *sc, s7_pointer sym, s7_pointer val) +{ + if (!s7_is_integer(val)) s7_starlet_wrong_type_error_nr(sc, sym, val, sc->type_names[T_INTEGER]); + if (s7_integer_clamped_if_gmp(sc, val) <= 0) s7_starlet_out_of_range_error_nr(sc, sym, val, wrap_string(sc, "it should be positive", 21)); + return(val); +} + +static s7_pointer sl_integer_geq_0(s7_scheme *sc, s7_pointer sym, s7_pointer val) +{ + if (!s7_is_integer(val)) s7_starlet_wrong_type_error_nr(sc, sym, val, sc->type_names[T_INTEGER]); + if (s7_integer_clamped_if_gmp(sc, val) < 0) s7_starlet_out_of_range_error_nr(sc, sym, val, wrap_string(sc, "it should not be negative", 25)); + return(val); +} + +#if WITH_HISTORY +static void sl_set_history_size(s7_scheme *sc, s7_int iv) +{ + s7_pointer p1, p2; + if (iv > MAX_HISTORY_SIZE) iv = MAX_HISTORY_SIZE; + if (iv > sc->true_history_size) + { + /* splice in the new cells, reattach the circles */ + s7_pointer p3; + s7_pointer next1 = cdr(sc->eval_history1); + s7_pointer next2 = cdr(sc->eval_history2); + s7_pointer next3 = cdr(sc->history_pairs); + set_cdr(sc->eval_history1, semipermanent_list(sc, iv - sc->true_history_size)); + set_cdr(sc->eval_history2, semipermanent_list(sc, iv - sc->true_history_size)); + set_cdr(sc->history_pairs, semipermanent_list(sc, iv - sc->true_history_size)); + for (p3 = cdr(sc->history_pairs); is_pair(cdr(p3)); p3 = cdr(p3)) set_car(p3, semipermanent_list(sc, 1)); + set_car(p3, semipermanent_list(sc, 1)); + set_cdr(p3, next3); + for (p1 = sc->eval_history1, p2 = sc->eval_history2; is_pair(cdr(p1)); p1 = cdr(p1), p2 = cdr(p2)); + set_cdr(p1, next1); + set_cdr(p2, next2); + sc->true_history_size = iv; + } + sc->history_size = iv; + /* clear out both buffers to avoid GC confusion */ + for (p1 = sc->eval_history1, p2 = sc->eval_history2; ; p2 = cdr(p2)) + { + set_car(p1, sc->nil); + set_car(p2, sc->nil); + p1 = cdr(p1); + if (p1 == sc->eval_history1) break; + } +} +#endif + +#if WITH_GMP +static s7_pointer set_bignum_precision(s7_scheme *sc, int32_t precision) +{ + mp_prec_t bits = (mp_prec_t)precision; + s7_pointer bpi; + if (precision <= 1) /* (set! (*s7* 'bignum-precision) 1) causes mpfr to segfault! (also 0 and -1) */ + sole_arg_out_of_range_error_nr(sc, wrap_string(sc, "set! (*s7* 'bignum-precision)", 29), wrap_integer(sc, precision), wrap_string(sc, "has to be greater than 1", 24)); + mpfr_set_default_prec(bits); + mpc_set_default_precision(bits); + bpi = big_pi(sc); + global_slot(sc->pi_symbol)->object.slt.val = bpi; /* don't check immutable flag here (if debugging) -- i.e. don't use slot_set_value! */ + slot_set_value(initial_slot(sc->pi_symbol), bpi); /* if #_pi occurs after precision set, make sure #_pi is still legit (not a free cell) */ + return(sc->F); +} +#endif + +static s7_pointer sl_set_stacktrace_defaults(s7_scheme *sc, s7_pointer sym, s7_pointer val) +{ + if (!is_pair(val)) + s7_starlet_wrong_type_error_nr(sc, sym, val, sc->type_names[T_PAIR]); + if (s7_list_length(sc, val) != 5) + s7_starlet_wrong_type_error_nr(sc, sym, val, wrap_string(sc, "a list with 5 entries", 21)); + if (!is_t_integer(car(val))) + sl_stacktrace_wrong_type_error_nr(sc, sym, 1, car(val), wrap_string(sc, "an integer (stack frames)", 25), val); + if (!is_t_integer(cadr(val))) + sl_stacktrace_wrong_type_error_nr(sc, sym, 2, cadr(val), wrap_string(sc, "an integer (cols-for-data)", 26), val); + if (!is_t_integer(caddr(val))) + sl_stacktrace_wrong_type_error_nr(sc, sym, 3, caddr(val), wrap_string(sc, "an integer (line length)", 24), val); + if (!is_t_integer(cadddr(val))) + sl_stacktrace_wrong_type_error_nr(sc, sym, 4, cadddr(val), wrap_string(sc, "an integer (comment position)", 29), val); + if (!is_boolean(s7_list_ref(sc, val, 4))) + sl_stacktrace_wrong_type_error_nr(sc, sym, 5, s7_list_ref(sc, val, 4), wrap_string(sc, "a boolean (treat-data-as-comment)", 33), val); + sc->stacktrace_defaults = copy_proper_list(sc, val); + return(val); +} + +static s7_pointer sl_set_gc_stats(s7_scheme *sc, s7_pointer sym, s7_pointer val) +{ + if (is_boolean(val)) + { + sc->gc_stats = ((val == sc->T) ? GC_STATS : 0); + return(val); + } + if (!s7_is_integer(val)) + s7_starlet_wrong_type_error_nr(sc, sym, val, sc->type_names[T_BOOLEAN]); + sc->gc_stats = s7_integer_clamped_if_gmp(sc, val); + if (sc->gc_stats >= 16) /* gc_stats is uint32_t */ + { + sc->gc_stats = 0; + s7_starlet_out_of_range_error_nr(sc, sym, val, wrap_string(sc, "it should be between 0 and 15", 29)); + } + return(val); +} + +static s7_pointer sl_set_gc_info(s7_scheme *sc, s7_pointer sym, s7_pointer val) /* ticks_per_second is not settable */ +{ + if (val == sc->F) + { + sc->gc_total_time = 0; + sc->gc_calls = 0; + } + else + if ((is_pair(val)) && (s7_is_integer(car(val))) && + (is_pair(cdr(val))) && (s7_is_integer(cadr(val)))) /* caddr is ticks_per_second which can't sensibly be set */ + { + sc->gc_total_time = s7_integer(car(val)); + sc->gc_calls = s7_integer(cadr(val)); + } + else s7_starlet_wrong_type_error_nr(sc, sym, val, wrap_string(sc, "#f or a list of two or three integers (the third is ignored)", 60)); + return(sc->F); +} + +static s7_pointer sl_set_profile(s7_scheme *sc, s7_pointer sym, s7_pointer val) +{ + if (!s7_is_integer(val)) + s7_starlet_wrong_type_error_nr(sc, sym, val, sc->type_names[T_INTEGER]); + sc->profile = s7_integer_clamped_if_gmp(sc, val); + sc->debug_or_profile = ((sc->debug > 1) || (sc->profile > 0)); + if (sc->profile > 0) + { + if (!is_memq(make_symbol(sc, "profile.scm", 11), s7_symbol_value(sc, sc->features_symbol))) + s7_load(sc, "profile.scm"); + if (!sc->profile_data) + make_profile_info(sc); + if (!sc->profile_out) + sc->profile_out = s7_make_function(sc, "profile-out", g_profile_out, 2, 0, false, NULL); + } + return(val); +} + +static s7_pointer sl_set_debug(s7_scheme *sc, s7_pointer sym, s7_pointer val) +{ + if (!s7_is_integer(val)) + s7_starlet_wrong_type_error_nr(sc, sym, val, sc->type_names[T_INTEGER]); + sc->debug = s7_integer_clamped_if_gmp(sc, val); + sc->debug_or_profile = ((sc->debug > 1) || (sc->profile > 0)); + if ((sc->debug > 0) && + (!is_memq(make_symbol(sc, "debug.scm", 9), s7_symbol_value(sc, sc->features_symbol)))) + s7_load(sc, "debug.scm"); + return(val); +} + +static s7_pointer sl_set_number_separator(s7_scheme *sc, s7_pointer sym, s7_pointer val) +{ +#if (!WITH_NUMBER_SEPARATOR) + s7_warn(sc, 128, "(set! (*s7* 'number-separator) ...) but number-separator is not included in this s7"); +#endif + if (!is_character(val)) + s7_starlet_wrong_type_error_nr(sc, sym, val, sc->type_names[T_CHARACTER]); + if ((is_char_numeric(val)) || (is_char_whitespace(val)) || (!t_number_separator_p[character(val)]) || + (character(val) == 'i') || (character(val) == 'e') || (character(val) == 'E')) + /* I guess +nan.0 and +inf.0 are not numeric literals, so we don't need to catch +n_a_n.0 */ + s7_starlet_wrong_type_error_nr(sc, sym, val, wrap_string(sc, "a printing, non-numeric character", 33)); + sc->number_separator = character(val); + return(val); +} + +static s7_pointer sl_set_bignum_precision(s7_scheme *sc, s7_pointer sym, s7_pointer val) +{ + s7_int iv; + iv = s7_integer_clamped_if_gmp(sc, sl_integer_gt_0(sc, sym, val)); + sc->bignum_precision = iv; +#if WITH_GMP + set_bignum_precision(sc, sc->bignum_precision); + mpfr_set_prec(sc->mpfr_1, sc->bignum_precision); + mpfr_set_prec(sc->mpfr_2, sc->bignum_precision); + mpc_set_prec(sc->mpc_1, sc->bignum_precision); + mpc_set_prec(sc->mpc_2, sc->bignum_precision); +#endif + return(val); +} + +static noreturn void sl_unsettable_error_nr(s7_scheme *sc, s7_pointer sym) +{ + error_nr(sc, sc->immutable_error_symbol, set_elist_2(sc, wrap_string(sc, "can't set (*s7* '~S)", 20), sym)); +} + +static s7_pointer s7_starlet_set_1(s7_scheme *sc, s7_pointer sym, s7_pointer val) +{ + s7_int iv; + + if ((S7_DEBUGGING) && (!is_symbol(sym))) + { + fprintf(stderr, "%s: %s\n", __func__, display(sym)); + sole_arg_wrong_type_error_nr(sc, sc->let_set_symbol, sym, sc->type_names[T_SYMBOL]); + } + if (is_keyword(sym)) + sym = keyword_symbol(sym); + + switch (s7_starlet_symbol(sym)) + { + case SL_ACCEPT_ALL_KEYWORD_ARGUMENTS: + if (!is_boolean(val)) s7_starlet_wrong_type_error_nr(sc, sym, val, sc->type_names[T_BOOLEAN]); + sc->accept_all_keyword_arguments = s7_boolean(sc, val); + return(val); + + case SL_AUTOLOADING: + if (!is_boolean(val)) s7_starlet_wrong_type_error_nr(sc, sym, val, sc->type_names[T_BOOLEAN]); + sc->is_autoloading = s7_boolean(sc, val); + return(val); + + case SL_BIGNUM_PRECISION: + return(sl_set_bignum_precision(sc, sym, val)); + + case SL_CATCHES: + case SL_CPU_TIME: + case SL_C_TYPES: + sl_unsettable_error_nr(sc, sym); + + case SL_DEBUG: + return(sl_set_debug(sc, sym, val)); + + case SL_DEFAULT_HASH_TABLE_LENGTH: + sc->default_hash_table_length = s7_integer_clamped_if_gmp(sc, sl_integer_gt_0(sc, sym, val)); + return(val); + + case SL_DEFAULT_RANDOM_STATE: + if (!is_random_state(val)) s7_starlet_wrong_type_error_nr(sc, sym, val, sc->type_names[T_RANDOM_STATE]); +#if (!WITH_GMP) + random_seed(sc->default_random_state) = random_seed(val); + random_carry(sc->default_random_state) = random_carry(val); +#endif + return(val); + + case SL_DEFAULT_RATIONALIZE_ERROR: + sc->default_rationalize_error = s7_real(sl_real_geq_0(sc, sym, val)); + return(val); + + case SL_EQUIVALENT_FLOAT_EPSILON: + sc->equivalent_float_epsilon = s7_real(sl_real_geq_0(sc, sym, val)); + return(val); + + case SL_EXPANSIONS: + if (!is_boolean(val)) s7_starlet_wrong_type_error_nr(sc, sym, val, sc->type_names[T_BOOLEAN]); + sc->is_expanding = s7_boolean(sc, val); + return(val); + + case SL_FILE_NAMES: case SL_FILENAMES: sl_unsettable_error_nr(sc, sym); + + case SL_FLOAT_FORMAT_PRECISION: /* float-format-precision should not be huge => hangs in snprintf -- what's a reasonable limit here? */ + iv = s7_integer_clamped_if_gmp(sc, sl_integer_geq_0(sc, sym, val)); + sc->float_format_precision = (iv < MAX_FLOAT_FORMAT_PRECISION) ? iv : MAX_FLOAT_FORMAT_PRECISION; + return(val); + + case SL_FREE_HEAP_SIZE: + case SL_GC_FREED: + case SL_GC_TOTAL_FREED: + case SL_GC_PROTECTED_OBJECTS: + sl_unsettable_error_nr(sc, sym); + + case SL_GC_TEMPS_SIZE: + sc->gc_temps_size = s7_integer_clamped_if_gmp(sc, sl_integer_gt_0(sc, sym, val)); + return(val); + case SL_GC_RESIZE_HEAP_FRACTION: + sc->gc_resize_heap_fraction = s7_real(sl_real_geq_0(sc, sym, val)); + return(val); + case SL_GC_RESIZE_HEAP_BY_4_FRACTION: + sc->gc_resize_heap_by_4_fraction = s7_real(sl_real_geq_0(sc, sym, val)); + return(val); + case SL_GC_STATS: + return(sl_set_gc_stats(sc, sym, val)); + case SL_GC_INFO: + return(sl_set_gc_info(sc, sym, val)); + + case SL_HASH_TABLE_FLOAT_EPSILON: + sc->hash_table_float_epsilon = s7_real(sl_real_geq_0(sc, sym, val)); + return(val); + + case SL_HEAP_SIZE: + iv = s7_integer_clamped_if_gmp(sc, sl_integer_geq_0(sc, sym, val)); + if (iv > sc->heap_size) + resize_heap_to(sc, iv); + return(val); + + case SL_HISTORY: /* (set! (*s7* 'history) val) */ + replace_current_code(sc, val); + return(val); + + case SL_HISTORY_ENABLED: /* (set! (*s7* 'history-enabled) #f|#t) */ + if (!is_boolean(val)) s7_starlet_wrong_type_error_nr(sc, sym, val, sc->type_names[T_BOOLEAN]); + return(s7_make_boolean(sc, s7_set_history_enabled(sc, s7_boolean(sc, val)))); + + case SL_HISTORY_SIZE: + iv = s7_integer_clamped_if_gmp(sc, sl_integer_geq_0(sc, sym, val)); +#if WITH_HISTORY + sl_set_history_size(sc, iv); +#else + sc->history_size = iv; +#endif + return(val); + + case SL_INITIAL_STRING_PORT_LENGTH: sc->initial_string_port_length = s7_integer_clamped_if_gmp(sc, sl_integer_gt_0(sc, sym, val)); return(val); + + case SL_MAJOR_VERSION: + case SL_MINOR_VERSION: + sl_unsettable_error_nr(sc, sym); + + case SL_MAX_FORMAT_LENGTH: sc->max_format_length = s7_integer_clamped_if_gmp(sc, sl_integer_gt_0(sc, sym, val)); return(val); + case SL_MAX_HEAP_SIZE: sc->max_heap_size = s7_integer_clamped_if_gmp(sc, sl_integer_gt_0(sc, sym, val)); return(val); + case SL_MAX_LIST_LENGTH: sc->max_list_length = s7_integer_clamped_if_gmp(sc, sl_integer_gt_0(sc, sym, val)); return(val); + case SL_MAX_PORT_DATA_SIZE: sc->max_port_data_size = s7_integer_clamped_if_gmp(sc, sl_integer_gt_0(sc, sym, val)); return(val); + + case SL_MAX_STACK_SIZE: + iv = s7_integer_clamped_if_gmp(sc, sl_integer_geq_0(sc, sym, val)); + if (iv < INITIAL_STACK_SIZE) + s7_starlet_out_of_range_error_nr(sc, sym, val, wrap_string(sc, "it should be greater than the initial stack size", 48)); + sc->max_stack_size = (uint32_t)iv; + return(val); + + case SL_MAX_STRING_LENGTH: sc->max_string_length = s7_integer_clamped_if_gmp(sc, sl_integer_gt_0(sc, sym, val)); return(val); + case SL_MAX_VECTOR_DIMENSIONS: sc->max_vector_dimensions = s7_integer_clamped_if_gmp(sc, sl_integer_gt_0(sc, sym, val)); return(val); + case SL_MAX_VECTOR_LENGTH: sc->max_vector_length = s7_integer_clamped_if_gmp(sc, sl_integer_gt_0(sc, sym, val)); return(val); + + case SL_MEMORY_USAGE: + case SL_MOST_NEGATIVE_FIXNUM: + case SL_MOST_POSITIVE_FIXNUM: + sl_unsettable_error_nr(sc, sym); + + case SL_MUFFLE_WARNINGS: + if (!is_boolean(val)) s7_starlet_wrong_type_error_nr(sc, sym, val, sc->type_names[T_BOOLEAN]); + sc->muffle_warnings = s7_boolean(sc, val); + return(val); + + case SL_NUMBER_SEPARATOR: /* I think no PL uses the separator in output */ + return(sl_set_number_separator(sc, sym, val)); + + case SL_OPENLETS: + if (!is_boolean(val)) s7_starlet_wrong_type_error_nr(sc, sym, val, sc->type_names[T_BOOLEAN]); + sc->has_openlets = s7_boolean(sc, val); + return(val); + + case SL_OUTPUT_PORT_DATA_SIZE: + sc->output_port_data_size = s7_integer_clamped_if_gmp(sc, sl_integer_gt_0(sc, sym, val)); + return(val); + case SL_PRINT_LENGTH: + sc->print_length = s7_integer_clamped_if_gmp(sc, sl_integer_geq_0(sc, sym, val)); + return(val); + + case SL_PROFILE: + return(sl_set_profile(sc, sym, val)); + case SL_PROFILE_INFO: + if (val != sc->F) s7_starlet_wrong_type_error_nr(sc, sym, val, wrap_string(sc, "#f (to clear the table)", 23)); + return(clear_profile_info(sc)); + case SL_PROFILE_PREFIX: + if ((is_symbol(val)) || val == sc->F) {sc->profile_prefix = val; return(val);} + s7_starlet_wrong_type_error_nr(sc, sym, val, wrap_string(sc, "a symbol or #f", 14)); + + case SL_ROOTLET_SIZE: + sl_unsettable_error_nr(sc, sym); + + case SL_SAFETY: + if (!s7_is_integer(val)) + s7_starlet_wrong_type_error_nr(sc, sym, val, sc->type_names[T_INTEGER]); + if ((s7_integer_clamped_if_gmp(sc, val) > 2) || (s7_integer_clamped_if_gmp(sc, val) < -1)) + s7_starlet_out_of_range_error_nr(sc, sym, val, wrap_string(sc, "it should be between -1 (no safety) and 2 (max safety)", 54)); + sc->safety = s7_integer_clamped_if_gmp(sc, val); + return(val); + + case SL_STACKTRACE_DEFAULTS: + return(sl_set_stacktrace_defaults(sc, sym,val)); + + case SL_STACK: + case SL_STACK_SIZE: + case SL_STACK_TOP: + sl_unsettable_error_nr(sc, sym); + + case SL_UNDEFINED_CONSTANT_WARNINGS: + if (!is_boolean(val)) s7_starlet_wrong_type_error_nr(sc, sym, val, sc->type_names[T_BOOLEAN]); + sc->undefined_constant_warnings = s7_boolean(sc, val); + return(val); + + case SL_UNDEFINED_IDENTIFIER_WARNINGS: + if (!is_boolean(val)) s7_starlet_wrong_type_error_nr(sc, sym, val, sc->type_names[T_BOOLEAN]); + sc->undefined_identifier_warnings = s7_boolean(sc, val); + return(val); + + case SL_VERSION: + sl_unsettable_error_nr(sc, sym); + + default: + error_nr(sc, sc->out_of_range_symbol, + set_elist_2(sc, wrap_string(sc, "can't set (*s7* '~S); no such field in *s7*", 43), sym)); + } + return(sc->undefined); +} + +s7_pointer s7_starlet_set(s7_scheme *sc, s7_pointer sym, s7_pointer new_value) +{ + if (is_symbol(sym)) + { + if (is_keyword(sym)) + sym = keyword_symbol(sym); + if (s7_starlet_symbol(sym) != SL_NO_FIELD) + return(s7_starlet_set_1(sc, sym, new_value)); + } + return(sc->undefined); +} + +s7_pointer s7_let_field_set(s7_scheme *sc, s7_pointer sym, s7_pointer new_value) {return(s7_starlet_set(sc, sym, new_value));} + +static void init_s7_starlet_immutable_field(void) +{ + s7_starlet_immutable_field = (bool *)Calloc(SL_NUM_FIELDS, sizeof(bool)); + s7_starlet_immutable_field[SL_CATCHES] = true; + s7_starlet_immutable_field[SL_CPU_TIME] = true; + s7_starlet_immutable_field[SL_C_TYPES] = true; + s7_starlet_immutable_field[SL_FILE_NAMES] = true; + s7_starlet_immutable_field[SL_FILENAMES] = true; + s7_starlet_immutable_field[SL_FREE_HEAP_SIZE] = true; + s7_starlet_immutable_field[SL_GC_FREED] = true; + s7_starlet_immutable_field[SL_GC_TOTAL_FREED] = true; + s7_starlet_immutable_field[SL_GC_PROTECTED_OBJECTS] = true; + s7_starlet_immutable_field[SL_MEMORY_USAGE] = true; + s7_starlet_immutable_field[SL_MOST_NEGATIVE_FIXNUM] = true; + s7_starlet_immutable_field[SL_MOST_POSITIVE_FIXNUM] = true; + s7_starlet_immutable_field[SL_ROOTLET_SIZE] = true; + s7_starlet_immutable_field[SL_STACK] = true; + s7_starlet_immutable_field[SL_STACK_SIZE] = true; + s7_starlet_immutable_field[SL_STACK_TOP] = true; + s7_starlet_immutable_field[SL_VERSION] = true; + s7_starlet_immutable_field[SL_MAJOR_VERSION] = true; + s7_starlet_immutable_field[SL_MINOR_VERSION] = true; +} + + +/* ---------------- gdbinit annotated stacktrace ---------------- */ +#if (!MS_WINDOWS) +/* s7bt, s7btfull: gdb stacktrace decoding */ + +static const char *decoded_name(s7_scheme *sc, const s7_pointer p) +{ + if (p == sc->value) return("sc->value"); + if (p == sc->args) return("sc->args"); + if (p == sc->code) return("sc->code"); + if (p == sc->cur_code) return("sc->cur_code"); + if (p == sc->curlet) return("sc->curlet"); + if (p == sc->nil) return("()"); + if (p == sc->T) return("#t"); + if (p == sc->F) return("#f"); + if (p == eof_object) return("eof_object"); + if (p == sc->undefined) return("undefined"); + if (p == sc->unspecified) return("unspecified"); + if (p == sc->no_value) return("no_value"); + if (p == sc->unused) return("#"); + if (p == sc->symbol_table) return("symbol_table"); + if (p == sc->rootlet) return("rootlet"); + if (p == sc->s7_starlet) return("*s7*"); /* this is the function */ + if (p == sc->unlet) return("unlet"); + if (p == sc->owlet) return("owlet"); + if (p == sc->standard_input) return("*stdin*"); + if (p == sc->standard_output) return("*stdout*"); + if (p == sc->standard_error) return("*stderr*"); + if (p == sc->else_symbol) return("else_symbol"); + if (p == current_input_port(sc)) return("current-input-port"); + if (p == current_output_port(sc)) return("current-output-port"); + if (p == current_error_port(sc)) return("current-error_port"); + return((p == sc->stack) ? "stack" : NULL); +} + +static bool is_decodable(s7_scheme *sc, const s7_pointer p) +{ + int32_t i; + s7_pointer *tp = sc->heap; + s7_pointer *heap_top = (s7_pointer *)(sc->heap + sc->heap_size); + + /* check symbol-table */ + for (i = 0; i < SYMBOL_TABLE_SIZE; i++) + for (s7_pointer x = vector_element(sc->symbol_table, i); is_not_null(x); x = cdr(x)) + { + s7_pointer sym = car(x); + if ((sym == p) || + ((is_global(sym)) && (is_slot(global_slot(sym))) && (p == global_value(sym)))) + return(true); + } + + for (i = 0; i < NUM_CHARS; i++) if (p == chars[i]) return(true); + for (i = 0; i < NUM_SMALL_INTS; i++) if (p == small_ints[i]) return(true); + + /* check the heap */ + while (tp < heap_top) + if (p == (*tp++)) + return(true); + return(false); +} + +const char *s7_decode_bt(s7_scheme *sc); +const char *s7_decode_bt(s7_scheme *sc) +{ + FILE *fp = fopen("gdb.txt", "r"); + if (fp) + { + int64_t size; + size_t bytes; + bool in_quotes = false, old_stop = sc->stop_at_error; + uint8_t *bt; + block_t *bt_block; + + sc->stop_at_error = false; + fseek(fp, 0, SEEK_END); + size = ftell(fp); + rewind(fp); + + bt_block = mallocate(sc, (size + 1) * sizeof(uint8_t)); + bt = (uint8_t *)block_data(bt_block); + bytes = fread(bt, sizeof(uint8_t), size, fp); + if (bytes != (size_t)size) + { + fclose(fp); + liberate(sc, bt_block); + return(" oops "); + } + bt[size] = '\0'; + fclose(fp); + + for (int64_t i = 0; i < size; i++) + { + fputc(bt[i], stdout); + if ((bt[i] == '"') && ((i == 0) || (bt[i - 1] != '\\'))) + in_quotes = (!in_quotes); + else + if ((!in_quotes) && (i < size - 8) && + ((bt[i] == '=') && + (((bt[i + 1] == '0') && (bt[i + 2] == 'x')) || + ((bt[i + 1] == ' ') && (bt[i + 2] == '0') && (bt[i + 3] == 'x'))))) + { + void *vp; + int32_t vals = sscanf((const char *)(bt + i + 1), "%p", &vp); + if ((vp) && (vals == 1)) + { + int32_t k; + for (k = i + ((bt[i + 2] == 'x') ? 3 : 4); (k < size) && (is_digit(bt[k], 16)); k++); + if ((bt[k] != ' ') || (bt[k + 1] != '"')) + { + if (vp == (void *)sc) + { + if (bt[i + 1] == ' ') fputc(' ', stdout); + fprintf(stdout, "%s[s7]%s", bold_text, unbold_text); + i = k - 1; + } + else + { + s7_pointer p = (s7_pointer)vp; + const char *dname = decoded_name(sc, p); + if (dname) + { + if (bt[i + 1] == ' ') fputc(' ', stdout); + fprintf(stdout, "%s[%s]%s", bold_text, dname, unbold_text); + } + if ((dname) || (is_decodable(sc, p))) + { + if (bt[i + 1] == ' ') fputc(' ', stdout); + i = k - 1; + if (s7_is_valid(sc, p)) + { + s7_pointer strp = object_to_truncated_string(sc, p, 80); + if (dname) fprintf(stdout, " "); + fprintf(stdout, "%s%s%s", bold_text, string_value(strp), unbold_text); + if ((is_pair(p)) && + (has_location(p))) + { + uint32_t line = pair_line_number(p), file = pair_file_number(p); + if (line > 0) + fprintf(stdout, " %s(%s[%u])%s", bold_text, string_value(sc->file_names[file]), line, unbold_text); + }}}}}}}} + liberate(sc, bt_block); + sc->stop_at_error = old_stop; + } + return(""); +} +#endif + + +/* -------------------------------- initialization -------------------------------- */ +static void init_fx_function(void) +{ + fx_function = (s7_function *)Calloc(NUM_OPS, sizeof(s7_function)); + + fx_function[HOP_SAFE_C_NC] = fx_c_nc; + fx_function[HOP_SAFE_C_S] = fx_c_s; + fx_function[HOP_SAFE_C_SC] = fx_c_sc; + fx_function[HOP_SAFE_C_CS] = fx_c_cs; + fx_function[HOP_SAFE_C_CQ] = fx_c_cq; + fx_function[HOP_SAFE_C_FF] = fx_c_ff; + fx_function[HOP_SAFE_C_SS] = fx_c_ss; + fx_function[HOP_SAFE_C_opNCq] = fx_c_opncq; + fx_function[HOP_SAFE_C_opSq] = fx_c_opsq; + fx_function[HOP_SAFE_C_opSSq] = fx_c_opssq; + fx_function[HOP_SAFE_C_opSCq] = fx_c_opscq; + fx_function[HOP_SAFE_C_opCSq] = fx_c_opcsq; + fx_function[HOP_SAFE_C_opSq_S] = fx_c_opsq_s; + fx_function[HOP_SAFE_C_opSq_C] = fx_c_opsq_c; + fx_function[HOP_SAFE_C_opSq_CS] = fx_c_opsq_cs; + fx_function[HOP_SAFE_C_S_opSq] = fx_c_s_opsq; + fx_function[HOP_SAFE_C_C_opSq] = fx_c_c_opsq; + fx_function[HOP_SAFE_C_opCSq_C] = fx_c_opcsq_c; + fx_function[HOP_SAFE_C_opCSq_S] = fx_c_opcsq_s; + fx_function[HOP_SAFE_C_S_opCSq] = fx_c_s_opcsq; + fx_function[HOP_SAFE_C_opSSq_C] = fx_c_opssq_c; + fx_function[HOP_SAFE_C_opSCq_C] = fx_c_opscq_c; + fx_function[HOP_SAFE_C_opSSq_S] = fx_c_opssq_s; + fx_function[HOP_SAFE_C_S_opSSq] = fx_c_s_opssq; + fx_function[HOP_SAFE_C_C_opSSq] = fx_c_c_opssq; + fx_function[HOP_SAFE_C_S_opSCq] = fx_c_s_opscq; + fx_function[HOP_SAFE_C_C_opSCq] = fx_c_c_opscq; + fx_function[HOP_SAFE_C_opSq_opSq] = fx_c_opsq_opsq; + fx_function[HOP_SAFE_C_opSq_opSSq] = fx_c_opsq_opssq; + fx_function[HOP_SAFE_C_opSSq_opSq] = fx_c_opssq_opsq; + fx_function[HOP_SAFE_C_opSSq_opSSq] = fx_c_opssq_opssq; + fx_function[HOP_SAFE_C_op_opSqq] = fx_c_op_opsqq; + fx_function[HOP_SAFE_C_op_S_opSqq] = fx_c_op_s_opsqq; + fx_function[HOP_SAFE_C_op_opSq_Sq] = fx_c_op_opsq_sq; + fx_function[HOP_SAFE_C_op_opSSqq_S] = fx_c_op_opssqq_s; + + fx_function[HOP_SAFE_C_SSC] = fx_c_ssc; + fx_function[HOP_SAFE_C_SSS] = fx_c_sss; + fx_function[HOP_SAFE_C_SCS] = fx_c_scs; + fx_function[HOP_SAFE_C_SCC] = fx_c_scc; + fx_function[HOP_SAFE_C_CSS] = fx_c_css; + fx_function[HOP_SAFE_C_CSC] = fx_c_csc; + fx_function[HOP_SAFE_C_CCS] = fx_c_ccs; + fx_function[HOP_SAFE_C_NS] = fx_c_ns; + + fx_function[HOP_SAFE_C_A] = fx_c_a; + fx_function[HOP_SAFE_C_AA] = fx_c_aa; + fx_function[HOP_SAFE_C_SA] = fx_c_sa; + fx_function[HOP_SAFE_C_AS] = fx_c_as; + fx_function[HOP_SAFE_C_CA] = fx_c_ca; + fx_function[HOP_SAFE_C_AC] = fx_c_ac; + fx_function[HOP_SAFE_C_AAA] = fx_c_aaa; + fx_function[HOP_SAFE_C_CAC] = fx_c_cac; + fx_function[HOP_SAFE_C_CSA] = fx_c_csa; + fx_function[HOP_SAFE_C_SCA] = fx_c_sca; + fx_function[HOP_SAFE_C_SAS] = fx_c_sas; + fx_function[HOP_SAFE_C_SAA] = fx_c_saa; + fx_function[HOP_SAFE_C_SSA] = fx_c_ssa; + fx_function[HOP_SAFE_C_ASS] = fx_c_ass; + fx_function[HOP_SAFE_C_AGG] = fx_c_agg; + fx_function[HOP_SAFE_C_ALL_CA] = fx_c_all_ca; + fx_function[HOP_SAFE_C_NA] = fx_c_na; + fx_function[HOP_SAFE_C_4A] = fx_c_4a; + fx_function[HOP_SAFE_C_opAq] = fx_c_opaq; + fx_function[HOP_SAFE_C_opAAq] = fx_c_opaaq; + fx_function[HOP_SAFE_C_opAAAq] = fx_c_opaaaq; + fx_function[HOP_SAFE_C_opAq_S] = fx_c_opaq_s; + fx_function[HOP_SAFE_C_S_opAq] = fx_c_s_opaq; + fx_function[HOP_SAFE_C_S_opAAq] = fx_c_s_opaaq; + + fx_function[HOP_HASH_TABLE_INCREMENT] = fx_hash_table_increment; + + fx_function[HOP_SAFE_THUNK_A] = fx_safe_thunk_a; + fx_function[HOP_SAFE_CLOSURE_S_A] = fx_safe_closure_s_a; + fx_function[HOP_SAFE_CLOSURE_A_A] = fx_safe_closure_a_a; + fx_function[HOP_SAFE_CLOSURE_SS_A] = fx_safe_closure_ss_a; + fx_function[HOP_SAFE_CLOSURE_AA_A] = fx_safe_closure_aa_a; + fx_function[HOP_SAFE_CLOSURE_3S_A] = fx_safe_closure_3s_a; + fx_function[HOP_SAFE_CLOSURE_S_TO_S] = fx_safe_closure_s_to_s; + fx_function[HOP_SAFE_CLOSURE_S_TO_SC] = fx_safe_closure_s_to_sc; + fx_function[HOP_SAFE_CLOSURE_A_TO_SC] = fx_safe_closure_a_to_sc; + + fx_function[OP_COND_NA_NA] = fx_cond_na_na; +#if (!WITH_GMP) + fx_function[OP_CASE_A_I_S_A] = fx_case_a_i_s_a; +#endif + fx_function[OP_CASE_A_E_S_A] = fx_case_a_e_s_a; + fx_function[OP_CASE_A_G_S_A] = fx_case_a_g_s_a; + fx_function[OP_CASE_A_S_G_A] = fx_case_a_s_g_a; + fx_function[OP_IF_A_C_C] = fx_if_a_c_c; + fx_function[OP_IF_A_A] = fx_if_a_a; + fx_function[OP_IF_S_A_A] = fx_if_s_a_a; + fx_function[OP_IF_A_A_A] = fx_if_a_a_a; + fx_function[OP_IF_AND2_S_A] = fx_if_and2_s_a; + fx_function[OP_IF_NOT_A_A] = fx_if_not_a_a; + fx_function[OP_IF_NOT_A_A_A] = fx_if_not_a_a_a; + fx_function[OP_IF_IS_TYPE_S_A_A] = fx_if_is_type_s_a_a; + fx_function[OP_OR_2A] = fx_or_2a; + fx_function[OP_OR_S_2] = fx_or_s_2; + fx_function[OP_OR_S_TYPE_2] = fx_or_s_type_2; + fx_function[OP_OR_3A] = fx_or_3a; + fx_function[OP_OR_N] = fx_or_n; + fx_function[OP_AND_2A] = fx_and_2a; + fx_function[OP_AND_S_2] = fx_and_s_2; + fx_function[OP_AND_3A] = fx_and_3a; + fx_function[OP_AND_N] = fx_and_n; + fx_function[OP_BEGIN_NA] = fx_begin_na; + fx_function[OP_BEGIN_AA] = fx_begin_aa; + fx_function[OP_LET_TEMP_A_A] = fx_let_temp_a_a; + fx_function[OP_IMPLICIT_S7_STARLET_REF_S] = fx_implicit_s7_starlet_ref_s; + fx_function[OP_WITH_LET_S] = fx_with_let_s; + + /* these are ok even if a "z" branch is taken -- in that case the body does not have the is_optimized bit, so is_fxable returns false */ + fx_function[OP_TC_AND_A_OR_A_LA] = fx_tc_and_a_or_a_la; + fx_function[OP_TC_OR_A_AND_A_LA] = fx_tc_or_a_and_a_la; + fx_function[OP_TC_OR_A_A_AND_A_A_LA] = fx_tc_or_a_a_and_a_a_la; + fx_function[OP_TC_AND_A_OR_A_LAA] = fx_tc_and_a_or_a_laa; + fx_function[OP_TC_OR_A_AND_A_LAA] = fx_tc_or_a_and_a_laa; + fx_function[OP_TC_AND_A_OR_A_L3A] = fx_tc_and_a_or_a_l3a; + fx_function[OP_TC_OR_A_AND_A_L3A] = fx_tc_or_a_and_a_l3a; + fx_function[OP_TC_AND_A_OR_A_A_LA] = fx_tc_and_a_or_a_a_la; + fx_function[OP_TC_OR_A_AND_A_A_LA] = fx_tc_or_a_and_a_a_la; + fx_function[OP_TC_IF_A_Z_LA] = fx_tc_if_a_z_la; + fx_function[OP_TC_IF_A_LA_Z] = fx_tc_if_a_la_z; + fx_function[OP_TC_COND_A_Z_LA] = fx_tc_cond_a_z_la; + fx_function[OP_TC_COND_A_LA_Z] = fx_tc_cond_a_la_z; + fx_function[OP_TC_IF_A_Z_LAA] = fx_tc_if_a_z_laa; + fx_function[OP_TC_IF_A_LAA_Z] = fx_tc_if_a_laa_z; + fx_function[OP_TC_COND_A_Z_LAA] = fx_tc_cond_a_z_laa; + fx_function[OP_TC_COND_A_LAA_Z] = fx_tc_cond_a_laa_z; + fx_function[OP_TC_IF_A_Z_L3A] = fx_tc_if_a_z_l3a; + fx_function[OP_TC_IF_A_L3A_Z] = fx_tc_if_a_l3a_z; + fx_function[OP_TC_IF_A_Z_IF_A_Z_LA] = fx_tc_if_a_z_if_a_z_la; + fx_function[OP_TC_IF_A_Z_IF_A_LA_Z] = fx_tc_if_a_z_if_a_la_z; + fx_function[OP_TC_COND_A_Z_A_Z_LA] = fx_tc_cond_a_z_a_z_la; + fx_function[OP_TC_COND_A_Z_A_LA_Z] = fx_tc_cond_a_z_a_la_z; + fx_function[OP_TC_AND_A_IF_A_Z_LA] = fx_tc_and_a_if_a_z_la; + fx_function[OP_TC_AND_A_IF_A_LA_Z] = fx_tc_and_a_if_a_la_z; + fx_function[OP_TC_IF_A_Z_IF_A_LAA_Z] = fx_tc_if_a_z_if_a_laa_z; + fx_function[OP_TC_IF_A_Z_IF_A_Z_LAA] = fx_tc_if_a_z_if_a_z_laa; + fx_function[OP_TC_COND_A_Z_A_Z_LAA] = fx_tc_cond_a_z_a_z_laa; + fx_function[OP_TC_COND_A_Z_A_LAA_Z] = fx_tc_cond_a_z_a_laa_z; + fx_function[OP_TC_IF_A_Z_IF_A_L3A_L3A] = fx_tc_if_a_z_if_a_l3a_l3a; + fx_function[OP_TC_CASE_LA] = fx_tc_case_la; + fx_function[OP_TC_OR_A_AND_A_A_L3A] = fx_tc_or_a_and_a_a_l3a; + fx_function[OP_TC_LET_IF_A_Z_LA] = fx_tc_let_if_a_z_la; + fx_function[OP_TC_LET_IF_A_Z_LAA] = fx_tc_let_if_a_z_laa; + fx_function[OP_TC_LET_WHEN_LAA] = fx_tc_let_when_laa; + fx_function[OP_TC_LET_UNLESS_LAA] = fx_tc_let_unless_laa; + fx_function[OP_TC_LET_COND] = fx_tc_let_cond; + fx_function[OP_TC_COND_A_Z_A_LAA_LAA] = fx_tc_cond_a_z_a_laa_laa; + + fx_function[OP_RECUR_IF_A_A_opA_LAq] = fx_recur_if_a_a_opa_laq; + fx_function[OP_RECUR_IF_A_opA_LAq_A] = fx_recur_if_a_opa_laq_a; + fx_function[OP_RECUR_IF_A_A_AND_A_LAA_LAA] = fx_recur_if_a_a_and_a_laa_laa; + fx_function[OP_RECUR_COND_A_A_A_A_opLA_LAq] = fx_recur_cond_a_a_a_a_opla_laq; /* very few calls (only s7test) */ + fx_function[OP_RECUR_AND_A_OR_A_LAA_LAA] = fx_recur_and_a_or_a_laa_laa; /* very few calls (lint) */ +} + +static void init_opt_functions(s7_scheme *sc) +{ +#if (!WITH_PURE_S7) + s7_set_b_7pp_function(sc, global_value(sc->char_ci_lt_symbol), char_ci_lt_b_7pp); + s7_set_b_7pp_function(sc, global_value(sc->char_ci_leq_symbol), char_ci_leq_b_7pp); + s7_set_b_7pp_function(sc, global_value(sc->char_ci_gt_symbol), char_ci_gt_b_7pp); + s7_set_b_7pp_function(sc, global_value(sc->char_ci_geq_symbol), char_ci_geq_b_7pp); + s7_set_b_7pp_function(sc, global_value(sc->char_ci_eq_symbol), char_ci_eq_b_7pp); + s7_set_b_7pp_function(sc, global_value(sc->string_ci_lt_symbol), string_ci_lt_b_7pp); + s7_set_b_7pp_function(sc, global_value(sc->string_ci_leq_symbol), string_ci_leq_b_7pp); + s7_set_b_7pp_function(sc, global_value(sc->string_ci_gt_symbol), string_ci_gt_b_7pp); + s7_set_b_7pp_function(sc, global_value(sc->string_ci_geq_symbol), string_ci_geq_b_7pp); + s7_set_b_7pp_function(sc, global_value(sc->string_ci_eq_symbol), string_ci_eq_b_7pp); + + s7_set_b_pp_unchecked_function(sc, global_value(sc->char_ci_lt_symbol), char_ci_lt_b_unchecked); + s7_set_b_pp_unchecked_function(sc, global_value(sc->char_ci_leq_symbol), char_ci_leq_b_unchecked); + s7_set_b_pp_unchecked_function(sc, global_value(sc->char_ci_gt_symbol), char_ci_gt_b_unchecked); + s7_set_b_pp_unchecked_function(sc, global_value(sc->char_ci_geq_symbol), char_ci_geq_b_unchecked); + s7_set_b_pp_unchecked_function(sc, global_value(sc->char_ci_eq_symbol), char_ci_eq_b_unchecked); + s7_set_b_pp_unchecked_function(sc, global_value(sc->string_ci_lt_symbol), string_ci_lt_b_unchecked); + s7_set_b_pp_unchecked_function(sc, global_value(sc->string_ci_leq_symbol), string_ci_leq_b_unchecked); + s7_set_b_pp_unchecked_function(sc, global_value(sc->string_ci_gt_symbol), string_ci_gt_b_unchecked); + s7_set_b_pp_unchecked_function(sc, global_value(sc->string_ci_geq_symbol), string_ci_geq_b_unchecked); + s7_set_b_pp_unchecked_function(sc, global_value(sc->string_ci_eq_symbol), string_ci_eq_b_unchecked); + + s7_set_p_pp_function(sc, global_value(sc->vector_append_symbol), vector_append_p_pp); + s7_set_p_ppp_function(sc, global_value(sc->vector_append_symbol), vector_append_p_ppp); + s7_set_i_i_function(sc, global_value(sc->integer_length_symbol), integer_length_i_i); + s7_set_i_7p_function(sc, global_value(sc->string_length_symbol), string_length_i_7p); + s7_set_i_7p_function(sc, global_value(sc->vector_length_symbol), vector_length_i_7p); + s7_set_p_p_function(sc, global_value(sc->vector_to_list_symbol), vector_to_list_p_p); + s7_set_p_p_function(sc, global_value(sc->string_to_list_symbol), string_to_list_p_p); + s7_set_p_p_function(sc, global_value(sc->vector_length_symbol), vector_length_p_p); + s7_set_b_7p_function(sc, global_value(sc->is_exact_symbol), is_exact_b_7p); + s7_set_b_7p_function(sc, global_value(sc->is_inexact_symbol), is_inexact_b_7p); + s7_set_p_p_function(sc, global_value(sc->exact_to_inexact_symbol), exact_to_inexact_p_p); + s7_set_p_p_function(sc, global_value(sc->inexact_to_exact_symbol), inexact_to_exact_p_p); +#endif + + s7_set_p_pp_function(sc, global_value(sc->float_vector_ref_symbol), float_vector_ref_p_pp); + s7_set_d_7pi_function(sc, global_value(sc->float_vector_ref_symbol), float_vector_ref_d_7pi); + s7_set_d_7pii_function(sc, global_value(sc->float_vector_ref_symbol), float_vector_ref_d_7pii); + s7_set_d_7piii_function(sc, global_value(sc->float_vector_ref_symbol), float_vector_ref_d_7piii); + s7_set_d_7pid_function(sc, global_value(sc->float_vector_set_symbol), float_vector_set_d_7pid); + s7_set_d_7piid_function(sc, global_value(sc->float_vector_set_symbol), float_vector_set_d_7piid); + s7_set_d_7piiid_function(sc, global_value(sc->float_vector_set_symbol), float_vector_set_d_7piiid); + + s7_set_p_pp_function(sc, global_value(sc->int_vector_ref_symbol), int_vector_ref_p_pp); + s7_set_i_7pi_function(sc, global_value(sc->int_vector_ref_symbol), int_vector_ref_i_7pi); + s7_set_i_7pii_function(sc, global_value(sc->int_vector_ref_symbol), int_vector_ref_i_7pii); + s7_set_i_7piii_function(sc, global_value(sc->int_vector_ref_symbol), int_vector_ref_i_7piii); + s7_set_i_7pii_function(sc, global_value(sc->int_vector_set_symbol), int_vector_set_i_7pii); + s7_set_i_7piii_function(sc, global_value(sc->int_vector_set_symbol), int_vector_set_i_7piii); + + s7_set_i_7pi_function(sc, global_value(sc->byte_vector_ref_symbol), byte_vector_ref_i_7pi); + s7_set_i_7pii_function(sc, global_value(sc->byte_vector_ref_symbol), byte_vector_ref_i_7pii); + s7_set_i_7pii_function(sc, global_value(sc->byte_vector_set_symbol), byte_vector_set_i_7pii); + s7_set_i_7piii_function(sc, global_value(sc->byte_vector_set_symbol), byte_vector_set_i_7piii); + + s7_set_p_pp_function(sc, global_value(sc->vector_ref_symbol), vector_ref_p_pp); + s7_set_p_pi_function(sc, global_value(sc->vector_ref_symbol), vector_ref_p_pi); + s7_set_p_pii_function(sc, global_value(sc->vector_ref_symbol), vector_ref_p_pii); + s7_set_p_pip_function(sc, global_value(sc->vector_set_symbol), vector_set_p_pip); + s7_set_p_piip_function(sc, global_value(sc->vector_set_symbol), vector_set_p_piip); + s7_set_p_pi_unchecked_function(sc, global_value(sc->vector_ref_symbol), vector_ref_p_pi_unchecked); + s7_set_p_pip_unchecked_function(sc, global_value(sc->vector_set_symbol), vector_set_p_pip_unchecked); + s7_set_p_ppp_function(sc, global_value(sc->vector_set_symbol), vector_set_p_ppp); + s7_set_p_ppp_function(sc, global_value(sc->int_vector_set_symbol), int_vector_set_p_ppp); + + s7_set_p_pp_function(sc, global_value(sc->list_ref_symbol), list_ref_p_pp); + s7_set_p_pi_function(sc, global_value(sc->list_ref_symbol), list_ref_p_pi); + s7_set_p_pip_function(sc, global_value(sc->list_set_symbol), list_set_p_pip); + s7_set_p_pi_unchecked_function(sc, global_value(sc->list_ref_symbol), list_ref_p_pi_unchecked); + s7_set_p_pip_unchecked_function(sc, global_value(sc->list_set_symbol), list_set_p_pip_unchecked); + s7_set_p_p_function(sc, global_value(sc->cyclic_sequences_symbol), cyclic_sequences_p_p); + s7_set_p_pp_function(sc, global_value(sc->let_ref_symbol), let_ref); + s7_set_p_ppp_function(sc, global_value(sc->let_set_symbol), let_set); + s7_set_p_pi_function(sc, global_value(sc->string_ref_symbol), string_ref_p_pi); + s7_set_p_pp_function(sc, global_value(sc->string_ref_symbol), string_ref_p_pp); + s7_set_p_pip_function(sc, global_value(sc->string_set_symbol), string_set_p_pip); + s7_set_p_pi_unchecked_function(sc, global_value(sc->string_ref_symbol), string_ref_p_pi_unchecked); + s7_set_p_pip_unchecked_function(sc, global_value(sc->string_set_symbol), string_set_p_pip_unchecked); + s7_set_p_pp_function(sc, global_value(sc->hash_table_ref_symbol), hash_table_ref_p_pp); + s7_set_p_ppp_function(sc, global_value(sc->hash_table_set_symbol), hash_table_set_p_ppp); + + s7_set_p_ii_function(sc, global_value(sc->complex_symbol), complex_p_ii); + s7_set_p_dd_function(sc, global_value(sc->complex_symbol), complex_p_dd); + s7_set_p_i_function(sc, global_value(sc->number_to_string_symbol), number_to_string_p_i); + s7_set_p_p_function(sc, global_value(sc->number_to_string_symbol), number_to_string_p_p); + s7_set_p_pp_function(sc, global_value(sc->number_to_string_symbol), number_to_string_p_pp); + s7_set_p_p_function(sc, global_value(sc->string_to_number_symbol), string_to_number_p_p); + s7_set_p_pp_function(sc, global_value(sc->string_to_number_symbol), string_to_number_p_pp); + + s7_set_p_p_function(sc, global_value(sc->car_symbol), car_p_p); + s7_set_p_pp_function(sc, global_value(sc->set_car_symbol), set_car_p_pp); + s7_set_p_p_function(sc, global_value(sc->cdr_symbol), cdr_p_p); + s7_set_p_pp_function(sc, global_value(sc->set_cdr_symbol), set_cdr_p_pp); + s7_set_p_p_function(sc, global_value(sc->caar_symbol), caar_p_p); + s7_set_p_p_function(sc, global_value(sc->cadr_symbol), cadr_p_p); + s7_set_p_p_function(sc, global_value(sc->cdar_symbol), cdar_p_p); + s7_set_p_p_function(sc, global_value(sc->cddr_symbol), cddr_p_p); + s7_set_p_p_function(sc, global_value(sc->caddr_symbol), caddr_p_p); + s7_set_p_p_function(sc, global_value(sc->caadr_symbol), caadr_p_p); + s7_set_p_p_function(sc, global_value(sc->cadar_symbol), cadar_p_p); + s7_set_p_p_function(sc, global_value(sc->cdddr_symbol), cdddr_p_p); + s7_set_p_p_function(sc, global_value(sc->cdadr_symbol), cdadr_p_p); + s7_set_p_p_function(sc, global_value(sc->cddar_symbol), cddar_p_p); + s7_set_p_p_function(sc, global_value(sc->cdaar_symbol), cdaar_p_p); + s7_set_p_p_function(sc, global_value(sc->caaar_symbol), caaar_p_p); + s7_set_p_p_function(sc, global_value(sc->caddar_symbol), caddar_p_p); + s7_set_p_p_function(sc, global_value(sc->caaddr_symbol), caaddr_p_p); + s7_set_p_p_function(sc, global_value(sc->cadddr_symbol), cadddr_p_p); + s7_set_p_p_function(sc, global_value(sc->cadadr_symbol), cadadr_p_p); + s7_set_p_p_function(sc, global_value(sc->cddadr_symbol), cddadr_p_p); + s7_set_p_p_function(sc, global_value(sc->cdddar_symbol), cdddar_p_p); + s7_set_p_p_function(sc, global_value(sc->cddddr_symbol), cddddr_p_p); + + s7_set_p_p_function(sc, global_value(sc->string_symbol), string_p_p); + s7_set_p_p_function(sc, global_value(sc->string_to_symbol_symbol), string_to_symbol_p_p); + s7_set_p_p_function(sc, global_value(sc->symbol_to_string_symbol), symbol_to_string_p_p); + s7_set_p_p_function(sc, global_value(sc->symbol_symbol), string_to_symbol_p_p); + s7_set_p_pp_function(sc, global_value(sc->symbol_symbol), symbol_p_pp); + s7_set_p_function(sc, global_value(sc->newline_symbol), newline_p); + s7_set_p_p_function(sc, global_value(sc->newline_symbol), newline_p_p); + s7_set_p_p_function(sc, global_value(sc->display_symbol), display_p_p); + s7_set_p_pp_function(sc, global_value(sc->display_symbol), display_p_pp); + s7_set_p_p_function(sc, global_value(sc->write_symbol), write_p_p); + s7_set_p_pp_function(sc, global_value(sc->write_symbol), write_p_pp); + s7_set_p_p_function(sc, global_value(sc->write_char_symbol), write_char_p_p); + s7_set_p_pp_function(sc, global_value(sc->write_char_symbol), write_char_p_pp); + s7_set_p_pp_function(sc, global_value(sc->write_string_symbol), write_string_p_pp); + s7_set_p_pp_function(sc, global_value(sc->read_line_symbol), read_line_p_pp); + s7_set_p_p_function(sc, global_value(sc->read_line_symbol), read_line_p_p); + s7_set_p_pp_function(sc, global_value(sc->inlet_symbol), inlet_p_pp); + s7_set_i_7p_function(sc, global_value(sc->port_line_number_symbol), s7_port_line_number); + s7_set_p_pp_function(sc, global_value(sc->cons_symbol), cons_p_pp); + s7_set_p_function(sc, global_value(sc->open_output_string_symbol), s7_open_output_string); + s7_set_p_ppi_function(sc, global_value(sc->char_position_symbol), char_position_p_ppi); + s7_set_p_pp_function(sc, global_value(sc->append_symbol), s7_append); + s7_set_p_pp_function(sc, global_value(sc->string_append_symbol), string_append_p_pp); + s7_set_p_ppp_function(sc, global_value(sc->append_symbol), append_p_ppp); + s7_set_p_function(sc, global_value(sc->values_symbol), values_p); + s7_set_p_p_function(sc, global_value(sc->values_symbol), values_p_p); + s7_set_p_pp_function(sc, global_value(sc->member_symbol), member_p_pp); + s7_set_p_pp_function(sc, global_value(sc->assoc_symbol), assoc_p_pp); + + s7_set_i_i_function(sc, global_value(sc->abs_symbol), abs_i_i); + s7_set_d_d_function(sc, global_value(sc->abs_symbol), abs_d_d); + s7_set_p_p_function(sc, global_value(sc->abs_symbol), abs_p_p); + s7_set_i_i_function(sc, global_value(sc->magnitude_symbol), magnitude_i_i); + s7_set_d_d_function(sc, global_value(sc->magnitude_symbol), magnitude_d_d); + s7_set_p_p_function(sc, global_value(sc->magnitude_symbol), magnitude_p_p); + + s7_set_p_d_function(sc, global_value(sc->sin_symbol), sin_p_d); + s7_set_p_p_function(sc, global_value(sc->sin_symbol), sin_p_p); + s7_set_p_d_function(sc, global_value(sc->cos_symbol), cos_p_d); + s7_set_p_p_function(sc, global_value(sc->cos_symbol), cos_p_p); + s7_set_p_p_function(sc, global_value(sc->tan_symbol), tan_p_p); + s7_set_p_p_function(sc, global_value(sc->asin_symbol), asin_p_p); + s7_set_p_p_function(sc, global_value(sc->acos_symbol), acos_p_p); + s7_set_p_p_function(sc, global_value(sc->sinh_symbol), sinh_p_p); + s7_set_p_p_function(sc, global_value(sc->cosh_symbol), cosh_p_p); + s7_set_p_p_function(sc, global_value(sc->asinh_symbol), asinh_p_p); + s7_set_p_p_function(sc, global_value(sc->acosh_symbol), acosh_p_p); + s7_set_p_p_function(sc, global_value(sc->atanh_symbol), atanh_p_p); + s7_set_p_p_function(sc, global_value(sc->tanh_symbol), tanh_p_p); + s7_set_d_d_function(sc, global_value(sc->sin_symbol), sin_d_d); + s7_set_d_d_function(sc, global_value(sc->cos_symbol), cos_d_d); + s7_set_d_d_function(sc, global_value(sc->sinh_symbol), sinh_d_d); + s7_set_p_d_function(sc, global_value(sc->sinh_symbol), sinh_p_d); + s7_set_d_d_function(sc, global_value(sc->cosh_symbol), cosh_d_d); + s7_set_p_d_function(sc, global_value(sc->cosh_symbol), cosh_p_d); + s7_set_d_d_function(sc, global_value(sc->exp_symbol), exp_d_d); + s7_set_p_d_function(sc, global_value(sc->exp_symbol), exp_p_d); + + s7_set_p_d_function(sc, global_value(sc->rationalize_symbol), rationalize_p_d); + s7_set_p_i_function(sc, global_value(sc->rationalize_symbol), rationalize_p_i); + s7_set_i_i_function(sc, global_value(sc->rationalize_symbol), rationalize_i_i); + s7_set_p_p_function(sc, global_value(sc->truncate_symbol), truncate_p_p); + s7_set_p_p_function(sc, global_value(sc->round_symbol), round_p_p); + s7_set_p_p_function(sc, global_value(sc->ceiling_symbol), ceiling_p_p); + s7_set_p_p_function(sc, global_value(sc->floor_symbol), floor_p_p); + s7_set_p_pp_function(sc, global_value(sc->max_symbol), max_p_pp); + s7_set_p_pp_function(sc, global_value(sc->min_symbol), min_p_pp); + s7_set_p_p_function(sc, global_value(sc->sqrt_symbol), sqrt_p_p); +#if (!WITH_GMP) + s7_set_p_pp_function(sc, global_value(sc->expt_symbol), expt_p_pp); + /* same problem affects big_log|logior|logand|logxor|lcm|gcd|rationalize|remainder|modulo -- *_p_* will fail in gmp s7 */ + s7_set_p_d_function(sc, global_value(sc->ceiling_symbol), ceiling_p_d); + s7_set_p_d_function(sc, global_value(sc->floor_symbol), floor_p_d); + s7_set_p_d_function(sc, global_value(sc->truncate_symbol), truncate_p_d); + s7_set_p_d_function(sc, global_value(sc->round_symbol), round_p_d); +#endif + s7_set_d_7dd_function(sc, global_value(sc->remainder_symbol), remainder_d_7dd); + s7_set_i_7ii_function(sc, global_value(sc->remainder_symbol), remainder_i_7ii); + s7_set_i_7ii_function(sc, global_value(sc->quotient_symbol), quotient_i_7ii); + s7_set_d_7dd_function(sc, global_value(sc->modulo_symbol), modulo_d_7dd); + s7_set_i_ii_function(sc, global_value(sc->modulo_symbol), modulo_i_ii); + s7_set_p_dd_function(sc, global_value(sc->multiply_symbol), mul_p_dd); + s7_set_p_dd_function(sc, global_value(sc->add_symbol), add_p_dd); + s7_set_p_ii_function(sc, global_value(sc->add_symbol), add_p_ii); + s7_set_p_dd_function(sc, global_value(sc->subtract_symbol), subtract_p_dd); + s7_set_p_ii_function(sc, global_value(sc->subtract_symbol), subtract_p_ii); + + s7_set_p_pp_function(sc, global_value(sc->modulo_symbol), modulo_p_pp); + s7_set_p_pi_function(sc, global_value(sc->modulo_symbol), modulo_p_pi); + s7_set_p_pp_function(sc, global_value(sc->remainder_symbol), remainder_p_pp); + s7_set_p_pi_function(sc, global_value(sc->remainder_symbol), remainder_p_pi); + s7_set_p_pp_function(sc, global_value(sc->quotient_symbol), quotient_p_pp); + s7_set_p_pi_function(sc, global_value(sc->quotient_symbol), quotient_p_pi); + s7_set_p_pp_function(sc, global_value(sc->subtract_symbol), subtract_p_pp); + s7_set_p_pp_function(sc, global_value(sc->add_symbol), add_p_pp); + s7_set_p_ppp_function(sc, global_value(sc->add_symbol), add_p_ppp); + s7_set_p_pp_function(sc, global_value(sc->multiply_symbol), multiply_p_pp); + s7_set_p_ppp_function(sc, global_value(sc->multiply_symbol), multiply_p_ppp); + s7_set_p_pp_function(sc, global_value(sc->divide_symbol), divide_p_pp); + s7_set_p_p_function(sc, global_value(sc->divide_symbol), invert_p_p); + s7_set_p_p_function(sc, global_value(sc->subtract_symbol), negate_p_p); + s7_set_p_p_function(sc, global_value(sc->is_even_symbol), is_even_p_p); + s7_set_p_p_function(sc, global_value(sc->is_odd_symbol), is_odd_p_p); + + s7_set_p_p_function(sc, global_value(sc->random_symbol), random_p_p); + s7_set_d_7d_function(sc, global_value(sc->random_symbol), random_d_7d); + s7_set_i_7i_function(sc, global_value(sc->random_symbol), random_i_7i); + + s7_set_p_d_function(sc, global_value(sc->float_vector_symbol), float_vector_p_d); + s7_set_p_i_function(sc, global_value(sc->int_vector_symbol), int_vector_p_i); + s7_set_i_i_function(sc, global_value(sc->round_symbol), round_i_i); + s7_set_i_i_function(sc, global_value(sc->floor_symbol), floor_i_i); + s7_set_i_i_function(sc, global_value(sc->ceiling_symbol), ceiling_i_i); + s7_set_i_i_function(sc, global_value(sc->truncate_symbol), truncate_i_i); + + s7_set_d_d_function(sc, global_value(sc->tan_symbol), tan_d_d); + s7_set_d_dd_function(sc, global_value(sc->atan_symbol), atan_d_dd); + s7_set_d_d_function(sc, global_value(sc->tanh_symbol), tanh_d_d); + s7_set_p_p_function(sc, global_value(sc->exp_symbol), exp_p_p); +#if (!WITH_GMP) + s7_set_i_7ii_function(sc, global_value(sc->ash_symbol), ash_i_7ii); + s7_set_i_7d_function(sc, global_value(sc->round_symbol), round_i_7d); + s7_set_i_7d_function(sc, global_value(sc->floor_symbol), floor_i_7d); + s7_set_i_7d_function(sc, global_value(sc->ceiling_symbol), ceiling_i_7d); + s7_set_i_7p_function(sc, global_value(sc->floor_symbol), floor_i_7p); + s7_set_i_7p_function(sc, global_value(sc->ceiling_symbol), ceiling_i_7p); + s7_set_i_7d_function(sc, global_value(sc->truncate_symbol), truncate_i_7d); +#endif + + s7_set_d_d_function(sc, global_value(sc->add_symbol), add_d_d); + s7_set_d_d_function(sc, global_value(sc->subtract_symbol), subtract_d_d); + s7_set_d_d_function(sc, global_value(sc->multiply_symbol), multiply_d_d); + s7_set_d_7d_function(sc, global_value(sc->divide_symbol), divide_d_7d); + s7_set_d_dd_function(sc, global_value(sc->add_symbol), add_d_dd); + s7_set_d_dd_function(sc, global_value(sc->subtract_symbol), subtract_d_dd); + s7_set_d_dd_function(sc, global_value(sc->multiply_symbol), multiply_d_dd); + s7_set_d_7dd_function(sc, global_value(sc->divide_symbol), divide_d_7dd); + s7_set_d_ddd_function(sc, global_value(sc->add_symbol), add_d_ddd); + s7_set_d_ddd_function(sc, global_value(sc->subtract_symbol), subtract_d_ddd); + s7_set_d_ddd_function(sc, global_value(sc->multiply_symbol), multiply_d_ddd); + s7_set_d_dddd_function(sc, global_value(sc->add_symbol), add_d_dddd); + s7_set_d_dddd_function(sc, global_value(sc->subtract_symbol), subtract_d_dddd); + s7_set_d_dddd_function(sc, global_value(sc->multiply_symbol), multiply_d_dddd); + s7_set_p_i_function(sc, global_value(sc->divide_symbol), divide_p_i); + s7_set_p_ii_function(sc, global_value(sc->divide_symbol), divide_p_ii); + s7_set_d_dd_function(sc, global_value(sc->max_symbol), max_d_dd); + s7_set_d_dd_function(sc, global_value(sc->min_symbol), min_d_dd); + s7_set_d_ddd_function(sc, global_value(sc->max_symbol), max_d_ddd); + s7_set_d_ddd_function(sc, global_value(sc->min_symbol), min_d_ddd); + s7_set_d_dddd_function(sc, global_value(sc->max_symbol), max_d_dddd); + s7_set_d_dddd_function(sc, global_value(sc->min_symbol), min_d_dddd); + s7_set_i_ii_function(sc, global_value(sc->max_symbol), max_i_ii); + s7_set_i_ii_function(sc, global_value(sc->min_symbol), min_i_ii); + s7_set_i_iii_function(sc, global_value(sc->max_symbol), max_i_iii); + s7_set_i_iii_function(sc, global_value(sc->min_symbol), min_i_iii); + s7_set_i_i_function(sc, global_value(sc->subtract_symbol), subtract_i_i); + s7_set_i_ii_function(sc, global_value(sc->add_symbol), add_i_ii); + s7_set_i_iii_function(sc, global_value(sc->add_symbol), add_i_iii); + s7_set_i_ii_function(sc, global_value(sc->subtract_symbol), subtract_i_ii); + s7_set_i_iii_function(sc, global_value(sc->subtract_symbol), subtract_i_iii); + s7_set_i_ii_function(sc, global_value(sc->multiply_symbol), multiply_i_ii); + s7_set_i_iii_function(sc, global_value(sc->multiply_symbol), multiply_i_iii); + + s7_set_i_i_function(sc, global_value(sc->lognot_symbol), lognot_i_i); + s7_set_i_ii_function(sc, global_value(sc->logior_symbol), logior_i_ii); + s7_set_i_ii_function(sc, global_value(sc->logxor_symbol), logxor_i_ii); + s7_set_i_ii_function(sc, global_value(sc->logand_symbol), logand_i_ii); + s7_set_i_iii_function(sc, global_value(sc->logior_symbol), logior_i_iii); + s7_set_i_iii_function(sc, global_value(sc->logxor_symbol), logxor_i_iii); + s7_set_i_iii_function(sc, global_value(sc->logand_symbol), logand_i_iii); + s7_set_b_7ii_function(sc, global_value(sc->logbit_symbol), logbit_b_7ii); + s7_set_b_7pp_function(sc, global_value(sc->logbit_symbol), logbit_b_7pp); + + s7_set_i_7p_function(sc, global_value(sc->numerator_symbol), numerator_i_7p); + s7_set_i_7p_function(sc, global_value(sc->denominator_symbol), denominator_i_7p); + s7_set_i_7p_function(sc, global_value(sc->char_to_integer_symbol), char_to_integer_i_7p); + s7_set_i_7p_function(sc, global_value(sc->hash_table_entries_symbol), hash_table_entries_i_7p); + s7_set_i_7p_function(sc, global_value(sc->tree_leaves_symbol), tree_leaves_i_7p); + s7_set_p_p_function(sc, global_value(sc->char_to_integer_symbol), char_to_integer_p_p); + + s7_set_b_p_function(sc, global_value(sc->is_boolean_symbol), s7_is_boolean); + s7_set_b_p_function(sc, global_value(sc->is_byte_symbol), is_byte); + s7_set_b_p_function(sc, global_value(sc->is_byte_vector_symbol), is_byte_vector_b_p); + s7_set_b_p_function(sc, global_value(sc->is_c_object_symbol), s7_is_c_object); + s7_set_b_p_function(sc, global_value(sc->is_char_symbol), s7_is_character); + s7_set_b_p_function(sc, global_value(sc->is_complex_symbol), s7_is_complex); + s7_set_b_p_function(sc, global_value(sc->is_continuation_symbol), is_continuation_b_p); + s7_set_b_p_function(sc, global_value(sc->is_c_pointer_symbol), s7_is_c_pointer); + s7_set_b_p_function(sc, global_value(sc->is_dilambda_symbol), s7_is_dilambda); + s7_set_b_p_function(sc, global_value(sc->is_eof_object_symbol), is_eof_object_b_p); + s7_set_b_7p_function(sc, global_value(sc->is_even_symbol), is_even_b_7p); + s7_set_b_7p_function(sc, global_value(sc->is_odd_symbol), is_odd_b_7p); + s7_set_b_p_function(sc, global_value(sc->is_float_symbol), is_float_b); + s7_set_b_p_function(sc, global_value(sc->is_float_vector_symbol), s7_is_float_vector); + s7_set_b_p_function(sc, global_value(sc->is_gensym_symbol), is_gensym_b_p); + s7_set_b_p_function(sc, global_value(sc->is_hash_table_symbol), s7_is_hash_table); + s7_set_b_7p_function(sc, global_value(sc->is_infinite_symbol), is_infinite_b_7p); + s7_set_b_7p_function(sc, global_value(sc->is_nan_symbol), is_nan_b_7p); + s7_set_b_p_function(sc, global_value(sc->is_input_port_symbol), is_input_port_b); + s7_set_b_p_function(sc, global_value(sc->is_integer_symbol), s7_is_integer); + s7_set_b_p_function(sc, global_value(sc->is_int_vector_symbol), s7_is_int_vector); + s7_set_b_p_function(sc, global_value(sc->is_keyword_symbol), s7_is_keyword); + s7_set_b_p_function(sc, global_value(sc->is_let_symbol), s7_is_let); + s7_set_b_p_function(sc, global_value(sc->is_list_symbol), is_list_b); + s7_set_b_p_function(sc, global_value(sc->is_macro_symbol), is_macro_b); + s7_set_b_p_function(sc, global_value(sc->is_number_symbol), s7_is_number); + s7_set_b_p_function(sc, global_value(sc->is_output_port_symbol), is_output_port_b); + s7_set_b_p_function(sc, global_value(sc->is_pair_symbol), s7_is_pair); + s7_set_b_p_function(sc, global_value(sc->is_null_symbol), is_null_b_p); + s7_set_b_7p_function(sc, global_value(sc->is_port_closed_symbol), is_port_closed_b_7p); + s7_set_b_p_function(sc, global_value(sc->is_procedure_symbol), s7_is_procedure); + s7_set_b_7p_function(sc, global_value(sc->is_proper_list_symbol), s7_is_proper_list); + s7_set_b_p_function(sc, global_value(sc->is_random_state_symbol), s7_is_random_state); + s7_set_b_p_function(sc, global_value(sc->is_rational_symbol), s7_is_rational); + s7_set_b_p_function(sc, global_value(sc->is_real_symbol), s7_is_real); + s7_set_b_p_function(sc, global_value(sc->is_sequence_symbol), is_sequence_b); + s7_set_b_p_function(sc, global_value(sc->is_string_symbol), s7_is_string); + s7_set_b_p_function(sc, global_value(sc->is_symbol_symbol), s7_is_symbol); + s7_set_b_p_function(sc, global_value(sc->is_syntax_symbol), s7_is_syntax); + s7_set_b_p_function(sc, global_value(sc->is_vector_symbol), s7_is_vector); + s7_set_b_7p_function(sc, global_value(sc->is_iterator_symbol), is_iterator_b_7p); + + s7_set_b_7p_function(sc, global_value(sc->is_char_alphabetic_symbol), is_char_alphabetic_b_7p); + s7_set_b_7p_function(sc, global_value(sc->is_char_lower_case_symbol), is_char_lower_case_b_7p); + s7_set_b_7p_function(sc, global_value(sc->is_char_numeric_symbol), is_char_numeric_b_7p); + s7_set_b_7p_function(sc, global_value(sc->is_char_upper_case_symbol), is_char_upper_case_b_7p); + s7_set_b_7p_function(sc, global_value(sc->is_char_whitespace_symbol), is_char_whitespace_b_7p); + + s7_set_b_p_function(sc, global_value(sc->is_openlet_symbol), s7_is_openlet); + s7_set_b_7p_function(sc, global_value(sc->iterator_is_at_end_symbol), iterator_is_at_end_b_7p); + s7_set_b_7p_function(sc, global_value(sc->is_zero_symbol), is_zero_b_7p); + s7_set_b_7p_function(sc, global_value(sc->is_negative_symbol), is_negative_b_7p); + s7_set_b_7p_function(sc, global_value(sc->is_positive_symbol), is_positive_b_7p); + s7_set_b_7p_function(sc, global_value(sc->not_symbol), not_b_7p); + s7_set_b_7p_function(sc, global_value(sc->is_provided_symbol), is_provided_b_7p); + s7_set_b_7p_function(sc, global_value(sc->is_defined_symbol), is_defined_b_7p); + s7_set_b_7pp_function(sc, global_value(sc->is_defined_symbol), is_defined_b_7pp); + s7_set_b_7pp_function(sc, global_value(sc->tree_memq_symbol), s7_tree_memq); + s7_set_b_7p_function(sc, global_value(sc->tree_is_cyclic_symbol), tree_is_cyclic); + s7_set_b_7pp_function(sc, global_value(sc->tree_set_memq_symbol), tree_set_memq_b_7pp); + s7_set_p_pp_function(sc, global_value(sc->tree_set_memq_symbol), tree_set_memq_p_pp); + s7_set_b_p_function(sc, global_value(sc->is_immutable_symbol), s7_is_immutable); + + s7_set_p_p_function(sc, global_value(sc->is_proper_list_symbol), is_proper_list_p_p); + s7_set_p_p_function(sc, global_value(sc->is_pair_symbol), is_pair_p_p); + s7_set_p_p_function(sc, global_value(sc->is_char_symbol), is_char_p_p); + s7_set_p_p_function(sc, global_value(sc->is_constant_symbol), is_constant_p_p); + s7_set_b_7p_function(sc, global_value(sc->is_constant_symbol), is_constant_b_7p); + s7_set_p_p_function(sc, global_value(sc->type_of_symbol), s7_type_of); + s7_set_p_i_function(sc, global_value(sc->integer_to_char_symbol), integer_to_char_p_i); + s7_set_p_p_function(sc, global_value(sc->integer_to_char_symbol), integer_to_char_p_p); + s7_set_p_p_function(sc, global_value(sc->iterate_symbol), iterate_p_p); + s7_set_p_p_function(sc, global_value(sc->list_symbol), list_p_p); + s7_set_p_pp_function(sc, global_value(sc->list_symbol), list_p_pp); + s7_set_p_ppp_function(sc, global_value(sc->list_symbol), list_p_ppp); + s7_set_p_pp_function(sc, global_value(sc->list_tail_symbol), list_tail_p_pp); + s7_set_p_pp_function(sc, global_value(sc->make_list_symbol), make_list_p_pp); + s7_set_p_pp_function(sc, global_value(sc->assq_symbol), assq_p_pp); + s7_set_p_pp_function(sc, global_value(sc->assv_symbol), assv_p_pp); + s7_set_p_pp_function(sc, global_value(sc->memq_symbol), memq_p_pp); + s7_set_p_pp_function(sc, global_value(sc->memv_symbol), memv_p_pp); + s7_set_p_p_function(sc, global_value(sc->tree_leaves_symbol), tree_leaves_p_p); + s7_set_p_p_function(sc, global_value(sc->length_symbol), s7_length); + s7_set_p_p_function(sc, global_value(sc->pair_line_number_symbol), pair_line_number_p_p); + s7_set_p_p_function(sc, global_value(sc->port_line_number_symbol), port_line_number_p_p); + s7_set_p_p_function(sc, global_value(sc->port_filename_symbol), port_filename_p_p); + s7_set_p_p_function(sc, global_value(sc->c_pointer_info_symbol), c_pointer_info_p_p); + s7_set_p_p_function(sc, global_value(sc->c_pointer_type_symbol), c_pointer_type_p_p); + s7_set_p_p_function(sc, global_value(sc->c_pointer_weak1_symbol), c_pointer_weak1_p_p); + s7_set_p_p_function(sc, global_value(sc->c_pointer_weak2_symbol), c_pointer_weak2_p_p); + s7_set_p_p_function(sc, global_value(sc->is_char_alphabetic_symbol), is_char_alphabetic_p_p); + s7_set_p_p_function(sc, global_value(sc->is_char_whitespace_symbol), is_char_whitespace_p_p); + s7_set_p_p_function(sc, global_value(sc->is_char_numeric_symbol), is_char_numeric_p_p); + s7_set_p_p_function(sc, global_value(sc->char_upcase_symbol), char_upcase_p_p); + s7_set_p_p_function(sc, global_value(sc->read_char_symbol), read_char_p_p); + s7_set_p_i_function(sc, global_value(sc->make_string_symbol), make_string_p_i); + s7_set_p_ii_function(sc, global_value(sc->make_int_vector_symbol), make_int_vector_p_ii); + s7_set_p_ii_function(sc, global_value(sc->make_byte_vector_symbol), make_byte_vector_p_ii); + s7_set_p_pp_function(sc, global_value(sc->vector_symbol), vector_p_pp); + s7_set_p_p_function(sc, global_value(sc->signature_symbol), s7_signature); + s7_set_p_p_function(sc, global_value(sc->copy_symbol), copy_p_p); + s7_set_p_p_function(sc, global_value(sc->reverse_symbol), reverse_p_p); + s7_set_p_p_function(sc, global_value(sc->object_to_let_symbol), object_to_let_p_p); + s7_set_p_p_function(sc, global_value(sc->outlet_symbol), outlet_p_p); + s7_set_p_p_function(sc, global_value(sc->make_iterator_symbol), s7_make_iterator); + +#if WITH_SYSTEM_EXTRAS + s7_set_b_7p_function(sc, global_value(sc->is_directory_symbol), is_directory_b_7p); + s7_set_b_7p_function(sc, global_value(sc->file_exists_symbol), file_exists_b_7p); +#endif + + s7_set_b_i_function(sc, global_value(sc->is_even_symbol), is_even_i); + s7_set_b_i_function(sc, global_value(sc->is_odd_symbol), is_odd_i); + s7_set_b_i_function(sc, global_value(sc->is_zero_symbol), is_zero_i); + s7_set_b_d_function(sc, global_value(sc->is_zero_symbol), is_zero_d); + s7_set_p_p_function(sc, global_value(sc->is_zero_symbol), is_zero_p_p); + s7_set_p_p_function(sc, global_value(sc->is_positive_symbol), is_positive_p_p); + s7_set_p_p_function(sc, global_value(sc->is_negative_symbol), is_negative_p_p); + s7_set_p_p_function(sc, global_value(sc->real_part_symbol), real_part_p_p); + s7_set_p_p_function(sc, global_value(sc->imag_part_symbol), imag_part_p_p); + s7_set_b_i_function(sc, global_value(sc->is_positive_symbol), is_positive_i); + s7_set_b_d_function(sc, global_value(sc->is_positive_symbol), is_positive_d); + s7_set_b_i_function(sc, global_value(sc->is_negative_symbol), is_negative_i); + s7_set_b_d_function(sc, global_value(sc->is_negative_symbol), is_negative_d); + + s7_set_p_pi_function(sc, global_value(sc->lt_symbol), lt_p_pi); + s7_set_b_pi_function(sc, global_value(sc->lt_symbol), lt_b_pi); + s7_set_p_pi_function(sc, global_value(sc->leq_symbol), leq_p_pi); + s7_set_b_pi_function(sc, global_value(sc->leq_symbol), leq_b_pi); + s7_set_p_pi_function(sc, global_value(sc->gt_symbol), gt_p_pi); + s7_set_b_pi_function(sc, global_value(sc->gt_symbol), gt_b_pi); + s7_set_p_pi_function(sc, global_value(sc->geq_symbol), geq_p_pi); + s7_set_b_pi_function(sc, global_value(sc->geq_symbol), geq_b_pi); + /* no ip pd dp! */ + s7_set_b_pi_function(sc, global_value(sc->num_eq_symbol), num_eq_b_pi); + s7_set_p_pi_function(sc, global_value(sc->num_eq_symbol), num_eq_p_pi); + s7_set_p_pi_function(sc, global_value(sc->add_symbol), add_p_pi); + s7_set_p_pi_function(sc, global_value(sc->subtract_symbol), g_sub_xi); + s7_set_p_pi_function(sc, global_value(sc->multiply_symbol), multiply_p_pi); + + s7_set_p_ii_function(sc, global_value(sc->num_eq_symbol), num_eq_p_ii); + s7_set_p_dd_function(sc, global_value(sc->num_eq_symbol), num_eq_p_dd); + s7_set_p_pp_function(sc, global_value(sc->num_eq_symbol), num_eq_p_pp); + s7_set_b_7pp_function(sc, global_value(sc->num_eq_symbol), num_eq_b_7pp); + s7_set_b_ii_function(sc, global_value(sc->num_eq_symbol), num_eq_b_ii); + s7_set_b_dd_function(sc, global_value(sc->num_eq_symbol), num_eq_b_dd); + + s7_set_p_ii_function(sc, global_value(sc->lt_symbol), lt_p_ii); + s7_set_p_dd_function(sc, global_value(sc->lt_symbol), lt_p_dd); + s7_set_p_pp_function(sc, global_value(sc->lt_symbol), lt_p_pp); + s7_set_b_7pp_function(sc, global_value(sc->lt_symbol), lt_b_7pp); + s7_set_b_ii_function(sc, global_value(sc->lt_symbol), lt_b_ii); + s7_set_b_dd_function(sc, global_value(sc->lt_symbol), lt_b_dd); + + s7_set_b_ii_function(sc, global_value(sc->leq_symbol), leq_b_ii); + s7_set_p_dd_function(sc, global_value(sc->leq_symbol), leq_p_dd); + s7_set_p_ii_function(sc, global_value(sc->leq_symbol), leq_p_ii); + s7_set_b_dd_function(sc, global_value(sc->leq_symbol), leq_b_dd); + s7_set_p_pp_function(sc, global_value(sc->leq_symbol), leq_p_pp); + s7_set_b_7pp_function(sc, global_value(sc->leq_symbol), leq_b_7pp); + + s7_set_b_ii_function(sc, global_value(sc->gt_symbol), gt_b_ii); + s7_set_b_dd_function(sc, global_value(sc->gt_symbol), gt_b_dd); + s7_set_p_dd_function(sc, global_value(sc->gt_symbol), gt_p_dd); + s7_set_p_ii_function(sc, global_value(sc->gt_symbol), gt_p_ii); + s7_set_p_pp_function(sc, global_value(sc->gt_symbol), gt_p_pp); + s7_set_b_7pp_function(sc, global_value(sc->gt_symbol), gt_b_7pp); + + s7_set_b_ii_function(sc, global_value(sc->geq_symbol), geq_b_ii); + s7_set_b_dd_function(sc, global_value(sc->geq_symbol), geq_b_dd); + s7_set_p_ii_function(sc, global_value(sc->geq_symbol), geq_p_ii); + s7_set_p_dd_function(sc, global_value(sc->geq_symbol), geq_p_dd); + s7_set_p_pp_function(sc, global_value(sc->geq_symbol), geq_p_pp); + s7_set_b_7pp_function(sc, global_value(sc->geq_symbol), geq_b_7pp); + + s7_set_b_pp_function(sc, global_value(sc->is_eq_symbol), s7_is_eq); + s7_set_p_pp_function(sc, global_value(sc->is_eq_symbol), is_eq_p_pp); + s7_set_b_7pp_function(sc, global_value(sc->is_eqv_symbol), s7_is_eqv); + s7_set_p_pp_function(sc, global_value(sc->is_eqv_symbol), is_eqv_p_pp); + s7_set_b_7pp_function(sc, global_value(sc->is_equal_symbol), s7_is_equal); + s7_set_b_7pp_function(sc, global_value(sc->is_equivalent_symbol), s7_is_equivalent); + s7_set_p_pp_function(sc, global_value(sc->is_equal_symbol), is_equal_p_pp); + s7_set_p_pp_function(sc, global_value(sc->is_equivalent_symbol), is_equivalent_p_pp); + s7_set_p_pp_function(sc, global_value(sc->char_eq_symbol), char_eq_p_pp); + s7_set_p_pp_function(sc, global_value(sc->make_float_vector_symbol), make_float_vector_p_pp); + s7_set_p_pp_function(sc, global_value(sc->setter_symbol), setter_p_pp); + s7_set_p_pp_function(sc, global_value(sc->complex_symbol), complex_p_pp); + s7_set_p_pp_function(sc, global_value(sc->string_eq_symbol), string_eq_p_pp); + s7_set_p_pp_function(sc, global_value(sc->string_lt_symbol), string_lt_p_pp); + s7_set_p_pp_function(sc, global_value(sc->string_gt_symbol), string_gt_p_pp); + + s7_set_b_7pp_function(sc, global_value(sc->char_lt_symbol), char_lt_b_7pp); + s7_set_b_7pp_function(sc, global_value(sc->char_leq_symbol), char_leq_b_7pp); + s7_set_b_7pp_function(sc, global_value(sc->char_gt_symbol), char_gt_b_7pp); + s7_set_b_7pp_function(sc, global_value(sc->char_geq_symbol), char_geq_b_7pp); + s7_set_b_7pp_function(sc, global_value(sc->char_eq_symbol), char_eq_b_7pp); + s7_set_b_7pp_function(sc, global_value(sc->string_lt_symbol), string_lt_b_7pp); + s7_set_b_7pp_function(sc, global_value(sc->string_leq_symbol), string_leq_b_7pp); + s7_set_b_7pp_function(sc, global_value(sc->string_gt_symbol), string_gt_b_7pp); + s7_set_b_7pp_function(sc, global_value(sc->string_geq_symbol), string_geq_b_7pp); + s7_set_b_7pp_function(sc, global_value(sc->string_eq_symbol), string_eq_b_7pp); + + s7_set_b_pp_unchecked_function(sc, global_value(sc->char_lt_symbol), char_lt_b_unchecked); + s7_set_b_pp_unchecked_function(sc, global_value(sc->char_leq_symbol), char_leq_b_unchecked); + s7_set_b_pp_unchecked_function(sc, global_value(sc->char_gt_symbol), char_gt_b_unchecked); + s7_set_b_pp_unchecked_function(sc, global_value(sc->char_geq_symbol), char_geq_b_unchecked); + s7_set_b_pp_unchecked_function(sc, global_value(sc->char_eq_symbol), char_eq_b_unchecked); + s7_set_b_pp_unchecked_function(sc, global_value(sc->string_lt_symbol), string_lt_b_unchecked); + s7_set_b_pp_unchecked_function(sc, global_value(sc->string_leq_symbol), string_leq_b_unchecked); + s7_set_b_pp_unchecked_function(sc, global_value(sc->string_gt_symbol), string_gt_b_unchecked); + s7_set_b_pp_unchecked_function(sc, global_value(sc->string_geq_symbol), string_geq_b_unchecked); + s7_set_b_pp_unchecked_function(sc, global_value(sc->string_eq_symbol), string_eq_b_unchecked); + + s7_set_b_7pp_function(sc, global_value(sc->is_aritable_symbol), is_aritable_b_7pp); +} + +static void init_features(s7_scheme *sc) +{ + s7_provide(sc, "s7"); + s7_provide(sc, "s7-" S7_VERSION); + s7_provide(sc, "ratios"); /* changed from ratio 22-Aug-23; r7rs uses the plural */ + +#if HAVE_COMPLEX_NUMBERS + s7_provide(sc, "complex-numbers"); +#endif +#if WITH_GMP + s7_provide(sc, "gmp"); +#else + s7_provide(sc, "ieee-float"); /* why would anyone care? -- this is for r7rs -- why singular this time? */ +#endif +#if WITH_PURE_S7 + s7_provide(sc, "pure-s7"); +#endif +#if WITH_EXTRA_EXPONENT_MARKERS + s7_provide(sc, "dfls-exponents"); +#endif +#if HAVE_OVERFLOW_CHECKS + s7_provide(sc, "overflow-checks"); +#endif +#if WITH_SYSTEM_EXTRAS + s7_provide(sc, "system-extras"); +#endif +#if WITH_IMMUTABLE_UNQUOTE + s7_provide(sc, "immutable-unquote"); +#endif +#if S7_DEBUGGING + s7_provide(sc, "debugging"); +#endif +#if WITH_NUMBER_SEPARATOR + s7_provide(sc, "number-separator"); +#endif +#if WITH_HISTORY + s7_provide(sc, "history"); +#endif +#if WITH_C_LOADER + s7_provide(sc, "dlopen"); +#endif +#if (!DISABLE_AUTOLOAD) + s7_provide(sc, "autoload"); +#endif +#if S7_ALIGNED + s7_provide(sc, "aligned"); +#endif +#if POINTER_32 + s7_provide(sc, "32-bit"); +#endif + +#ifdef __APPLE__ + s7_provide(sc, "osx"); +#endif +#ifdef __linux__ + s7_provide(sc, "linux"); +#endif +#ifdef __OpenBSD__ + s7_provide(sc, "openbsd"); +#endif +#ifdef __NetBSD__ + s7_provide(sc, "netbsd"); +#endif +#ifdef __FreeBSD__ + s7_provide(sc, "freebsd"); +#endif +#if MS_WINDOWS + s7_provide(sc, "windows"); +#endif +#ifdef __bfin__ + s7_provide(sc, "blackfin"); +#endif +#ifdef __ANDROID__ + s7_provide(sc, "android"); +#endif +#ifdef __MSYS__ + s7_provide(sc, "msys2"); /* from chai xiaoxiang */ +#endif +#ifdef __MINGW32__ /* this is also defined in mingw64 */ + s7_provide(sc, "mingw"); +#endif +#ifdef __CYGWIN__ + s7_provide(sc, "cygwin"); /* this is also defined in msys2 */ +#endif +#ifdef __hpux + s7_provide(sc, "hpux"); +#endif +#if defined(__sun) && defined(__SVR4) + s7_provide(sc, "solaris"); +#endif + +#ifdef __SUNPRO_C + s7_provide(sc, "sunpro_c"); +#endif +#ifdef __clang__ + s7_provide(sc, "clang"); +#endif +#ifdef __GNUC__ + s7_provide(sc, "gcc"); +#endif +#ifdef __TINYC__ + s7_provide(sc, "tcc"); /* appears to be 3-4 times slower than gcc (compilation is at least 10 times faster however) */ +#endif +#ifdef __EMSCRIPTEN__ + s7_provide(sc, "emscripten"); +#endif +#ifdef _MSC_VER + s7_provide(sc, "msvc"); +#endif +} + +static void init_wrappers(s7_scheme *sc) +{ + s7_pointer cp, qp; + #define NUM_INTEGER_WRAPPERS 4 + #define NUM_REAL_WRAPPERS 4 + + sc->integer_wrappers = semipermanent_list(sc, NUM_INTEGER_WRAPPERS); + for (cp = sc->integer_wrappers, qp = sc->integer_wrappers; is_pair(cp); qp = cp, cp = cdr(cp)) + { + s7_pointer p = alloc_pointer(sc); + car(cp) = p; + full_type(p) = T_INTEGER | T_IMMUTABLE | T_MUTABLE | T_UNHEAP; /* mutable to turn off set_has_number_name */ + integer(p) = 0; + } + cdr(qp) = sc->integer_wrappers; + + sc->real_wrappers = semipermanent_list(sc, NUM_REAL_WRAPPERS); + for (cp = sc->real_wrappers, qp = sc->real_wrappers; is_pair(cp); qp = cp, cp = cdr(cp)) + { + s7_pointer p = alloc_pointer(sc); + car(cp) = p; + full_type(p) = T_REAL | T_IMMUTABLE | T_MUTABLE | T_UNHEAP; + real(p) = 0.0; + } + cdr(qp) = sc->real_wrappers; + + sc->string_wrappers = semipermanent_list(sc, NUM_STRING_WRAPPERS); + for (cp = sc->string_wrappers, qp = sc->string_wrappers; is_pair(cp); qp = cp, cp = cdr(cp)) + { + s7_pointer p = alloc_pointer(sc); + car(cp) = p; + full_type(p) = T_STRING | T_IMMUTABLE | T_SAFE_PROCEDURE | T_UNHEAP; + string_block(p) = NULL; + string_value(p) = NULL; + string_length(p) = 0; + string_hash(p) = 0; + } + cdr(qp) = sc->string_wrappers; +} + +static s7_pointer syntax(s7_scheme *sc, const char *name, opcode_t op, s7_pointer min_args, s7_pointer max_args, const char *doc) +{ + uint64_t hash = raw_string_hash((const uint8_t *)name, safe_strlen(name)); + uint32_t loc = hash % SYMBOL_TABLE_SIZE; + s7_pointer x = new_symbol(sc, name, safe_strlen(name), hash, loc); + s7_pointer syn = alloc_pointer(sc); + + set_full_type(syn, T_SYNTAX | T_SYNTACTIC | T_DONT_EVAL_ARGS | T_GLOBAL | T_UNHEAP); + syntax_opcode(syn) = op; + syntax_set_symbol(syn, x); + syntax_min_args(syn) = integer(min_args); + syntax_max_args(syn) = ((max_args == max_arity) ? -1 : integer(max_args)); + syntax_documentation(syn) = doc; + set_global_slot(x, make_semipermanent_slot(sc, x, syn)); + set_initial_slot(x, make_semipermanent_slot(sc, x, syn)); /* set_local_slot(x, global_slot(x)); */ + set_type_bit(x, T_SYMBOL | T_SYNTACTIC | T_GLOBAL | T_UNHEAP); + symbol_set_local_slot_unchecked(x, 0LL, sc->nil); + symbol_clear_ctr(x); + return(x); +} + +static s7_pointer definer_syntax(s7_scheme *sc, const char *name, opcode_t op, s7_pointer min_args, s7_pointer max_args, const char *doc) +{ + s7_pointer x = syntax(sc, name, op, min_args, max_args, doc); + set_syntax_is_definer(x); + return(x); +} + +static s7_pointer binder_syntax(s7_scheme *sc, const char *name, opcode_t op, s7_pointer min_args, s7_pointer max_args, const char *doc) +{ + s7_pointer x = syntax(sc, name, op, min_args, max_args, doc); + set_syntax_is_binder(x); + return(x); +} + +static s7_pointer copy_args_syntax(s7_scheme *sc, const char *name, opcode_t op, s7_pointer min_args, s7_pointer max_args, const char *doc) +{ + s7_pointer x = syntax(sc, name, op, min_args, max_args, doc); + s7_pointer p = global_value(x); + full_type(p) |= T_COPY_ARGS; + return(x); +} + +static s7_pointer make_unique(s7_scheme *sc, const char* name, uint64_t typ) +{ + s7_pointer p = alloc_pointer(sc); + set_full_type(p, typ | T_IMMUTABLE | T_UNHEAP); + if (typ != T_UNUSED) set_optimize_op(p, OP_CONSTANT); + if (typ == T_UNDEFINED) /* sc->undefined here to avoid the undefined_constant_warning */ + { + undefined_set_name_length(p, safe_strlen(name)); + undefined_name(p) = copy_string_with_length(name, undefined_name_length(p)); + } + else + { + unique_name_length(p) = safe_strlen(name); + unique_name(p) = copy_string_with_length(name, unique_name_length(p)); + add_saved_pointer(sc, (void *)unique_name(p)); + } + return(p); +} + +static s7_pointer symbol_set_1(s7_scheme *sc, s7_pointer sym, s7_pointer val) +{ + s7_pointer slot = s7_slot(sc, sym); + if (!is_slot(slot)) + error_nr(sc, sc->wrong_type_arg_symbol, set_elist_2(sc, wrap_string(sc, "set!: '~S is unbound", 20), sym)); + if (is_immutable_slot(slot)) + immutable_object_error_nr(sc, set_elist_3(sc, immutable_error_string, sc->symbol_symbol, sym)); + slot_set_value(slot, val); + return(val); +} + +static s7_pointer g_symbol_set(s7_scheme *sc, s7_pointer args) /* (set! (symbol ) ) */ +{ + s7_int i = 0, len; + s7_pointer lst, val; + if (is_null(cddr(args))) + return(symbol_set_1(sc, g_symbol(sc, set_plist_1(sc, car(args))), cadr(args))); + len = proper_list_length(args) - 1; + lst = safe_list_if_possible(sc, len); + if (in_heap(lst)) gc_protect_via_stack(sc, lst); + for (s7_pointer ap = args, lp = lst; i < len; ap = cdr(ap), lp = cdr(lp), i++) car(lp) = car(ap); + val = symbol_set_1(sc, g_symbol(sc, lst), s7_list_ref(sc, args, len)); + if (in_heap(lst)) unstack(sc); else clear_list_in_use(lst); + return(val); +} + +static void init_setters(s7_scheme *sc) +{ + sc->vector_set_function = global_value(sc->vector_set_symbol); + set_is_setter(sc->vector_set_symbol); + /* not float-vector-set! here */ + + sc->list_set_function = global_value(sc->list_set_symbol); + set_is_setter(sc->list_set_symbol); + + sc->hash_table_set_function = global_value(sc->hash_table_set_symbol); + set_is_setter(sc->hash_table_set_symbol); + + sc->let_set_function = global_value(sc->let_set_symbol); + set_is_setter(sc->let_set_symbol); + + sc->string_set_function = global_value(sc->string_set_symbol); + set_is_setter(sc->string_set_symbol); + + set_is_setter(sc->byte_vector_set_symbol); + set_is_setter(sc->set_car_symbol); + set_is_setter(sc->set_cdr_symbol); + set_is_safe_setter(sc->byte_vector_set_symbol); + set_is_safe_setter(sc->int_vector_set_symbol); + set_is_safe_setter(sc->float_vector_set_symbol); + set_is_safe_setter(sc->string_set_symbol); + +#if (WITH_PURE_S7) + /* we need to be able at least to set (current-output-port) to #f */ + c_function_set_setter(global_value(sc->current_input_port_symbol), + s7_make_safe_function(sc, "#", g_set_current_input_port, 1, 0, false, "*stdin* setter")); + c_function_set_setter(global_value(sc->current_output_port_symbol), + s7_make_safe_function(sc, "#", g_set_current_output_port, 1, 0, false, "*stdout* setter")); +#else + set_is_setter(sc->set_current_input_port_symbol); + set_is_setter(sc->set_current_output_port_symbol); + s7_function_set_setter(sc, sc->current_input_port_symbol, sc->set_current_input_port_symbol); + s7_function_set_setter(sc, sc->current_output_port_symbol, sc->set_current_output_port_symbol); +#endif + + set_is_setter(sc->set_current_error_port_symbol); + s7_function_set_setter(sc, sc->current_error_port_symbol, sc->set_current_error_port_symbol); + /* despite the similar names, current-error-port is different from the other two, and a setter is needed + * in scheme because error and warn send output to it by default. It is not a "dynamic variable". + */ + + s7_function_set_setter(sc, sc->car_symbol, sc->set_car_symbol); + s7_function_set_setter(sc, sc->cdr_symbol, sc->set_cdr_symbol); + s7_function_set_setter(sc, sc->hash_table_ref_symbol, sc->hash_table_set_symbol); + s7_function_set_setter(sc, sc->vector_ref_symbol, sc->vector_set_symbol); + s7_function_set_setter(sc, sc->float_vector_ref_symbol, sc->float_vector_set_symbol); + s7_function_set_setter(sc, sc->int_vector_ref_symbol, sc->int_vector_set_symbol); + s7_function_set_setter(sc, sc->byte_vector_ref_symbol, sc->byte_vector_set_symbol); + s7_function_set_setter(sc, sc->list_ref_symbol, sc->list_set_symbol); + s7_function_set_setter(sc, sc->let_ref_symbol, sc->let_set_symbol); + s7_function_set_setter(sc, sc->string_ref_symbol, sc->string_set_symbol); + c_function_set_setter(global_value(sc->outlet_symbol), + s7_make_safe_function(sc, "#", g_set_outlet, 2, 0, false, "outlet setter")); + c_function_set_setter(global_value(sc->port_line_number_symbol), + s7_make_safe_function(sc, "#", g_set_port_line_number, 1, 1, false, "port-line setter")); + c_function_set_setter(global_value(sc->port_position_symbol), + s7_make_safe_function(sc, "#", g_set_port_position, 2, 0, false, "port-position setter")); + c_function_set_setter(global_value(sc->vector_typer_symbol), + s7_make_safe_function(sc, "#", g_set_vector_typer, 2, 0, false, "vector-typer setter")); + c_function_set_setter(global_value(sc->hash_table_key_typer_symbol), + s7_make_safe_function(sc, "#", g_set_hash_table_key_typer, 2, 0, false, "hash-table-key-typer setter")); + c_function_set_setter(global_value(sc->hash_table_value_typer_symbol), + s7_make_safe_function(sc, "#", g_set_hash_table_value_typer, 2, 0, false, "hash-table-value-typer setter")); + c_function_set_setter(global_value(sc->symbol_symbol), s7_make_safe_function(sc, "symbol-set", g_symbol_set, 2, 0, true, "symbol setter")); +} + +static void init_syntax(s7_scheme *sc) +{ + #define H_quote "(quote obj) returns obj unevaluated. 'obj is an abbreviation for (quote obj)." + #define H_if "(if expr true-stuff optional-false-stuff) evaluates expr, then if it is true, evaluates true-stuff; otherwise, \ +if optional-false-stuff exists, it is evaluated." + #define H_when "(when expr ...) evaluates expr, and if it is true, evaluates each form in its body, returning the value of the last" + #define H_unless "(unless expr ...) evaluates expr, and if it is false, evaluates each form in its body, returning the value of the last" + #define H_begin "(begin ...) evaluates each form in its body, returning the value of the last one" + #define H_set "(set! variable value) sets the value of variable to value." + #define H_let "(let ((var val)...) ...) binds each variable to its initial value, then evaluates its body,\ +returning the value of the last form. The let variables are local to it, and are not available for use until all have been initialized." + #define H_let_star "(let* ((var val)...) ...) binds each variable to its initial value, then evaluates its body, \ +returning the value of the last form. The let* variables are local to it, and are available immediately." + #define H_letrec "(letrec ((var (lambda ...)))...) is like let, but var can refer to itself in its value \ +(i.e. you can define local recursive functions)" + #define H_letrec_star "(letrec* ((var val))...) is like letrec, but successive bindings are handled as in let*" + #define H_cond "(cond (expr clause...)...) is like if..then. Each expr is evaluated in order, and if one is not #f, \ +the associated clauses are evaluated, whereupon cond returns." + #define H_and "(and expr expr ...) evaluates each of its arguments in order, quitting (and returning #f) \ +as soon as one of them returns #f. If all are non-#f, it returns the last value." + #define H_or "(or expr expr ...) evaluates each of its arguments in order, quitting as soon as one of them is not #f. \ +If all are #f, or returns #f." + #define H_case "(case val ((key...) clause...)...) looks for val in the various lists of keys, and if a \ +match is found (via eqv?), the associated clauses are evaluated, and case returns." + #define H_do "(do (vars...) (loop control and return value) ...) is a do-loop." + #define H_lambda "(lambda args ...) returns a function." + #define H_lambda_star "(lambda* args ...) returns a function; the args list can have default values, \ +the parameters themselves can be accessed via keywords." + #define H_define "(define var val) assigns val to the variable (symbol) var. (define (func args) ...) is \ +shorthand for (define func (lambda args ...))" + #define H_define_star "(define* (func args) ...) defines a function with optional/keyword arguments." + #define H_define_constant "(define-constant var val) defines var to be a constant (it can't be set or bound), with the value val." + #define H_define_macro "(define-macro (mac args) ...) defines mac to be a macro." + #define H_define_macro_star "(define-macro* (mac args) ...) defines mac to be a macro with optional/keyword arguments." + #define H_macro "(macro args ...) defines an unnamed macro." + #define H_macro_star "(macro* args ...) defines an unnamed macro with optional/keyword arguments." + #define H_define_expansion "(define-expansion (mac args) ...) defines mac to be a read-time macro." + #define H_define_expansion_star "(define-expansion* (mac args) ...) defines mac to be a read-time macro*." + #define H_define_bacro "(define-bacro (mac args) ...) defines mac to be a bacro." + #define H_define_bacro_star "(define-bacro* (mac args) ...) defines mac to be a bacro with optional/keyword arguments." + #define H_bacro "(bacro args ...) defines an unnamed bacro." + #define H_bacro_star "(bacro* args ...) defines an unnamed bacro with optional/keyword arguments." + #define H_with_baffle "(with-baffle ...) evaluates its body in a context that blocks re-entry via call/cc." + #define H_macroexpand "(macroexpand macro-call) returns the result of the expansion phase of evaluating the macro call." + #define H_with_let "(with-let let ...) evaluates its body in the environment let." + #define H_let_temporarily "(let-temporarily ((var value)...) . body) sets each var to its new value, evals body, \ +then returns each var to its original value." + + sc->quote_symbol = syntax(sc, "quote", OP_QUOTE, int_one, int_one, H_quote); + sc->if_symbol = syntax(sc, "if", OP_IF, int_two, int_three, H_if); + sc->when_symbol = syntax(sc, "when", OP_WHEN, int_two, max_arity, H_when); + sc->unless_symbol = syntax(sc, "unless", OP_UNLESS, int_two, max_arity, H_unless); + sc->begin_symbol = syntax(sc, "begin", OP_BEGIN, int_zero, max_arity, H_begin); /* (begin) is () */ + sc->set_symbol = syntax(sc, "set!", OP_SET, int_two, int_two, H_set); + sc->cond_symbol = copy_args_syntax(sc, "cond", OP_COND, int_one, max_arity, H_cond); + sc->and_symbol = copy_args_syntax(sc, "and", OP_AND, int_zero, max_arity, H_and); + sc->or_symbol = copy_args_syntax(sc, "or", OP_OR, int_zero, max_arity, H_or); + sc->case_symbol = syntax(sc, "case", OP_CASE, int_two, max_arity, H_case); + sc->macroexpand_symbol = syntax(sc, "macroexpand", OP_MACROEXPAND, int_one, int_one, H_macroexpand); + sc->let_temporarily_symbol = syntax(sc, "let-temporarily", OP_LET_TEMPORARILY, int_two, max_arity, H_let_temporarily); + sc->define_symbol = definer_syntax(sc, "define", OP_DEFINE, int_two, max_arity, H_define); + sc->define_star_symbol = definer_syntax(sc, "define*", OP_DEFINE_STAR, int_two, max_arity, H_define_star); + sc->define_constant_symbol = definer_syntax(sc, "define-constant", OP_DEFINE_CONSTANT, int_two, max_arity, H_define_constant); + sc->define_macro_symbol = definer_syntax(sc, "define-macro", OP_DEFINE_MACRO, int_two, max_arity, H_define_macro); + sc->define_macro_star_symbol = definer_syntax(sc, "define-macro*", OP_DEFINE_MACRO_STAR, int_two, max_arity, H_define_macro_star); + sc->define_expansion_symbol = definer_syntax(sc, "define-expansion",OP_DEFINE_EXPANSION, int_two, max_arity, H_define_expansion); + sc->define_expansion_star_symbol = definer_syntax(sc, "define-expansion*",OP_DEFINE_EXPANSION_STAR, int_two, max_arity, H_define_expansion_star); + sc->define_bacro_symbol = definer_syntax(sc, "define-bacro", OP_DEFINE_BACRO, int_two, max_arity, H_define_bacro); + sc->define_bacro_star_symbol = definer_syntax(sc, "define-bacro*", OP_DEFINE_BACRO_STAR, int_two, max_arity, H_define_bacro_star); + sc->let_symbol = binder_syntax(sc, "let", OP_LET, int_two, max_arity, H_let); + sc->let_star_symbol = binder_syntax(sc, "let*", OP_LET_STAR, int_two, max_arity, H_let_star); + sc->letrec_symbol = binder_syntax(sc, "letrec", OP_LETREC, int_two, max_arity, H_letrec); + sc->letrec_star_symbol = binder_syntax(sc, "letrec*", OP_LETREC_STAR, int_two, max_arity, H_letrec_star); + sc->do_symbol = binder_syntax(sc, "do", OP_DO, int_two, max_arity, H_do); /* 2 because body can be null */ + sc->lambda_symbol = binder_syntax(sc, "lambda", OP_LAMBDA, int_two, max_arity, H_lambda); + sc->lambda_star_symbol = binder_syntax(sc, "lambda*", OP_LAMBDA_STAR, int_two, max_arity, H_lambda_star); + sc->macro_symbol = binder_syntax(sc, "macro", OP_MACRO, int_two, max_arity, H_macro); + sc->macro_star_symbol = binder_syntax(sc, "macro*", OP_MACRO_STAR, int_two, max_arity, H_macro_star); + sc->bacro_symbol = binder_syntax(sc, "bacro", OP_BACRO, int_two, max_arity, H_bacro); + sc->bacro_star_symbol = binder_syntax(sc, "bacro*", OP_BACRO_STAR, int_two, max_arity, H_bacro_star); + sc->with_let_symbol = binder_syntax(sc, "with-let", OP_WITH_LET, int_one, max_arity, H_with_let); + sc->with_baffle_symbol = binder_syntax(sc, "with-baffle", OP_WITH_BAFFLE, int_zero, max_arity, H_with_baffle); /* (with-baffle) is () */ + set_local_slot(sc->with_let_symbol, global_slot(sc->with_let_symbol)); /* for set_locals */ + set_immutable(sc->with_let_symbol); + set_immutable_slot(global_slot(sc->with_let_symbol)); + sc->setter_symbol = make_symbol(sc, "setter", 6); + +#if WITH_IMMUTABLE_UNQUOTE + /* this code solves the various unquote redefinition troubles + * if "," -> "(unquote...)" in the reader, (let (, (lambda (x) (+ x 1))) ,,,,'1) -> 5 + */ + sc->unquote_symbol = make_symbol(sc, ",", 1); + set_immutable(sc->unquote_symbol); +#else + sc->unquote_symbol = make_symbol(sc, "unquote", 7); +#endif + + sc->feed_to_symbol = make_symbol(sc, "=>", 2); + sc->body_symbol = make_symbol(sc, "body", 4); + sc->read_error_symbol = make_symbol(sc, "read-error", 10); + sc->string_read_error_symbol = make_symbol(sc, "string-read-error", 17); + sc->syntax_error_symbol = make_symbol(sc, "syntax-error", 12); + sc->unbound_variable_symbol = make_symbol(sc, "unbound-variable", 16); + sc->wrong_type_arg_symbol = make_symbol(sc, "wrong-type-arg", 14); + sc->wrong_number_of_args_symbol = make_symbol(sc, "wrong-number-of-args", 20); + sc->format_error_symbol = make_symbol(sc, "format-error", 12); + sc->autoload_error_symbol = make_symbol(sc, "autoload-error", 14); + sc->out_of_range_symbol = make_symbol(sc, "out-of-range", 12); + sc->out_of_memory_symbol = make_symbol(sc, "out-of-memory", 13); + sc->io_error_symbol = make_symbol(sc, "io-error", 8); + sc->missing_method_symbol = make_symbol(sc, "missing-method", 14); + sc->number_to_real_symbol = make_symbol(sc, "number_to_real", 14); + sc->invalid_escape_function_symbol = make_symbol(sc, "invalid-escape-function", 23); + sc->immutable_error_symbol = make_symbol(sc, "immutable-error", 15); + sc->division_by_zero_symbol = make_symbol(sc, "division-by-zero", 16); + sc->bad_result_symbol = make_symbol(sc, "bad-result", 10); + sc->no_setter_symbol = make_symbol(sc, "no-setter", 9); + sc->baffled_symbol = make_symbol(sc, "baffled!", 8); + sc->value_symbol = make_symbol(sc, "value", 5); + sc->type_symbol = make_symbol(sc, "type", 4); + sc->position_symbol = make_symbol(sc, "position", 8); + sc->file_symbol = make_symbol(sc, "file", 4); + sc->line_symbol = make_symbol(sc, "line", 4); + sc->function_symbol = make_symbol(sc, "function", 8); + sc->else_symbol = make_symbol(sc, "else", 4); + s7_make_slot(sc, sc->nil, sc->else_symbol, sc->else_symbol); + slot_set_value(initial_slot(sc->else_symbol), sc->T); + /* if we set #_else to 'else, it can pick up a local else value: (let ((else #f)) (cond (#_else 2)...)) */ + + sc->allow_other_keys_keyword = s7_make_keyword(sc, "allow-other-keys"); + sc->rest_keyword = s7_make_keyword(sc, "rest"); + sc->if_keyword = s7_make_keyword(sc, "if"); /* internal optimizer local-let marker */ + sc->readable_keyword = s7_make_keyword(sc, "readable"); + sc->display_keyword = s7_make_keyword(sc, "display"); + sc->write_keyword = s7_make_keyword(sc, "write"); +} + +static void init_rootlet(s7_scheme *sc) +{ + /* most of init_rootlet (the built-in functions for example), could be shared by all s7 instances. + * currently, each s7_init call allocates room for them, then s7_free frees it -- kinda wasteful. + * allocate separately filling unlet then set symbols in init_rootlet by running through unlet and calling s7_define for each? + * need pre-unlet separate from thread-local unlet (dynamic loads). + * but currently the init_unlet run through the symbol table is wasting lots of time. + * unlet has only c_functions/syntax but should we support #_gsl* etc? + * split init_unlet, add load to defun macros + */ + s7_pointer sym; + init_syntax(sc); + + sc->owlet = init_owlet(sc); + + sc->wrong_type_arg_info = semipermanent_list(sc, 6); + set_car(sc->wrong_type_arg_info, s7_make_semipermanent_string(sc, "~A ~:D argument, ~S, is ~A but should be ~A")); + + sc->sole_arg_wrong_type_info = semipermanent_list(sc, 5); + set_car(sc->sole_arg_wrong_type_info, s7_make_semipermanent_string(sc, "~A argument, ~S, is ~A but should be ~A")); + + sc->out_of_range_info = semipermanent_list(sc, 5); + set_car(sc->out_of_range_info, s7_make_semipermanent_string(sc, "~A ~:D argument, ~S, is out of range (~A)")); + + sc->sole_arg_out_of_range_info = semipermanent_list(sc, 4); + set_car(sc->sole_arg_out_of_range_info, s7_make_semipermanent_string(sc, "~A argument, ~S, is out of range (~A)")); + + sc->gc_off = false; + + #define defun(Scheme_Name, C_Name, Req, Opt, Rst) \ + s7_define_typed_function(sc, Scheme_Name, g_ ## C_Name, Req, Opt, Rst, H_ ## C_Name, Q_ ## C_Name) + + #define unsafe_defun(Scheme_Name, C_Name, Req, Opt, Rst) \ + s7_define_unsafe_typed_function(sc, Scheme_Name, g_ ## C_Name, Req, Opt, Rst, H_ ## C_Name, Q_ ## C_Name) + + #define semisafe_defun(Scheme_Name, C_Name, Req, Opt, Rst) \ + s7_define_semisafe_typed_function(sc, Scheme_Name, g_ ## C_Name, Req, Opt, Rst, H_ ## C_Name, Q_ ## C_Name) + + #define b_defun(Scheme_Name, C_Name, Opt, SymId, Marker, Simple) \ + define_bool_function(sc, Scheme_Name, g_ ## C_Name, Opt, H_ ## C_Name, Q_ ## C_Name, SymId, Marker, Simple, b_ ## C_Name ## _setter) + + /* we need the sc->is_* symbols first for the procedure signature lists */ + sc->is_boolean_symbol = make_symbol(sc, "boolean?", 8); + sc->pl_bt = s7_make_signature(sc, 2, sc->is_boolean_symbol, sc->T); + + sc->is_symbol_symbol = b_defun("symbol?", is_symbol, 0, T_SYMBOL, mark_symbol_vector, true); + sc->is_syntax_symbol = b_defun("syntax?", is_syntax, 0, T_SYNTAX, just_mark_vector, true); + sc->is_gensym_symbol = b_defun("gensym?", is_gensym, 0, T_FREE, mark_symbol_vector, true); + sc->is_keyword_symbol = b_defun("keyword?", is_keyword, 0, T_FREE, just_mark_vector, true); + sc->is_let_symbol = b_defun("let?", is_let, 0, T_LET, mark_vector_1, false); + sc->is_openlet_symbol = b_defun("openlet?", is_openlet, 0, T_FREE, mark_vector_1, false); + sc->is_iterator_symbol = b_defun("iterator?", is_iterator, 0, T_ITERATOR, mark_vector_1, false); + sc->is_macro_symbol = b_defun("macro?", is_macro, 0, T_FREE, mark_vector_1, false); + sc->is_c_pointer_symbol = b_defun("c-pointer?", is_c_pointer, 1, T_C_POINTER, mark_vector_1, false); + sc->is_input_port_symbol = b_defun("input-port?", is_input_port, 0, T_INPUT_PORT, mark_vector_1, true); + sc->is_output_port_symbol = b_defun("output-port?", is_output_port, 0, T_OUTPUT_PORT, mark_simple_vector, true); + sc->is_eof_object_symbol = b_defun("eof-object?", is_eof_object, 0, T_EOF, just_mark_vector, true); + sc->is_integer_symbol = b_defun("integer?", is_integer, 0, (WITH_GMP) ? T_FREE : T_INTEGER, mark_simple_vector, true); + sc->is_byte_symbol = b_defun("byte?", is_byte, 0, T_FREE, mark_simple_vector, true); + sc->is_number_symbol = b_defun("number?", is_number, 0, T_FREE, mark_simple_vector, true); + sc->is_real_symbol = b_defun("real?", is_real, 0, T_FREE, mark_simple_vector, true); + sc->is_float_symbol = b_defun("float?", is_float, 0, T_FREE, mark_simple_vector, true); + sc->is_complex_symbol = b_defun("complex?", is_complex, 0, T_FREE, mark_simple_vector, true); + sc->is_rational_symbol = b_defun("rational?", is_rational, 0, T_FREE, mark_simple_vector, true); + sc->is_random_state_symbol = b_defun("random-state?", is_random_state, 0, T_RANDOM_STATE, mark_simple_vector, true); + sc->is_char_symbol = b_defun("char?", is_char, 0, T_CHARACTER, just_mark_vector, true); + sc->is_string_symbol = b_defun("string?", is_string, 0, T_STRING, mark_simple_vector, true); + sc->is_list_symbol = b_defun("list?", is_list, 0, T_FREE, mark_vector_1, false); + sc->is_pair_symbol = b_defun("pair?", is_pair, 0, T_PAIR, mark_vector_1, false); + sc->is_vector_symbol = b_defun("vector?", is_vector, 0, T_FREE, mark_vector_1, false); + sc->is_float_vector_symbol = b_defun("float-vector?", is_float_vector, 0, T_FLOAT_VECTOR, mark_simple_vector, true); + sc->is_int_vector_symbol = b_defun("int-vector?", is_int_vector, 0, T_INT_VECTOR, mark_simple_vector, true); + sc->is_byte_vector_symbol = b_defun("byte-vector?", is_byte_vector, 0, T_BYTE_VECTOR, mark_simple_vector, true); + sc->is_hash_table_symbol = b_defun("hash-table?", is_hash_table, 0, T_HASH_TABLE, mark_vector_1, false); + sc->is_continuation_symbol = b_defun("continuation?", is_continuation, 0, T_CONTINUATION, mark_vector_1, false); + sc->is_procedure_symbol = b_defun("procedure?", is_procedure, 0, T_FREE, mark_vector_1, false); + sc->is_dilambda_symbol = b_defun("dilambda?", is_dilambda, 0, T_FREE, mark_vector_1, false); + /* set above */ b_defun("boolean?", is_boolean, 0, T_BOOLEAN, just_mark_vector, true); + sc->is_proper_list_symbol = b_defun("proper-list?", is_proper_list, 0, T_FREE, mark_vector_1, false); + sc->is_sequence_symbol = b_defun("sequence?", is_sequence, 0, T_FREE, mark_vector_1, false); + sc->is_null_symbol = b_defun("null?", is_null, 0, T_NIL, just_mark_vector, true); + sc->is_undefined_symbol = b_defun("undefined?", is_undefined, 0, T_UNDEFINED, just_mark_vector, true); + sc->is_unspecified_symbol = b_defun("unspecified?", is_unspecified, 0, T_UNSPECIFIED, just_mark_vector, true); + sc->is_c_object_symbol = b_defun("c-object?", is_c_object, 0, T_C_OBJECT, mark_vector_1, false); + sc->is_subvector_symbol = b_defun("subvector?", is_subvector, 0, T_FREE, mark_vector_1, false); + sc->is_weak_hash_table_symbol = b_defun("weak-hash-table?", is_weak_hash_table, 0, T_FREE, mark_vector_1, false); + sc->is_goto_symbol = b_defun("goto?", is_goto, 0, T_GOTO, mark_vector_1, true); + + /* these are for signatures */ + sc->not_symbol = defun("not", not, 1, 0, false); + sc->is_integer_or_real_at_end_symbol = make_symbol(sc, "integer:real?", 13); + sc->is_integer_or_any_at_end_symbol = make_symbol(sc, "integer:any?", 12); + + sc->pl_p = s7_make_signature(sc, 2, sc->T, sc->is_pair_symbol); + sc->pl_tl = s7_make_signature(sc, 3, + s7_make_signature(sc, 2, sc->is_pair_symbol, sc->not_symbol), sc->T, sc->is_list_symbol); /* memq and memv signature */ + sc->pl_bc = s7_make_signature(sc, 2, sc->is_boolean_symbol, sc->is_char_symbol); + sc->pl_bn = s7_make_signature(sc, 2, sc->is_boolean_symbol, sc->is_number_symbol); + sc->pl_nn = s7_make_signature(sc, 2, sc->is_number_symbol, sc->is_number_symbol); + sc->pl_sf = s7_make_signature(sc, 3, sc->T, sc->is_string_symbol, s7_make_signature(sc, 2, sc->is_procedure_symbol, sc->is_macro_symbol)); + sc->pcl_bt = s7_make_circular_signature(sc, 1, 2, sc->is_boolean_symbol, sc->T); + sc->pcl_bc = s7_make_circular_signature(sc, 1, 2, sc->is_boolean_symbol, sc->is_char_symbol); + sc->pcl_bs = s7_make_circular_signature(sc, 1, 2, sc->is_boolean_symbol, sc->is_string_symbol); + sc->pcl_i = s7_make_circular_signature(sc, 0, 1, sc->is_integer_symbol); + sc->pcl_r = s7_make_circular_signature(sc, 0, 1, sc->is_real_symbol); + sc->pcl_f = s7_make_circular_signature(sc, 0, 1, sc->is_rational_symbol); + sc->pcl_n = s7_make_circular_signature(sc, 0, 1, sc->is_number_symbol); + sc->pcl_s = s7_make_circular_signature(sc, 0, 1, sc->is_string_symbol); + sc->pcl_v = s7_make_circular_signature(sc, 0, 1, sc->is_vector_symbol); + sc->pcl_c = s7_make_circular_signature(sc, 0, 1, sc->is_char_symbol); + sc->pcl_e = s7_make_circular_signature(sc, 0, 1, + s7_make_signature(sc, 4, sc->is_let_symbol, sc->is_procedure_symbol, sc->is_macro_symbol, sc->is_c_object_symbol)); + + sc->values_symbol = make_symbol(sc, "values", 6); + + sc->is_bignum_symbol = defun("bignum?", is_bignum, 1, 0, false); + sc->bignum_symbol = defun("bignum", bignum, 1, 1, false); + + sc->gensym_symbol = defun("gensym", gensym, 0, 1, false); + sc->symbol_table_symbol = defun("symbol-table", symbol_table, 0, 0, false); + sc->symbol_to_string_symbol = defun("symbol->string", symbol_to_string, 1, 0, false); + sc->string_to_symbol_symbol = defun("string->symbol", string_to_symbol, 1, 0, false); + sc->symbol_symbol = defun("symbol", symbol, 1, 0, true); + sc->symbol_to_value_symbol = defun("symbol->value", symbol_to_value, 1, 1, false); + sc->symbol_to_dynamic_value_symbol = defun("symbol->dynamic-value", symbol_to_dynamic_value, 1, 0, false); + sc->immutable_symbol = defun("immutable!", immutable, 1, 0, false); + sc->is_immutable_symbol = defun("immutable?", is_immutable, 1, 0, false); + sc->is_constant_symbol = defun("constant?", is_constant, 1, 0, false); + sc->string_to_keyword_symbol = defun("string->keyword", string_to_keyword, 1, 0, false); + sc->symbol_to_keyword_symbol = defun("symbol->keyword", symbol_to_keyword, 1, 0, false); + sc->keyword_to_symbol_symbol = defun("keyword->symbol", keyword_to_symbol, 1, 0, false); + + sc->outlet_symbol = unsafe_defun("outlet", outlet, 1, 0, false); + sc->rootlet_symbol = unsafe_defun("rootlet", rootlet, 0, 0, false); /* unsafe else unbound var in g_is_defined_in_rootlet? */ + sc->curlet_symbol = unsafe_defun("curlet", curlet, 0, 0, false); /* (define (f a) (curlet)) exports the funclet */ + set_func_is_definer(sc->curlet_symbol); + sc->unlet_symbol = defun("unlet", unlet, 0, 0, false); + set_local_slot(sc->unlet_symbol, global_slot(sc->unlet_symbol)); /* for set_locals */ + set_immutable(sc->unlet_symbol); + set_immutable_slot(global_slot(sc->unlet_symbol)); + /* unlet (and with-let) don't actually need to be immutable, but s7.html says they are... */ + sc->is_funclet_symbol = defun("funclet?", is_funclet, 1, 0, false); + sc->sublet_symbol = defun("sublet", sublet, 1, 0, true); + sc->varlet_symbol = semisafe_defun("varlet", varlet, 2, 0, true); /* was 1,0 13-Aug-22 */ + set_func_is_definer(sc->varlet_symbol); + sc->cutlet_symbol = semisafe_defun("cutlet", cutlet, 2, 0, true); /* was 1,0 13-Aug-22 */ + set_func_is_definer(sc->cutlet_symbol); + sc->inlet_symbol = defun("inlet", inlet, 0, 0, true); + sc->owlet_symbol = defun("owlet", owlet, 0, 0, false); + sc->coverlet_symbol = defun("coverlet", coverlet, 1, 0, false); + sc->openlet_symbol = defun("openlet", openlet, 1, 0, false); + sc->let_ref_symbol = defun("let-ref", let_ref, 2, 0, false); + set_immutable(sc->let_ref_symbol); /* 16-Sep-19 */ + set_immutable_slot(global_slot(sc->let_ref_symbol)); + sc->let_set_symbol = defun("let-set!", let_set, 3, 0, false); + set_immutable(sc->let_set_symbol); + set_immutable_slot(global_slot(sc->let_set_symbol)); + sc->let_ref_fallback_symbol = make_symbol(sc, "let-ref-fallback", 16); + sc->let_set_fallback_symbol = make_symbol(sc, "let-set-fallback", 16); /* was let-set!-fallback until 9-Oct-17 */ + + sc->make_iterator_symbol = defun("make-iterator", make_iterator, 1, 1, false); + sc->iterate_symbol = defun("iterate", iterate, 1, 0, false); + sc->iterator_sequence_symbol = defun("iterator-sequence", iterator_sequence, 1, 0, false); + sc->iterator_is_at_end_symbol = defun("iterator-at-end?", iterator_is_at_end, 1, 0, false); + + sc->is_provided_symbol = defun("provided?", is_provided, 1, 0, false); + sc->provide_symbol = semisafe_defun("provide", provide, 1, 0, false); /* can add *features* to curlet */ + set_func_is_definer(sc->provide_symbol); + sc->is_defined_symbol = defun("defined?", is_defined, 1, 2, false); + + sc->c_object_type_symbol = defun("c-object-type", c_object_type, 1, 0, false); + sc->c_pointer_symbol = defun("c-pointer", c_pointer, 1, 4, false); + sc->c_pointer_info_symbol = defun("c-pointer-info", c_pointer_info, 1, 0, false); + sc->c_pointer_type_symbol = defun("c-pointer-type", c_pointer_type, 1, 0, false); + sc->c_pointer_weak1_symbol = defun("c-pointer-weak1", c_pointer_weak1, 1, 0, false); + sc->c_pointer_weak2_symbol = defun("c-pointer-weak2", c_pointer_weak2, 1, 0, false); + sc->c_pointer_to_list_symbol = defun("c-pointer->list", c_pointer_to_list, 1, 0, false); + + sc->port_file_symbol = defun("port-file", port_file, 1, 0, false); + sc->port_position_symbol = defun("port-position", port_position, 1, 0, false); + sc->port_line_number_symbol = defun("port-line-number", port_line_number, 0, 1, false); + sc->port_filename_symbol = defun("port-filename", port_filename, 0, 1, false); + sc->pair_line_number_symbol = defun("pair-line-number", pair_line_number, 1, 0, false); + sc->pair_filename_symbol = defun("pair-filename", pair_filename, 1, 0, false); + sc->is_port_closed_symbol = defun("port-closed?", is_port_closed, 1, 0, false); + + sc->current_input_port_symbol = defun("current-input-port", current_input_port, 0, 0, false); + sc->current_output_port_symbol = defun("current-output-port", current_output_port, 0, 0, false); + sc->current_error_port_symbol = defun("current-error-port", current_error_port, 0, 0, false); + sc->set_current_error_port_symbol = defun("set-current-error-port", set_current_error_port, 1, 0, false); +#if (!WITH_PURE_S7) + sc->let_to_list_symbol = defun("let->list", let_to_list, 1, 0, false); + sc->set_current_input_port_symbol = defun("set-current-input-port", set_current_input_port, 1, 0, false); + sc->set_current_output_port_symbol = defun("set-current-output-port", set_current_output_port, 1, 0, false); + sc->is_char_ready_symbol = defun("char-ready?", is_char_ready, 0, 1, false); /* the least-used scheme function */ +#endif + + sc->close_input_port_symbol = defun("close-input-port", close_input_port, 1, 0, false); + sc->close_output_port_symbol = defun("close-output-port", close_output_port, 1, 0, false); + sc->flush_output_port_symbol = defun("flush-output-port", flush_output_port, 0, 1, false); + sc->open_input_file_symbol = defun("open-input-file", open_input_file, 1, 1, false); + sc->open_output_file_symbol = defun("open-output-file", open_output_file, 1, 1, false); + sc->open_input_string_symbol = defun("open-input-string", open_input_string, 1, 0, false); + sc->open_output_string_symbol = defun("open-output-string", open_output_string, 0, 0, false); + sc->get_output_string_symbol = defun("get-output-string", get_output_string, 1, 1, false); + sc->get_output_string_uncopied = s7_make_safe_function(sc, "get-output-string", g_get_output_string_uncopied, 1, 1, false, NULL); + sc->open_input_function_symbol = defun("open-input-function",open_input_function, 1, 0, false); + sc->open_output_function_symbol = defun("open-output-function",open_output_function, 1, 0, false); + + sc->closed_input_function = s7_make_safe_function(sc, "closed-input-function", g_closed_input_function_port, 2, 0, false, "input-function error"), + sc->closed_output_function = s7_make_safe_function(sc, "closed-output-function", g_closed_output_function_port, 1, 0, false, "output-function error"), + + sc->newline_symbol = defun("newline", newline, 0, 1, false); + sc->write_symbol = defun("write", write, 1, 1, false); + sc->display_symbol = defun("display", display, 1, 1, false); + sc->read_char_symbol = defun("read-char", read_char, 0, 1, false); + sc->peek_char_symbol = defun("peek-char", peek_char, 0, 1, false); + sc->write_char_symbol = defun("write-char", write_char, 1, 1, false); + sc->write_string_symbol = defun("write-string", write_string, 1, 3, false); + sc->read_byte_symbol = defun("read-byte", read_byte, 0, 1, false); + sc->write_byte_symbol = defun("write-byte", write_byte, 1, 1, false); + sc->read_line_symbol = defun("read-line", read_line, 0, 2, false); + sc->read_string_symbol = defun("read-string", read_string, 1, 1, false); + sc->read_symbol = semisafe_defun("read", read, 0, 1, false); + /* read can't be safe because it messes with the stack, expecting to be all by itself in the call sequence + * (not embedded in OP_SAFE_C_opSq for example) -- that is, it pushes OP_READ_INTERNAL, then returns + * expecting continue (goto top-of-eval-loop), which would be nonsense if arg=fn|x_proc(read) -> fn|x_proc(arg). + * a safe procedure leaves its argument list alone, does not push anything on the stack, + * and leaves sc->code|args unscathed (fx_call assumes that is the case). The stack part can + * be hidden: if a c_function calls s7_apply_function (lambda passed as arg as in some clm gens) + * then is called with args that use fx*, and the lambda func does the same, the two calls + * can step on each other. + */ + + sc->call_with_input_string_symbol = semisafe_defun("call-with-input-string", call_with_input_string, 2, 0, false); /* body unsafe if func=read */ + sc->call_with_input_file_symbol = semisafe_defun("call-with-input-file", call_with_input_file, 2, 0, false); + sc->with_input_from_string_symbol = semisafe_defun("with-input-from-string", with_input_from_string, 2, 0, false); + sc->with_input_from_file_symbol = semisafe_defun("with-input-from-file", with_input_from_file, 2, 0, false); + + sc->call_with_output_string_symbol = semisafe_defun("call-with-output-string", call_with_output_string, 1, 0, false); + sc->call_with_output_file_symbol = semisafe_defun("call-with-output-file", call_with_output_file, 2, 0, false); + sc->with_output_to_string_symbol = semisafe_defun("with-output-to-string", with_output_to_string, 1, 0, false); + sc->with_output_to_file_symbol = semisafe_defun("with-output-to-file", with_output_to_file, 2, 0, false); + +#if WITH_SYSTEM_EXTRAS + sc->is_directory_symbol = defun("directory?", is_directory, 1, 0, false); + sc->file_exists_symbol = defun("file-exists?", file_exists, 1, 0, false); + sc->delete_file_symbol = defun("delete-file", delete_file, 1, 0, false); + sc->getenv_symbol = defun("getenv", getenv, 1, 0, false); + sc->system_symbol = defun("system", system, 1, 1, false); +#if (!MS_WINDOWS) + sc->directory_to_list_symbol = defun("directory->list", directory_to_list, 1, 0, false); + sc->file_mtime_symbol = defun("file-mtime", file_mtime, 1, 0, false); +#endif +#endif + + sc->real_part_symbol = defun("real-part", real_part, 1, 0, false); + sc->imag_part_symbol = defun("imag-part", imag_part, 1, 0, false); + sc->numerator_symbol = defun("numerator", numerator, 1, 0, false); + sc->denominator_symbol = defun("denominator", denominator, 1, 0, false); + sc->is_even_symbol = defun("even?", is_even, 1, 0, false); + sc->is_odd_symbol = defun("odd?", is_odd, 1, 0, false); + sc->is_zero_symbol = defun("zero?", is_zero, 1, 0, false); + sc->is_positive_symbol = defun("positive?", is_positive, 1, 0, false); + sc->is_negative_symbol = defun("negative?", is_negative, 1, 0, false); + sc->is_infinite_symbol = defun("infinite?", is_infinite, 1, 0, false); + sc->is_nan_symbol = defun("nan?", is_nan, 1, 0, false); + sc->complex_symbol = defun("complex", complex, 2, 0, false); + + sc->add_symbol = defun("+", add, 0, 0, true); set_all_integer_and_float(sc->add_symbol); + sc->subtract_symbol = defun("-", subtract, 1, 0, true); set_all_integer_and_float(sc->subtract_symbol); + sc->multiply_symbol = defun("*", multiply, 0, 0, true); set_all_integer_and_float(sc->multiply_symbol); + sc->divide_symbol = defun("/", divide, 1, 0, true); set_all_float(sc->divide_symbol); + sc->min_symbol = defun("min", min, 1, 0, true); set_all_integer_and_float(sc->min_symbol); + sc->max_symbol = defun("max", max, 1, 0, true); set_all_integer_and_float(sc->max_symbol); + + sc->quotient_symbol = defun("quotient", quotient, 2, 0, false); set_all_integer(sc->quotient_symbol); + sc->remainder_symbol = defun("remainder", remainder, 2, 0, false); set_all_integer(sc->remainder_symbol); + sc->modulo_symbol = defun("modulo", modulo, 2, 0, false); set_all_integer(sc->modulo_symbol); + sc->num_eq_symbol = defun("=", num_eq, 2, 0, true); + sc->lt_symbol = defun("<", less, 2, 0, true); + sc->gt_symbol = defun(">", greater, 2, 0, true); + sc->leq_symbol = defun("<=", less_or_equal, 2, 0, true); + sc->geq_symbol = defun(">=", greater_or_equal, 2, 0, true); + sc->gcd_symbol = defun("gcd", gcd, 0, 0, true); + sc->lcm_symbol = defun("lcm", lcm, 0, 0, true); + sc->rationalize_symbol = defun("rationalize", rationalize, 1, 1, false); + sc->random_symbol = defun("random", random, 1, 1, false); set_all_integer_and_float(sc->random_symbol); + sc->random_state_symbol = defun("random-state", random_state, 0, (WITH_GMP) ? 1 : 2, false); + sc->expt_symbol = defun("expt", expt, 2, 0, false); + sc->log_symbol = defun("log", log, 1, 1, false); + sc->ash_symbol = defun("ash", ash, 2, 0, false); + sc->exp_symbol = defun("exp", exp, 1, 0, false); set_all_float(sc->exp_symbol); + sc->abs_symbol = defun("abs", abs, 1, 0, false); set_all_integer_and_float(sc->abs_symbol); + sc->magnitude_symbol = defun("magnitude", magnitude, 1, 0, false); set_all_integer_and_float(sc->magnitude_symbol); + sc->angle_symbol = defun("angle", angle, 1, 0, false); + sc->sin_symbol = defun("sin", sin, 1, 0, false); set_all_float(sc->sin_symbol); + sc->cos_symbol = defun("cos", cos, 1, 0, false); set_all_float(sc->cos_symbol); + sc->tan_symbol = defun("tan", tan, 1, 0, false); set_all_float(sc->tan_symbol); + sc->sinh_symbol = defun("sinh", sinh, 1, 0, false); set_all_float(sc->sinh_symbol); + sc->cosh_symbol = defun("cosh", cosh, 1, 0, false); set_all_float(sc->cosh_symbol); + sc->tanh_symbol = defun("tanh", tanh, 1, 0, false); set_all_float(sc->tanh_symbol); + sc->asin_symbol = defun("asin", asin, 1, 0, false); + sc->acos_symbol = defun("acos", acos, 1, 0, false); + sc->atan_symbol = defun("atan", atan, 1, 1, false); + sc->asinh_symbol = defun("asinh", asinh, 1, 0, false); + sc->acosh_symbol = defun("acosh", acosh, 1, 0, false); + sc->atanh_symbol = defun("atanh", atanh, 1, 0, false); + sc->sqrt_symbol = defun("sqrt", sqrt, 1, 0, false); + sc->floor_symbol = defun("floor", floor, 1, 0, false); + sc->ceiling_symbol = defun("ceiling", ceiling, 1, 0, false); + sc->truncate_symbol = defun("truncate", truncate, 1, 0, false); + sc->round_symbol = defun("round", round, 1, 0, false); + sc->logand_symbol = defun("logand", logand, 0, 0, true); + sc->logior_symbol = defun("logior", logior, 0, 0, true); + sc->logxor_symbol = defun("logxor", logxor, 0, 0, true); + sc->lognot_symbol = defun("lognot", lognot, 1, 0, false); + sc->logbit_symbol = defun("logbit?", logbit, 2, 0, false); + sc->integer_decode_float_symbol = defun("integer-decode-float", integer_decode_float, 1, 0, false); + sc->nan_symbol = defun("nan", nan, 0, 1, false); /* (nan) -> +nan.0, (nan 123) -> +nan.123 */ + sc->nan_payload_symbol = defun("nan-payload", nan_payload, 1, 0, false); + +#if (!WITH_PURE_S7) + sc->integer_length_symbol = defun("integer-length", integer_length, 1, 0, false); + sc->inexact_to_exact_symbol = defun("inexact->exact", inexact_to_exact, 1, 0, false); + sc->exact_to_inexact_symbol = defun("exact->inexact", exact_to_inexact, 1, 0, false); + sc->is_exact_symbol = defun("exact?", is_exact, 1, 0, false); + sc->is_inexact_symbol = defun("inexact?", is_inexact, 1, 0, false); +#endif + sc->random_state_to_list_symbol = defun("random-state->list", random_state_to_list, 0, 1, false); + sc->number_to_string_symbol = defun("number->string", number_to_string, 1, 1, false); + sc->string_to_number_symbol = defun("string->number", string_to_number, 1, 1, false); + + sc->char_upcase_symbol = defun("char-upcase", char_upcase, 1, 0, false); + sc->char_downcase_symbol = defun("char-downcase", char_downcase, 1, 0, false); + sc->char_to_integer_symbol = defun("char->integer", char_to_integer, 1, 0, false); + sc->integer_to_char_symbol = defun("integer->char", integer_to_char, 1, 0, false); + + sc->is_char_upper_case_symbol = defun("char-upper-case?", is_char_upper_case, 1, 0, false); + sc->is_char_lower_case_symbol = defun("char-lower-case?", is_char_lower_case, 1, 0, false); + sc->is_char_alphabetic_symbol = defun("char-alphabetic?", is_char_alphabetic, 1, 0, false); + sc->is_char_numeric_symbol = defun("char-numeric?", is_char_numeric, 1, 0, false); + sc->is_char_whitespace_symbol = defun("char-whitespace?", is_char_whitespace, 1, 0, false); + + sc->char_eq_symbol = defun("char=?", chars_are_equal, 2, 0, true); + sc->char_lt_symbol = defun("charchar_gt_symbol = defun("char>?", chars_are_greater, 2, 0, true); + sc->char_leq_symbol = defun("char<=?", chars_are_leq, 2, 0, true); + sc->char_geq_symbol = defun("char>=?", chars_are_geq, 2, 0, true); + sc->char_position_symbol = defun("char-position", char_position, 2, 1, false); + sc->string_position_symbol = defun("string-position", string_position, 2, 1, false); + + sc->make_string_symbol = defun("make-string", make_string, 1, 1, false); + sc->string_ref_symbol = defun("string-ref", string_ref, 2, 0, false); + sc->string_set_symbol = defun("string-set!", string_set, 3, 0, false); + + sc->string_eq_symbol = defun("string=?", strings_are_equal, 2, 0, true); + sc->string_lt_symbol = defun("stringstring_gt_symbol = defun("string>?", strings_are_greater, 2, 0, true); + sc->string_leq_symbol = defun("string<=?", strings_are_leq, 2, 0, true); + sc->string_geq_symbol = defun("string>=?", strings_are_geq, 2, 0, true); + +#if (!WITH_PURE_S7) + sc->char_ci_eq_symbol = defun("char-ci=?", chars_are_ci_equal, 2, 0, true); + sc->char_ci_lt_symbol = defun("char-cichar_ci_gt_symbol = defun("char-ci>?", chars_are_ci_greater, 2, 0, true); + sc->char_ci_leq_symbol = defun("char-ci<=?", chars_are_ci_leq, 2, 0, true); + sc->char_ci_geq_symbol = defun("char-ci>=?", chars_are_ci_geq, 2, 0, true); + sc->string_ci_eq_symbol = defun("string-ci=?", strings_are_ci_equal, 2, 0, true); + sc->string_ci_lt_symbol = defun("string-cistring_ci_gt_symbol = defun("string-ci>?", strings_are_ci_greater, 2, 0, true); + sc->string_ci_leq_symbol = defun("string-ci<=?", strings_are_ci_leq, 2, 0, true); + sc->string_ci_geq_symbol = defun("string-ci>=?", strings_are_ci_geq, 2, 0, true); + sc->string_fill_symbol = defun("string-fill!", string_fill, 2, 2, false); + sc->list_to_string_symbol = defun("list->string", list_to_string, 1, 0, false); + sc->string_length_symbol = defun("string-length", string_length, 1, 0, false); + sc->string_to_list_symbol = defun("string->list", string_to_list, 1, 2, false); +#endif + sc->string_copy_symbol = defun("string-copy", string_copy, 1, 3, false); + + sc->string_downcase_symbol = defun("string-downcase", string_downcase, 1, 0, false); + sc->string_upcase_symbol = defun("string-upcase", string_upcase, 1, 0, false); + sc->string_append_symbol = defun("string-append", string_append, 0, 0, true); + sc->substring_symbol = defun("substring", substring, 2, 1, false); + sc->string_symbol = defun("string", string, 0, 0, true); + sc->object_to_string_symbol = defun("object->string", object_to_string, 1, 2, false); + sc->format_symbol = defun("format", format, 2, 0, true); + sc->object_to_let_symbol = defun("object->let", object_to_let, 1, 0, false); + + sc->cons_symbol = defun("cons", cons, 2, 0, false); + sc->car_symbol = defun("car", car, 1, 0, false); + sc->cdr_symbol = defun("cdr", cdr, 1, 0, false); + sc->set_car_symbol = defun("set-car!", set_car, 2, 0, false); + sc->set_cdr_symbol = defun("set-cdr!", set_cdr, 2, 0, false); + sc->caar_symbol = defun("caar", caar, 1, 0, false); + sc->cadr_symbol = defun("cadr", cadr, 1, 0, false); + sc->cdar_symbol = defun("cdar", cdar, 1, 0, false); + sc->cddr_symbol = defun("cddr", cddr, 1, 0, false); + sc->caaar_symbol = defun("caaar", caaar, 1, 0, false); + sc->caadr_symbol = defun("caadr", caadr, 1, 0, false); + sc->cadar_symbol = defun("cadar", cadar, 1, 0, false); + sc->cdaar_symbol = defun("cdaar", cdaar, 1, 0, false); + sc->caddr_symbol = defun("caddr", caddr, 1, 0, false); + sc->cdddr_symbol = defun("cdddr", cdddr, 1, 0, false); + sc->cdadr_symbol = defun("cdadr", cdadr, 1, 0, false); + sc->cddar_symbol = defun("cddar", cddar, 1, 0, false); + sc->caaaar_symbol = defun("caaaar", caaaar, 1, 0, false); + sc->caaadr_symbol = defun("caaadr", caaadr, 1, 0, false); + sc->caadar_symbol = defun("caadar", caadar, 1, 0, false); + sc->cadaar_symbol = defun("cadaar", cadaar, 1, 0, false); + sc->caaddr_symbol = defun("caaddr", caaddr, 1, 0, false); + sc->cadddr_symbol = defun("cadddr", cadddr, 1, 0, false); + sc->cadadr_symbol = defun("cadadr", cadadr, 1, 0, false); + sc->caddar_symbol = defun("caddar", caddar, 1, 0, false); + sc->cdaaar_symbol = defun("cdaaar", cdaaar, 1, 0, false); + sc->cdaadr_symbol = defun("cdaadr", cdaadr, 1, 0, false); + sc->cdadar_symbol = defun("cdadar", cdadar, 1, 0, false); + sc->cddaar_symbol = defun("cddaar", cddaar, 1, 0, false); + sc->cdaddr_symbol = defun("cdaddr", cdaddr, 1, 0, false); + sc->cddddr_symbol = defun("cddddr", cddddr, 1, 0, false); + sc->cddadr_symbol = defun("cddadr", cddadr, 1, 0, false); + sc->cdddar_symbol = defun("cdddar", cdddar, 1, 0, false); + + sc->assq_symbol = defun("assq", assq, 2, 0, false); + sc->assv_symbol = defun("assv", assv, 2, 0, false); + sc->assoc_symbol = semisafe_defun("assoc", assoc, 2, 1, false); + sc->memq_symbol = defun("memq", memq, 2, 0, false); + sc->memv_symbol = defun("memv", memv, 2, 0, false); + sc->member_symbol = semisafe_defun("member", member, 2, 1, false); + + sc->list_symbol = defun("list", list, 0, 0, true); + sc->list_ref_symbol = defun("list-ref", list_ref, 2, 0, true); + sc->list_set_symbol = defun("list-set!", list_set, 3, 0, true); + sc->list_tail_symbol = defun("list-tail", list_tail, 2, 0, false); + sc->make_list_symbol = defun("make-list", make_list, 1, 1, false); + + sc->length_symbol = defun("length", length, 1, 0, false); + sc->copy_symbol = defun("copy", copy, 1, 3, false); + /* set_is_definer(sc->copy_symbol); */ /* (copy (inlet 'a 1) (curlet)), but this check needs to be smarter */ + sc->fill_symbol = defun("fill!", fill, 2, 2, false); + sc->reverse_symbol = defun("reverse", reverse, 1, 0, false); + sc->reverseb_symbol = defun("reverse!", reverse_in_place, 1, 0, false); + sc->sort_symbol = unsafe_defun("sort!", sort, 2, 0, false); /* not semisafe! */ + sc->append_symbol = defun("append", append, 0, 0, true); + sc->qq_append_symbol = defun("[list*]", qq_append, 2, 0, false); + +#if (!WITH_PURE_S7) + sc->vector_append_symbol = defun("vector-append", vector_append, 0, 0, true); + sc->list_to_vector_symbol = defun("list->vector", list_to_vector, 1, 0, false); + sc->vector_fill_symbol = defun("vector-fill!", vector_fill, 2, 2, false); + sc->vector_length_symbol = defun("vector-length", vector_length, 1, 0, false); + sc->vector_to_list_symbol = defun("vector->list", vector_to_list, 1, 2, false); +#else + sc->vector_append_symbol = sc->append_symbol; + sc->vector_fill_symbol = sc->fill_symbol; + sc->string_fill_symbol = sc->fill_symbol; +#endif + sc->vector_ref_symbol = defun("vector-ref", vector_ref, 2, 0, true); + sc->vector_set_symbol = defun("vector-set!", vector_set, 3, 0, true); + sc->vector_dimension_symbol = defun("vector-dimension", vector_dimension, 2, 0, false); + sc->vector_dimensions_symbol = defun("vector-dimensions", vector_dimensions, 1, 0, false); + sc->vector_rank_symbol = defun("vector-rank", vector_rank, 1, 0, false); + sc->make_vector_symbol = defun("make-vector", make_vector, 1, 2, false); + sc->vector_symbol = defun("vector", vector, 0, 0, true); + set_is_setter(sc->vector_symbol); /* like cons, I guess */ + sc->vector_typer_symbol = defun("vector-typer", vector_typer, 1, 0, false); + + sc->subvector_symbol = defun("subvector", subvector, 1, 3, false); + sc->subvector_position_symbol = defun("subvector-position", subvector_position, 1, 0, false); + sc->subvector_vector_symbol = defun("subvector-vector", subvector_vector, 1, 0, false); + + sc->float_vector_symbol = defun("float-vector", float_vector, 0, 0, true); + sc->make_float_vector_symbol = defun("make-float-vector", make_float_vector, 1, 1, false); + sc->float_vector_set_symbol = defun("float-vector-set!", float_vector_set, 3, 0, true); + sc->float_vector_ref_symbol = defun("float-vector-ref", float_vector_ref, 2, 0, true); + + sc->int_vector_symbol = defun("int-vector", int_vector, 0, 0, true); + sc->make_int_vector_symbol = defun("make-int-vector", make_int_vector, 1, 1, false); + sc->int_vector_set_symbol = defun("int-vector-set!", int_vector_set, 3, 0, true); + sc->int_vector_ref_symbol = defun("int-vector-ref", int_vector_ref, 2, 0, true); + + sc->byte_vector_symbol = defun("byte-vector", byte_vector, 0, 0, true); + sc->make_byte_vector_symbol = defun("make-byte-vector", make_byte_vector, 1, 1, false); + sc->byte_vector_ref_symbol = defun("byte-vector-ref", byte_vector_ref, 2, 0, true); + sc->byte_vector_set_symbol = defun("byte-vector-set!", byte_vector_set, 3, 0, true); + sc->string_to_byte_vector_symbol = defun("string->byte-vector", string_to_byte_vector, 1, 0, false); + sc->byte_vector_to_string_symbol = defun("byte-vector->string", byte_vector_to_string, 1, 0, false); + + sc->hash_table_symbol = defun("hash-table", hash_table, 0, 0, true); + sc->make_hash_table_symbol = defun("make-hash-table", make_hash_table, 0, 3, false); + sc->make_weak_hash_table_symbol = defun("make-weak-hash-table", make_weak_hash_table,0, 3, false); + sc->weak_hash_table_symbol = defun("weak-hash-table", weak_hash_table, 0, 0, true); + sc->hash_table_ref_symbol = defun("hash-table-ref", hash_table_ref, 2, 0, true); + sc->hash_table_set_symbol = defun("hash-table-set!", hash_table_set, 3, 0, false); + sc->hash_table_entries_symbol = defun("hash-table-entries", hash_table_entries, 1, 0, false); + sc->hash_code_symbol = defun("hash-code", hash_code, 1, 1, false); + sc->dummy_equal_hash_table = make_dummy_hash_table(sc); + sc->hash_table_key_typer_symbol = defun("hash-table-key-typer", hash_table_key_typer, 1, 0, false); + sc->hash_table_value_typer_symbol = defun("hash-table-value-typer", hash_table_value_typer, 1, 0, false); + + sc->cyclic_sequences_symbol = defun("cyclic-sequences", cyclic_sequences, 1, 0, false); + sc->call_cc_symbol = semisafe_defun("call/cc", call_cc, 1, 0, false); + sc->call_with_current_continuation_symbol = unsafe_defun("call-with-current-continuation", call_cc, 1, 0, false); + sc->call_with_exit_symbol = semisafe_defun("call-with-exit", call_with_exit, 1, 0, false); + + sc->load_symbol = semisafe_defun("load", load, 1, 1, false); + sc->autoload_symbol = defun("autoload", autoload, 2, 0, false); + sc->eval_symbol = semisafe_defun("eval", eval, 1, 1, false); + set_func_is_definer(sc->eval_symbol); + sc->eval_string_symbol = semisafe_defun("eval-string", eval_string, 1, 1, false); + set_func_is_definer(sc->eval_string_symbol); + sc->apply_symbol = unsafe_defun("apply", apply, 1, 0, true); /* not semisafe (note that type is reset below) */ + set_func_is_definer(sc->apply_symbol); + /* yow... (apply (inlet) (f)) in do body where (f) returns '(define...) -- see s7test.scm under apply + * perhaps better: if closure returns a definer in some way set its name as a definer? even this is not fool-proof + */ + + sc->for_each_symbol = semisafe_defun("for-each", for_each, 2, 0, true); + sc->map_symbol = semisafe_defun("map", map, 2, 0, true); + sc->dynamic_wind_symbol = semisafe_defun("dynamic-wind", dynamic_wind, 3, 0, false); + sc->dynamic_unwind_symbol = semisafe_defun("dynamic-unwind", dynamic_unwind, 2, 0, false); + sc->catch_symbol = semisafe_defun("catch", catch, 3, 0, false); + sc->throw_symbol = unsafe_defun("throw", throw, 1, 0, true); + sc->error_symbol = unsafe_defun("error", error, 1, 0, true); /* was 0,0 -- 1-Aug-22 */ + /* unsafe example: catch if macro as error handler, (define-macro (m . args) `(apply ,(car args) ',(cadr args))) (catch #t (lambda () (error abs -1)) m) */ + sc->stacktrace_symbol = defun("stacktrace", stacktrace, 0, 5, false); + + /* sc->values_symbol = */ unsafe_defun("values", values, 0, 0, true); /* values_symbol set above for signatures, not semisafe! */ + sc->apply_values_symbol = unsafe_defun("apply-values", apply_values, 0, 1, false); + set_immutable(sc->apply_values_symbol); + set_immutable_slot(global_slot(sc->apply_values_symbol)); + sc->list_values_symbol = defun("list-values", list_values, 0, 0, true); + set_immutable(sc->list_values_symbol); + set_immutable_slot(global_slot(sc->list_values_symbol)); + + sc->documentation_symbol = defun("documentation", documentation, 1, 0, false); + sc->signature_symbol = defun("signature", signature, 1, 0, false); + sc->help_symbol = defun("help", help, 1, 0, false); + sc->procedure_source_symbol = defun("procedure-source", procedure_source, 1, 0, false); + sc->funclet_symbol = defun("funclet", funclet, 1, 0, false); + sc->_function__symbol = defun("*function*", function, 0, 2, false); + sc->dilambda_symbol = defun("dilambda", dilambda, 2, 0, false); + { + s7_pointer get_func; + get_func = s7_typed_dilambda(sc, "setter", g_setter, 1, 1, g_set_setter, 2, 1, H_setter, Q_setter, NULL); + set_immutable(c_function_setter(get_func)); + } + sc->arity_symbol = defun("arity", arity, 1, 0, false); + sc->is_aritable_symbol = defun("aritable?", is_aritable, 2, 0, false); + + sc->is_eq_symbol = defun("eq?", is_eq, 2, 0, false); + sc->is_eqv_symbol = defun("eqv?", is_eqv, 2, 0, false); + sc->is_equal_symbol = defun("equal?", is_equal, 2, 0, false); + sc->is_equivalent_symbol = defun("equivalent?", is_equivalent, 2, 0, false); + sc->type_of_symbol = defun("type-of", type_of, 1, 0, false); + + sc->gc_symbol = semisafe_defun("gc", gc, 0, 1, false); + defun("emergency-exit", emergency_exit, 0, 1, false); + sc->exit_symbol = defun("exit", exit, 0, 1, false); + +#if WITH_GCC + s7_define_function(sc, "abort", g_abort, 0, 0, false, "drop into gdb I hope"); +#endif +#if S7_DEBUGGING + defun("heap-scan", heap_scan, 1, 0, false); + defun("heap-analyze", heap_analyze, 0, 0, false); + defun("heap-holder", heap_holder, 1, 0, false); + defun("heap-holders", heap_holders, 1, 0, false); + + defun("show-stack", show_stack, 0, 0, false); + defun("show-op-stack", show_op_stack, 0, 0, false); + defun("op-stack?", is_op_stack, 0, 0, false); +#endif + s7_define_function(sc, "s7-optimize", g_optimize, 1, 0, false, "short-term debugging aid"); + sc->c_object_set_function = s7_make_safe_function(sc, "#", g_c_object_set, 1, 0, true, "c-object setter"); + /* c_function_signature(sc->c_object_set_function) = s7_make_circular_signature(sc, 2, 3, sc->T, sc->is_c_object_symbol, sc->T); */ + + set_scope_safe(global_value(sc->call_with_input_string_symbol)); + set_scope_safe(global_value(sc->call_with_input_file_symbol)); + set_scope_safe(global_value(sc->call_with_output_string_symbol)); + set_scope_safe(global_value(sc->call_with_output_file_symbol)); + set_scope_safe(global_value(sc->with_input_from_string_symbol)); + set_scope_safe(global_value(sc->with_input_from_file_symbol)); + set_scope_safe(global_value(sc->with_output_to_string_symbol)); + set_scope_safe(global_value(sc->with_output_to_file_symbol)); + set_maybe_safe(global_value(sc->assoc_symbol)); + set_scope_safe(global_value(sc->assoc_symbol)); + set_maybe_safe(global_value(sc->member_symbol)); + set_scope_safe(global_value(sc->member_symbol)); + set_scope_safe(global_value(sc->sort_symbol)); + set_scope_safe(global_value(sc->call_with_exit_symbol)); + set_scope_safe(global_value(sc->for_each_symbol)); + set_maybe_safe(global_value(sc->for_each_symbol)); + set_scope_safe(global_value(sc->map_symbol)); + set_maybe_safe(global_value(sc->map_symbol)); + set_scope_safe(global_value(sc->dynamic_wind_symbol)); + set_scope_safe(global_value(sc->catch_symbol)); + set_scope_safe(global_value(sc->throw_symbol)); + set_scope_safe(global_value(sc->error_symbol)); + set_scope_safe(global_value(sc->apply_values_symbol)); + + sc->tree_leaves_symbol = defun("tree-leaves", tree_leaves, 1, 0, false); + sc->tree_memq_symbol = defun("tree-memq", tree_memq, 2, 0, false); + sc->tree_set_memq_symbol = defun("tree-set-memq", tree_set_memq, 2, 0, false); + sc->tree_count_symbol = defun("tree-count", tree_count, 2, 1, false); + sc->tree_is_cyclic_symbol = defun("tree-cyclic?", tree_is_cyclic, 1, 0, false); + + sc->quasiquote_symbol = s7_define_macro(sc, "quasiquote", g_quasiquote, 1, 0, false, H_quasiquote); /* is this considered syntax? r7rs says yes; also unquote */ + sc->profile_in_symbol = unsafe_defun("profile-in", profile_in, 2, 0, false); /* calls dynamic-unwind */ + sc->profile_out = NULL; + + /* -------- *features* -------- */ + sc->features_symbol = s7_define_variable_with_documentation(sc, "*features*", sc->nil, "list of currently available features ('complex-numbers, etc)"); + s7_set_setter(sc, sc->features_symbol, s7_make_safe_function(sc, "#", g_features_set, 2, 0, false, "*features* setter")); + + /* -------- *load-path* -------- */ + sc->load_path_symbol = s7_define_variable_with_documentation(sc, "*load-path*", list_1(sc, make_string_with_length(sc, ".", 1)), /* not plist! */ + "*load-path* is a list of directories (strings) that the load function searches if it is passed an incomplete file name"); + s7_set_setter(sc, sc->load_path_symbol, s7_make_safe_function(sc, "#", g_load_path_set, 2, 0, false, "*load-path* setter")); + +#ifdef CLOAD_DIR + sc->cload_directory_symbol = s7_define_variable(sc, "*cload-directory*", s7_make_string(sc, (char *)CLOAD_DIR)); + s7_add_to_load_path(sc, (const char *)CLOAD_DIR); +#else + sc->cload_directory_symbol = s7_define_variable(sc, "*cload-directory*", nil_string); +#endif + s7_set_setter(sc, sc->cload_directory_symbol, + s7_make_safe_function(sc, "#", g_cload_directory_set, 2, 0, false, "*cload-directory* setter")); + + /* -------- *autoload* -------- this pretends to be a hash-table or environment, but it's actually a function */ + sc->autoloader_symbol = s7_define_typed_function(sc, "*autoload*", g_autoloader, 1, 0, false, H_autoloader, Q_autoloader); + c_function_set_setter(global_value(sc->autoloader_symbol), global_value(sc->autoload_symbol)); /* (set! (*autoload* x) y) */ + + sc->libraries_symbol = s7_define_variable_with_documentation(sc, "*libraries*", sc->nil, "list of currently loaded libraries (libc.scm, etc)"); + s7_set_setter(sc, sc->libraries_symbol, s7_make_safe_function(sc, "#", g_libraries_set, 2, 0, false, "*libraries* setter")); + + s7_autoload(sc, make_symbol(sc, "cload.scm", 9), s7_make_semipermanent_string(sc, "cload.scm")); + s7_autoload(sc, make_symbol(sc, "lint.scm", 8), s7_make_semipermanent_string(sc, "lint.scm")); + s7_autoload(sc, make_symbol(sc, "stuff.scm", 9), s7_make_semipermanent_string(sc, "stuff.scm")); + s7_autoload(sc, make_symbol(sc, "mockery.scm", 11), s7_make_semipermanent_string(sc, "mockery.scm")); + s7_autoload(sc, make_symbol(sc, "write.scm", 9), s7_make_semipermanent_string(sc, "write.scm")); + s7_autoload(sc, make_symbol(sc, "reactive.scm", 12), s7_make_semipermanent_string(sc, "reactive.scm")); + s7_autoload(sc, make_symbol(sc, "repl.scm", 8), s7_make_semipermanent_string(sc, "repl.scm")); + s7_autoload(sc, make_symbol(sc, "r7rs.scm", 8), s7_make_semipermanent_string(sc, "r7rs.scm")); + s7_autoload(sc, make_symbol(sc, "profile.scm", 11), s7_make_semipermanent_string(sc, "profile.scm")); + s7_autoload(sc, make_symbol(sc, "debug.scm", 9), s7_make_semipermanent_string(sc, "debug.scm")); + s7_autoload(sc, make_symbol(sc, "case.scm", 8), s7_make_semipermanent_string(sc, "case.scm")); + + s7_autoload(sc, make_symbol(sc, "libc.scm", 8), s7_make_semipermanent_string(sc, "libc.scm")); + s7_autoload(sc, make_symbol(sc, "libm.scm", 8), s7_make_semipermanent_string(sc, "libm.scm")); /* repl.scm adds *libm* */ + s7_autoload(sc, make_symbol(sc, "libdl.scm", 9), s7_make_semipermanent_string(sc, "libdl.scm")); + s7_autoload(sc, make_symbol(sc, "libgsl.scm", 10), s7_make_semipermanent_string(sc, "libgsl.scm")); /* repl.scm adds *libgsl* */ + s7_autoload(sc, make_symbol(sc, "libgdbm.scm", 11), s7_make_semipermanent_string(sc, "libgdbm.scm")); + s7_autoload(sc, make_symbol(sc, "libutf8proc.scm", 15), s7_make_semipermanent_string(sc, "libutf8proc.scm")); + + sc->require_symbol = s7_define_macro(sc, "require", g_require, 1, 0, true, H_require); + sc->stacktrace_defaults = s7_list(sc, 5, int_three, small_int(45), small_int(80), small_int(45), sc->T); /* assume NUM_SMALL_INTS >= NUM_CHARS == 256 */ + + /* -------- *#readers* -------- */ + sym = s7_define_variable_with_documentation(sc, "*#readers*", sc->nil, "list of current reader macros"); + sc->sharp_readers = global_slot(sym); + s7_set_setter(sc, sym, s7_make_safe_function(sc, "#", g_sharp_readers_set, 2, 0, false, "*#readers* setter")); + + sc->local_documentation_symbol = make_symbol(sc, "+documentation+", 15); + sc->local_signature_symbol = make_symbol(sc, "+signature+", 11); + sc->local_setter_symbol = make_symbol(sc, "+setter+", 8); + sc->local_iterator_symbol = make_symbol(sc, "+iterator+", 10); + + init_features(sc); + init_setters(sc); +} + +#if (!MS_WINDOWS) +static pthread_mutex_t init_lock = PTHREAD_MUTEX_INITIALIZER; +#endif + +s7_scheme *s7_init(void) +{ + int32_t i; + s7_scheme *sc; + static bool already_inited = false; + +#if (!MS_WINDOWS) + setlocale(LC_NUMERIC, "C"); /* use decimal point in floats */ + pthread_mutex_lock(&init_lock); +#endif + + if (!already_inited) + { + init_types(); + init_ctables(); + init_mark_functions(); + init_display_functions(); + init_length_functions(); + init_equals(); + init_hash_maps(); + init_pows(); + init_int_limits(); + init_small_ints(); + init_uppers(); + init_chars(); + init_strings(); + init_fx_function(); + init_catchers(); + init_s7_starlet_immutable_field(); + already_inited = true; + } +#if S7_DEBUGGING + init_never_unheaped(); +#endif +#if (!MS_WINDOWS) + pthread_mutex_unlock(&init_lock); +#endif + sc = (s7_scheme *)Calloc(1, sizeof(s7_scheme)); /* not malloc! */ +#if S7_DEBUGGING || POINTER_32 || WITH_WARNINGS || DISABLE_FILE_OUTPUT + cur_sc = sc; /* for gdb/debugging */ +#endif + sc->gc_off = true; /* sc->args and so on are not set yet, so a gc during init -> segfault */ + sc->gc_in_progress = false; + sc->gc_stats = 0; + + sc->saved_pointers = (void **)Malloc(INITIAL_SAVED_POINTERS_SIZE * sizeof(void *)); + sc->saved_pointers_loc = 0; + sc->saved_pointers_size = INITIAL_SAVED_POINTERS_SIZE; + + init_gc_caches(sc); + sc->semipermanent_cells = 0; + sc->alloc_pointer_k = ALLOC_POINTER_SIZE; + sc->alloc_pointer_cells = NULL; + sc->alloc_big_pointer_k = ALLOC_BIG_POINTER_SIZE; + sc->alloc_big_pointer_cells = NULL; + sc->alloc_function_k = ALLOC_FUNCTION_SIZE; + sc->alloc_function_cells = NULL; + sc->alloc_symbol_k = ALLOC_SYMBOL_SIZE; + sc->alloc_symbol_cells = NULL; + sc->num_to_str_size = -1; + sc->num_to_str = NULL; + init_block_lists(sc); + sc->alloc_string_k = ALLOC_STRING_SIZE; + sc->alloc_string_cells = NULL; + sc->alloc_opt_func_cells = NULL; + sc->alloc_opt_func_k = ALLOC_FUNCTION_SIZE; + sc->longjmp_ok = false; + sc->setjmp_loc = NO_SET_JUMP; + sc->max_vector_length = (1LL << 32); + sc->max_string_length = 1073741824; /* 1 << 30 */ + sc->max_format_length = 10000; + sc->max_list_length = 1073741824; + sc->max_vector_dimensions = 512; + sc->strbuf_size = INITIAL_STRBUF_SIZE; + sc->strbuf = (char *)Calloc(sc->strbuf_size, 1); + sc->print_width = sc->max_string_length; + sc->short_print = false; + sc->in_with_let = false; + sc->object_out_locked = false; + sc->has_openlets = true; + sc->is_expanding = true; + sc->accept_all_keyword_arguments = false; + sc->muffle_warnings = false; + sc->initial_string_port_length = 128; + sc->format_depth = -1; + sc->singletons = (s7_pointer *)Calloc(256, sizeof(s7_pointer)); + add_saved_pointer(sc, sc->singletons); + sc->read_line_buf = NULL; + sc->read_line_buf_size = 0; + sc->stop_at_error = true; + + sc->nil = make_unique(sc, "()", T_NIL); + sc->unused = make_unique(sc, "#", T_UNUSED); + sc->T = make_unique(sc, "#t", T_BOOLEAN); + sc->F = make_unique(sc, "#f", T_BOOLEAN); + sc->undefined = make_unique(sc, "#", T_UNDEFINED); + sc->unspecified = make_unique(sc, "#", T_UNSPECIFIED); + sc->no_value = make_unique(sc, (SHOW_EVAL_OPS) ? "#" : "#", T_UNSPECIFIED); + + unique_car(sc->nil) = sc->unspecified; + unique_cdr(sc->nil) = sc->unspecified; + /* this is mixing two different s7_cell structs, cons and envr, but luckily envr has two initial s7_pointer fields, equivalent to car and cdr, so + * let_id which is the same as opt1 is unaffected. To get the names built-in, I'll append unique_name and unique_name_length fields to the envr struct. + */ + let_set_id(sc->nil, -1); + unique_cdr(sc->unspecified) = sc->unspecified; + + sc->t1_1 = semipermanent_cons(sc, sc->unused, sc->nil, T_PAIR | T_IMMUTABLE); + sc->t2_2 = semipermanent_cons(sc, sc->unused, sc->nil, T_PAIR | T_IMMUTABLE); + sc->t2_1 = semipermanent_cons(sc, sc->unused, sc->t2_2, T_PAIR | T_IMMUTABLE); + sc->t3_3 = semipermanent_cons(sc, sc->unused, sc->nil, T_PAIR | T_IMMUTABLE); + sc->t3_2 = semipermanent_cons(sc, sc->unused, sc->t3_3, T_PAIR | T_IMMUTABLE); + sc->t3_1 = semipermanent_cons(sc, sc->unused, sc->t3_2, T_PAIR | T_IMMUTABLE); + sc->t4_1 = semipermanent_cons(sc, sc->unused, sc->t3_1, T_PAIR | T_IMMUTABLE); + sc->u1_1 = semipermanent_cons(sc, sc->unused, sc->nil, T_PAIR | T_IMMUTABLE); /* ulist */ + + sc->safe_lists[0] = sc->nil; + for (i = 1; i < NUM_SAFE_PRELISTS; i++) + sc->safe_lists[i] = semipermanent_list(sc, i); + for (i = NUM_SAFE_PRELISTS; i < NUM_SAFE_LISTS; i++) + sc->safe_lists[i] = sc->nil; + sc->current_safe_list = 0; + + sc->input_port_stack_size = INPUT_PORT_STACK_INITIAL_SIZE; + sc->input_port_stack = (s7_pointer *)Malloc(sc->input_port_stack_size * sizeof(s7_pointer)); + sc->input_port_stack_loc = 0; + + sc->code = sc->nil; +#if WITH_HISTORY + sc->eval_history1 = semipermanent_list(sc, DEFAULT_HISTORY_SIZE); + sc->eval_history2 = semipermanent_list(sc, DEFAULT_HISTORY_SIZE); + sc->history_pairs = semipermanent_list(sc, DEFAULT_HISTORY_SIZE); + sc->history_sink = semipermanent_list(sc, 1); + cdr(sc->history_sink) = sc->history_sink; + { + s7_pointer p1, p2, p3; + for (p3 = sc->history_pairs; is_pair(cdr(p3)); p3 = cdr(p3)) set_car(p3, semipermanent_list(sc, 1)); + set_car(p3, semipermanent_list(sc, 1)); + set_cdr(p3, sc->history_pairs); + for (p1 = sc->eval_history1, p2 = sc->eval_history2; is_pair(cdr(p1)); p1 = cdr(p1), p2 = cdr(p2)); + set_cdr(p1, sc->eval_history1); + set_cdr(p2, sc->eval_history2); + sc->cur_code = sc->eval_history1; + sc->using_history1 = true; + sc->old_cur_code = sc->cur_code; + } +#else + sc->cur_code = sc->F; +#endif + sc->args = sc->nil; + sc->value = sc->nil; + sc->w = sc->unused; + sc->x = sc->unused; + sc->y = sc->unused; + sc->z = sc->unused; + sc->temp1 = sc->unused; + sc->temp2 = sc->unused; + sc->temp3 = sc->unused; + sc->temp4 = sc->unused; + sc->temp5 = sc->unused; + sc->temp7 = sc->unused; + sc->temp8 = sc->unused; + sc->temp9 = sc->unused; + sc->temp10 = sc->unused; + sc->rec_p1 = sc->unused; + sc->rec_p2 = sc->unused; + + sc->begin_hook = NULL; + sc->autoload_table = sc->nil; + sc->autoload_names = NULL; + sc->autoload_names_sizes = NULL; + sc->autoloaded_already = NULL; + sc->autoload_names_loc = 0; +#if DISABLE_AUTOLOAD + sc->is_autoloading = false; +#else + sc->is_autoloading = true; +#endif + sc->rec_stack = NULL; + + sc->heap_size = INITIAL_HEAP_SIZE; + if ((sc->heap_size % 32) != 0) + sc->heap_size = 32 * (int64_t)ceil((double)(sc->heap_size) / 32.0); + sc->heap = (s7_pointer *)Malloc(sc->heap_size * sizeof(s7_pointer)); + sc->free_heap = (s7_cell **)Malloc(sc->heap_size * sizeof(s7_cell *)); + sc->free_heap_top = (s7_cell **)(sc->free_heap + INITIAL_HEAP_SIZE); + sc->free_heap_trigger = (s7_cell **)(sc->free_heap + GC_TRIGGER_SIZE); + sc->previous_free_heap_top = sc->free_heap_top; + { + s7_cell *cells = (s7_cell *)Malloc(INITIAL_HEAP_SIZE * sizeof(s7_cell)); /* was calloc 14-Apr-22 */ + add_saved_pointer(sc, (void *)cells); + for (i = 0; i < INITIAL_HEAP_SIZE; i++) /* LOOP_4 here is slower! */ + { + sc->heap[i] = &cells[i]; + sc->free_heap[i] = sc->heap[i]; +#if S7_DEBUGGING + sc->heap[i]->debugger_bits = 0; sc->heap[i]->gc_line = 0; sc->heap[i]->gc_func = NULL; +#endif + clear_type(sc->heap[i]); + i++; + sc->heap[i] = &cells[i]; + sc->free_heap[i] = sc->heap[i]; +#if S7_DEBUGGING + sc->heap[i]->debugger_bits = 0; sc->heap[i]->gc_line = 0; sc->heap[i]->gc_func = NULL; +#endif + clear_type(sc->heap[i]); + } + sc->heap_blocks = (heap_block_t *)Malloc(sizeof(heap_block_t)); + sc->heap_blocks->start = (intptr_t)cells; + sc->heap_blocks->end = (intptr_t)cells + (sc->heap_size * sizeof(s7_cell)); + sc->heap_blocks->offset = 0; + sc->heap_blocks->next = NULL; + } + sc->gc_temps_size = GC_TEMPS_SIZE; + sc->gc_resize_heap_fraction = GC_RESIZE_HEAP_FRACTION; + sc->gc_resize_heap_by_4_fraction = GC_RESIZE_HEAP_BY_4_FRACTION; + sc->max_heap_size = (1LL << 62); + sc->gc_calls = 0; + sc->gc_total_time = 0; + + sc->max_port_data_size = (1LL << 62); +#ifndef OUTPUT_PORT_DATA_SIZE + #define OUTPUT_PORT_DATA_SIZE 2048 +#endif + sc->output_port_data_size = OUTPUT_PORT_DATA_SIZE; + + /* this has to precede s7_make_* allocations */ + sc->protected_setters_size = INITIAL_PROTECTED_OBJECTS_SIZE; + sc->protected_setters_loc = 0; + sc->protected_setters = make_vector_1(sc, INITIAL_PROTECTED_OBJECTS_SIZE, FILLED, T_VECTOR); + sc->protected_setter_symbols = make_vector_1(sc, INITIAL_PROTECTED_OBJECTS_SIZE, FILLED, T_VECTOR); + + sc->protected_objects_size = INITIAL_PROTECTED_OBJECTS_SIZE; + sc->protected_objects_free_list = (s7_int *)Malloc(INITIAL_PROTECTED_OBJECTS_SIZE * sizeof(s7_int)); + sc->protected_objects_free_list_loc = INITIAL_PROTECTED_OBJECTS_SIZE - 1; + sc->protected_objects = make_vector_1(sc, INITIAL_PROTECTED_OBJECTS_SIZE, FILLED, T_VECTOR); + for (i = 0; i < INITIAL_PROTECTED_OBJECTS_SIZE; i++) /* using # as the not-set indicator here lets that value leak out! */ + { + vector_element(sc->protected_objects, i) = sc->unused; + vector_element(sc->protected_setters, i) = sc->unused; + vector_element(sc->protected_setter_symbols, i) = sc->unused; + sc->protected_objects_free_list[i] = i; + } + + sc->stack = make_vector_1(sc, INITIAL_STACK_SIZE, FILLED, T_VECTOR); + /* if not_filled, segfault in gc_mark in mark_stack_1 after size check? probably unfilled OP_BARRIER etc? */ + sc->stack_start = vector_elements(sc->stack); /* stack type set below */ + sc->stack_end = sc->stack_start; + sc->stack_size = INITIAL_STACK_SIZE; + sc->stack_resize_trigger = (s7_pointer *)(sc->stack_start + (INITIAL_STACK_SIZE - STACK_RESIZE_TRIGGER)); + set_full_type(sc->stack, T_STACK); + sc->max_stack_size = (1 << 30); + stack_clear_flags(sc->stack); + initialize_op_stack(sc); + + /* keep the symbol table out of the heap */ + sc->symbol_table = (s7_pointer)Malloc(sizeof(s7_cell)); /* was calloc 14-Apr-22 */ + full_type(sc->symbol_table) = T_VECTOR | T_UNHEAP; + vector_length(sc->symbol_table) = SYMBOL_TABLE_SIZE; + vector_elements(sc->symbol_table) = (s7_pointer *)Malloc(SYMBOL_TABLE_SIZE * sizeof(s7_pointer)); + vector_getter(sc->symbol_table) = normal_vector_getter; + vector_setter(sc->symbol_table) = normal_vector_setter; + normal_vector_fill(sc->symbol_table, sc->nil); + + { /* sc->opts */ + opt_info *os = (opt_info *)Malloc(OPTS_SIZE * sizeof(opt_info)); /* was calloc, 17-Oct-21 */ + add_saved_pointer(sc, os); + for (i = 0; i < OPTS_SIZE; i++) + { + opt_info *o = &os[i]; + sc->opts[i] = o; + o->sc = sc; + }} + + for (i = 0; i < NUM_TYPES; i++) + sc->type_names[i] = s7_make_semipermanent_string(sc, (const char *)type_name_from_type(i, INDEFINITE_ARTICLE)); + +#if WITH_MULTITHREAD_CHECKS + sc->lock_count = 0; + { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&sc->lock, &attr); + } +#endif + + sc->c_object_types = NULL; + sc->c_object_types_size = 0; + sc->num_c_object_types = 0; + sc->typnam = NULL; + sc->typnam_len = 0; + sc->default_rationalize_error = 1.0e-12; + sc->hash_table_float_epsilon = 1.0e-12; + sc->equivalent_float_epsilon = 1.0e-15; + sc->float_format_precision = WRITE_REAL_PRECISION; + sc->number_separator = '\0'; + sc->default_hash_table_length = 8; + sc->gensym_counter = 0; + sc->capture_let_counter = 0; + sc->continuation_counter = 0; + sc->f_class = 0; + sc->add_class = 0; + sc->num_eq_class = 0; + sc->let_number = 0; + sc->format_column = 0; + sc->format_ports = NULL; + sc->file_names = NULL; + sc->file_names_size = 0; + sc->file_names_top = -1; + sc->s7_call_line = 0; + sc->s7_call_file = NULL; + sc->s7_call_name = NULL; + sc->safety = NO_SAFETY; + sc->debug = 0; + sc->profile = 0; + sc->profile_position = 0; + sc->debug_or_profile = false; + sc->profiling_gensyms = false; + sc->profile_data = NULL; + sc->profile_prefix = sc->F; + sc->print_length = DEFAULT_PRINT_LENGTH; + sc->history_size = DEFAULT_HISTORY_SIZE; + sc->true_history_size = DEFAULT_HISTORY_SIZE; + sc->baffle_ctr = 0; + sc->map_call_ctr = 0; + sc->syms_tag = 0; + sc->syms_tag2 = 0; + sc->class_name_symbol = make_symbol(sc, "class-name", 10); + sc->name_symbol = make_symbol(sc, "name", 4); + sc->trace_in_symbol = make_symbol(sc, "trace-in", 8); + sc->size_symbol = make_symbol(sc, "size", 4); + sc->is_mutable_symbol = make_symbol(sc, "mutable?", 8); + sc->file__symbol = make_symbol(sc, "FILE*", 5); + sc->circle_info = init_circle_info(sc); + sc->fdats = (format_data_t **)Calloc(8, sizeof(format_data_t *)); + sc->num_fdats = 8; + sc->mlist_1 = semipermanent_list(sc, 1); + sc->mlist_2 = semipermanent_list(sc, 2); + sc->plist_1 = semipermanent_list(sc, 1); + sc->plist_2 = semipermanent_list(sc, 2); + sc->plist_2_2 = cdr(sc->plist_2); + sc->plist_3 = semipermanent_list(sc, 3); + sc->qlist_2 = semipermanent_list(sc, 2); + sc->qlist_3 = semipermanent_cons(sc, sc->unused, sc->qlist_2, T_PAIR | T_IMMUTABLE); + sc->clist_1 = semipermanent_list(sc, 1); + sc->clist_2 = semipermanent_list(sc, 2); + sc->dlist_1 = semipermanent_list(sc, 1); + sc->elist_1 = semipermanent_cons(sc, sc->unused, sc->nil, T_PAIR | T_IMMUTABLE | T_IS_ELIST); + sc->elist_2 = semipermanent_list(sc, 2); set_is_elist(sc->elist_2); + sc->elist_3 = semipermanent_list(sc, 3); set_is_elist(sc->elist_3); + sc->elist_4 = semipermanent_cons(sc, sc->unused, sc->elist_3, T_PAIR | T_IMMUTABLE | T_IS_ELIST); + sc->elist_5 = semipermanent_cons(sc, sc->unused, sc->elist_4, T_PAIR | T_IMMUTABLE | T_IS_ELIST); + sc->elist_6 = semipermanent_cons(sc, sc->unused, sc->elist_5, T_PAIR | T_IMMUTABLE | T_IS_ELIST); + sc->elist_7 = semipermanent_cons(sc, sc->unused, sc->elist_6, T_PAIR | T_IMMUTABLE | T_IS_ELIST); + sc->undefined_identifier_warnings = false; + sc->undefined_constant_warnings = false; + sc->wrap_only = make_wrap_only(sc); + sc->unentry = (hash_entry_t *)Malloc(sizeof(hash_entry_t)); + hash_entry_set_value(sc->unentry, sc->F); + sc->begin_op = OP_BEGIN_NO_HOOK; + /* we used to laboriously set various other fields to null, but the calloc takes care of that */ + sc->tree_pointers = NULL; + sc->tree_pointers_size = 0; + sc->tree_pointers_top = 0; + + sc->rootlet = make_vector_1(sc, INITIAL_ROOTLET_SIZE, FILLED, T_VECTOR); + set_full_type(sc->rootlet, T_LET | T_SAFE_PROCEDURE); + sc->rootlet_entries = 0; + for (i = 0; i < INITIAL_ROOTLET_SIZE; i++) rootlet_element(sc->rootlet, i) = sc->nil; + sc->curlet = sc->nil; + sc->shadow_rootlet = sc->nil; + sc->objstr_max_len = S7_INT64_MAX; + + init_wrappers(sc); + init_standard_ports(sc); + init_rootlet(sc); + init_open_input_function_choices(sc); + + { + s7_pointer p; + new_cell(sc, p, T_RANDOM_STATE); /* s7_set_default_random_state might set sc->default_random_state, so this shouldn't be permanent */ + sc->default_random_state = p; + + sc->bignum_precision = DEFAULT_BIGNUM_PRECISION; +#if WITH_GMP + sc->bigints = NULL; + sc->bigrats = NULL; + sc->bigflts = NULL; + sc->bigcmps = NULL; + + mpfr_set_default_prec((mp_prec_t)DEFAULT_BIGNUM_PRECISION); + mpc_set_default_precision((mp_prec_t)DEFAULT_BIGNUM_PRECISION); + + mpz_inits(sc->mpz_1, sc->mpz_2, sc->mpz_3, sc->mpz_4, NULL); + mpq_inits(sc->mpq_1, sc->mpq_2, sc->mpq_3, NULL); + mpfr_inits2(DEFAULT_BIGNUM_PRECISION, sc->mpfr_1, sc->mpfr_2, sc->mpfr_3, NULL); + mpc_init(sc->mpc_1); + mpc_init(sc->mpc_2); + + mpz_set_ui(sc->mpz_1, (uint64_t)my_clock()); + gmp_randinit_default(random_gmp_state(p)); + gmp_randseed(random_gmp_state(p), sc->mpz_1); + + sc->pi_symbol = s7_define_constant(sc, "pi", big_pi(sc)); /* not actually a constant because it changes with bignum-precision */ + set_initial_slot(sc->pi_symbol, make_semipermanent_slot(sc, sc->pi_symbol, big_pi(sc))); /* s7_make_slot does not handle this */ + s7_provide(sc, "gmp"); +#else + random_seed(p) = (uint64_t)my_clock(); /* used to be time(NULL), but that means separate threads can get the same random number sequence */ + random_carry(p) = 1675393560; + sc->pi_symbol = s7_define_constant(sc, "pi", real_pi); +#endif + } + for (i = 0; i < 10; i++) sc->singletons[(uint8_t)'0' + i] = small_int(i); + sc->singletons[(uint8_t)'+'] = sc->add_symbol; + sc->singletons[(uint8_t)'-'] = sc->subtract_symbol; + sc->singletons[(uint8_t)'*'] = sc->multiply_symbol; + sc->singletons[(uint8_t)'/'] = sc->divide_symbol; + sc->singletons[(uint8_t)'<'] = sc->lt_symbol; + sc->singletons[(uint8_t)'>'] = sc->gt_symbol; + sc->singletons[(uint8_t)'='] = sc->num_eq_symbol; + + init_choosers(sc); + init_typers(sc); + init_opt_functions(sc); + s7_set_history_enabled(sc, false); +#if S7_DEBUGGING + init_tc_rec(sc); +#endif + + init_unlet(sc); + init_signatures(sc); /* depends on procedure symbols */ + sc->s7_starlet = make_s7_starlet(sc); + s7_set_history_enabled(sc, true); + +#if (!WITH_PURE_S7) + s7_define_variable(sc, "make-rectangular", global_value(sc->complex_symbol)); + s7_eval_c_string(sc, "(define make-polar \n\ + (let ((+signature+ '(number? real? real?))) \n\ + (lambda (mag ang) \n\ + (if (and (real? mag) (real? ang)) \n\ + (complex (* mag (cos ang)) (* mag (sin ang))) \n\ + (error 'wrong-type-arg \"make-polar arguments should be real\")))))"); + + s7_eval_c_string(sc, "(define (call-with-values producer consumer) (apply consumer (list (producer))))"); + /* (consumer (producer)) will work in any "normal" context. If consumer is syntax and then subsequently not syntax, there is confusion */ + + s7_eval_c_string(sc, "(define-macro (multiple-value-bind vars expression . body) \n\ + (list (cons 'lambda (cons vars body)) expression))"); + + s7_eval_c_string(sc, "(define-macro (cond-expand . clauses) \n\ + (if (null? clauses) \n\ + (error 'syntax-error \"cond-expand: no clauses?\")) \n\ + (letrec ((traverse (lambda (tree) \n\ + (if (pair? tree) \n\ + (cons (traverse (car tree)) \n\ + (case (cdr tree) ((())) (else => traverse))) \n\ + (if (memq tree '(and or not else)) tree \n\ + (and (symbol? tree) (provided? tree))))))) \n\ + (cons 'cond (map (lambda (clause) \n\ + (if (pair? clause) \n\ + (cons (traverse (car clause)) \n\ + (case (cdr clause) ((()) '(#f)) (else))) \n\ + (error 'syntax-error \"cond-expand: clause is not a pair, ~S\" clause))) \n\ + clauses))))"); + /* cond-expand should expand into an expansion (or inline macro?) so that if there's no else clause, we can add (else (values)) + * r7rs says: "If none of the s evaluate to #t, then if there is an else clause, its s are included. Otherwise, the cond-expand has no effect." + * The code above returns #, but I read that prose to say that (begin 23 (cond-expand (surreals 1) (foonly 2))) should evaluate to 23. + */ +#endif + + s7_eval_c_string(sc, "(define-expansion (reader-cond . clauses) \n\ + (if (null? clauses) \n\ + (error 'syntax-error \"reader-cond: no clauses?\")) \n\ + (call-with-exit \n\ + (lambda (return) \n\ + (for-each \n\ + (lambda (clause) \n\ + (if (not (pair? clause)) \n\ + (error 'syntax-error \"reader-cond: clause is not a pair, ~S\" clause)) \n\ + (let ((val (eval (car clause)))) \n\ + (when val \n\ + (return \n\ + (cond ((null? (cdr clause)) val) \n\ + ((eq? (cadr clause) '=>) ((eval (caddr clause)) val)) \n\ + ((null? (cddr clause)) (cadr clause)) \n\ + (else (apply values (cdr clause)))))))) \n\ + clauses) \n\ + (values))))"); /* this is not redundant */ /* map above ignores trailing cdr if improper */ + /* was (return `(values ,@(cdr clause))) snd-14, begin snd-13, snd-23 (else (apply values (map quote (cdr clause)))) */ + /* we need "values" (not "begin") to make sure all entries are plugged in at the source location? but that's not how "cond" works */ + /* maybe a better name: reader-cond-values? or reader-values or splicing-cond? */ + + s7_eval_c_string(sc, "(define make-hook \n\ + (let ((+documentation+ \"(make-hook . pars) returns a new hook (a function) that passes the parameters to its function list.\")) \n\ + (lambda hook-args \n\ + (let ((body ())) \n\ + (apply lambda* hook-args \n\ + `((let ((result #)) \n\ + (let ((hook (openlet (sublet (curlet) 'let-ref-fallback (lambda (e sym) #))))) \n\ + (for-each (lambda (hook-function) (hook-function hook)) body) \n\ + result))))))))"); + /* (procedure-source (make-hook 'x 'y)): (lambda* (x y) (let ((result #)) ... result)), see stuff.scm for commentary */ + + s7_eval_c_string(sc, "(define hook-functions \n\ + (let ((+signature+ '(#t procedure?)) \n\ + (+documentation+ \"(hook-functions hook) gets or sets the list of functions associated with the hook\")) \n\ + (dilambda \n\ + (lambda (hook) \n\ + (when (or (not (procedure? hook)) (continuation? hook) (goto? hook)) \n\ + (error 'wrong-type-arg \"hook-functions hook must be a procedure created by make-hook: ~S\" hook)) \n\ + ((funclet hook) 'body)) \n\ + (lambda (hook lst) \n\ + (when (or (not (procedure? hook)) (continuation? hook) (goto? hook)) \n\ + (error 'wrong-type-arg \"hook-functions hook must be a procedure created by make-hook: ~S\" hook)) \n\ + (if (do ((p lst (cdr p))) \n\ + ((not (and (pair? p) \n\ + (procedure? (car p)) \n\ + (aritable? (car p) 1))) \n\ + (null? p))) \n\ + (set! ((funclet hook) 'body) lst) \n\ + (error 'wrong-type-arg \"hook-functions must be a list of functions, each accepting one argument: ~S\" lst))))))"); + + /* -------- *unbound-variable-hook* -------- */ + sc->unbound_variable_hook = s7_eval_c_string(sc, "(make-hook 'variable)"); + s7_define_constant_with_documentation(sc, "*unbound-variable-hook*", sc->unbound_variable_hook, + "*unbound-variable-hook* functions are called when an unbound variable is encountered, passed (hook 'variable)."); + + /* -------- *missing-close-paren-hook* -------- */ + sc->missing_close_paren_hook = s7_eval_c_string(sc, "(make-hook)"); + s7_define_constant_with_documentation(sc, "*missing-close-paren-hook*", sc->missing_close_paren_hook, + "*missing-close-paren-hook* functions are called when the reader thinks a close paren is missing"); + + /* -------- *load-hook* -------- */ + sc->load_hook = s7_eval_c_string(sc, "(make-hook 'name)"); + s7_define_constant_with_documentation(sc, "*load-hook*", sc->load_hook, + "*load-hook* functions are invoked by load, passing the to-be-loaded filename as (hook 'name)"); + + /* -------- *autoload-hook* -------- */ + sc->autoload_hook = s7_eval_c_string(sc, "(make-hook 'name 'file)"); + s7_define_constant_with_documentation(sc, "*autoload-hook*", sc->autoload_hook, + "*autoload-hook* functions are invoked by autoload, passing the to-be-autoloaded filename as (hook 'name) and (hook 'file))"); + + /* -------- *error-hook* -------- */ + sc->error_hook = s7_eval_c_string(sc, "(make-hook 'type 'data)"); + s7_define_constant_with_documentation(sc, "*error-hook*", sc->error_hook, + "*error-hook* functions are called in the error handler, passed (hook 'type) and (hook 'data)."); + + /* -------- *read-error-hook* -------- */ + sc->read_error_hook = s7_eval_c_string(sc, "(make-hook 'type 'data)"); + s7_define_constant_with_documentation(sc, "*read-error-hook*", sc->read_error_hook, + "*read-error-hook* functions are called by the reader if it is unhappy, passing the current program string as (hook 'data)."); + + /* -------- *rootlet-redefinition-hook* -------- */ + sc->rootlet_redefinition_hook = s7_eval_c_string(sc, "(make-hook 'name 'value)"); + s7_define_constant_with_documentation(sc, "*rootlet-redefinition-hook*", sc->rootlet_redefinition_hook, + "*rootlet-redefinition-hook* functions are called when a top-level variable's value is changed, (hook 'name 'value)."); + + sc->let_temp_hook = s7_eval_c_string(sc, "(make-hook 'type 'data)"); + +#if S7_DEBUGGING + s7_define_function(sc, "report-missed-calls", g_report_missed_calls, 0, 0, false, NULL); /* tc/recur tests in s7test.scm */ + /* if (!s7_type_names[0]) {fprintf(stderr, "no type_names\n"); gdb_break();} */ /* no longer necessary? (used to disable a dumb gcc warning) */ + if (strcmp(op_names[HOP_SAFE_C_PP], "h_safe_c_pp") != 0) fprintf(stderr, "c op_name: %s\n", op_names[HOP_SAFE_C_PP]); + if (strcmp(op_names[OP_SET_WITH_LET_2], "set_with_let_2") != 0) fprintf(stderr, "set op_name: %s\n", op_names[OP_SET_WITH_LET_2]); + if (NUM_OPS != 926) fprintf(stderr, "size: cell: %d, block: %d, max op: %d, opt: %d\n", (int)sizeof(s7_cell), (int)sizeof(block_t), NUM_OPS, (int)sizeof(opt_info)); + /* cell size: 48, 120 if debugging, block size: 40, opt: 128 or 280 */ +#endif + + return(sc); +} + + +/* -------------------------------- s7_free -------------------------------- */ +static void gc_list_free(gc_list_t *g) +{ + free(g->list); + free(g); +} + +void s7_free(s7_scheme *sc) +{ + /* free the memory associated with sc + * most pointers are in the saved_pointers table, but any that might be realloc'd need to be handled explicitly + * valgrind --leak-check=full --show-reachable=yes --suppressions=/home/bil/cl/free.supp repl s7test.scm + * valgrind --leak-check=full --show-reachable=yes --gen-suppressions=all --error-limit=no --log-file=raw.log repl s7test.scm + */ + s7_int i; + gc_list_t *gp; + + /* g_gc(sc, sc->nil); */ /* probably not needed (my simple tests work fine if the gc call is omitted) */ /* removed 14-Apr-22 */ + /* s7_quit(sc); */ /* not always needed -- will clean up the C stack if we haven't returned to the top level */ + + gp = sc->c_objects; /* do this first since they might involve gc_unprotect etc */ + for (i = 0; i < gp->loc; i++) + { + s7_pointer s1 = gp->list[i]; + if (c_object_gc_free(sc, s1)) + (*(c_object_gc_free(sc, s1)))(sc, s1); + else (*(c_object_free(sc, s1)))(c_object_value(s1)); + } + gc_list_free(gp); + + gp = sc->vectors; + for (i = 0; i < gp->loc; i++) + if (block_index(unchecked_vector_block(gp->list[i])) == TOP_BLOCK_LIST) + free(block_data(unchecked_vector_block(gp->list[i]))); + gc_list_free(gp); + gc_list_free(sc->multivectors); /* I assume vector_dimension_info won't need 131072 bytes */ + + gp = sc->strings; + for (i = 0; i < gp->loc; i++) + if (block_index(unchecked_string_block(gp->list[i])) == TOP_BLOCK_LIST) + free(block_data(unchecked_string_block(gp->list[i]))); + gc_list_free(gp); + + gp = sc->output_ports; + for (i = 0; i < gp->loc; i++) + { + if ((unchecked_port_data_block(gp->list[i])) && + (block_index(unchecked_port_data_block(gp->list[i])) == TOP_BLOCK_LIST)) + free(block_data(unchecked_port_data_block(gp->list[i]))); /* the file contents, port_block is other stuff */ + if ((is_file_port(gp->list[i])) && + (!port_is_closed(gp->list[i]))) + fclose(port_file(gp->list[i])); + } + gc_list_free(gp); + + gp = sc->input_ports; + for (i = 0; i < gp->loc; i++) + if ((unchecked_port_data_block(gp->list[i])) && + (block_index(unchecked_port_data_block(gp->list[i])) == TOP_BLOCK_LIST)) + free(block_data(unchecked_port_data_block(gp->list[i]))); /* the file contents, port_block is other stuff */ + gc_list_free(gp); + gc_list_free(sc->input_string_ports); /* port_data_block is null, port_block is the const char* data, so I assume it is handled elsewhere */ + + gp = sc->hash_tables; + for (i = 0; i < gp->loc; i++) + if (block_index(unchecked_hash_table_block(gp->list[i])) == TOP_BLOCK_LIST) + free(block_data(unchecked_hash_table_block(gp->list[i]))); + gc_list_free(gp); + +#if WITH_GMP + /* free lists */ + {bigint *p, *np; for (p = sc->bigints; p; p = np) {mpz_clear(p->n); np = p->nxt; free(p);}} + {bigrat *p, *np; for (p = sc->bigrats; p; p = np) {mpq_clear(p->q); np = p->nxt; free(p);}} + {bigflt *p, *np; for (p = sc->bigflts; p; p = np) {mpfr_clear(p->x); np = p->nxt; free(p);}} + {bigcmp *p, *np; for (p = sc->bigcmps; p; p = np) {mpc_clear(p->z); np = p->nxt; free(p);}} + + gp = sc->big_integers; + for (i = 0; i < gp->loc; i++) {bigint *p; p = big_integer_bgi(gp->list[i]); mpz_clear(p->n); free(p);} + gc_list_free(gp); + + gp = sc->big_ratios; + for (i = 0; i < gp->loc; i++) {bigrat *p; p = big_ratio_bgr(gp->list[i]); mpq_clear(p->q); free(p);} + gc_list_free(gp); + + gp = sc->big_reals; + for (i = 0; i < gp->loc; i++) {bigflt *p; p = big_real_bgf(gp->list[i]); mpfr_clear(p->x); free(p);} + gc_list_free(gp); + + gp = sc->big_complexes; + for (i = 0; i < gp->loc; i++) {bigcmp *p; p = big_complex_bgc(gp->list[i]); mpc_clear(p->z); free(p);} + gc_list_free(gp); + + gp = sc->big_random_states; + for (i = 0; i < gp->loc; i++) gmp_randclear(random_gmp_state(gp->list[i])); + gc_list_free(gp); + + gmp_randclear(random_gmp_state(sc->default_random_state)); + + /* temps */ + if (sc->ratloc) free_rat_locals(sc); + mpz_clears(sc->mpz_1, sc->mpz_2, sc->mpz_3, sc->mpz_4, NULL); + mpq_clears(sc->mpq_1, sc->mpq_2, sc->mpq_3, NULL); + mpfr_clears(sc->mpfr_1, sc->mpfr_2, sc->mpfr_3, NULL); + mpc_clear(sc->mpc_1); + mpc_clear(sc->mpc_2); + /* I claim the leftovers (864 bytes, all from mpfr_cosh) are gmp's fault */ +#endif + + free(undefined_name(sc->undefined)); + gp = sc->undefineds; + for (i = 0; i < gp->loc; i++) + free(undefined_name(gp->list[i])); + gc_list_free(gp); + + gc_list_free(sc->gensyms); + gc_list_free(sc->continuations); /* stack is simple vector (handled above) */ + gc_list_free(sc->weak_refs); + gc_list_free(sc->weak_hash_iterators); + gc_list_free(sc->opt1_funcs); + + free(port_port(sc->standard_output)); + free(port_port(sc->standard_error)); + free(port_port(sc->standard_input)); + + if (sc->autoload_names) free(sc->autoload_names); + if (sc->autoload_names_sizes) free(sc->autoload_names_sizes); + if (sc->autoloaded_already) + { + for (i = 0; i < sc->autoload_names_loc; i++) + if (sc->autoloaded_already[i]) free(sc->autoloaded_already[i]); + free(sc->autoloaded_already); + } + for (block_t *top = sc->block_lists[TOP_BLOCK_LIST]; top; top = block_next(top)) + if (block_data(top)) + free(block_data(top)); + + for (i = 0; i < sc->saved_pointers_loc; i++) + free(sc->saved_pointers[i]); + free(sc->saved_pointers); + + { + gc_obj_t *g, *gnxt; + heap_block_t *hpnxt; + for (g = sc->semipermanent_lets; g; g = gnxt) {gnxt = g->nxt; free(g);} + for (g = sc->semipermanent_objects; g; g = gnxt) {gnxt = g->nxt; free(g);} + for (heap_block_t *hp = sc->heap_blocks; hp; hp = hpnxt) {hpnxt = hp->next; free(hp);} + } + + free(sc->heap); + free(sc->free_heap); + free(vector_elements(sc->symbol_table)); /* alloc'd directly, not via block */ + free(sc->symbol_table); + free(sc->unlet); + free(sc->setters); + free(sc->op_stack); + if (sc->tree_pointers) free(sc->tree_pointers); + free(sc->num_to_str); + free(sc->protected_objects_free_list); + if (sc->read_line_buf) free(sc->read_line_buf); + free(sc->strbuf); + free(sc->circle_info->objs); + free(sc->circle_info->refs); + free(sc->circle_info->defined); + free(sc->circle_info); + if (sc->file_names) free(sc->file_names); + free(sc->unentry); + free(sc->input_port_stack); + if (sc->typnam) free(sc->typnam); + + for (i = 0; i < sc->num_fdats; i++) + if (sc->fdats[i]) /* init val is NULL */ + { + if (sc->fdats[i]->curly_str) + free(sc->fdats[i]->curly_str); + free(sc->fdats[i]); + } + free(sc->fdats); + + if (sc->profile_data) + { + free(sc->profile_data->funcs); + free(sc->profile_data->let_names); + free(sc->profile_data->files); + free(sc->profile_data->lines); + free(sc->profile_data->excl); + free(sc->profile_data->timing_data); + free(sc->profile_data); + } + if (sc->c_object_types) + { + for (i = 0; i < sc->num_c_object_types; i++) + { + c_object_t *c_type = sc->c_object_types[i]; + if (c_type->scheme_name) {free(c_type->scheme_name); c_type->scheme_name = NULL;} + free(c_type); + } + free(sc->c_object_types); + } + free(sc); +} + + +/* -------------------------------- repl -------------------------------- */ +#ifndef USE_SND + #define USE_SND 0 +#endif +#ifndef WITH_MAIN + #define WITH_MAIN 0 +#endif + +#if WITH_MAIN && WITH_NOTCURSES + #include "nrepl.c" + /* gcc -o nrepl s7.c -O2 -I. -Wl,-export-dynamic -lm -ldl -DWITH_MAIN -DWITH_NOTCURSES -lnotcurses-core */ +#else + +static void dumb_repl(s7_scheme *sc) +{ + while (true) + { + char buffer[512]; + fprintf(stdout, "\n> "); + if (!fgets(buffer, 512, stdin)) break; /* error or ctrl-D */ + if (((buffer[0] != '\n') || (strlen(buffer) > 1))) + { + char response[1024]; + snprintf(response, 1024, "(write %s)", buffer); + s7_eval_c_string(sc, response); + }} + fprintf(stdout, "\n"); + if (ferror(stdin)) + fprintf(stderr, "read error on stdin\n"); +} + +void s7_repl(s7_scheme *sc) +{ +#if (!WITH_C_LOADER) + dumb_repl(sc); +#else +#if WITH_NOTCURSES + s7_load(sc, "nrepl.scm"); +#else + /* try to get lib_s7.so from the repl's directory, and set *libc*. + * otherwise repl.scm will try to load libc.scm which will try to build libc_s7.so locally, but that requires s7.h + */ + bool repl_loaded = false; + s7_pointer e = s7_inlet(sc, set_clist_2(sc, make_symbol(sc, "init_func", 9), make_symbol(sc, "libc_s7_init", 12))); + s7_int gc_loc = s7_gc_protect(sc, e); + s7_pointer old_e = s7_set_curlet(sc, e); /* e is now (curlet) so loaded names from libc will be placed there, not in (rootlet) */ + s7_pointer val = s7_load_with_environment(sc, "libc_s7.so", e); + if (val) + { + s7_pointer libs = global_slot(sc->libraries_symbol); + uint64_t hash = raw_string_hash((const uint8_t *)"*libc*", 6); /* hack around an idiotic gcc 10.2.1 warning */ + s7_define(sc, sc->nil, new_symbol(sc, "*libc*", 6, hash, hash % SYMBOL_TABLE_SIZE), e); + slot_set_value(libs, cons(sc, cons(sc, s7_make_semipermanent_string(sc, "libc.scm"), e), slot_value(libs))); + } + + s7_set_curlet(sc, old_e); /* restore incoming (curlet) */ + s7_gc_unprotect_at(sc, gc_loc); + + if (!val) /* s7_load was unable to find/load libc_s7.so */ + dumb_repl(sc); + else + { +#if S7_DEBUGGING + s7_autoload(sc, make_symbol(sc, "compare-calls", 13), s7_make_string(sc, "compare-calls.scm")); + s7_autoload(sc, make_symbol(sc, "get-overheads", 13), s7_make_string(sc, "compare-calls.scm")); +#endif + s7_provide(sc, "libc.scm"); + if (!repl_loaded) s7_load(sc, "repl.scm"); + s7_eval_c_string(sc, "((*repl* 'run))"); + } +#endif +#endif +} + +#if WITH_MAIN && (!USE_SND) + +#if (!MS_WINDOWS) && WITH_C_LOADER +static char *realdir(const char *filename) /* this code courtesy Lassi Kortela 4-Nov-19 */ +{ + char *path; + char *p; + /* s7_repl wants to load libc_s7.o (for tcsetattr et al), but if it is started in a directory other than the libc_s7.so + * directory, it fails (it tries to build the library but that requires s7.h and libc.scm). So here we are trying to + * guess the libc_s7 directory from the command line program name. This can't work in general, but it works often + * enough to be worth the effort. If S7_LOAD_PATH is set, it is used instead. + */ + if (!strchr(filename, '/')) + return(NULL); + + if (!(path = realpath(filename, NULL))) /* in Windows maybe GetModuleFileName(NULL, buffer, buffer_size) */ + { + fprintf(stderr, "%s: %s\n", strerror(errno), filename); + exit(2); + } + if (!(p = strrchr(path, '/'))) + { + free(path); + fprintf(stderr, "please provide the full pathname for %s\n", filename); + exit(2); + } + if (p > path) *p = '\0'; else p[1] = 0; + return(path); +} +#endif + +int main(int argc, char **argv) +{ + s7_scheme *sc = s7_init(); + fprintf(stderr, "s7: %s\n", S7_DATE); + + if (argc == 2) + { + fprintf(stderr, "load %s\n", argv[1]); + if (!s7_load(sc, argv[1])) + { + fprintf(stderr, "can't load %s\n", argv[1]); + return(2); + }} + else + { +#if (MS_WINDOWS) || (!WITH_C_LOADER) || ((defined(__linux__)) && (!defined(__GLIBC__))) /* musl? */ + dumb_repl(sc); +#else +#ifdef S7_LOAD_PATH + s7_add_to_load_path(sc, S7_LOAD_PATH); +#else + char *dir = realdir(argv[0]); + if (dir) + { + s7_add_to_load_path(sc, dir); + free(dir); + } +#endif + s7_repl(sc); +#endif + } + return(0); +} + +/* in Linux: gcc s7.c -o repl -DWITH_MAIN -I. -O2 -g -ldl -lm -Wl,-export-dynamic ; also need libc.scm cload.scm repl.scm to get a decent repl + * in *BSD: gcc s7.c -o repl -DWITH_MAIN -I. -O2 -g -lm -Wl,-export-dynamic + * in OSX: gcc s7.c -o repl -DWITH_MAIN -I. -O2 -g -lm + * (clang also needs LDFLAGS="-Wl,-export-dynamic" in Linux and "-fPIC") + * in msys2: gcc s7.c -o s7 -DWITH_MAIN -I. -O2 -g -ldl -lm -Wl,-export-all-symbols,--out-implib,s7.lib + * for tcc: tcc -o s7 s7.c -I. -lm -DWITH_MAIN -ldl -rdynamic -DWITH_C_LOADER + * + * for nrepl: gcc s7.c -o repl -DWITH_MAIN -DWITH_NOTCURSES -I. -O2 -g -lnotcurses-core -ldl -lm -Wl,-export-dynamic + * + * (s7.c compile time 27-Oct-22 49 secs) + * musl works, but there is some problem in libgsl.scm with gsl/gsl_blas.h I think + */ +#endif +#endif + +/* --------------------------------------------------- + * 20.9 21.0 22.0 23.0 23.6 23.7 + * --------------------------------------------------- + * tpeak 115 114 108 105 102 102 + * tref 691 687 463 459 459 459 + * index 1026 1016 973 967 969 970 + * tmock 1177 1165 1057 1019 1027 1027 + * tvect 2519 2464 1772 1669 1647 1647 + * timp 2637 2575 1930 1694 1716 1709 + * texit ---- ---- 1778 1741 1765 1765 + * s7test 1873 1831 1818 1829 1846 1859 + * thook ---- ---- 2590 2030 2046 2044 + * tauto ---- ---- 2562 2048 2063 2046 + * lt 2187 2172 2150 2185 2199 2198 + * dup 3805 3788 2492 2239 2234 2214 + * tcopy 8035 5546 2539 2375 2380 2381 + * tread 2440 2421 2419 2408 2417 2399 + * fbench 2688 2583 2460 2430 2458 2458 + * trclo 2735 2574 2454 2445 2461 2461 + * titer 2865 2842 2641 2509 2465 2465 + * tload ---- ---- 3046 2404 2531 2502 + * tmat 3065 3042 2524 2578 2586 2582 + * tb 2735 2681 2612 2604 2630 2630 + * tsort 3105 3104 2856 2804 2828 2828 + * tobj 4016 3970 3828 3577 3576 3511 + * teq 4068 4045 3536 3486 3588 3568 + * tio 3816 3752 3683 3620 3616 3604 + * tmac 3950 3873 3033 3677 3688 3685 + * tclo 4787 4735 4390 4384 4448 4445 + * tcase 4960 4793 4439 4430 4448 4448 + * tlet 7775 5640 4450 4427 4452 4452 + * tfft 7820 7729 4755 4476 4511 4512 + * tstar 6139 5923 5519 4449 4554 4553 + * tmap 8869 8774 4489 4541 4618 4618 + * tshoot 5525 5447 5183 5055 5048 5044 + * tform 5357 5348 5307 5316 5402 5167 + * tstr 6880 6342 5488 5162 5194 5199 + * tnum 6348 6013 5433 5396 5410 5408 5403 + * tlamb 6423 6273 5720 5560 5620 5620 + * tmisc 8869 7612 6435 6076 6222 6216 + * tgsl 8485 7802 6373 6282 6229 6230 + * tlist 7896 7546 6558 6240 6284 6280 + * tset ---- ---- ---- 6260 6290 6306 + * tari 13.0 12.7 6827 6543 6490 6490 + * trec 6936 6922 6521 6588 6581 6581 + * tleft 10.4 10.2 7657 7479 7627 7611 + * tgc 11.9 11.1 8177 7857 7958 7958 + * thash 11.8 11.7 9734 9479 9483 9535 + * cb 11.2 11.0 9658 9564 9631 9626 + * tgen 11.2 11.4 12.0 12.1 12.1 12.1 + * tall 15.6 15.6 15.6 15.6 15.1 15.1 + * calls 36.7 37.5 37.0 37.5 37.1 37.3 + * sg ---- ---- 55.9 55.8 55.3 55.3 + * lg ---- ---- 105.2 106.4 107.2 107.1 + * tbig 177.4 175.8 156.5 148.1 145.8 145.9 + * --------------------------------------------------- + * + * snd-region|select: (since we can't check for consistency when set), should there be more elaborate writable checks for default-output-header|sample-type? + * safety for exp->mac? check-define-macro in lint (given eval-string, we can't do this in s7.c I think) + * *read-error-hook* is only triggered in #... -- it is reader-error? (see also reader-cond bug) + * more preset access pointers? + * cdr(expr)->expr and caddr matter! check cdr(expr)->cdr(arg) cases and all caddrs (719?) + * max-string-length is not applied very consistently (not in read-line, or various other make_string_with_length cases) + * others: symbol->string, symbol, number->string, s7_make_string, read_string_constant, object->string, format, port_to_let, + * string-append, eval-string?, port-filename? + */ diff --git a/extra/dat-dpi/src/sleep.c b/extra/dat-dpi/src/sleep.c new file mode 100644 index 0000000..320c679 --- /dev/null +++ b/extra/dat-dpi/src/sleep.c @@ -0,0 +1,24 @@ +#include "sleep.h" +#include +#include +#include +#include +#ifdef _WIN32 +#include +#define DAT_MKDIR(path) _mkdir(path) +#else +#define DAT_MKDIR(path) mkdir(path, 0700) +#endif + +static int make_directory(const char *path) { + if (DAT_MKDIR(path) == 0 || errno == EEXIST) return 0; + return -1; +} + +int sleep_init(const char *root) { + char path[1024]; int n; + if (!root || !*root) { errno = EINVAL; return -1; } + n = snprintf(path, sizeof(path), "%s/.dat", root); + if (n < 0 || (size_t)n >= sizeof(path)) { errno = ENAMETOOLONG; return -1; } + return make_directory(path); +} diff --git a/extra/dat-dpi/src/sleep_bitfield.c b/extra/dat-dpi/src/sleep_bitfield.c new file mode 100644 index 0000000..09ea633 --- /dev/null +++ b/extra/dat-dpi/src/sleep_bitfield.c @@ -0,0 +1,34 @@ +#include "sleep_bitfield.h" +#include +#include + +static int bit_get(const uint8_t *bits, size_t bytes, size_t bit) { + if (bit >= bytes * 8) { errno = ERANGE; return -1; } + return (bits[bit >> 3] >> (7 - (bit & 7))) & 1; +} +static int bit_set(uint8_t *bits, size_t bytes, size_t bit, int value) { + uint8_t mask; if (bit >= bytes * 8) { errno = ERANGE; return -1; } + mask = (uint8_t)(1u << (7 - (bit & 7))); if (value) bits[bit >> 3] |= mask; else bits[bit >> 3] &= (uint8_t)~mask; return 0; +} +static unsigned byte_state(uint8_t v) { return v == 0 ? 0u : (v == 0xff ? 3u : 2u); } +void sleep_bitfield_clear(sleep_bitfield_t *f) { if (f) memset(f, 0, sizeof(*f)); } +int sleep_bitfield_get_data(const sleep_bitfield_t *f, size_t bit) { if (!f) { errno = EINVAL; return -1; } return bit_get(f->data, sizeof(f->data), bit); } +int sleep_bitfield_get_tree(const sleep_bitfield_t *f, size_t bit) { if (!f) { errno = EINVAL; return -1; } return bit_get(f->tree, sizeof(f->tree), bit); } +void sleep_bitfield_reindex(sleep_bitfield_t *f) { + size_t pair; if (!f) return; memset(f->index, 0, sizeof(f->index)); + for (pair = 0; pair < 512; pair++) { unsigned tuple = byte_state(f->data[pair*2]) | (byte_state(f->data[pair*2+1]) << 2); size_t byte = pair >> 1; + f->index[byte] |= (uint8_t)(tuple << ((pair & 1) ? 4 : 0)); } +} +int sleep_bitfield_set_data(sleep_bitfield_t *f, size_t bit, int present) { if (!f) { errno = EINVAL; return -1; } if (bit_set(f->data, sizeof(f->data), bit, present)) return -1; sleep_bitfield_reindex(f); return 0; } +int sleep_bitfield_set_tree(sleep_bitfield_t *f, size_t bit, int present) { if (!f) { errno = EINVAL; return -1; } return bit_set(f->tree, sizeof(f->tree), bit, present); } +size_t sleep_bitfield_next_data(const sleep_bitfield_t *f, size_t start, int present) { + size_t bit; if (!f) return SIZE_MAX; for (bit = start; bit < sizeof(f->data)*8; bit++) if (bit_get(f->data, sizeof(f->data), bit) == !!present) return bit; return SIZE_MAX; +} +int sleep_bitfield_encode(const sleep_bitfield_t *f, uint8_t *out, size_t cap) { + if (!f || !out) { errno = EINVAL; return -1; } if (cap < sizeof(*f)) { errno = ENOSPC; return -1; } + memcpy(out, f->data, 1024); memcpy(out+1024, f->tree, 2048); memcpy(out+3072, f->index, 256); memcpy(out+3328, f->reserved, 256); return 0; +} +int sleep_bitfield_decode(sleep_bitfield_t *f, const uint8_t *in, size_t len) { + if (!f || !in) { errno = EINVAL; return -1; } if (len < sizeof(*f)) { errno = EPROTO; return -1; } + memcpy(f->data, in, 1024); memcpy(f->tree, in+1024, 2048); memcpy(f->index, in+3072, 256); memcpy(f->reserved, in+3328, 256); return 0; +} diff --git a/extra/dat-dpi/src/sleep_crypto.c b/extra/dat-dpi/src/sleep_crypto.c new file mode 100644 index 0000000..8d78c96 --- /dev/null +++ b/extra/dat-dpi/src/sleep_crypto.c @@ -0,0 +1,116 @@ +#include "sleep_crypto.h" +#include +#include +#include +#include +#include + +static void put64be(uint8_t *p, uint64_t v) { + unsigned int i; + for (i = 0; i < 8; i++) p[i] = (uint8_t)(v >> (56 - i * 8)); +} + +static int encode_roots(uint8_t **out, size_t *out_len, const uint8_t *roots, + size_t nroots, const uint64_t *indices, const uint64_t *lengths) { + size_t i, len; + uint8_t *p; + if (!out || !out_len || (nroots && (!roots || !indices || !lengths))) { errno = EINVAL; return -1; } + if (nroots > (SIZE_MAX - 1) / SLEEP_ROOT_RECORD_BYTES) { errno = EOVERFLOW; return -1; } + len = 1 + nroots * SLEEP_ROOT_RECORD_BYTES; + p = malloc(len); + if (!p) return -1; + p[0] = 2; /* legacy SLEEP root domain */ + for (i = 0; i < nroots; i++) { + memcpy(p + 1 + i * 48, roots + i * 32, 32); + put64be(p + 33 + i * 48, indices[i]); + put64be(p + 41 + i * 48, lengths[i]); + } + *out = p; *out_len = len; return 0; +} + +int sleep_crypto_init(void) { return sodium_init() < 0 ? -1 : 0; } + +int sleep_stream_xsalsa20_xor(uint8_t *out, const uint8_t *in, size_t length, + const uint8_t nonce[24], const uint8_t key[32], + uint64_t stream_offset) +{ + uint8_t *aligned; + size_t skip = (size_t)(stream_offset & 63u), total; + uint64_t counter = stream_offset >> 6; + if ((!out && length) || (!in && length) || !nonce || !key || + length > SIZE_MAX - skip) { errno = EINVAL; return -1; } + total = skip + length; + if (!total) return 0; + if (counter > UINT64_MAX - (total / 64u + !!(total % 64u))) { errno = EOVERFLOW; return -1; } + aligned = calloc(total, 1); + if (!aligned) return -1; + if (length) memcpy(aligned + skip, in, length); + if (crypto_stream_xsalsa20_xor_ic(aligned, aligned, total, nonce, counter, key) != 0) { + free(aligned); errno = EIO; return -1; + } + if (length) memmove(out, aligned + skip, length); + sodium_memzero(aligned, total); + free(aligned); + return 0; +} + +int sleep_compute_discovery_key(const uint8_t pubkey[32], uint8_t out[32]) +{ + static const uint8_t domain[] = "hypercore"; + if (!pubkey || !out) { errno = EINVAL; return -1; } + /* Dat discovery keys are BLAKE2b(key=feed public key, message="hypercore"). */ + return crypto_generichash(out, 32, domain, sizeof(domain) - 1, pubkey, 32); +} + +int sleep_gen_keypair(sleep_keypair_t *kp) { + if (!kp) { errno = EINVAL; return -1; } + if (crypto_sign_keypair(kp->pubkey, kp->seckey) !=0) + return -1; + return 0; +} + +int sleep_save_pubkey(const char *path, const uint8_t pubkey[SLEEP_PUB_KEY_BYTES]) { + FILE *f = fopen(path, "wb"); + size_t written; + if (!f) return -1; + written = fwrite(pubkey, 1, SLEEP_PUB_KEY_BYTES, f); + fclose(f); + return (written == SLEEP_PUB_KEY_BYTES) ? 0 : -1; +} + +int sleep_load_pubkey(const char *path, uint8_t pubkey[SLEEP_PUB_KEY_BYTES]) { + FILE *f = fopen(path, "rb"); + if (!f) return -1; + size_t read = fread(pubkey, 1, SLEEP_PUB_KEY_BYTES, f); + fclose(f); + return (read == SLEEP_PUB_KEY_BYTES) ? 0 : -1; +} + +int sleep_save_keypair(const char *path,const sleep_keypair_t*kp){FILE*f;size_t n;if(!path||!kp){errno=EINVAL;return-1;}f=fopen(path,"wb");if(!f)return-1;n=fwrite(kp,1,sizeof(*kp),f);if(fclose(f))return-1;return n==sizeof(*kp)?0:-1;} +int sleep_load_keypair(const char *path,sleep_keypair_t*kp){FILE*f;size_t n;if(!path||!kp){errno=EINVAL;return-1;}f=fopen(path,"rb");if(!f)return-1;n=fread(kp,1,sizeof(*kp),f);fclose(f);return n==sizeof(*kp)?0:-1;} + +int sleep_blake2b(uint8_t out[SLEEP_BLAKE2B_BYTES], const uint8_t *in, size_t inlen) { + return crypto_generichash(out, SLEEP_BLAKE2B_BYTES, in, inlen, NULL, 0); +} + +int sleep_sign(uint8_t sig[SLEEP_SIGNATURE_BYTES], const uint8_t *msg, size_t msglen, const sleep_keypair_t *kp) { + return crypto_sign_detached(sig, NULL, msg, msglen, kp->seckey); +} + +int sleep_verify(const uint8_t sig[SLEEP_SIGNATURE_BYTES], const uint8_t *msg, size_t msglen, const uint8_t pubkey[SLEEP_PUB_KEY_BYTES]) { + return crypto_sign_verify_detached(sig, msg, msglen, pubkey); +} + +int sleep_sign_roots(uint8_t sig[SLEEP_SIGNATURE_BYTES], const uint8_t *roots, size_t nroots, const uint64_t *indices, const uint64_t *lengths, const sleep_keypair_t *kp) { + uint8_t *message, digest[32]; size_t len; int rc; + if (encode_roots(&message, &len, roots, nroots, indices, lengths)) return -1; + rc = sleep_blake2b(digest, message, len); free(message); + return rc ? rc : sleep_sign(sig, digest, sizeof(digest), kp); +} + +int sleep_verify_roots(const uint8_t sig[SLEEP_SIGNATURE_BYTES], const uint8_t *roots, size_t nroots, const uint64_t *indices, const uint64_t *lengths, const uint8_t pub[SLEEP_PUB_KEY_BYTES]) { + uint8_t *message, digest[32]; size_t len; int rc; + if (encode_roots(&message, &len, roots, nroots, indices, lengths)) return -1; + rc = sleep_blake2b(digest, message, len); free(message); + return rc ? rc : sleep_verify(sig, digest, sizeof(digest), pub); +} diff --git a/extra/dat-dpi/src/sleep_feed.c b/extra/dat-dpi/src/sleep_feed.c new file mode 100644 index 0000000..ffd5e33 --- /dev/null +++ b/extra/dat-dpi/src/sleep_feed.c @@ -0,0 +1,131 @@ +#define _POSIX_C_SOURCE 200809L +#include "sleep_feed.h" +#include "sleep.h" +#include "sleep_crypto.h" +#include "sleep_writer.h" +#include "sleep_bitfield.h" +#include "sleep_proof.h" +#include +#include +#include +#include +#include + +static uint64_t get64be(const uint8_t *p) { uint64_t v=0; unsigned i; for(i=0;i<8;i++) v=(v<<8)|p[i]; return v; } +static uint32_t get32be(const uint8_t *p) { return ((uint32_t)p[0]<<24)|((uint32_t)p[1]<<16)|((uint32_t)p[2]<<8)|p[3]; } +static void put64be(uint8_t *p,uint64_t v){unsigned i;for(i=0;i<8;i++)p[i]=(uint8_t)(v>>(56-i*8));} +static void put32be(uint8_t *p,uint32_t v){p[0]=(uint8_t)(v>>24);p[1]=(uint8_t)(v>>16);p[2]=(uint8_t)(v>>8);p[3]=(uint8_t)v;} +static int path_for(char *out, size_t cap, const sleep_feed_t *f, const char *suffix) { int n=snprintf(out,cap,"%s/%s.%s",f->directory,f->name,suffix); if(n<0||(size_t)n>=cap){errno=ENAMETOOLONG;return -1;}return 0; } +static int read_file(const char *path, uint8_t **out, size_t *len) { + FILE *fp=fopen(path,"rb"); long n; uint8_t *p; if(!fp) return -1; + if(fseek(fp,0,SEEK_END)||(n=ftell(fp))<0||fseek(fp,0,SEEK_SET)){fclose(fp);return -1;} + p=malloc(n?(size_t)n:1); if(!p){fclose(fp);return -1;} if(n&&fread(p,1,(size_t)n,fp)!=(size_t)n){free(p);fclose(fp);errno=EIO;return -1;} + fclose(fp);*out=p;*len=(size_t)n;return 0; +} +static int reserve_feed(sleep_feed_t *f,size_t needed,int with_proofs){ + size_t capacity=f->capacity?f->capacity:8,old=f->capacity; + uint8_t **blocks,**proofs; + size_t *lengths,*proof_lengths; + if(needed<=f->capacity&&(!with_proofs||f->proofs))return 0; + while(capacitySIZE_MAX/2){errno=EOVERFLOW;return-1;}capacity*=2;} + if(capacity>SIZE_MAX/sizeof(*f->blocks)||capacity>SIZE_MAX/sizeof(*f->lengths)){errno=EOVERFLOW;return-1;} + blocks=realloc(f->blocks,capacity*sizeof(*blocks));if(!blocks)return-1;f->blocks=blocks; + lengths=realloc(f->lengths,capacity*sizeof(*lengths));if(!lengths)return-1;f->lengths=lengths; + memset(f->blocks+old,0,(capacity-old)*sizeof(*f->blocks)); + memset(f->lengths+old,0,(capacity-old)*sizeof(*f->lengths)); + if(with_proofs||f->proofs){ + proofs=realloc(f->proofs,capacity*sizeof(*proofs));if(!proofs)return-1;f->proofs=proofs; + proof_lengths=realloc(f->proof_lengths,capacity*sizeof(*proof_lengths));if(!proof_lengths)return-1;f->proof_lengths=proof_lengths; + memset(f->proofs+old,0,(capacity-old)*sizeof(*f->proofs)); + memset(f->proof_lengths+old,0,(capacity-old)*sizeof(*f->proof_lengths)); + } + f->capacity=capacity;return 0; +} +static int append_remote_record(sleep_feed_t *f,size_t index,const uint8_t *data,size_t len,const uint8_t *proof,size_t proof_len){ + static const uint8_t magic[8]={'S','F','E','E','D','R','1',0}; + char path[1200];uint8_t header[40],record[16];FILE *fp;long end; + if(len>UINT32_MAX||proof_len>UINT32_MAX||path_for(path,sizeof(path),f,"remote"))return-1; + fp=fopen(path,"ab+");if(!fp)return-1; + if(fseek(fp,0,SEEK_END)||(end=ftell(fp))<0){fclose(fp);return-1;} + if(end==0){memcpy(header,magic,8);memcpy(header+8,f->keypair.pubkey,32);if(fwrite(header,1,sizeof(header),fp)!=sizeof(header)){fclose(fp);errno=EIO;return-1;}} + put64be(record,(uint64_t)index);put32be(record+8,(uint32_t)len);put32be(record+12,(uint32_t)proof_len); + if(fwrite(record,1,sizeof(record),fp)!=sizeof(record)||(len&&fwrite(data,1,len,fp)!=len)||(proof_len&&fwrite(proof,1,proof_len,fp)!=proof_len)||fflush(fp)||fsync(fileno(fp))){fclose(fp);errno=EIO;return-1;} + return fclose(fp); +} +int sleep_feed_open(sleep_feed_t *f,const char *dir,const char *name,const sleep_keypair_t *kp) { + char path[1200]; uint8_t *tree=NULL,*data=NULL; size_t tree_len=0,data_len=0,count,i,off=0; + if(!f||!dir||!name){errno=EINVAL;return -1;} memset(f,0,sizeof(*f)); + if(strlen(dir)>=sizeof(f->directory)||strlen(name)>=sizeof(f->name)){errno=ENAMETOOLONG;return -1;} strcpy(f->directory,dir);strcpy(f->name,name); + if(kp){f->keypair=*kp;f->writable=1;} + if(path_for(path,sizeof(path),f,"tree")||read_file(path,&tree,&tree_len)) { + if(errno==ENOENT&&kp)return 0; + if(errno==ENOENT&&!kp){ + char key_path[1200]; uint8_t public_key[32]; int n; + n=snprintf(key_path,sizeof(key_path),"%s/%s.key",dir,name); + if(n<0||(size_t)n>=sizeof(key_path)){errno=ENAMETOOLONG;return-1;} + if(sleep_load_pubkey(key_path,public_key))return-1; + return sleep_feed_open_remote(f,dir,name,public_key); + } + return -1; + } + if(tree_len<32||(tree_len-32)%40){free(tree);errno=EPROTO;return -1;} count=((tree_len-32)/40+1)/2; + if(path_for(path,sizeof(path),f,"data")||read_file(path,&data,&data_len)){free(tree);return -1;} + f->blocks=calloc(count,sizeof(*f->blocks));f->lengths=calloc(count,sizeof(*f->lengths));f->capacity=count;if(count&&(!f->blocks||!f->lengths))goto fail; + for(i=0;iSIZE_MAX||n>data_len-off){errno=EPROTO;goto fail;}f->lengths[i]=(size_t)n;f->blocks[i]=malloc(n?(size_t)n:1);if(!f->blocks[i])goto fail;if(n)memcpy(f->blocks[i],data+off,(size_t)n);off+=(size_t)n;f->length++;} + free(tree);free(data);return 0; +fail: free(tree);free(data);sleep_feed_close(f);return -1; +} +int sleep_feed_open_remote(sleep_feed_t *f,const char *dir,const char *name,const uint8_t public_key[32]) { + static const uint8_t magic[8]={'S','F','E','E','D','R','1',0}; + char path[1200];uint8_t *bytes=NULL;size_t length=0,off=40; + if(!f||!dir||!name||!public_key){errno=EINVAL;return-1;} + if(strlen(dir)>=sizeof(f->directory)||strlen(name)>=sizeof(f->name)){errno=ENAMETOOLONG;return-1;} + memset(f,0,sizeof(*f));strcpy(f->directory,dir);strcpy(f->name,name); + memcpy(f->keypair.pubkey,public_key,32);f->remote_only=1;f->writable=0;f->persist_remote=1; + if(path_for(path,sizeof(path),f,"remote")||read_file(path,&bytes,&length)){ + if(errno==ENOENT)return 0; + sleep_feed_close(f);return-1; + } + if(length<40||memcmp(bytes,magic,8)||memcmp(bytes+8,public_key,32))goto corrupt; + f->persist_remote=0; + while(off=SLEEP_BITFIELD_DATA_BYTES*8u||!proof_len||data_len>length-off||proof_len>length-off-data_len)goto corrupt; + if(sleep_proof_decode(&proof,bytes+off+data_len,proof_len)||proof.block!=index|| + sleep_proof_verify(&proof,bytes+off,data_len,public_key))goto corrupt; + if(sleep_feed_append_remote(f,(size_t)index,bytes+off,data_len, + bytes+off+data_len,proof_len))goto corrupt; + off+=(size_t)data_len+proof_len; + } + f->persist_remote=1;free(bytes);return 0; +corrupt: + free(bytes);sleep_feed_close(f);errno=EBADMSG;return-1; +} +int sleep_feed_load_remote(sleep_feed_t *f,const uint8_t public_key[32]) { + sleep_feed_t cached; + int persist,rc=0; + size_t i; + if(!f||!public_key||f->writable){errno=EINVAL;return-1;} + if(sleep_feed_open_remote(&cached,f->directory,f->name,public_key))return-1; + persist=f->persist_remote; + f->persist_remote=0; + memcpy(f->keypair.pubkey,public_key,32); + for(i=0;ipersist_remote=persist; + sleep_feed_close(&cached); + return rc; +} +size_t sleep_feed_length(const sleep_feed_t *f){return f?f->length:0;} +int sleep_feed_read(const sleep_feed_t *f,size_t index,uint8_t **out,size_t *len){uint8_t *p;if(!f||!out||!len||index>=f->length){errno=EINVAL;return -1;}if(!f->blocks[index]){errno=ENODATA;return-1;}p=malloc(f->lengths[index]?f->lengths[index]:1);if(!p)return -1;if(f->lengths[index])memcpy(p,f->blocks[index],f->lengths[index]);*out=p;*len=f->lengths[index];return 0;} +int sleep_feed_append(sleep_feed_t *f,const uint8_t *data,size_t len){uint8_t *copy;if(!f||!f->writable||(!data&&len)){errno=EINVAL;return-1;}if(reserve_feed(f,f->length+1,0))return-1;copy=malloc(len?len:1);if(!copy)return-1;if(len)memcpy(copy,data,len);f->blocks[f->length]=copy;f->lengths[f->length]=len;f->length++;f->dirty=1;return 0;} +int sleep_feed_append_remote(sleep_feed_t *f,size_t index,const uint8_t *data,size_t len,const uint8_t *proof,size_t proof_len){uint8_t *copy,*proof_copy;if(!f||f->writable||index>=SLEEP_BITFIELD_DATA_BYTES*8u||(!data&&len)||!proof||!proof_len){errno=EINVAL;return-1;}if(indexlength&&f->blocks[index]){errno=EEXIST;return-1;}if(reserve_feed(f,index+1,1))return-1;copy=malloc(len?len:1);proof_copy=malloc(proof_len);if(!copy||!proof_copy){free(copy);free(proof_copy);return-1;}if(len)memcpy(copy,data,len);memcpy(proof_copy,proof,proof_len);if(f->persist_remote&&append_remote_record(f,index,data,len,proof,proof_len)){free(copy);free(proof_copy);return-1;}f->blocks[index]=copy;f->lengths[index]=len;f->proofs[index]=proof_copy;f->proof_lengths[index]=proof_len;if(f->length<=index)f->length=index+1;f->remote_only=1;return 0;} +int sleep_feed_truncate(sleep_feed_t*f,size_t length){size_t i;if(!f||!f->writable||length>f->length){errno=EINVAL;return-1;}for(i=length;ilength;i++)free(f->blocks[i]);f->length=length;f->dirty=1;return 0;} +int sleep_feed_has(const sleep_feed_t*f,size_t index){return f&&indexlength&&f->blocks[index]!=NULL;} +int sleep_feed_byte_offset(const sleep_feed_t*f,size_t index,uint64_t*out){size_t i;uint64_t n=0;if(!f||!out||index>f->length){errno=EINVAL;return-1;}for(i=0;iblocks[i]){errno=ENODATA;return-1;}if(UINT64_MAX-nlengths[i]){errno=EOVERFLOW;return-1;}n+=f->lengths[i];}*out=n;return 0;} +int sleep_feed_flush(sleep_feed_t *f){if(!f||!f->writable){errno=EINVAL;return-1;}if(!f->dirty)return 0;if(sleep_write_feed(f->directory,f->name,(const uint8_t *const*)f->blocks,f->lengths,f->length,&f->keypair))return-1;f->dirty=0;return 0;} +void sleep_feed_close(sleep_feed_t *f){size_t i;if(!f)return;for(i=0;ilength;i++){free(f->blocks?f->blocks[i]:NULL);free(f->proofs?f->proofs[i]:NULL);}free(f->blocks);free(f->lengths);free(f->proofs);free(f->proof_lengths);memset(f,0,sizeof(*f));} diff --git a/extra/dat-dpi/src/sleep_proof.c b/extra/dat-dpi/src/sleep_proof.c new file mode 100644 index 0000000..0ea69c8 --- /dev/null +++ b/extra/dat-dpi/src/sleep_proof.c @@ -0,0 +1,302 @@ +#define _POSIX_C_SOURCE 200809L +#include "sleep_proof.h" + +#include "sleep.h" +#include "sleep_crypto.h" +#include +#include +#include +#include + +#define PROOF_FIXED_BYTES 51u +#define PROOF_NODE_BYTES 48u + +static void put64(uint8_t *p, uint64_t value) +{ + unsigned i; + for (i = 0; i < 8; i++) p[i] = (uint8_t)(value >> (56 - i * 8)); +} + +static uint64_t get64(const uint8_t *p) +{ + uint64_t value = 0; + unsigned i; + for (i = 0; i < 8; i++) value = (value << 8) | p[i]; + return value; +} + +static int read_file(const char *path, uint8_t **data, size_t *length) +{ + FILE *file; + long size; + uint8_t *buffer; + file = fopen(path, "rb"); + if (!file) return -1; + if (fseek(file, 0, SEEK_END) || (size = ftell(file)) < 0 || + fseek(file, 0, SEEK_SET)) { fclose(file); return -1; } + buffer = malloc(size ? (size_t)size : 1); + if (!buffer) { fclose(file); return -1; } + if (size && fread(buffer, 1, (size_t)size, file) != (size_t)size) { + free(buffer); fclose(file); errno = EIO; return -1; + } + fclose(file); + *data = buffer; + *length = (size_t)size; + return 0; +} + +static int add_node(sleep_proof_node_t *nodes, size_t *count, + uint64_t index, const uint8_t *entry) +{ + sleep_proof_node_t *node; + if (*count >= SLEEP_PROOF_MAX_NODES) { errno = EOVERFLOW; return -1; } + node = &nodes[(*count)++]; + memcpy(node->hash, entry, 32); + node->index = index; + node->length = get64(entry + 32); + return 0; +} + +void sleep_proof_init(sleep_proof_t *proof, uint64_t block) +{ + if (proof) { memset(proof, 0, sizeof(*proof)); proof->block = block; } +} + +int sleep_proof_add_node(sleep_proof_t *proof, uint64_t index, + uint64_t length, const uint8_t hash[32]) +{ + sleep_proof_node_t *node; + if (!proof || !hash || proof->node_count >= SLEEP_PROOF_MAX_NODES) { + errno = EINVAL; return -1; + } + node = &proof->nodes[proof->node_count++]; + node->index = index; node->length = length; memcpy(node->hash, hash, 32); + return 0; +} + +int sleep_proof_add_root(sleep_proof_t *proof, uint64_t index, + uint64_t length, const uint8_t hash[32]) +{ + sleep_proof_node_t *root; + if (!proof || !hash || proof->root_count >= SLEEP_PROOF_MAX_NODES) { + errno = EINVAL; return -1; + } + root = &proof->roots[proof->root_count++]; + root->index = index; root->length = length; memcpy(root->hash, hash, 32); + return 0; +} + +int sleep_proof_create(const sleep_feed_t *feed, uint64_t block, + sleep_proof_t *proof) +{ + char tree_path[1200], sig_path[1200]; + uint8_t *tree = NULL, *signatures = NULL; + size_t tree_len = 0, sig_len = 0, node_count, root_count, i; + uint64_t root_indexes[SLEEP_PROOF_MAX_NODES], index; + const uint8_t *leaf; + int is_root; + if (!feed || !proof || !feed->length || block >= feed->length || feed->dirty) { + errno = feed && feed->dirty ? EBUSY : EINVAL; + return -1; + } + /* A read-only replica can forward the publisher's original signed proof. */ + if (feed->proofs && feed->proof_lengths && feed->proofs[block]) + return sleep_proof_decode(proof, feed->proofs[block], feed->proof_lengths[block]); + if (snprintf(tree_path, sizeof(tree_path), "%s/%s.tree", feed->directory, + feed->name) >= (int)sizeof(tree_path) || + snprintf(sig_path, sizeof(sig_path), "%s/%s.signatures", feed->directory, + feed->name) >= (int)sizeof(sig_path)) { errno = ENAMETOOLONG; return -1; } + if (read_file(tree_path, &tree, &tree_len) || + read_file(sig_path, &signatures, &sig_len)) goto fail; + if (tree_len < SLEEP_HEADER_SIZE || (tree_len - SLEEP_HEADER_SIZE) % SLEEP_TREE_ENTRY_SIZE || + sig_len < SLEEP_HEADER_SIZE || (sig_len - SLEEP_HEADER_SIZE) / SLEEP_SIG_ENTRY_SIZE < feed->length) { + errno = EPROTO; goto fail; + } + node_count = (tree_len - SLEEP_HEADER_SIZE) / SLEEP_TREE_ENTRY_SIZE; + root_count = sleep_tree_full_roots((uint64_t)feed->length * 2, + root_indexes, SLEEP_PROOF_MAX_NODES); + if (!root_count || root_count > SLEEP_PROOF_MAX_NODES || block > UINT64_MAX / 2) { + errno = EOVERFLOW; goto fail; + } + sleep_proof_init(proof, block); + index = block * 2; + if (index >= node_count) { errno = EPROTO; goto fail; } + leaf = tree + SLEEP_HEADER_SIZE + (size_t)index * SLEEP_TREE_ENTRY_SIZE; + proof->block_length = get64(leaf + 32); + memcpy(proof->block_hash, leaf, 32); + for (;;) { + is_root = 0; + for (i = 0; i < root_count; i++) if (root_indexes[i] == index) is_root = 1; + if (is_root) break; + { + uint64_t sibling; + const uint8_t *entry; + if (sleep_tree_sibling(index, &sibling) || sibling >= node_count) { + errno = EPROTO; goto fail; + } + entry = tree + SLEEP_HEADER_SIZE + (size_t)sibling * SLEEP_TREE_ENTRY_SIZE; + if (add_node(proof->nodes, &proof->node_count, sibling, entry) || + sleep_tree_parent(index) == UINT64_MAX) goto fail; + index = sleep_tree_parent(index); + } + } + for (i = 0; i < root_count; i++) { + uint64_t root = root_indexes[i]; + const uint8_t *entry; + if (root >= node_count) { errno = EPROTO; goto fail; } + entry = tree + SLEEP_HEADER_SIZE + (size_t)root * SLEEP_TREE_ENTRY_SIZE; + if (add_node(proof->roots, &proof->root_count, root, entry)) goto fail; + } + memcpy(proof->signature, + signatures + SLEEP_HEADER_SIZE + (feed->length - 1) * SLEEP_SIG_ENTRY_SIZE, + sizeof(proof->signature)); + proof->has_signature = 1; + free(tree); free(signatures); + return 0; +fail: + free(tree); free(signatures); memset(proof, 0, sizeof(*proof)); + return -1; +} + +int sleep_proof_from_nodes(uint64_t block, const uint8_t *value, + size_t value_len, const sleep_proof_node_t *nodes, + size_t node_count, const uint8_t signature[64], + uint64_t feed_length, sleep_proof_t *proof) +{ + uint64_t root_indexes[SLEEP_PROOF_MAX_NODES], index; + size_t root_count, i, j; + int is_root; + if (!proof || (!value && value_len) || !signature || + (node_count && !nodes) || !feed_length || block >= feed_length || + feed_length > UINT64_MAX / 2 || block > UINT64_MAX / 2) { + errno = EINVAL; + return -1; + } + root_count = sleep_tree_full_roots(feed_length * 2, root_indexes, + SLEEP_PROOF_MAX_NODES); + if (!root_count || root_count > SLEEP_PROOF_MAX_NODES) { errno = EOVERFLOW; return -1; } + sleep_proof_init(proof, block); + proof->block_length = value_len; + if (sleep_tree_hash_block(value, value_len, proof->block_hash)) return -1; + index = block * 2; + for (;;) { + is_root = 0; + for (i = 0; i < root_count; i++) if (root_indexes[i] == index) is_root = 1; + if (is_root) break; + { + uint64_t sibling; + if (sleep_tree_sibling(index, &sibling)) goto malformed; + for (j = 0; j < node_count; j++) if (nodes[j].index == sibling) break; + if (j == node_count || sleep_proof_add_node(proof, sibling, + nodes[j].length, nodes[j].hash)) goto malformed; + index = sleep_tree_parent(index); + if (index == UINT64_MAX) goto malformed; + } + } + for (i = 0; i < root_count; i++) { + for (j = 0; j < node_count; j++) if (nodes[j].index == root_indexes[i]) break; + if (j == node_count || sleep_proof_add_root(proof, nodes[j].index, + nodes[j].length, nodes[j].hash)) goto malformed; + } + memcpy(proof->signature, signature, 64); + proof->has_signature = 1; + return 0; +malformed: + memset(proof, 0, sizeof(*proof)); + errno = EBADMSG; + return -1; +} + +static void encode_node(uint8_t *out, const sleep_proof_node_t *node) +{ + memcpy(out, node->hash, 32); put64(out + 32, node->index); put64(out + 40, node->length); +} + +int sleep_proof_encode(const sleep_proof_t *proof, uint8_t *out, + size_t capacity, size_t *written) +{ + size_t need, off = 0, i; + if (!proof || !out || proof->node_count > SLEEP_PROOF_MAX_NODES || + proof->root_count > SLEEP_PROOF_MAX_NODES) { errno = EINVAL; return -1; } + need = PROOF_FIXED_BYTES + (proof->node_count + proof->root_count) * PROOF_NODE_BYTES + + (proof->has_signature ? 64 : 0); + if (capacity < need) { errno = ENOSPC; return -1; } + put64(out + off, proof->block); off += 8; + put64(out + off, proof->block_length); off += 8; + memcpy(out + off, proof->block_hash, 32); off += 32; + out[off++] = (uint8_t)proof->node_count; + out[off++] = (uint8_t)!!proof->has_signature; + out[off++] = (uint8_t)proof->root_count; + for (i = 0; i < proof->node_count; i++, off += PROOF_NODE_BYTES) encode_node(out + off, &proof->nodes[i]); + for (i = 0; i < proof->root_count; i++, off += PROOF_NODE_BYTES) encode_node(out + off, &proof->roots[i]); + if (proof->has_signature) { memcpy(out + off, proof->signature, 64); off += 64; } + if (written) *written = off; + return 0; +} + +static void decode_node(sleep_proof_node_t *node, const uint8_t *in) +{ + memcpy(node->hash, in, 32); node->index = get64(in + 32); node->length = get64(in + 40); +} + +int sleep_proof_decode(sleep_proof_t *proof, const uint8_t *input, size_t input_len) +{ + size_t off = 0, i, need; + if (!proof || !input || input_len < PROOF_FIXED_BYTES) { errno = EPROTO; return -1; } + sleep_proof_init(proof, get64(input)); off = 8; + proof->block_length = get64(input + off); off += 8; + memcpy(proof->block_hash, input + off, 32); off += 32; + proof->node_count = input[off++]; + proof->has_signature = !!input[off++]; + proof->root_count = input[off++]; + if (proof->node_count > SLEEP_PROOF_MAX_NODES || proof->root_count > SLEEP_PROOF_MAX_NODES) { + errno = EPROTO; return -1; + } + need = PROOF_FIXED_BYTES + (proof->node_count + proof->root_count) * PROOF_NODE_BYTES + + (proof->has_signature ? 64 : 0); + if (input_len != need) { errno = EPROTO; return -1; } + for (i = 0; i < proof->node_count; i++, off += PROOF_NODE_BYTES) decode_node(&proof->nodes[i], input + off); + for (i = 0; i < proof->root_count; i++, off += PROOF_NODE_BYTES) decode_node(&proof->roots[i], input + off); + if (proof->has_signature) memcpy(proof->signature, input + off, 64); + return 0; +} + +int sleep_proof_verify(const sleep_proof_t *proof, const uint8_t *block, + size_t block_len, const uint8_t public_key[32]) +{ + uint8_t hash[32], parent[32], root_hashes[SLEEP_PROOF_MAX_NODES * 32]; + uint64_t indices[SLEEP_PROOF_MAX_NODES], lengths[SLEEP_PROOF_MAX_NODES]; + uint64_t index, length; + size_t i, r; + int root_found = 0; + if (!proof || (!block && block_len) || !public_key || !proof->has_signature || + !proof->root_count || proof->root_count > SLEEP_PROOF_MAX_NODES) { errno = EINVAL; return -1; } + if (proof->block > UINT64_MAX / 2 || proof->block_length != block_len || + sleep_tree_hash_block(block, block_len, hash) || memcmp(hash, proof->block_hash, 32)) { + errno = EBADMSG; return -1; + } + index = proof->block * 2; + length = block_len; + for (i = 0; i < proof->node_count; i++) { + const sleep_proof_node_t *node = &proof->nodes[i]; + uint64_t sibling; + if (sleep_tree_sibling(index, &sibling) || node->index != sibling || + UINT64_MAX - length < node->length) { errno = EBADMSG; return -1; } + if (sibling < index) { + if (sleep_tree_hash_parent(node->hash, hash, node->length + length, parent)) return -1; + } else if (sleep_tree_hash_parent(hash, node->hash, length + node->length, parent)) return -1; + memcpy(hash, parent, 32); length += node->length; index = sleep_tree_parent(index); + } + for (r = 0; r < proof->root_count; r++) { + const sleep_proof_node_t *root = &proof->roots[r]; + indices[r] = root->index; lengths[r] = root->length; + memcpy(root_hashes + r * 32, root->hash, 32); + if (root->index == index && root->length == length && !memcmp(root->hash, hash, 32)) root_found = 1; + if (r && proof->roots[r - 1].index >= root->index) { errno = EBADMSG; return -1; } + } + if (!root_found || sleep_verify_roots(proof->signature, root_hashes, + proof->root_count, indices, lengths, public_key)) { + errno = EBADMSG; return -1; + } + return 0; +} diff --git a/extra/dat-dpi/src/sleep_proto.c b/extra/dat-dpi/src/sleep_proto.c new file mode 100644 index 0000000..688f786 --- /dev/null +++ b/extra/dat-dpi/src/sleep_proto.c @@ -0,0 +1,58 @@ +#include "sleep_proto.h" +#include +#include +#include +static size_t bounded_len(const char *s,size_t cap){size_t n=0;while(n= 128) { v >>= 7; n++; } return n; } +int sleep_varint_encode(uint64_t v, uint8_t *out, size_t cap, size_t *written) { + size_t need = sleep_varint_size(v), i = 0; if (!out || cap < need) { errno = out ? ENOSPC : EINVAL; return -1; } + while (v >= 128) { out[i++] = (uint8_t)((v & 127) | 128); v >>= 7; } out[i++] = (uint8_t)v; + if (written) *written = i; + return 0; +} +int sleep_varint_decode(const uint8_t *in, size_t len, uint64_t *value, size_t *used) { + uint64_t v = 0; size_t i; if (!in || !value) { errno = EINVAL; return -1; } + for (i = 0; i < len && i < 10; i++) { uint8_t b = in[i]; if (i == 9 && b > 1) { errno = EOVERFLOW; return -1; } + v |= (uint64_t)(b & 127) << (i * 7); if (!(b & 128)) { *value = v; if (used) *used = i + 1; return 0; } } + errno = len < 10 ? EAGAIN : EOVERFLOW; return -1; +} +static int put_field(uint8_t tag, const uint8_t *data, size_t len, uint8_t *out, size_t cap, size_t *off) { + size_t n; if (*off >= cap) { errno = ENOSPC; return -1; } out[(*off)++] = tag; + if (sleep_varint_encode(len, out + *off, cap - *off, &n)) return -1; + *off += n; + if (len > cap - *off) { errno = ENOSPC; return -1; } memcpy(out + *off, data, len); *off += len; return 0; +} +int sleep_metadata_header_encode(const sleep_metadata_header_t *h, uint8_t *out, size_t cap, size_t *written) { + size_t off = 0, type_len; if (!h || !out || h->content_len > 32) { errno = EINVAL; return -1; } type_len = strlen(h->type); + if (put_field(0x0a, (const uint8_t *)h->type, type_len, out, cap, &off) || put_field(0x12, h->content, h->content_len, out, cap, &off)) return -1; + if (written) *written = off; + return 0; +} +int sleep_metadata_header_decode(sleep_metadata_header_t *h, const uint8_t *in, size_t len) { + size_t off = 0; if (!h || (!in && len)) { errno = EINVAL; return -1; } memset(h, 0, sizeof(*h)); + while (off < len) { uint8_t tag = in[off++]; uint64_t n64; size_t n, copy; + if (sleep_varint_decode(in + off, len - off, &n64, &n)) return -1; + off += n; + if (n64 > len - off) { errno = EPROTO; return -1; } + if (tag == 0x0a) { copy = n64 < sizeof(h->type)-1 ? (size_t)n64 : sizeof(h->type)-1; memcpy(h->type, in+off, copy); } + else if (tag == 0x12) { if (n64 > 32) { errno = EOVERFLOW; return -1; } memcpy(h->content, in+off, (size_t)n64); h->content_len = (size_t)n64; } + off += (size_t)n64; } + return 0; +} +int sleep_write_metadata_header(const char *path, const uint8_t key[32]) { + sleep_metadata_header_t h; uint8_t buf[128]; size_t len; FILE *f; if (!path || !key) { errno = EINVAL; return -1; } + memset(&h, 0, sizeof(h)); strcpy(h.type, "hyperdrive"); memcpy(h.content, key, 32); h.content_len = 32; + if (sleep_metadata_header_encode(&h, buf, sizeof(buf), &len)) return -1; + f = fopen(path, "wb"); + if (!f) return -1; + if (fwrite(buf, 1, len, f) != len) { fclose(f); errno = EIO; return -1; } return fclose(f); +} + +static int put_uint_field(uint8_t tag,uint64_t value,uint8_t *out,size_t cap,size_t *off){size_t n;if(*off>=cap){errno=ENOSPC;return-1;}out[(*off)++]=tag;if(sleep_varint_encode(value,out+*off,cap-*off,&n))return-1;*off+=n;return 0;} +int sleep_metadata_entry_encode(const sleep_metadata_entry_t *e,uint8_t *out,size_t cap,size_t *written){size_t off=0,path_len,link_len;if(!e||!out){errno=EINVAL;return-1;}path_len=bounded_len(e->path,sizeof(e->path));link_len=bounded_len(e->linkname,sizeof(e->linkname)); + if(put_field(0x0a,(const uint8_t*)e->path,path_len,out,cap,&off)||put_uint_field(0x10,e->type,out,cap,&off)||put_uint_field(0x18,e->mode,out,cap,&off)||put_uint_field(0x20,e->size,out,cap,&off)||put_uint_field(0x28,e->mtime,out,cap,&off)||put_uint_field(0x30,e->block_offset,out,cap,&off)||put_uint_field(0x38,e->block_count,out,cap,&off))return-1; + if(link_len&&put_field(0x42,(const uint8_t*)e->linkname,link_len,out,cap,&off))return-1; + if(written)*written=off; + return 0;} +int sleep_metadata_entry_decode(sleep_metadata_entry_t *e,const uint8_t *in,size_t len){size_t off=0;if(!e||(!in&&len)){errno=EINVAL;return-1;}memset(e,0,sizeof(*e));while(offlen-off){errno=EPROTO;return-1;}if(tag==0x0a){copy=valuepath)-1?(size_t)value:sizeof(e->path)-1;memcpy(e->path,in+off,copy);}else if(tag==0x42){copy=valuelinkname)-1?(size_t)value:sizeof(e->linkname)-1;memcpy(e->linkname,in+off,copy);}off+=(size_t)value;}else if((tag&7)==0){if(sleep_varint_decode(in+off,len-off,&value,&n))return-1;off+=n;switch(tag){case 0x10:e->type=(uint32_t)value;break;case 0x18:e->mode=(uint32_t)value;break;case 0x20:e->size=value;break;case 0x28:e->mtime=value;break;case 0x30:e->block_offset=value;break;case 0x38:e->block_count=value;break;default:break;}}else{errno=EPROTO;return-1;}}return 0;} diff --git a/extra/dat-dpi/src/sleep_replication.c b/extra/dat-dpi/src/sleep_replication.c new file mode 100644 index 0000000..2d467fa --- /dev/null +++ b/extra/dat-dpi/src/sleep_replication.c @@ -0,0 +1,160 @@ +#include "sleep_replication.h" +#include +#include +#include + +int sleep_replication_init(sleep_replication_t *r, sleep_feed_t *feed, + uint64_t channel) +{ + if (!r || !feed) { errno = EINVAL; return -1; } + memset(r, 0, sizeof(*r)); + r->feed = feed; + r->channel = channel; + r->live = 1; + r->uploading = 1; + r->downloading = 1; + return 0; +} + +int sleep_replication_have(sleep_replication_t *r, uint64_t start, + uint64_t length) +{ + uint64_t i, end; + if (!r || start > UINT64_MAX - length) { errno = EINVAL; return -1; } + end = start + length; + for (i = start; i < end && i < SLEEP_BITFIELD_DATA_BYTES * 8u; i++) + if (sleep_bitfield_set_data(&r->remote_bitfield, (size_t)i, 1)) return -1; + if (end > r->remote_length) r->remote_length = end; + return 0; +} + +int sleep_replication_unhave(sleep_replication_t *r, uint64_t start, + uint64_t length) +{ + uint64_t i, end; + size_t last = SIZE_MAX; + if (!r || start > UINT64_MAX - length) { errno = EINVAL; return -1; } + end = start + length; + for (i = start; i < end && i < SLEEP_BITFIELD_DATA_BYTES * 8u; i++) + if (sleep_bitfield_set_data(&r->remote_bitfield, (size_t)i, 0)) return -1; + for (i = 0; i < SLEEP_BITFIELD_DATA_BYTES * 8u; i++) + if (sleep_bitfield_get_data(&r->remote_bitfield, (size_t)i) == 1) last = (size_t)i; + r->remote_length = last == SIZE_MAX ? 0 : (uint64_t)last + 1; + return 0; +} + +int sleep_replication_request(sleep_replication_t *r, + const sleep_replication_request_t *q) +{ + size_t i, tail; + if (!r || !q) { errno = EINVAL; return -1; } + for (i = 0; i < r->request_count; i++) { + size_t slot = (r->request_head + i) % SLEEP_REPLICATION_QUEUE; + if (r->requests[slot].index == q->index) return 0; + } + if (r->request_count >= SLEEP_REPLICATION_QUEUE) { errno = ENOSPC; return -1; } + tail = (r->request_head + r->request_count) % SLEEP_REPLICATION_QUEUE; + r->requests[tail] = *q; + r->request_count++; + return 0; +} + +int sleep_replication_want(sleep_replication_t *r, uint64_t start, + uint64_t length) +{ + uint64_t i; + size_t additions = 0; + if (!r || start > UINT64_MAX - length) { errno = EINVAL; return -1; } + if (length > SLEEP_REPLICATION_QUEUE) { errno = ENOSPC; return -1; } + for (i = start; i < start + length; i++) { + if (!sleep_replication_is_requested(r, i)) additions++; + } + if (additions > SLEEP_REPLICATION_QUEUE - r->request_count) { + errno = ENOSPC; + return -1; + } + for (i = start; i < start + length; i++) { + sleep_replication_request_t q = {i, 0, 1, 1}; + if (sleep_replication_request(r, &q)) return -1; + } + return 0; +} + +int sleep_replication_next(sleep_replication_t *r, + sleep_replication_request_t *q) +{ + if (!r || !q) { errno = EINVAL; return -1; } + if (!r->request_count) return 0; + *q = r->requests[r->request_head]; + r->request_head = (r->request_head + 1) % SLEEP_REPLICATION_QUEUE; + r->request_count--; + return 1; +} + +int sleep_replication_complete(sleep_replication_t *r, uint64_t index) +{ + sleep_replication_request_t kept[SLEEP_REPLICATION_QUEUE]; + size_t i, count = 0; + if (!r) { errno = EINVAL; return -1; } + for (i = 0; i < r->request_count; i++) { + size_t slot = (r->request_head + i) % SLEEP_REPLICATION_QUEUE; + if (r->requests[slot].index != index) kept[count++] = r->requests[slot]; + } + memcpy(r->requests, kept, count * sizeof(*kept)); + r->request_head = 0; + r->request_count = count; + return 0; +} + +int sleep_replication_is_requested(const sleep_replication_t *r, uint64_t index) +{ + size_t i; + if (!r) { errno = EINVAL; return -1; } + for (i = 0; i < r->request_count; i++) { + size_t slot = (r->request_head + i) % SLEEP_REPLICATION_QUEUE; + if (r->requests[slot].index == index) return 1; + } + return 0; +} + +int sleep_replication_unwant(sleep_replication_t *r, uint64_t start, + uint64_t length) +{ + uint64_t i; + if (!r || start > UINT64_MAX - length || length > SLEEP_REPLICATION_QUEUE) { errno = EINVAL; return -1; } + for (i = start; i < start + length; i++) + if (sleep_replication_complete(r, i)) return -1; + return 0; +} + +void sleep_replication_clear_requests(sleep_replication_t *r) +{ + if (!r) return; + r->request_head = 0; + r->request_count = 0; +} + +int sleep_replication_receive(sleep_replication_t *r, const uint8_t *block, + size_t len, const sleep_proof_t *proof, + const uint8_t public_key[32]) +{ + uint8_t encoded[8192]; + uint8_t *old = NULL; + size_t encoded_len, old_len; + if (!r || !r->feed || (!block && len) || !proof || !public_key) { + errno = EINVAL; + return -1; + } + if (proof->block >= SLEEP_BITFIELD_DATA_BYTES * 8u || + sleep_proof_verify(proof, block, len, public_key)) return -1; + if (sleep_proof_encode(proof, encoded, sizeof(encoded), &encoded_len) || + (sleep_feed_has(r->feed, (size_t)proof->block) && + sleep_feed_read(r->feed, (size_t)proof->block, &old, &old_len))) return -1; + if (old) { + int same = old_len == len && !memcmp(old, block, len); + free(old); + if (!same) { errno = EBADMSG; return -1; } + } else if (sleep_feed_append_remote(r->feed, (size_t)proof->block, block, len, + encoded, encoded_len)) return -1; + return sleep_replication_have(r, proof->block, 1); +} diff --git a/extra/dat-dpi/src/sleep_tree.c b/extra/dat-dpi/src/sleep_tree.c new file mode 100644 index 0000000..244e02e --- /dev/null +++ b/extra/dat-dpi/src/sleep_tree.c @@ -0,0 +1,287 @@ +/* + * SLEEP Merkle tree. + * + * The on-disk tree is flat-tree/in-order notation: + * + * 3 + * 1 5 + * 0 2 4 6 + * + * even indexes = leaves + * odd indexes = parent nodes + * + * SLEEP adds domain-separated LEAF/PARENT/ROOT hashing and stores + * aggregate byte lengths in every tree entry. + */ +#include "sleep.h" + +#include +#include +#include +#include + +#ifdef SLEEP_SALT +#include +#endif + +static unsigned int sleep_tree_ctz64(uint64_t x) +{ +#if defined(__GNUC__) || defined(__clang__) + if (x == 0) + return 64; + return (unsigned int)__builtin_ctzll(x); +#else + unsigned int n = 0; + + if (x == 0) + return 64; + + while ((x & 1ULL) == 0) { + x >>= 1; + n++; + } + + return n; +#endif +} + +uint64_t sleep_tree_index(uint64_t depth, uint64_t offset) +{ + if (depth >= 63) { + errno = EOVERFLOW; + return UINT64_MAX; + } + + return ((1ULL + (2ULL * offset)) << depth) - 1ULL; +} + +uint64_t sleep_tree_depth(uint64_t index) +{ + return sleep_tree_ctz64(index + 1ULL); +} + +uint64_t sleep_tree_offset(uint64_t index, uint64_t depth) +{ + if ((index & 1ULL) == 0) + return index / 2ULL; + + if (depth == UINT64_MAX) + depth = sleep_tree_depth(index); + + return (((index + 1ULL) >> depth) - 1ULL) / 2ULL; +} + +uint64_t sleep_tree_parent(uint64_t index) +{ + uint64_t depth = sleep_tree_depth(index); + uint64_t offset = sleep_tree_offset(index, depth); + + return sleep_tree_index(depth + 1ULL, offset >> 1); +} + +int sleep_tree_sibling(uint64_t index, uint64_t *out) +{ + uint64_t depth; + uint64_t offset; + + if (out == NULL) { + errno = EINVAL; + return -1; + } + + depth = sleep_tree_depth(index); + offset = sleep_tree_offset(index, depth); + + *out = sleep_tree_index(depth, offset & 1ULL ? offset - 1ULL + : offset + 1ULL); + return 0; +} + +int sleep_tree_left_child(uint64_t index, uint64_t *out) +{ + uint64_t depth; + uint64_t offset; + + if (out == NULL) { + errno = EINVAL; + return -1; + } + + if ((index & 1ULL) == 0) { + errno = EINVAL; + return -1; + } + + depth = sleep_tree_depth(index); + if (depth == 0) { + errno = EINVAL; + return -1; + } + + offset = sleep_tree_offset(index, depth); + *out = sleep_tree_index(depth - 1ULL, offset * 2ULL); + return 0; +} + +int sleep_tree_right_child(uint64_t index, uint64_t *out) +{ + uint64_t depth; + uint64_t offset; + + if (out == NULL) { + errno = EINVAL; + return -1; + } + + if ((index & 1ULL) == 0) { + errno = EINVAL; + return -1; + } + + depth = sleep_tree_depth(index); + if (depth == 0) { + errno = EINVAL; + return -1; + } + + offset = sleep_tree_offset(index, depth); + *out = sleep_tree_index(depth - 1ULL, offset * 2ULL + 1ULL); + return 0; +} + +uint64_t sleep_tree_left_span(uint64_t index) +{ + uint64_t depth; + + if ((index & 1ULL) == 0) + return index; + + depth = sleep_tree_depth(index); + return sleep_tree_offset(index, depth) * (1ULL << (depth + 1ULL)); +} + +uint64_t sleep_tree_right_span(uint64_t index) +{ + uint64_t depth; + + if ((index & 1ULL) == 0) + return index; + + depth = sleep_tree_depth(index); + return (sleep_tree_offset(index, depth) + 1ULL) * + (1ULL << (depth + 1ULL)) - 2ULL; +} + +void sleep_tree_spans(uint64_t index, uint64_t *left, uint64_t *right) +{ + if (left != NULL) + *left = sleep_tree_left_span(index); + if (right != NULL) + *right = sleep_tree_right_span(index); +} + +size_t sleep_tree_count(uint64_t index) +{ + uint64_t depth; + + if ((index & 1ULL) == 0) + return 1; + + depth = sleep_tree_depth(index); + if (depth >= sizeof(size_t) * 8 - 1) + return SIZE_MAX; + + return ((size_t)1 << (depth + 1ULL)) - 1; +} + +size_t sleep_tree_count_leaves(uint64_t index) +{ + return (sleep_tree_count(index) + 1) / 2; +} + +size_t sleep_tree_full_roots(uint64_t index, uint64_t *out, size_t capacity) +{ + uint64_t blocks; + uint64_t offset = 0; + uint64_t factor = 1; + size_t count = 0; + + if (index & 1ULL) { + errno = EINVAL; + return 0; + } + + /* mafintosh/flat-tree fullRoots(index) works on index / 2. */ + blocks = index / 2ULL; + + while (blocks != 0) { + while (factor <= UINT64_MAX / 2ULL && factor * 2ULL <= blocks) + factor *= 2ULL; + + if (out != NULL && count < capacity) + out[count] = offset + factor - 1ULL; + + count++; + offset += 2ULL * factor; + blocks -= factor; + factor = 1; + } + + return count; +} + +#ifdef SLEEP_SALT +int sleep_tree_hash_block(const uint8_t *data, size_t len, + uint8_t out_hash[32]) +{ + uint8_t header[9]; + crypto_generichash_state state; + + if (out_hash == NULL || (data == NULL && len != 0)) { + errno = EINVAL; + return -1; + } + + header[0] = SLEEP_HASH_LEAF; + for (int i = 0; i < 8; i++) + header[1 + i] = (uint8_t)((uint64_t)len >> (56 - i * 8)); + + if (crypto_generichash_init(&state, NULL, 0, 32) != 0) + return -1; + if (crypto_generichash_update(&state, header, sizeof(header)) != 0) + return -1; + if (len != 0 && crypto_generichash_update(&state, data, len) != 0) + return -1; + + return crypto_generichash_final(&state, out_hash, 32); +} + +int sleep_tree_hash_parent(const uint8_t left[32], + const uint8_t right[32], + uint64_t length, + uint8_t out_hash[32]) +{ + uint8_t header[9]; + crypto_generichash_state state; + + if (left == NULL || right == NULL || out_hash == NULL) { + errno = EINVAL; + return -1; + } + + header[0] = SLEEP_HASH_PARENT; + for (int i = 0; i < 8; i++) + header[1 + i] = (uint8_t)(length >> (56 - i * 8)); + + if (crypto_generichash_init(&state, NULL, 0, 32) != 0) + return -1; + if (crypto_generichash_update(&state, header, sizeof(header)) != 0) + return -1; + if (crypto_generichash_update(&state, left, 32) != 0) + return -1; + if (crypto_generichash_update(&state, right, 32) != 0) + return -1; + + return crypto_generichash_final(&state, out_hash, 32); +} +#endif diff --git a/extra/dat-dpi/src/sleep_utp.c b/extra/dat-dpi/src/sleep_utp.c new file mode 100644 index 0000000..8717e6f --- /dev/null +++ b/extra/dat-dpi/src/sleep_utp.c @@ -0,0 +1,114 @@ +// hyperswarm dht +#include "sleep_utp.h" +#include +#include +#include +#include "dat_socket.h" +static void put16(uint8_t*p,uint16_t v){p[0]=(uint8_t)(v>>8);p[1]=(uint8_t)v;}static void put32(uint8_t*p,uint32_t v){p[0]=(uint8_t)(v>>24);p[1]=(uint8_t)(v>>16);p[2]=(uint8_t)(v>>8);p[3]=(uint8_t)v;}static uint16_t get16(const uint8_t*p){return((uint16_t)p[0]<<8)|p[1];}static uint32_t get32(const uint8_t*p){return((uint32_t)p[0]<<24)|((uint32_t)p[1]<<16)|((uint32_t)p[2]<<8)|p[3];} +int sleep_utp_init(sleep_utp_t*u,dat_socket_t fd){if(!u||fd==DAT_INVALID_SOCKET){errno=EINVAL;return-1;}memset(u,0,sizeof(*u));u->socket=fd;u->state=SLEEP_UTP_CLOSED;u->receive_window=1024*1024;u->send_window=SLEEP_UTP_WINDOW*SLEEP_UTP_MTU;u->congestion_window=2*SLEEP_UTP_MTU;u->slow_start_threshold=32*SLEEP_UTP_MTU;u->round_trip_time=500;u->round_trip_variance=250;u->send_id=(uint16_t)rand();u->receive_id=u->send_id+1;return 0;} +int sleep_utp_packet_encode(const sleep_utp_packet_t*p,uint8_t*out,size_t cap,size_t*written){size_t off=20;if(!p||!out||(!p->payload&&p->payload_len)){errno=EINVAL;return-1;}if(p->type>SLEEP_UTP_SYN||p->version!=1){errno=EPROTO;return-1;}if(p->type==SLEEP_UTP_DATA&&p->payload_len>SLEEP_UTP_MTU){errno=EMSGSIZE;return-1;}if(cap<20+(p->has_selective_ack?6:0)+p->payload_len){errno=ENOSPC;return-1;}out[0]=(uint8_t)((p->type<<4)|(p->version&15));out[1]=p->has_selective_ack?1:0;put16(out+2,p->connection_id);put32(out+4,p->timestamp);put32(out+8,p->timestamp_diff);put32(out+12,p->window_size);put16(out+16,p->sequence);put16(out+18,p->acknowledgement);if(p->has_selective_ack){out[off++]=0;out[off++]=4;put32(out+off,p->selective_ack);off+=4;}if(p->payload_len)memcpy(out+off,p->payload,p->payload_len);if(written)*written=off+p->payload_len;return 0;} +int sleep_utp_packet_decode(sleep_utp_packet_t *p, const uint8_t *in, size_t len) +{ + size_t off = 20; + uint8_t ext; + if (!p || !in || len < 20) { errno = EPROTO; return -1; } + memset(p, 0, sizeof(*p)); + p->type = in[0] >> 4; + p->version = in[0] & 15; + if (p->type > SLEEP_UTP_SYN || p->version != 1) { errno = EPROTO; return -1; } + ext = in[1]; + p->connection_id = get16(in + 2); + p->timestamp = get32(in + 4); + p->timestamp_diff = get32(in + 8); + p->window_size = get32(in + 12); + p->sequence = get16(in + 16); + p->acknowledgement = get16(in + 18); + while (ext) { + uint8_t next, n; + if (len - off < 2) { errno = EPROTO; return -1; } + next = in[off++]; + n = in[off++]; + if (n > len - off) { errno = EPROTO; return -1; } + if (ext == 1) { + if (n != 4 || p->has_selective_ack) { errno = EPROTO; return -1; } + p->has_selective_ack = 1; + p->selective_ack = get32(in + off); + } + off += n; + ext = next; + } + p->payload = in + off; + p->payload_len = len - off; + if ((p->type == SLEEP_UTP_DATA && p->payload_len > SLEEP_UTP_MTU) || + (p->type != SLEEP_UTP_DATA && p->payload_len != 0)) { + errno = p->type == SLEEP_UTP_DATA ? EMSGSIZE : EPROTO; + return -1; + } + return 0; +} +static int emit_sequence(sleep_utp_t*u,uint8_t type,const uint8_t*data,size_t len,uint16_t sequence,int advance,int track){sleep_utp_packet_t p;uint8_t packet[26+SLEEP_UTP_MTU];size_t n,i;sleep_utp_slot_t*tracked=NULL;if(track&&type==SLEEP_UTP_DATA){tracked=&u->unacked[sequence%SLEEP_UTP_WINDOW];if(tracked->used){errno=EAGAIN;return-1;}}memset(&p,0,sizeof(p));p.type=type;p.version=1;p.connection_id=u->send_id;p.window_size=u->receive_window;p.sequence=sequence;p.acknowledgement=u->acknowledgement;if(type==SLEEP_UTP_STATE){for(i=0;i<32;i++){uint16_t seq=(uint16_t)(u->acknowledgement+2+i);sleep_utp_slot_t*s=&u->reorder[seq%SLEEP_UTP_WINDOW];if(s->used&&s->sequence==seq)p.selective_ack|=(uint32_t)1<socket,packet,n,0,(struct sockaddr*)&u->remote,u->remote_len)!=(ssize_t)n)return-1;if(advance)u->sequence++;if(tracked){tracked->sequence=sequence;tracked->length=len;if(len)memcpy(tracked->data,data,len);tracked->used=1;tracked->sent_at=0;tracked->retries=0;u->bytes_in_flight+=len;}return 0;} +static int emit(sleep_utp_t*u,uint8_t type,const uint8_t*data,size_t len){int advance=type==SLEEP_UTP_DATA||type==SLEEP_UTP_SYN||type==SLEEP_UTP_FIN;return emit_sequence(u,type,data,len,u->sequence,advance,type==SLEEP_UTP_DATA);} +int sleep_utp_connect(sleep_utp_t*u,const struct sockaddr*r,socklen_t n){if(!u||!r||n>sizeof(u->remote)){errno=EINVAL;return-1;}memcpy(&u->remote,r,n);u->remote_len=n;u->state=SLEEP_UTP_SYN_SENT;u->retransmit_at=0;u->syn_retries=0;return emit(u,SLEEP_UTP_SYN,NULL,0);} +int sleep_utp_accept(sleep_utp_t*u,const sleep_utp_packet_t*syn,const struct sockaddr*r,socklen_t n){if(!u||!syn||!r||n>sizeof(u->remote)){errno=EINVAL;return-1;}if(syn->type!=SLEEP_UTP_SYN||syn->version!=1){errno=EPROTO;return-1;}memcpy(&u->remote,r,n);u->remote_len=n;u->receive_id=syn->connection_id;u->send_id=(uint16_t)(syn->connection_id+1);u->sequence=(uint16_t)(syn->sequence+1);u->acknowledgement=syn->sequence;u->state=SLEEP_UTP_CONNECTED;return emit(u,SLEEP_UTP_STATE,NULL,0);} +int sleep_utp_input(sleep_utp_t *u, const uint8_t *buf, size_t len) +{ + sleep_utp_packet_t packet; + size_t i; + if (!u) { errno = EINVAL; return -1; } + if (sleep_utp_packet_decode(&packet, buf, len)) return -1; + if (packet.version != 1 || packet.type > SLEEP_UTP_SYN || + packet.connection_id != u->receive_id) { errno = EPROTO; return -1; } + u->send_window = packet.window_size; + for (i = 0; i < SLEEP_UTP_WINDOW; i++) { + sleep_utp_slot_t *slot = &u->unacked[i]; + uint16_t behind; + int acknowledged; + if (!slot->used) continue; + behind = (uint16_t)(packet.acknowledgement - slot->sequence); + acknowledged = behind < 0x8000; + if (!acknowledged && packet.has_selective_ack) { + uint16_t ahead = (uint16_t)(slot->sequence - (uint16_t)(packet.acknowledgement + 2)); + if (ahead < 32 && (packet.selective_ack & ((uint32_t)1 << ahead))) acknowledged = 1; + } + if (!acknowledged) continue; + u->bytes_in_flight = slot->length > u->bytes_in_flight ? 0 : u->bytes_in_flight - slot->length; + slot->used = 0; + if (u->congestion_window < u->slow_start_threshold) u->congestion_window += SLEEP_UTP_MTU; + else u->congestion_window += SLEEP_UTP_MTU * SLEEP_UTP_MTU / u->congestion_window; + } + if (packet.type == SLEEP_UTP_STATE && u->state == SLEEP_UTP_SYN_SENT) { + u->state = SLEEP_UTP_CONNECTED; + return 0; + } + if (packet.type == SLEEP_UTP_DATA) { + uint16_t distance = (uint16_t)(packet.sequence - (uint16_t)(u->acknowledgement + 1)); + sleep_utp_slot_t *slot; + if (distance >= 0x8000 || distance >= SLEEP_UTP_WINDOW) + return emit(u, SLEEP_UTP_STATE, NULL, 0); + slot = &u->reorder[packet.sequence % SLEEP_UTP_WINDOW]; + if (!slot->used) { + slot->sequence = packet.sequence; + slot->length = packet.payload_len; + if (packet.payload_len) memcpy(slot->data, packet.payload, packet.payload_len); + slot->used = 1; + } + for (;;) { + uint16_t next = (uint16_t)(u->acknowledgement + 1); + slot = &u->reorder[next % SLEEP_UTP_WINDOW]; + if (!slot->used || slot->sequence != next) break; + u->acknowledgement = next; + if (u->on_data) u->on_data(u, slot->data, slot->length); + slot->used = 0; + } + return emit(u, SLEEP_UTP_STATE, NULL, 0); + } + if (packet.type == SLEEP_UTP_FIN) { + u->state = SLEEP_UTP_CLOSED; + return emit(u, SLEEP_UTP_STATE, NULL, 0); + } + if (packet.type == SLEEP_UTP_RESET_PACKET) u->state = SLEEP_UTP_RESET; + return 0; +} +int sleep_utp_send(sleep_utp_t*u,const uint8_t*data,size_t len){size_t off=0,part,slots=0,i,needed;if(!u||u->state!=SLEEP_UTP_CONNECTED||(!data&&len)){errno=ENOTCONN;return-1;}needed=(len+SLEEP_UTP_MTU-1)/SLEEP_UTP_MTU;for(i=0;iunacked[i].used)slots++;if(needed>slots||u->bytes_in_flight>u->congestion_window||len>u->congestion_window-u->bytes_in_flight||len>u->send_window){errno=EAGAIN;return-1;}while(offSLEEP_UTP_MTU?SLEEP_UTP_MTU:len-off;if(emit(u,SLEEP_UTP_DATA,data+off,part))return-1;off+=part;}return 0;} +int sleep_utp_tick(sleep_utp_t*u,uint64_t now){size_t i;if(!u){errno=EINVAL;return-1;}if(u->state==SLEEP_UTP_SYN_SENT){if(!u->retransmit_at){u->retransmit_at=now+250;}else if(now>=u->retransmit_at){if(u->syn_retries>=8){u->state=SLEEP_UTP_RESET;errno=ETIMEDOUT;return-1;}if(emit_sequence(u,SLEEP_UTP_SYN,NULL,0,(uint16_t)(u->sequence-1),0,0))return-1;u->syn_retries++;u->retransmit_at=now+250;}}for(i=0;iunacked[i];if(!s->used){continue;}if(!s->sent_at){s->sent_at=now;continue;}if(now-s->sent_at>=u->round_trip_time+4*u->round_trip_variance){u->slow_start_threshold=u->congestion_window/2;u->congestion_window=SLEEP_UTP_MTU;s->sent_at=now;s->retries++;if(s->retries>8){u->state=SLEEP_UTP_RESET;errno=ETIMEDOUT;return-1;}if(emit_sequence(u,SLEEP_UTP_DATA,s->data,s->length,s->sequence,0,0))return-1;}}return 0;} +int sleep_utp_close(sleep_utp_t*u){if(!u){errno=EINVAL;return-1;}if(u->state==SLEEP_UTP_CONNECTED){u->state=SLEEP_UTP_FIN_SENT;return emit(u,SLEEP_UTP_FIN,NULL,0);}u->state=SLEEP_UTP_CLOSED;return 0;} diff --git a/extra/dat-dpi/src/sleep_verify.c b/extra/dat-dpi/src/sleep_verify.c new file mode 100644 index 0000000..2aae0d4 --- /dev/null +++ b/extra/dat-dpi/src/sleep_verify.c @@ -0,0 +1,589 @@ +#include "sleep.h" +#include "sleep_verify.h" + +#include +#include +#include +#include +#include + +#ifdef SLEEP_SALT +#include +#endif + + +static uint16_t +be16(const uint8_t *p) +{ + return ((uint16_t)p[0] << 8) | p[1]; +} + + +static uint32_t +be32(const uint8_t *p) +{ + return ((uint32_t)p[0] << 24) | + ((uint32_t)p[1] << 16) | + ((uint32_t)p[2] << 8) | + p[3]; +} + + +static uint64_t +be64(const uint8_t *p) +{ + return ((uint64_t)p[0] << 56) | + ((uint64_t)p[1] << 48) | + ((uint64_t)p[2] << 40) | + ((uint64_t)p[3] << 32) | + ((uint64_t)p[4] << 24) | + ((uint64_t)p[5] << 16) | + ((uint64_t)p[6] << 8) | + p[7]; +} + + +static int +load_file(const char *path, + uint8_t **buf, + size_t *len) +{ + FILE *f; + long size; + uint8_t *p; + + f = fopen(path, "rb"); + + if (!f) { + fprintf(stderr, + "sleep-verify: %s: %s\n", + path, + strerror(errno)); + return -1; + } + + if (fseek(f, 0, SEEK_END) != 0 || + (size = ftell(f)) < 0 || + fseek(f, 0, SEEK_SET) != 0) { + fclose(f); + return -1; + } + + p = malloc(size ? (size_t)size : 1); + + if (!p) { + fclose(f); + errno = ENOMEM; + return -1; + } + + if (size && + fread(p, 1, (size_t)size, f) != (size_t)size) { + free(p); + fclose(f); + errno = EIO; + return -1; + } + + fclose(f); + + *buf = p; + *len = (size_t)size; + + return 0; +} + + +static int +check_header(const uint8_t *buf, + size_t len, + uint32_t magic, + uint16_t entry_size, + const char *algo) +{ + size_t algo_len; + + if (len < SLEEP_HEADER_SIZE) + return -1; + + if (be32(buf) != magic || + buf[4] != 0 || + be16(buf + 5) != entry_size) + return -1; + + algo_len = buf[7]; + + if (algo_len != strlen(algo)) + return -1; + + return memcmp(buf + 8, algo, algo_len) == 0 ? 0 : -1; +} + + +static int +verify_tree_structure(const sleep_tree_entry_t *tree, + size_t node_count) +{ + size_t i; + + for (i = 1; i < node_count; i += 2) { + uint64_t left; + uint64_t right; + uint64_t left_len; + uint64_t right_len; + uint64_t length; + uint8_t hash[32]; + + if (sleep_tree_left_child(i, &left) != 0 || + sleep_tree_right_child(i, &right) != 0) + return -1; + + /* + * This parent belongs to a subtree that does not + * completely exist yet. + */ + if (right >= node_count) { + static const uint8_t zero[40]; + + if (memcmp(&tree[i], + zero, + sizeof(tree[i])) != 0) { + fprintf(stderr, + " tree: nonzero incomplete node %zu\n", + i); + return -1; + } + + continue; + } + + left_len = + be64((const uint8_t *)&tree[left].length_be); + + right_len = + be64((const uint8_t *)&tree[right].length_be); + + if (UINT64_MAX - left_len < right_len) + return -1; + + length = left_len + right_len; + + if (sleep_tree_hash_parent(tree[left].hash, + tree[right].hash, + length, + hash) != 0) + return -1; + + if (memcmp(hash, tree[i].hash, 32) != 0 || + be64((const uint8_t *)&tree[i].length_be) != length) { + fprintf(stderr, + " tree: parent mismatch at node %zu\n", + i); + return -1; + } + } + + return 0; +} + + +static int +verify_data_leaves(const uint8_t *data, + size_t data_len, + const sleep_tree_entry_t *tree, + size_t node_count) +{ + size_t leaf_count = (node_count + 1) / 2; + size_t offset = 0; + size_t i; + + for (i = 0; i < leaf_count; i++) { + size_t node = i * 2; + uint64_t len = + be64((const uint8_t *)&tree[node].length_be); + uint8_t hash[32]; + + if (len > data_len - offset) { + fprintf(stderr, + " data: leaf %zu exceeds metadata.data\n", + i); + return -1; + } + + if (sleep_tree_hash_block(data + offset, + (size_t)len, + hash) != 0) + return -1; + + if (memcmp(hash, tree[node].hash, 32) != 0) { + fprintf(stderr, + " data: leaf hash mismatch at %zu\n", + i); + return -1; + } + + offset += (size_t)len; + } + + if (offset != data_len) { + fprintf(stderr, + " data: tree consumes %zu bytes, file is %zu\n", + offset, + data_len); + return -1; + } + + return 0; +} + + +#ifdef SLEEP_SALT + +static int +verify_signatures(const uint8_t *signatures, + size_t signatures_len, + const uint8_t pubkey[32], + const sleep_tree_entry_t *tree, + size_t node_count, + size_t feed_length) +{ + size_t sequence; + uint64_t roots[64]; + uint8_t message[1 + 64 * 48]; + uint8_t digest[32]; + + if (signatures_len < SLEEP_HEADER_SIZE || + (signatures_len - SLEEP_HEADER_SIZE) % + SLEEP_SIG_ENTRY_SIZE != 0) + return -1; + + if ((signatures_len - SLEEP_HEADER_SIZE) / + SLEEP_SIG_ENTRY_SIZE != feed_length) + return -1; + + for (sequence = 1; + sequence <= feed_length; + sequence++) { + const uint8_t *signature = signatures + SLEEP_HEADER_SIZE + + (sequence - 1) * SLEEP_SIG_ENTRY_SIZE; + size_t root_count; + size_t off = 0; + size_t r; + size_t signature_byte; + int signature_present = 0; + + for (signature_byte = 0; + signature_byte < SLEEP_SIG_ENTRY_SIZE; + signature_byte++) + signature_present |= signature[signature_byte] != 0; + + /* Sparse content feeds retain empty signature slots. */ + if (!signature_present) + continue; + + root_count = + sleep_tree_full_roots((uint64_t)sequence * 2, + roots, + 64); + + message[off++] = SLEEP_HASH_ROOT; + + for (r = 0; r < root_count; r++) { + uint64_t index = roots[r]; + uint64_t length; + + if (index >= node_count) + return -1; + + memcpy(message + off, + tree[index].hash, + 32); + off += 32; + + message[off++] = (uint8_t)(index >> 56); + message[off++] = (uint8_t)(index >> 48); + message[off++] = (uint8_t)(index >> 40); + message[off++] = (uint8_t)(index >> 32); + message[off++] = (uint8_t)(index >> 24); + message[off++] = (uint8_t)(index >> 16); + message[off++] = (uint8_t)(index >> 8); + message[off++] = (uint8_t)index; + + length = + be64((const uint8_t *) + &tree[index].length_be); + + message[off++] = (uint8_t)(length >> 56); + message[off++] = (uint8_t)(length >> 48); + message[off++] = (uint8_t)(length >> 40); + message[off++] = (uint8_t)(length >> 32); + message[off++] = (uint8_t)(length >> 24); + message[off++] = (uint8_t)(length >> 16); + message[off++] = (uint8_t)(length >> 8); + message[off++] = (uint8_t)length; + } + + if (crypto_generichash(digest, + sizeof(digest), + message, + off, + NULL, + 0) != 0) + return -1; + + if (crypto_sign_verify_detached( + signature, + digest, + sizeof(digest), + pubkey) != 0) { + fprintf(stderr, + " signatures: failed at sequence %zu\n", + sequence); + return -1; + } + } + + return 0; +} + +#endif + + +static int +verify_feed(const char *dir, + const char *name, + int verify_data, + size_t *out_length) +{ + char path[4096]; + + uint8_t *tree_file = NULL; + uint8_t *sig_file = NULL; + uint8_t *key_file = NULL; + uint8_t *data_file = NULL; + uint8_t *bitfield = NULL; + + size_t tree_len = 0; + size_t sig_len = 0; + size_t key_len = 0; + size_t data_len = 0; + size_t bitfield_len = 0; + + size_t node_count; + size_t feed_length; + + int rc = -1; + + snprintf(path, + sizeof(path), + "%s/%s.tree", + dir, + name); + + if (load_file(path, &tree_file, &tree_len) != 0) + goto done; + + if (check_header(tree_file, + tree_len, + SLEEP_MAGIC_TREE, + SLEEP_TREE_ENTRY_SIZE, + SLEEP_ALGO_BLAKE2B) != 0) { + fprintf(stderr, " tree: invalid header\n"); + goto done; + } + + if ((tree_len - SLEEP_HEADER_SIZE) % + sizeof(sleep_tree_entry_t) != 0) + goto done; + + node_count = + (tree_len - SLEEP_HEADER_SIZE) / + sizeof(sleep_tree_entry_t); + + if (!node_count || !(node_count & 1)) + goto done; + + feed_length = (node_count + 1) / 2; + + if (verify_tree_structure( + (const sleep_tree_entry_t *)(tree_file + + SLEEP_HEADER_SIZE), + node_count) != 0) + goto done; + + snprintf(path, + sizeof(path), + "%s/%s.key", + dir, + name); + + if (load_file(path, &key_file, &key_len) != 0 || + key_len != 32) + goto done; + + snprintf(path, + sizeof(path), + "%s/%s.signatures", + dir, + name); + + if (load_file(path, &sig_file, &sig_len) != 0) + goto done; + + if (check_header(sig_file, + sig_len, + SLEEP_MAGIC_SIGNATURE, + SLEEP_SIG_ENTRY_SIZE, + SLEEP_ALGO_ED25519) != 0) + goto done; + +#ifdef SLEEP_SALT + + if (verify_signatures( + sig_file, + sig_len, + key_file, + (const sleep_tree_entry_t *)(tree_file + + SLEEP_HEADER_SIZE), + node_count, + feed_length) != 0) + goto done; + +#else + + fprintf(stderr, + " signatures: build with SLEEP_SALT\n"); + goto done; + +#endif + + snprintf(path, + sizeof(path), + "%s/%s.bitfield", + dir, + name); + + if (load_file(path, + &bitfield, + &bitfield_len) != 0) + goto done; + + if (bitfield_len < SLEEP_HEADER_SIZE || + be32(bitfield) != SLEEP_MAGIC_BITFIELD || + bitfield[4] != 0) + goto done; + + /* + * The SLEEP V2 document says 3328 here. + * + * The supplied historical Dat fixture says 3584. + * We deliberately accept both until the extra 256 bytes + * in the historical format have been identified. + */ + if (be16(bitfield + 5) != SLEEP_BITFIELD_ENTRY_SIZE && + be16(bitfield + 5) != 3584) + goto done; + + if (verify_data) { + snprintf(path, + sizeof(path), + "%s/%s.data", + dir, + name); + + if (load_file(path, + &data_file, + &data_len) != 0) + goto done; + + if (verify_data_leaves( + data_file, + data_len, + (const sleep_tree_entry_t *)(tree_file + + SLEEP_HEADER_SIZE), + node_count) != 0) + goto done; + } + + if (out_length) + *out_length = feed_length; + + rc = 0; + +done: + free(tree_file); + free(sig_file); + free(key_file); + free(data_file); + free(bitfield); + + return rc; +} + + +int +sleep_verify_dat(const char *dir) +{ + size_t metadata_length; + size_t content_length; + + printf("sleep-verify: %s\n", dir); + + printf("metadata: "); + + if (verify_feed(dir, + "metadata", + 1, + &metadata_length) != 0) { + printf("FAIL\n"); + return -1; + } + + printf("OK (%zu entries)\n", metadata_length); + + printf("content: "); + + if (verify_feed(dir, + "content", + 0, + &content_length) != 0) { + printf("FAIL\n"); + return -1; + } + + printf("OK (%zu entries)\n", content_length); + + printf("sleep-verify: PASS\n"); + + return 0; +} + + +#ifndef SLEEP_VERIFY_NO_MAIN +int +main(int argc, char **argv) +{ + if (argc != 2) { + fprintf(stderr, + "usage: %s .dat\n", + argv[0]); + return 2; + } + +#ifdef SLEEP_SALT + + if (sodium_init() < 0) + return 1; + +#else + + fprintf(stderr, + "sleep-verify: built without SLEEP_SALT\n"); + + return 1; + +#endif + + return sleep_verify_dat(argv[1]) == 0 ? 0 : 1; +} +#endif diff --git a/extra/dat-dpi/src/sleep_writer.c b/extra/dat-dpi/src/sleep_writer.c new file mode 100644 index 0000000..e45ce09 --- /dev/null +++ b/extra/dat-dpi/src/sleep_writer.c @@ -0,0 +1,530 @@ +#include "sleep_writer.h" +#include "sleep.h" + +#include +#include +#include +#include +#include + +#ifdef SLEEP_SALT +#include +#endif + +#define SLEEP_MAGIC_BITFIELD 0x05025700u +#define SLEEP_MAGIC_SIGNATURE 0x05025701u +#define SLEEP_MAGIC_TREE 0x05025702u + +#define SLEEP_BITFIELD_SIZE 3584u +#define SLEEP_DATA_BITS 8192u +#define SLEEP_TREE_BITS 16384u +#define SLEEP_INDEX_BYTES 256u + +#define SLEEP_HASH_BYTES 32u +#define SLEEP_STACK_MAX 64u + +typedef struct writer_node { + uint8_t hash[SLEEP_HASH_BYTES]; + uint64_t length; + uint64_t index; + uint64_t depth; +} writer_node_t; + +typedef struct writer_peak { + uint64_t index; + uint64_t depth; +} writer_peak_t; + +static void put_u16be(uint8_t *p, uint16_t v) +{ + p[0] = (uint8_t)(v >> 8); + p[1] = (uint8_t)v; +} + +static void put_u32be(uint8_t *p, uint32_t v) +{ + p[0] = (uint8_t)(v >> 24); + p[1] = (uint8_t)(v >> 16); + p[2] = (uint8_t)(v >> 8); + p[3] = (uint8_t)v; +} + +static void put_u64be(uint8_t *p, uint64_t v) +{ + p[0] = (uint8_t)(v >> 56); + p[1] = (uint8_t)(v >> 48); + p[2] = (uint8_t)(v >> 40); + p[3] = (uint8_t)(v >> 32); + p[4] = (uint8_t)(v >> 24); + p[5] = (uint8_t)(v >> 16); + p[6] = (uint8_t)(v >> 8); + p[7] = (uint8_t)v; +} + +static int make_path(char *out, size_t out_size, + const char *dir, const char *name, + const char *suffix) +{ + int n = snprintf(out, out_size, "%s/%s%s", dir, name, suffix); + if (n < 0 || (size_t)n >= out_size) { + errno = ENAMETOOLONG; + return -1; + } + return 0; +} + +static int write_header(FILE *f, uint32_t magic, + uint16_t entry_size, const char *algo) +{ + uint8_t header[SLEEP_HEADER_SIZE]; + size_t len = algo ? strlen(algo) : 0; + + if (len > 24) { + errno = EINVAL; + return -1; + } + + memset(header, 0, sizeof(header)); + put_u32be(header, magic); + put_u16be(header + 5, entry_size); + header[7] = (uint8_t)len; + if (len) + memcpy(header + 8, algo, len); + + if (fwrite(header, 1, sizeof(header), f) != sizeof(header)) { + errno = EIO; + return -1; + } + return 0; +} + +static int hash_leaf(const uint8_t *data, size_t len, + uint8_t out[32]) +{ +#ifdef SLEEP_SALT + uint8_t head[9]; + crypto_generichash_state st; + + head[0] = 0; + put_u64be(head + 1, (uint64_t)len); + + if (crypto_generichash_init(&st, NULL, 0, 32) != 0) + return -1; + if (crypto_generichash_update(&st, head, sizeof(head)) != 0) + return -1; + if (len && crypto_generichash_update(&st, data, len) != 0) + return -1; + return crypto_generichash_final(&st, out, 32); +#else + (void)data; (void)len; (void)out; + errno = ENOSYS; + return -1; +#endif +} + +static int hash_parent(const writer_node_t *left, + const writer_node_t *right, + uint8_t out[32]) +{ +#ifdef SLEEP_SALT + uint8_t head[9]; + crypto_generichash_state st; + + head[0] = 1; + put_u64be(head + 1, left->length + right->length); + + if (crypto_generichash_init(&st, NULL, 0, 32) != 0) + return -1; + if (crypto_generichash_update(&st, head, sizeof(head)) != 0) + return -1; + if (crypto_generichash_update(&st, left->hash, 32) != 0) + return -1; + if (crypto_generichash_update(&st, right->hash, 32) != 0) + return -1; + return crypto_generichash_final(&st, out, 32); +#else + (void)left; (void)right; (void)out; + errno = ENOSYS; + return -1; +#endif +} + +static int tree_build(const uint8_t *const *blocks, + const size_t *lengths, + size_t nblocks, + writer_node_t **out_nodes, + size_t *out_count) +{ + writer_node_t *nodes; + writer_peak_t stack[SLEEP_STACK_MAX]; + size_t stack_len = 0; + size_t node_count; + size_t i; + + if (nblocks > SIZE_MAX / 2 + 1) { + errno = EOVERFLOW; + return -1; + } + + node_count = nblocks ? nblocks * 2 - 1 : 0; + nodes = node_count ? calloc(node_count, sizeof(*nodes)) : NULL; + if (node_count && !nodes) { + errno = ENOMEM; + return -1; + } + + for (i = 0; i < nblocks; i++) { + writer_node_t *leaf; + uint64_t leaf_index = (uint64_t)(i * 2); + + leaf = &nodes[leaf_index]; + leaf->index = leaf_index; + leaf->depth = 0; + leaf->length = lengths[i]; + + if (hash_leaf(blocks[i], lengths[i], leaf->hash) != 0) + goto fail; + + stack[stack_len].index = leaf_index; + stack[stack_len].depth = 0; + stack_len++; + + while (stack_len >= 2 && + stack[stack_len - 1].depth == stack[stack_len - 2].depth) { + writer_peak_t right = stack[stack_len - 1]; + writer_peak_t left = stack[stack_len - 2]; + writer_node_t *ln = &nodes[left.index]; + writer_node_t *rn = &nodes[right.index]; + uint64_t parent_index; + writer_node_t *parent; + + stack_len -= 2; + parent_index = sleep_tree_parent(left.index); + if (parent_index >= node_count) { + errno = EINVAL; + goto fail; + } + + parent = &nodes[parent_index]; + parent->index = parent_index; + parent->depth = left.depth + 1; + parent->length = ln->length + rn->length; + + if (hash_parent(ln, rn, parent->hash) != 0) + goto fail; + + stack[stack_len].index = parent_index; + stack[stack_len].depth = parent->depth; + stack_len++; + } + } + + *out_nodes = nodes; + *out_count = node_count; + return 0; + +fail: + free(nodes); + return -1; +} + +static int write_tree(const char *path, + const writer_node_t *nodes, size_t node_count) +{ + FILE *f; + size_t i; + + f = fopen(path, "wb"); + if (!f) + return -1; + + if (write_header(f, SLEEP_MAGIC_TREE, + SLEEP_TREE_ENTRY_SIZE, SLEEP_ALGO_BLAKE2B) != 0) + goto fail; + + for (i = 0; i < node_count; i++) { + uint8_t entry[40]; + memcpy(entry, nodes[i].hash, 32); + put_u64be(entry + 32, nodes[i].length); + if (fwrite(entry, 1, sizeof(entry), f) != sizeof(entry)) { + errno = EIO; + goto fail; + } + } + + return fclose(f) == 0 ? 0 : -1; +fail: + fclose(f); + return -1; +} + +static int sign_state(const writer_node_t *nodes, + size_t block_count, + const sleep_keypair_t *keypair, + uint8_t signature[64]) +{ +#ifdef SLEEP_SALT + uint64_t root_indexes[64]; + size_t root_count; + size_t i; + size_t msg_len; + uint8_t *msg; + uint8_t digest[32]; + crypto_generichash_state st; + + /* The Dat/SLEEP root set is fullRoots(2 * length). */ + root_count = sleep_tree_full_roots((uint64_t)(2 * block_count), + root_indexes, 64); + + if (root_count > (SIZE_MAX - 1) / 48) { + errno = EOVERFLOW; + return -1; + } + msg_len = 1 + root_count * 48; + msg = malloc(msg_len); + if (!msg) { + errno = ENOMEM; + return -1; + } + + msg[0] = 2; + for (i = 0; i < root_count; i++) { + const writer_node_t *root = &nodes[root_indexes[i]]; + size_t off = 1 + i * 48; + + memcpy(msg + off, root->hash, 32); + put_u64be(msg + off + 32, root->index); + put_u64be(msg + off + 40, root->length); + } + + if (crypto_generichash_init(&st, NULL, 0, 32) != 0) + goto fail; + if (crypto_generichash_update(&st, msg, msg_len) != 0) + goto fail; + if (crypto_generichash_final(&st, digest, 32) != 0) + goto fail; + + free(msg); + + if (crypto_sign_detached(signature, NULL, + digest, sizeof(digest), + keypair->seckey) != 0) + return -1; + + return 0; + +fail: + free(msg); + return -1; +#else + (void)nodes; (void)block_count; (void)keypair; (void)signature; + errno = ENOSYS; + return -1; +#endif +} + +static int write_signatures(const char *path, + const writer_node_t *nodes, + size_t nblocks, + const sleep_keypair_t *keypair) +{ + FILE *f; + size_t length; + + f = fopen(path, "wb"); + if (!f) + return -1; + + if (write_header(f, SLEEP_MAGIC_SIGNATURE, + SLEEP_SIG_ENTRY_SIZE, SLEEP_ALGO_ED25519) != 0) + goto fail; + + for (length = 1; length <= nblocks; length++) { + uint8_t sig[64]; + + if (sign_state(nodes, length, keypair, sig) != 0) + goto fail; + if (fwrite(sig, 1, sizeof(sig), f) != sizeof(sig)) { + errno = EIO; + goto fail; + } + } + + return fclose(f) == 0 ? 0 : -1; +fail: + fclose(f); + return -1; +} + +static void set_bit(uint8_t *bits, size_t bit) +{ + bits[bit >> 3] |= (uint8_t)(1u << (7 - (bit & 7))); +} + +static unsigned int byte_state(uint8_t v) +{ + if (v == 0x00) + return 0; + if (v == 0xff) + return 3; + return 2; +} + +static void build_index(uint8_t index[SLEEP_INDEX_BYTES], + const uint8_t data[1024]) +{ + size_t pair; + + memset(index, 0, SLEEP_INDEX_BYTES); + + /* Four index bits describe every two bytes of the data bitfield. */ + for (pair = 0; pair < 512; pair++) { + unsigned int a = byte_state(data[pair * 2]); + unsigned int b = byte_state(data[pair * 2 + 1]); + unsigned int tuple = a | (b << 2); + size_t byte = pair >> 1; + index[byte] |= (uint8_t)(tuple << ((pair & 1) ? 4 : 0)); + } +} + +static int write_bitfield(const char *path, + size_t nblocks, + size_t node_count) +{ + uint8_t entry[SLEEP_BITFIELD_SIZE]; + uint8_t *data = entry; + uint8_t *tree = entry + 1024; + uint8_t *index = entry + 3072; + FILE *f; + size_t i; + + memset(entry, 0, sizeof(entry)); + + if (nblocks > SLEEP_DATA_BITS || node_count > SLEEP_TREE_BITS) { + errno = EOVERFLOW; + return -1; + } + + for (i = 0; i < nblocks; i++) + set_bit(data, i); + + for (i = 0; i < node_count; i++) + set_bit(tree, i); + + build_index(index, data); + + f = fopen(path, "wb"); + if (!f) + return -1; + + if (write_header(f, SLEEP_MAGIC_BITFIELD, + SLEEP_BITFIELD_SIZE, NULL) != 0) + goto fail; + if (fwrite(entry, 1, sizeof(entry), f) != sizeof(entry)) { + errno = EIO; + goto fail; + } + + return fclose(f) == 0 ? 0 : -1; +fail: + fclose(f); + return -1; +} + +static int write_data(const char *path, + const uint8_t *const *blocks, + const size_t *lengths, + size_t nblocks) +{ + FILE *f; + size_t i; + + f = fopen(path, "wb"); + if (!f) + return -1; + + for (i = 0; i < nblocks; i++) { + if (lengths[i] && + fwrite(blocks[i], 1, lengths[i], f) != lengths[i]) { + errno = EIO; + goto fail; + } + } + + return fclose(f) == 0 ? 0 : -1; +fail: + fclose(f); + return -1; +} + +static int write_key(const char *path, const sleep_keypair_t *keypair) +{ + FILE *f = fopen(path, "wb"); + if (!f) + return -1; + if (fwrite(keypair->pubkey, 1, 32, f) != 32) { + errno = EIO; + fclose(f); + return -1; + } + return fclose(f) == 0 ? 0 : -1; +} + +int sleep_write_feed(const char *feed_dir, + const char *feed_name, + const uint8_t *const *blocks, + const size_t *lengths, + size_t nblocks, + const sleep_keypair_t *keypair) +{ + char path[1024]; + writer_node_t *nodes = NULL; + size_t node_count = 0, i; + int rc = -1; + + if (!feed_dir || !feed_name || !keypair || + !feed_name[0] || !strcmp(feed_name, ".") || !strcmp(feed_name, "..") || + strchr(feed_name, '/') || strchr(feed_name, '\\') || + (nblocks && (!blocks || !lengths))) { + errno = EINVAL; + return -1; + } + for (i = 0; i < nblocks; i++) { + if (lengths[i] && !blocks[i]) { errno = EINVAL; return -1; } + } + + if (make_path(path, sizeof(path), feed_dir, feed_name, ".tree") != 0) + return -1; + + if (tree_build(blocks, lengths, nblocks, &nodes, &node_count) != 0) + return -1; + + if (write_tree(path, nodes, node_count) != 0) + goto done; + + if (make_path(path, sizeof(path), feed_dir, feed_name, ".signatures") != 0) + goto done; + if (write_signatures(path, nodes, nblocks, keypair) != 0) + goto done; + + if (make_path(path, sizeof(path), feed_dir, feed_name, ".key") != 0) + goto done; + if (write_key(path, keypair) != 0) + goto done; + + if (make_path(path, sizeof(path), feed_dir, feed_name, ".bitfield") != 0) + goto done; + if (write_bitfield(path, nblocks, node_count) != 0) + goto done; + + if (make_path(path, sizeof(path), feed_dir, feed_name, ".data") != 0) + goto done; + if (write_data(path, blocks, lengths, nblocks) != 0) + goto done; + + rc = 0; + +done: + free(nodes); + return rc; +} diff --git a/extra/dat-thang-current.png b/extra/dat-thang-current.png new file mode 100644 index 0000000000000000000000000000000000000000..0ae88ff260c2df094cfd0ee1686694eca4c72fc7 GIT binary patch literal 20271 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 BN<;tv literal 16798 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

    0`=$4dO+yOoLa*0{Mf$vvBfMMYcU$T69!Qu$ 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 literal 0 HcmV?d00001 diff --git a/extra/sleep_verify.c b/extra/sleep_verify.c new file mode 100644 index 0000000..aaa0422 --- /dev/null +++ b/extra/sleep_verify.c @@ -0,0 +1,576 @@ +#include "sleep.h" +#include "sleep_verify.h" + +#include +#include +#include +#include +#include + +#ifdef SLEEP_SALT +#include +#endif + + +static uint16_t +be16(const uint8_t *p) +{ + return ((uint16_t)p[0] << 8) | p[1]; +} + + +static uint32_t +be32(const uint8_t *p) +{ + return ((uint32_t)p[0] << 24) | + ((uint32_t)p[1] << 16) | + ((uint32_t)p[2] << 8) | + p[3]; +} + + +static uint64_t +be64(const uint8_t *p) +{ + return ((uint64_t)p[0] << 56) | + ((uint64_t)p[1] << 48) | + ((uint64_t)p[2] << 40) | + ((uint64_t)p[3] << 32) | + ((uint64_t)p[4] << 24) | + ((uint64_t)p[5] << 16) | + ((uint64_t)p[6] << 8) | + p[7]; +} + + +static int +load_file(const char *path, + uint8_t **buf, + size_t *len) +{ + FILE *f; + long size; + uint8_t *p; + + f = fopen(path, "rb"); + + if (!f) { + fprintf(stderr, + "sleep-verify: %s: %s\n", + path, + strerror(errno)); + return -1; + } + + if (fseek(f, 0, SEEK_END) != 0 || + (size = ftell(f)) < 0 || + fseek(f, 0, SEEK_SET) != 0) { + fclose(f); + return -1; + } + + p = malloc(size ? (size_t)size : 1); + + if (!p) { + fclose(f); + errno = ENOMEM; + return -1; + } + + if (size && + fread(p, 1, (size_t)size, f) != (size_t)size) { + free(p); + fclose(f); + errno = EIO; + return -1; + } + + fclose(f); + + *buf = p; + *len = (size_t)size; + + return 0; +} + + +static int +check_header(const uint8_t *buf, + size_t len, + uint32_t magic, + uint16_t entry_size, + const char *algo) +{ + size_t algo_len; + + if (len < SLEEP_HEADER_SIZE) + return -1; + + if (be32(buf) != magic || + buf[4] != 0 || + be16(buf + 5) != entry_size) + return -1; + + algo_len = buf[7]; + + if (algo_len != strlen(algo)) + return -1; + + return memcmp(buf + 8, algo, algo_len) == 0 ? 0 : -1; +} + + +static int +verify_tree_structure(const sleep_tree_entry_t *tree, + size_t node_count) +{ + size_t i; + + for (i = 1; i < node_count; i += 2) { + uint64_t left; + uint64_t right; + uint64_t left_len; + uint64_t right_len; + uint64_t length; + uint8_t hash[32]; + + if (!sleep_tree_left_child(i, &left) || + !sleep_tree_right_child(i, &right)) + return -1; + + /* + * This parent belongs to a subtree that does not + * completely exist yet. + */ + if (right >= node_count) { + static const uint8_t zero[40]; + + if (memcmp(&tree[i], + zero, + sizeof(tree[i])) != 0) { + fprintf(stderr, + " tree: nonzero incomplete node %zu\n", + i); + return -1; + } + + continue; + } + + left_len = + be64((const uint8_t *)&tree[left].length_be); + + right_len = + be64((const uint8_t *)&tree[right].length_be); + + if (UINT64_MAX - left_len < right_len) + return -1; + + length = left_len + right_len; + + if (sleep_tree_hash_parent(tree[left].hash, + tree[right].hash, + length, + hash) != 0) + return -1; + + if (memcmp(hash, tree[i].hash, 32) != 0 || + be64((const uint8_t *)&tree[i].length_be) != length) { + fprintf(stderr, + " tree: parent mismatch at node %zu\n", + i); + return -1; + } + } + + return 0; +} + + +static int +verify_data_leaves(const uint8_t *data, + size_t data_len, + const sleep_tree_entry_t *tree, + size_t node_count) +{ + size_t leaf_count = (node_count + 1) / 2; + size_t offset = 0; + size_t i; + + for (i = 0; i < leaf_count; i++) { + size_t node = i * 2; + uint64_t len = + be64((const uint8_t *)&tree[node].length_be); + uint8_t hash[32]; + + if (len > data_len - offset) { + fprintf(stderr, + " data: leaf %zu exceeds metadata.data\n", + i); + return -1; + } + + if (sleep_tree_hash_block(data + offset, + (size_t)len, + hash) != 0) + return -1; + + if (memcmp(hash, tree[node].hash, 32) != 0) { + fprintf(stderr, + " data: leaf hash mismatch at %zu\n", + i); + return -1; + } + + offset += (size_t)len; + } + + if (offset != data_len) { + fprintf(stderr, + " data: tree consumes %zu bytes, file is %zu\n", + offset, + data_len); + return -1; + } + + return 0; +} + + +#ifdef SLEEP_SALT + +static int +verify_signatures(const uint8_t *signatures, + size_t signatures_len, + const uint8_t pubkey[32], + const sleep_tree_entry_t *tree, + size_t node_count, + size_t feed_length) +{ + size_t sequence; + uint64_t roots[64]; + uint8_t message[1 + 64 * 48]; + uint8_t digest[32]; + + if (signatures_len < SLEEP_HEADER_SIZE || + (signatures_len - SLEEP_HEADER_SIZE) % + SLEEP_SIG_ENTRY_SIZE != 0) + return -1; + + if ((signatures_len - SLEEP_HEADER_SIZE) / + SLEEP_SIG_ENTRY_SIZE != feed_length) + return -1; + + for (sequence = 1; + sequence <= feed_length; + sequence++) { + size_t root_count; + size_t off = 0; + size_t r; + + root_count = + sleep_tree_full_roots(sequence, + roots, + 64); + + message[off++] = SLEEP_HASH_ROOT; + + for (r = 0; r < root_count; r++) { + uint64_t index = roots[r]; + uint64_t length; + + if (index >= node_count) + return -1; + + memcpy(message + off, + tree[index].hash, + 32); + off += 32; + + message[off++] = (uint8_t)(index >> 56); + message[off++] = (uint8_t)(index >> 48); + message[off++] = (uint8_t)(index >> 40); + message[off++] = (uint8_t)(index >> 32); + message[off++] = (uint8_t)(index >> 24); + message[off++] = (uint8_t)(index >> 16); + message[off++] = (uint8_t)(index >> 8); + message[off++] = (uint8_t)index; + + length = + be64((const uint8_t *) + &tree[index].length_be); + + message[off++] = (uint8_t)(length >> 56); + message[off++] = (uint8_t)(length >> 48); + message[off++] = (uint8_t)(length >> 40); + message[off++] = (uint8_t)(length >> 32); + message[off++] = (uint8_t)(length >> 24); + message[off++] = (uint8_t)(length >> 16); + message[off++] = (uint8_t)(length >> 8); + message[off++] = (uint8_t)length; + } + + if (crypto_generichash(digest, + sizeof(digest), + message, + off, + NULL, + 0) != 0) + return -1; + + if (crypto_sign_verify_detached( + signatures + + SLEEP_HEADER_SIZE + + (sequence - 1) * SLEEP_SIG_ENTRY_SIZE, + digest, + sizeof(digest), + pubkey) != 0) { + fprintf(stderr, + " signatures: failed at sequence %zu\n", + sequence); + return -1; + } + } + + return 0; +} + +#endif + + +static int +verify_feed(const char *dir, + const char *name, + int verify_data, + size_t *out_length) +{ + char path[4096]; + + uint8_t *tree_file = NULL; + uint8_t *sig_file = NULL; + uint8_t *key_file = NULL; + uint8_t *data_file = NULL; + uint8_t *bitfield = NULL; + + size_t tree_len = 0; + size_t sig_len = 0; + size_t key_len = 0; + size_t data_len = 0; + size_t bitfield_len = 0; + + size_t node_count; + size_t feed_length; + + int rc = -1; + + snprintf(path, + sizeof(path), + "%s/%s.tree", + dir, + name); + + if (load_file(path, &tree_file, &tree_len) != 0) + goto done; + + if (check_header(tree_file, + tree_len, + SLEEP_MAGIC_TREE, + SLEEP_TREE_ENTRY_SIZE, + SLEEP_ALGO_BLAKE2B) != 0) { + fprintf(stderr, " tree: invalid header\n"); + goto done; + } + + if ((tree_len - SLEEP_HEADER_SIZE) % + sizeof(sleep_tree_entry_t) != 0) + goto done; + + node_count = + (tree_len - SLEEP_HEADER_SIZE) / + sizeof(sleep_tree_entry_t); + + if (!node_count || !(node_count & 1)) + goto done; + + feed_length = (node_count + 1) / 2; + + if (verify_tree_structure( + (const sleep_tree_entry_t *)(tree_file + + SLEEP_HEADER_SIZE), + node_count) != 0) + goto done; + + snprintf(path, + sizeof(path), + "%s/%s.key", + dir, + name); + + if (load_file(path, &key_file, &key_len) != 0 || + key_len != 32) + goto done; + + snprintf(path, + sizeof(path), + "%s/%s.signatures", + dir, + name); + + if (load_file(path, &sig_file, &sig_len) != 0) + goto done; + + if (check_header(sig_file, + sig_len, + SLEEP_MAGIC_SIGNATURE, + SLEEP_SIG_ENTRY_SIZE, + SLEEP_ALGO_ED25519) != 0) + goto done; + +#ifdef SLEEP_SALT + + if (verify_signatures( + sig_file, + sig_len, + key_file, + (const sleep_tree_entry_t *)(tree_file + + SLEEP_HEADER_SIZE), + node_count, + feed_length) != 0) + goto done; + +#else + + fprintf(stderr, + " signatures: build with SLEEP_SALT\n"); + goto done; + +#endif + + snprintf(path, + sizeof(path), + "%s/%s.bitfield", + dir, + name); + + if (load_file(path, + &bitfield, + &bitfield_len) != 0) + goto done; + + if (bitfield_len < SLEEP_HEADER_SIZE || + be32(bitfield) != SLEEP_MAGIC_BITFIELD || + bitfield[4] != 0) + goto done; + + /* + * The SLEEP V2 document says 3328 here. + * + * The supplied historical Dat fixture says 3584. + * We deliberately accept both until the extra 256 bytes + * in the historical format have been identified. + */ + if (be16(bitfield + 5) != SLEEP_BITFIELD_ENTRY_SIZE && + be16(bitfield + 5) != 3584) + goto done; + + if (verify_data) { + snprintf(path, + sizeof(path), + "%s/%s.data", + dir, + name); + + if (load_file(path, + &data_file, + &data_len) != 0) + goto done; + + if (verify_data_leaves( + data_file, + data_len, + (const sleep_tree_entry_t *)(tree_file + + SLEEP_HEADER_SIZE), + node_count) != 0) + goto done; + } + + if (out_length) + *out_length = feed_length; + + rc = 0; + +done: + free(tree_file); + free(sig_file); + free(key_file); + free(data_file); + free(bitfield); + + return rc; +} + + +int +sleep_verify_dat(const char *dir) +{ + size_t metadata_length; + size_t content_length; + + printf("sleep-verify: %s\n", dir); + + printf("metadata: "); + + if (verify_feed(dir, + "metadata", + 1, + &metadata_length) != 0) { + printf("FAIL\n"); + return -1; + } + + printf("OK (%zu entries)\n", metadata_length); + + printf("content: "); + + if (verify_feed(dir, + "content", + 0, + &content_length) != 0) { + printf("FAIL\n"); + return -1; + } + + printf("OK (%zu entries)\n", content_length); + + printf("sleep-verify: PASS\n"); + + return 0; +} + + +int +main(int argc, char **argv) +{ + if (argc != 2) { + fprintf(stderr, + "usage: %s .dat\n", + argv[0]); + return 2; + } + +#ifdef SLEEP_SALT + + if (sodium_init() < 0) + return 1; + +#else + + fprintf(stderr, + "sleep-verify: built without SLEEP_SALT\n"); + + return 1; + +#endif + + return sleep_verify_dat(argv[1]) == 0 ? 0 : 1; +} \ No newline at end of file diff --git a/extra/sleep_verify.h b/extra/sleep_verify.h new file mode 100644 index 0000000..7be1419 --- /dev/null +++ b/extra/sleep_verify.h @@ -0,0 +1,6 @@ +#ifndef SLEEP_VERIFY_H +#define SLEEP_VERIFY_H + +int sleep_verify_dat(const char *dat_dir); + +#endif \ No newline at end of file diff --git a/extra/sleep_writer_pass/README.txt b/extra/sleep_writer_pass/README.txt new file mode 100644 index 0000000..57b235d --- /dev/null +++ b/extra/sleep_writer_pass/README.txt @@ -0,0 +1,16 @@ +SLEEP writer pass + +Files: + inc/sleep_writer.h + src/sleep_writer.c + src/sleep_tree.c + +Important change from the previous writer draft: + SLEEP/Dat root sets for a feed of N blocks use fullRoots(2*N), not fullRoots(2*(N-1)). + Thus N=83 produces roots 63, 143, 161, 164, matching the historical fixture's signatures. + +The writer emits one signature for every feed length, 1..N. + +The bitfield writer follows the documented 3328-byte SLEEP V2 layout. The historical fixture supplied with the project has a 3584-byte bitfield entry; the extra 256 bytes remain deliberately unresolved rather than guessed. + +The code requires SLEEP_SALT/libsodium for cryptographic operation. diff --git a/extra/sleep_writer_pass/inc/sleep_writer.h b/extra/sleep_writer_pass/inc/sleep_writer.h new file mode 100644 index 0000000..474bfab --- /dev/null +++ b/extra/sleep_writer_pass/inc/sleep_writer.h @@ -0,0 +1,18 @@ +#ifndef SLEEP_WRITER_H +#define SLEEP_WRITER_H +#include +#include +#include "sleep.h" +#ifdef __cplusplus +extern "C" { +#endif +int sleep_write_feed(const char *feed_dir, + const char *feed_name, + const uint8_t *const *blocks, + const size_t *lengths, + size_t nblocks, + const sleep_keypair_t *keypair); +#ifdef __cplusplus +} +#endif +#endif diff --git a/extra/sleep_writer_pass/src/sleep_tree.c b/extra/sleep_writer_pass/src/sleep_tree.c new file mode 100644 index 0000000..4339087 --- /dev/null +++ b/extra/sleep_writer_pass/src/sleep_tree.c @@ -0,0 +1,272 @@ +#include "sleep.h" + +#include +#include +#include +#include + +#ifdef SLEEP_SALT +#include +#endif + +static unsigned int sleep_tree_ctz64(uint64_t x) +{ +#if defined(__GNUC__) || defined(__clang__) + if (x == 0) + return 64; + return (unsigned int)__builtin_ctzll(x); +#else + unsigned int n = 0; + + if (x == 0) + return 64; + + while ((x & 1ULL) == 0) { + x >>= 1; + n++; + } + + return n; +#endif +} + +uint64_t sleep_tree_index(uint64_t depth, uint64_t offset) +{ + if (depth >= 63) { + errno = EOVERFLOW; + return UINT64_MAX; + } + + return ((1ULL + (2ULL * offset)) << depth) - 1ULL; +} + +uint64_t sleep_tree_depth(uint64_t index) +{ + return sleep_tree_ctz64(index + 1ULL); +} + +uint64_t sleep_tree_offset(uint64_t index, uint64_t depth) +{ + if ((index & 1ULL) == 0) + return index / 2ULL; + + if (depth == UINT64_MAX) + depth = sleep_tree_depth(index); + + return (((index + 1ULL) >> depth) - 1ULL) / 2ULL; +} + +uint64_t sleep_tree_parent(uint64_t index) +{ + uint64_t depth = sleep_tree_depth(index); + uint64_t offset = sleep_tree_offset(index, depth); + + return sleep_tree_index(depth + 1ULL, offset >> 1); +} + +int sleep_tree_sibling(uint64_t index, uint64_t *out) +{ + uint64_t depth; + uint64_t offset; + + if (out == NULL) { + errno = EINVAL; + return -1; + } + + depth = sleep_tree_depth(index); + offset = sleep_tree_offset(index, depth); + + *out = sleep_tree_index(depth, offset & 1ULL ? offset - 1ULL + : offset + 1ULL); + return 0; +} + +int sleep_tree_left_child(uint64_t index, uint64_t *out) +{ + uint64_t depth; + uint64_t offset; + + if (out == NULL) { + errno = EINVAL; + return -1; + } + + if ((index & 1ULL) == 0) { + errno = EINVAL; + return -1; + } + + depth = sleep_tree_depth(index); + if (depth == 0) { + errno = EINVAL; + return -1; + } + + offset = sleep_tree_offset(index, depth); + *out = sleep_tree_index(depth - 1ULL, offset * 2ULL); + return 0; +} + +int sleep_tree_right_child(uint64_t index, uint64_t *out) +{ + uint64_t depth; + uint64_t offset; + + if (out == NULL) { + errno = EINVAL; + return -1; + } + + if ((index & 1ULL) == 0) { + errno = EINVAL; + return -1; + } + + depth = sleep_tree_depth(index); + if (depth == 0) { + errno = EINVAL; + return -1; + } + + offset = sleep_tree_offset(index, depth); + *out = sleep_tree_index(depth - 1ULL, offset * 2ULL + 1ULL); + return 0; +} + +uint64_t sleep_tree_left_span(uint64_t index) +{ + uint64_t depth; + + if ((index & 1ULL) == 0) + return index; + + depth = sleep_tree_depth(index); + return sleep_tree_offset(index, depth) * (1ULL << (depth + 1ULL)); +} + +uint64_t sleep_tree_right_span(uint64_t index) +{ + uint64_t depth; + + if ((index & 1ULL) == 0) + return index; + + depth = sleep_tree_depth(index); + return (sleep_tree_offset(index, depth) + 1ULL) * + (1ULL << (depth + 1ULL)) - 2ULL; +} + +void sleep_tree_spans(uint64_t index, uint64_t *left, uint64_t *right) +{ + if (left != NULL) + *left = sleep_tree_left_span(index); + if (right != NULL) + *right = sleep_tree_right_span(index); +} + +size_t sleep_tree_count(uint64_t index) +{ + uint64_t depth; + + if ((index & 1ULL) == 0) + return 1; + + depth = sleep_tree_depth(index); + if (depth >= sizeof(size_t) * 8 - 1) + return SIZE_MAX; + + return ((size_t)1 << (depth + 1ULL)) - 1; +} + +size_t sleep_tree_count_leaves(uint64_t index) +{ + return (sleep_tree_count(index) + 1) / 2; +} + +size_t sleep_tree_full_roots(uint64_t index, uint64_t *out, size_t capacity) +{ + uint64_t blocks; + uint64_t offset = 0; + uint64_t factor = 1; + size_t count = 0; + + if (index & 1ULL) { + errno = EINVAL; + return 0; + } + + /* mafintosh/flat-tree fullRoots(index) works on index / 2. */ + blocks = index / 2ULL; + + while (blocks != 0) { + while (factor <= UINT64_MAX / 2ULL && factor * 2ULL <= blocks) + factor *= 2ULL; + + if (out != NULL && count < capacity) + out[count] = offset + factor - 1ULL; + + count++; + offset += 2ULL * factor; + blocks -= factor; + factor = 1; + } + + return count; +} + +#ifdef SLEEP_SALT +int sleep_tree_hash_block(const uint8_t *data, size_t len, + uint8_t out_hash[32]) +{ + uint8_t header[9]; + crypto_generichash_state state; + + if (out_hash == NULL || (data == NULL && len != 0)) { + errno = EINVAL; + return -1; + } + + header[0] = SLEEP_HASH_LEAF; + for (int i = 0; i < 8; i++) + header[1 + i] = (uint8_t)((uint64_t)len >> (56 - i * 8)); + + if (crypto_generichash_init(&state, NULL, 0, 32) != 0) + return -1; + if (crypto_generichash_update(&state, header, sizeof(header)) != 0) + return -1; + if (len != 0 && crypto_generichash_update(&state, data, len) != 0) + return -1; + + return crypto_generichash_final(&state, out_hash, 32); +} + +int sleep_tree_hash_parent(const uint8_t left[32], + const uint8_t right[32], + uint64_t length, + uint8_t out_hash[32]) +{ + uint8_t header[9]; + crypto_generichash_state state; + + if (left == NULL || right == NULL || out_hash == NULL) { + errno = EINVAL; + return -1; + } + + header[0] = SLEEP_HASH_PARENT; + for (int i = 0; i < 8; i++) + header[1 + i] = (uint8_t)(length >> (56 - i * 8)); + + if (crypto_generichash_init(&state, NULL, 0, 32) != 0) + return -1; + if (crypto_generichash_update(&state, header, sizeof(header)) != 0) + return -1; + if (crypto_generichash_update(&state, left, 32) != 0) + return -1; + if (crypto_generichash_update(&state, right, 32) != 0) + return -1; + + return crypto_generichash_final(&state, out_hash, 32); +} +#endif diff --git a/extra/sleep_writer_pass/src/sleep_writer.c b/extra/sleep_writer_pass/src/sleep_writer.c new file mode 100644 index 0000000..d790977 --- /dev/null +++ b/extra/sleep_writer_pass/src/sleep_writer.c @@ -0,0 +1,530 @@ +#include "sleep_writer.h" +#include "sleep.h" + +#include +#include +#include +#include +#include + +#ifdef SLEEP_SALT +#include +#endif + +#define SLEEP_MAGIC_BITFIELD 0x05025700u +#define SLEEP_MAGIC_SIGNATURE 0x05025701u +#define SLEEP_MAGIC_TREE 0x05025702u + +#define SLEEP_BITFIELD_SIZE 3328u +#define SLEEP_DATA_BITS 8192u +#define SLEEP_TREE_BITS 16384u +#define SLEEP_INDEX_BYTES 256u + +#define SLEEP_HASH_BYTES 32u +#define SLEEP_STACK_MAX 64u + +typedef struct writer_node { + uint8_t hash[SLEEP_HASH_BYTES]; + uint64_t length; + uint64_t index; + uint64_t depth; +} writer_node_t; + +typedef struct writer_peak { + uint64_t index; + uint64_t depth; +} writer_peak_t; + +static void put_u16be(uint8_t *p, uint16_t v) +{ + p[0] = (uint8_t)(v >> 8); + p[1] = (uint8_t)v; +} + +static void put_u32be(uint8_t *p, uint32_t v) +{ + p[0] = (uint8_t)(v >> 24); + p[1] = (uint8_t)(v >> 16); + p[2] = (uint8_t)(v >> 8); + p[3] = (uint8_t)v; +} + +static void put_u64be(uint8_t *p, uint64_t v) +{ + p[0] = (uint8_t)(v >> 56); + p[1] = (uint8_t)(v >> 48); + p[2] = (uint8_t)(v >> 40); + p[3] = (uint8_t)(v >> 32); + p[4] = (uint8_t)(v >> 24); + p[5] = (uint8_t)(v >> 16); + p[6] = (uint8_t)(v >> 8); + p[7] = (uint8_t)v; +} + +static int make_path(char *out, size_t out_size, + const char *dir, const char *name, + const char *suffix) +{ + int n = snprintf(out, out_size, "%s/%s%s", dir, name, suffix); + if (n < 0 || (size_t)n >= out_size) { + errno = ENAMETOOLONG; + return -1; + } + return 0; +} + +static int write_header(FILE *f, uint32_t magic, + uint16_t entry_size, const char *algo) +{ + uint8_t header[SLEEP_HEADER_SIZE]; + size_t len = algo ? strlen(algo) : 0; + + if (len > 24) { + errno = EINVAL; + return -1; + } + + memset(header, 0, sizeof(header)); + put_u32be(header, magic); + put_u16be(header + 5, entry_size); + header[7] = (uint8_t)len; + if (len) + memcpy(header + 8, algo, len); + + if (fwrite(header, 1, sizeof(header), f) != sizeof(header)) { + errno = EIO; + return -1; + } + return 0; +} + +static int hash_leaf(const uint8_t *data, size_t len, + uint8_t out[32]) +{ +#ifdef SLEEP_SALT + uint8_t head[9]; + crypto_generichash_state st; + + head[0] = 0; + put_u64be(head + 1, (uint64_t)len); + + if (crypto_generichash_init(&st, NULL, 0, 32) != 0) + return -1; + if (crypto_generichash_update(&st, head, sizeof(head)) != 0) + return -1; + if (len && crypto_generichash_update(&st, data, len) != 0) + return -1; + return crypto_generichash_final(&st, out, 32); +#else + (void)data; (void)len; (void)out; + errno = ENOSYS; + return -1; +#endif +} + +static int hash_parent(const writer_node_t *left, + const writer_node_t *right, + uint8_t out[32]) +{ +#ifdef SLEEP_SALT + uint8_t head[9]; + crypto_generichash_state st; + + head[0] = 1; + put_u64be(head + 1, left->length + right->length); + + if (crypto_generichash_init(&st, NULL, 0, 32) != 0) + return -1; + if (crypto_generichash_update(&st, head, sizeof(head)) != 0) + return -1; + if (crypto_generichash_update(&st, left->hash, 32) != 0) + return -1; + if (crypto_generichash_update(&st, right->hash, 32) != 0) + return -1; + return crypto_generichash_final(&st, out, 32); +#else + (void)left; (void)right; (void)out; + errno = ENOSYS; + return -1; +#endif +} + +static int tree_build(const uint8_t *const *blocks, + const size_t *lengths, + size_t nblocks, + writer_node_t **out_nodes, + size_t *out_count) +{ + writer_node_t *nodes; + writer_peak_t stack[SLEEP_STACK_MAX]; + size_t stack_len = 0; + size_t node_count; + size_t i; + + if (nblocks > (SIZE_MAX + 1) / 2) { + errno = EOVERFLOW; + return -1; + } + + node_count = nblocks ? nblocks * 2 - 1 : 0; + nodes = node_count ? calloc(node_count, sizeof(*nodes)) : NULL; + if (node_count && !nodes) { + errno = ENOMEM; + return -1; + } + + for (i = 0; i < nblocks; i++) { + writer_node_t *leaf; + uint64_t leaf_index = (uint64_t)(i * 2); + + leaf = &nodes[leaf_index]; + leaf->index = leaf_index; + leaf->depth = 0; + leaf->length = lengths[i]; + + if (hash_leaf(blocks[i], lengths[i], leaf->hash) != 0) + goto fail; + + stack[stack_len].index = leaf_index; + stack[stack_len].depth = 0; + stack_len++; + + while (stack_len >= 2 && + stack[stack_len - 1].depth == stack[stack_len - 2].depth) { + writer_peak_t right = stack[stack_len - 1]; + writer_peak_t left = stack[stack_len - 2]; + writer_node_t *ln = &nodes[left.index]; + writer_node_t *rn = &nodes[right.index]; + uint64_t parent_index; + writer_node_t *parent; + + stack_len -= 2; + parent_index = sleep_tree_parent(left.index); + if (parent_index >= node_count) { + errno = EINVAL; + goto fail; + } + + parent = &nodes[parent_index]; + parent->index = parent_index; + parent->depth = left.depth + 1; + parent->length = ln->length + rn->length; + + if (hash_parent(ln, rn, parent->hash) != 0) + goto fail; + + stack[stack_len].index = parent_index; + stack[stack_len].depth = parent->depth; + stack_len++; + } + } + + *out_nodes = nodes; + *out_count = node_count; + return 0; + +fail: + free(nodes); + return -1; +} + +static int write_tree(const char *path, + const writer_node_t *nodes, size_t node_count) +{ + FILE *f; + size_t i; + + f = fopen(path, "wb"); + if (!f) + return -1; + + if (write_header(f, SLEEP_MAGIC_TREE, + SLEEP_TREE_ENTRY_SIZE, SLEEP_ALGO_BLAKE2B) != 0) + goto fail; + + for (i = 0; i < node_count; i++) { + uint8_t entry[40]; + memcpy(entry, nodes[i].hash, 32); + put_u64be(entry + 32, nodes[i].length); + if (fwrite(entry, 1, sizeof(entry), f) != sizeof(entry)) { + errno = EIO; + goto fail; + } + } + + return fclose(f) == 0 ? 0 : -1; +fail: + fclose(f); + return -1; +} + +static int sign_state(const writer_node_t *nodes, + size_t block_count, + const sleep_keypair_t *keypair, + uint8_t signature[64]) +{ +#ifdef SLEEP_SALT + uint64_t root_indexes[64]; + size_t root_count; + size_t i; + size_t msg_len; + uint8_t *msg; + uint8_t digest[32]; + crypto_generichash_state st; + + /* The Dat/SLEEP root set is fullRoots(2 * length). */ + root_count = sleep_tree_full_roots((uint64_t)(2 * block_count), + root_indexes, 64); + + if (root_count > (SIZE_MAX - 1) / 48) { + errno = EOVERFLOW; + return -1; + } + msg_len = 1 + root_count * 48; + msg = malloc(msg_len); + if (!msg) { + errno = ENOMEM; + return -1; + } + + msg[0] = 2; + for (i = 0; i < root_count; i++) { + const writer_node_t *root = &nodes[root_indexes[i]]; + size_t off = 1 + i * 48; + + memcpy(msg + off, root->hash, 32); + put_u64be(msg + off + 32, root->index); + put_u64be(msg + off + 40, root->length); + } + + if (crypto_generichash_init(&st, NULL, 0, 32) != 0) + goto fail; + if (crypto_generichash_update(&st, msg, msg_len) != 0) + goto fail; + if (crypto_generichash_final(&st, digest, 32) != 0) + goto fail; + + free(msg); + + if (crypto_sign_detached(signature, NULL, + digest, sizeof(digest), + keypair->seckey) != 0) + return -1; + + return 0; + +fail: + free(msg); + return -1; +#else + (void)nodes; (void)block_count; (void)keypair; (void)signature; + errno = ENOSYS; + return -1; +#endif +} + +static int write_signatures(const char *path, + const writer_node_t *nodes, + size_t nblocks, + const sleep_keypair_t *keypair) +{ + FILE *f; + size_t length; + + f = fopen(path, "wb"); + if (!f) + return -1; + + if (write_header(f, SLEEP_MAGIC_SIGNATURE, + SLEEP_SIG_ENTRY_SIZE, SLEEP_ALGO_ED25519) != 0) + goto fail; + + for (length = 1; length <= nblocks; length++) { + uint8_t sig[64]; + + if (sign_state(nodes, length, keypair, sig) != 0) + goto fail; + if (fwrite(sig, 1, sizeof(sig), f) != sizeof(sig)) { + errno = EIO; + goto fail; + } + } + + return fclose(f) == 0 ? 0 : -1; +fail: + fclose(f); + return -1; +} + +static void set_bit(uint8_t *bits, size_t bit) +{ + bits[bit >> 3] |= (uint8_t)(1u << (bit & 7)); +} + +static unsigned int byte_state(uint8_t v) +{ + if (v == 0x00) + return 0; + if (v == 0xff) + return 3; + return 2; +} + +static void build_index(uint8_t index[SLEEP_INDEX_BYTES], + const uint8_t data[1024]) +{ + size_t pair; + + memset(index, 0, SLEEP_INDEX_BYTES); + + /* Four index bits describe every two bytes of the data bitfield. */ + for (pair = 0; pair < 512; pair++) { + unsigned int a = byte_state(data[pair * 2]); + unsigned int b = byte_state(data[pair * 2 + 1]); + unsigned int tuple = a | (b << 2); + size_t nibble = pair; + size_t byte = nibble >> 1; + + if ((nibble & 1) == 0) + index[byte] |= (uint8_t)tuple; + else + index[byte] |= (uint8_t)(tuple << 4); + } +} + +static int write_bitfield(const char *path, + size_t nblocks, + size_t node_count) +{ + uint8_t entry[SLEEP_BITFIELD_SIZE]; + uint8_t *data = entry; + uint8_t *tree = entry + 1024; + uint8_t *index = entry + 3072; + FILE *f; + size_t i; + + memset(entry, 0, sizeof(entry)); + + if (nblocks > SLEEP_DATA_BITS || node_count > SLEEP_TREE_BITS) { + errno = EOVERFLOW; + return -1; + } + + for (i = 0; i < nblocks; i++) + set_bit(data, i); + + for (i = 0; i < node_count; i++) + set_bit(tree, i); + + build_index(index, data); + + f = fopen(path, "wb"); + if (!f) + return -1; + + if (write_header(f, SLEEP_MAGIC_BITFIELD, + SLEEP_BITFIELD_SIZE, NULL) != 0) + goto fail; + if (fwrite(entry, 1, sizeof(entry), f) != sizeof(entry)) { + errno = EIO; + goto fail; + } + + return fclose(f) == 0 ? 0 : -1; +fail: + fclose(f); + return -1; +} + +static int write_data(const char *path, + const uint8_t *const *blocks, + const size_t *lengths, + size_t nblocks) +{ + FILE *f; + size_t i; + + f = fopen(path, "wb"); + if (!f) + return -1; + + for (i = 0; i < nblocks; i++) { + if (lengths[i] && + fwrite(blocks[i], 1, lengths[i], f) != lengths[i]) { + errno = EIO; + goto fail; + } + } + + return fclose(f) == 0 ? 0 : -1; +fail: + fclose(f); + return -1; +} + +static int write_key(const char *path, const sleep_keypair_t *keypair) +{ + FILE *f = fopen(path, "wb"); + if (!f) + return -1; + if (fwrite(keypair->pubkey, 1, 32, f) != 32) { + errno = EIO; + fclose(f); + return -1; + } + return fclose(f) == 0 ? 0 : -1; +} + +int sleep_write_feed(const char *feed_dir, + const char *feed_name, + const uint8_t *const *blocks, + const size_t *lengths, + size_t nblocks, + const sleep_keypair_t *keypair) +{ + char path[1024]; + writer_node_t *nodes = NULL; + size_t node_count = 0; + int rc = -1; + + if (!feed_dir || !feed_name || !keypair || + (nblocks && (!blocks || !lengths))) { + errno = EINVAL; + return -1; + } + + if (make_path(path, sizeof(path), feed_dir, feed_name, ".tree") != 0) + return -1; + + if (tree_build(blocks, lengths, nblocks, &nodes, &node_count) != 0) + return -1; + + if (write_tree(path, nodes, node_count) != 0) + goto done; + + if (make_path(path, sizeof(path), feed_dir, feed_name, ".signatures") != 0) + goto done; + if (write_signatures(path, nodes, nblocks, keypair) != 0) + goto done; + + if (make_path(path, sizeof(path), feed_dir, feed_name, ".key") != 0) + goto done; + if (write_key(path, keypair) != 0) + goto done; + + if (make_path(path, sizeof(path), feed_dir, feed_name, ".bitfield") != 0) + goto done; + if (write_bitfield(path, nblocks, node_count) != 0) + goto done; + + if (make_path(path, sizeof(path), feed_dir, feed_name, ".data") != 0) + goto done; + if (write_data(path, blocks, lengths, nblocks) != 0) + goto done; + + rc = 0; + +done: + free(nodes); + return rc; +} diff --git a/hypercore b/hypercore new file mode 100644 index 0000000..aee4d4c --- /dev/null +++ b/hypercore @@ -0,0 +1 @@ +NETWORK_PROTOCOL \ No newline at end of file diff --git a/inc/base32768.h b/inc/base32768.h new file mode 100644 index 0000000..56da68c --- /dev/null +++ b/inc/base32768.h @@ -0,0 +1,17 @@ +#ifndef BASE32768_H +#define BASE32768_H + +#include +#include + +/* UTF-8 C-string form of qntm/base32768's BMP-safe Base32768 encoding. */ +int base32768_encode(const uint8_t *input, size_t input_length, + char *output, size_t output_capacity); +int base32768_decode(const char *input, uint8_t *output, + size_t output_capacity, size_t *output_length); + +/* Enumerate the alphabet: bits=15 selects the 32,768 main characters; + * bits=7 selects the 128 possible final padding characters. */ +int base32768_codepoint_at(unsigned bits, uint32_t index, uint32_t *codepoint); + +#endif diff --git a/inc/cable.h b/inc/cable.h new file mode 100644 index 0000000..e0d60e2 --- /dev/null +++ b/inc/cable.h @@ -0,0 +1,81 @@ +#ifndef CABLE_H +#define CABLE_H + +#include +#include +#include "keys.h" +#include "sleep_feed.h" + +#define CABLE_KEY_BYTES 32u +#define CABLE_CHAT_TEXT_BYTES 768u +#define CABLE_CHAT_WIRE_BYTES (4u + 8u + 2u + CABLE_CHAT_TEXT_BYTES) +#define CABLE_CHAT_SEAL_BYTES (CABLE_CHAT_WIRE_BYTES + 48u) + +enum cable_address_scheme { + CABLE_ADDRESS_CABAL = 1, + CABLE_ADDRESS_CABLE = 2, + CABLE_ADDRESS_HYPER = 3 +}; + +typedef struct cable_chat_message { + uint64_t timestamp_ms; + char text[CABLE_CHAT_TEXT_BYTES + 1u]; + size_t text_length; +} cable_chat_message_t; + +/* A Cabal user address is that user's persistent feed public key. A room + * address may additionally carry a room moderation/admin key. */ +typedef struct cable_room_address { + uint8_t key[CABLE_KEY_BYTES]; + uint8_t admin_key[CABLE_KEY_BYTES]; + enum cable_address_scheme scheme; + int has_admin; +} cable_room_address_t; + +/* The schemes share a 32-byte key representation; the caller decides whether + * it is a room key or a user feed key. hyper:// is a migration alias only. */ +int cable_address_parse(const char *address, uint8_t key[CABLE_KEY_BYTES], + enum cable_address_scheme *scheme); +int cable_address_format(const uint8_t key[CABLE_KEY_BYTES], + enum cable_address_scheme scheme, char *out, size_t capacity); +int cable_room_address_parse(const char *address, cable_room_address_t *room); +int cable_room_address_format(const cable_room_address_t *room, + enum cable_address_scheme scheme, char *out, size_t capacity); +int cable_user_address_format(const uint8_t user_feed_key[CABLE_KEY_BYTES], + enum cable_address_scheme scheme, char *out, size_t capacity); +/* Cabal stores the local writer in a room's storage, so derive a per-room + * local feed-key path instead of sharing one global identity across rooms. */ +int cable_user_identity_path(const char *home, + const uint8_t room_key[CABLE_KEY_BYTES], + char *out, size_t capacity); +/* Create a Cabal room signing key and persist it in the room directory. */ +int cable_room_create(const char *home, uint8_t room_key[CABLE_KEY_BYTES]); +/* Store/load the user's local feed secret key. The public key is derived and + * checked on load. The file contains only the 64-byte Ed25519 secret key. */ +int cable_user_identity_load_or_create(const char *path, sleep_keypair_t *identity); +/* Cabal stores messages as JSON blocks in each user's Hypercore feed. These + * helpers use the same Hypercore 7.x on-disk tree/signature layout. */ +int cable_user_feed_open(const char *home, + const uint8_t room_key[CABLE_KEY_BYTES], + sleep_keypair_t *identity, sleep_feed_t *feed); +int cable_user_feed_append(const char *home, + const uint8_t room_key[CABLE_KEY_BYTES], + const uint8_t *record, size_t record_length, + uint8_t user_key[CABLE_KEY_BYTES]); +int cable_user_feed_publish(const char *home, + const uint8_t room_key[CABLE_KEY_BYTES], + const char *record_type, const char *nick, + const char *channel, const char *text, + uint8_t user_key[CABLE_KEY_BYTES]); +int cable_chat_message_encode(const cable_chat_message_t *message, + uint8_t *out, size_t capacity, size_t *written); +int cable_chat_message_decode(cable_chat_message_t *message, + const uint8_t *input, size_t length); +int cable_chat_seal(const uint8_t recipient_ed25519[32], + const uint8_t *message, size_t message_length, + uint8_t *out, size_t capacity, size_t *written); +int cable_chat_open(const sleep_keypair_t *identity, + const uint8_t *sealed, size_t sealed_length, + uint8_t *out, size_t capacity, size_t *written); + +#endif diff --git a/inc/cable_media.h b/inc/cable_media.h new file mode 100644 index 0000000..d313bf8 --- /dev/null +++ b/inc/cable_media.h @@ -0,0 +1,56 @@ +#ifndef CABLE_MEDIA_H +#define CABLE_MEDIA_H + +#include +#include + +#define CABLE_MEDIA_SESSION_BYTES 16u +#define CABLE_MEDIA_HEADER_BYTES 42u +#define CABLE_MEDIA_MAX_PACKET_BYTES 4096u +#define CABLE_MEDIA_AAC_RATE 48000u +#define CABLE_MEDIA_AAC_BITRATE 96000u +#define CABLE_MEDIA_AAC_CHANNELS 1u +#define CABLE_MEDIA_AAC_SAMPLES 1024u + +enum cable_media_kind { + CABLE_MEDIA_AUDIO_START = 1, + CABLE_MEDIA_AUDIO_FRAME = 2, + CABLE_MEDIA_AUDIO_STOP = 3, + CABLE_MEDIA_VIDEO_START = 4, + CABLE_MEDIA_VIDEO_FRAME = 5, + CABLE_MEDIA_VIDEO_STOP = 6 +}; + +typedef struct cable_media_packet { + uint8_t kind; + uint8_t session[CABLE_MEDIA_SESSION_BYTES]; + uint64_t sequence; + uint64_t timestamp_us; + const uint8_t *payload; + size_t payload_length; +} cable_media_packet_t; + +int cable_media_packet_write(const cable_media_packet_t *packet, + uint8_t *output, size_t capacity, size_t *written); +int cable_media_packet_read(cable_media_packet_t *packet, + const uint8_t *input, size_t length); + +typedef struct cable_aac_encoder { void *implementation; } cable_aac_encoder_t; +typedef struct cable_aac_decoder { void *implementation; } cable_aac_decoder_t; +typedef int (*cable_aac_frame_fn)(void *context, const uint8_t *adts, + size_t length); +typedef int (*cable_pcm_samples_fn)(void *context, const int16_t *samples, + size_t sample_count); + +int cable_aac_encoder_open(cable_aac_encoder_t *encoder); +int cable_aac_encoder_frame(cable_aac_encoder_t *encoder, + const int16_t samples[CABLE_MEDIA_AAC_SAMPLES], + cable_aac_frame_fn output, void *context); +void cable_aac_encoder_close(cable_aac_encoder_t *encoder); +int cable_aac_decoder_open(cable_aac_decoder_t *decoder); +int cable_aac_decoder_frame(cable_aac_decoder_t *decoder, + const uint8_t *adts, size_t length, + cable_pcm_samples_fn output, void *context); +void cable_aac_decoder_close(cable_aac_decoder_t *decoder); + +#endif diff --git a/inc/cable_room.h b/inc/cable_room.h new file mode 100644 index 0000000..be29eda --- /dev/null +++ b/inc/cable_room.h @@ -0,0 +1,30 @@ +#ifndef CABLE_ROOM_H +#define CABLE_ROOM_H + +#include "cable.h" + +#define CABLE_ROOM_CHANNEL_BYTES 64u +#define CABLE_ROOM_NICK_BYTES 64u + +enum cable_room_event_type { + CABLE_ROOM_PEER_JOIN = 1, + CABLE_ROOM_PEER_LEAVE = 2, + CABLE_ROOM_NICK = 3, + CABLE_ROOM_CHANNEL_JOIN = 4, + CABLE_ROOM_TEXT = 5 +}; + +typedef struct cable_room_event { + uint8_t type; + uint64_t timestamp_ms; + uint8_t sender[CABLE_KEY_BYTES]; + char channel[CABLE_ROOM_CHANNEL_BYTES + 1u]; + char nick[CABLE_ROOM_NICK_BYTES + 1u]; + char text[CABLE_CHAT_TEXT_BYTES + 1u]; + size_t text_length; +} cable_room_event_t; + +/* In-memory UI event only. Cable's network records are Cabal Core Hypercore + * values and never serialize this C structure. */ + +#endif diff --git a/inc/cable_video.h b/inc/cable_video.h new file mode 100644 index 0000000..5f58eec --- /dev/null +++ b/inc/cable_video.h @@ -0,0 +1,22 @@ +#ifndef CABLE_VIDEO_H +#define CABLE_VIDEO_H + +#include +#include + +typedef struct cable_video_source cable_video_source_t; + +/* Read an OBS MPEG-TS feed from a local UDP socket, normally port 5000. + * Pixels are copied as RGB24; a generation change means a new frame arrived. */ +cable_video_source_t *cable_video_source_open(const char *url); +void cable_video_source_close(cable_video_source_t *source); +int cable_video_source_frame(cable_video_source_t *source, uint8_t *rgb, + size_t capacity, unsigned *width, unsigned *height, + uint64_t *generation); +/* Encode/decode one low-resolution JPEG frame for volatile Cable media. */ +int cable_video_encode_jpeg(const uint8_t *rgb, unsigned width, unsigned height, + uint8_t *jpeg, size_t capacity, size_t *written); +int cable_video_decode_jpeg(const uint8_t *jpeg, size_t length, uint8_t *rgb, + size_t capacity, unsigned *width, unsigned *height); + +#endif diff --git a/inc/cesspool.h b/inc/cesspool.h index b9d44a1..deba488 100644 --- a/inc/cesspool.h +++ b/inc/cesspool.h @@ -1,14 +1,9 @@ -#ifndef CESSPOOL_H -#define CESSPOOL_H +#ifndef CESSPOOL_COMPAT_H +#define CESSPOOL_COMPAT_H -#include "key.h" +/* Compatibility include for existing Dat/Cable modules. The public Cesspool + * API and its independent network types live in libcess/include. */ +#include +#include "dat_net.h" -// bootstrap node -// dht -// discovery -// lookup (key) ↳ lookup1 -// announce -// ping -// holepunch udp - -#endif /* CESSPOOL_H */ +#endif diff --git a/inc/dat.h b/inc/dat.h index 71918e9..42bf30d 100644 --- a/inc/dat.h +++ b/inc/dat.h @@ -1,4 +1,114 @@ #ifndef DAT_H #define DAT_H +#include +#include +#include "keys.h" +#include "sleep_proto.h" + +#define DAT_KEY_BYTES 32u +#define DAT_BLOCK_BYTES (64u * 1024u) +#define DAT_URL_BYTES 71u +#define DAT_LINK_BYTES 4096u + +typedef struct dat_archive { + char dat_path[1024]; + uint8_t metadata_key[DAT_KEY_BYTES]; + uint8_t content_key[DAT_KEY_BYTES]; + sleep_keypair_t metadata_keys; + sleep_keypair_t content_keys; +} dat_archive_t; +typedef struct dat_link { + uint8_t key[DAT_KEY_BYTES]; + uint64_t version; + int has_version; + char path[2048]; +} dat_link_t; +typedef struct dat_checkout { + const dat_archive_t *archive; + uint64_t version; +} dat_checkout_t; +typedef struct dat_url { + char host[256]; + char path[2048]; + uint64_t version; + int has_version; + int wants_stat; + int force_directory_listing; +} dat_url_t; +typedef struct dat_path_info { + sleep_metadata_entry_t entry; + int exists; + int is_directory; +} dat_path_info_t; +typedef int (*dat_archive_change_fn)(const sleep_metadata_entry_t *entry, + uint64_t version, void *context); +typedef int (*dat_archive_readdir_fn)(const sleep_metadata_entry_t *entry, + const char *name, void *context); + +/* Creates a new two-feed legacy-style archive skeleton under root/.dat. */ +int dat_archive_create(dat_archive_t *archive, const char *root); +int dat_archive_open(dat_archive_t *archive, const char *root); +int dat_archive_open_writable(dat_archive_t *archive, const char *root, + const char *secret_directory); +int dat_archive_save_secrets(const dat_archive_t *archive, + const char *secret_directory); +void dat_archive_close(dat_archive_t *archive); +size_t dat_archive_entry_count(const dat_archive_t *archive); +uint64_t dat_archive_version(const dat_archive_t *archive); +int dat_archive_append_file(dat_archive_t *archive, const char *path, + const uint8_t *data, size_t length, + uint32_t mode, uint64_t mtime); +int dat_archive_append_directory(dat_archive_t *archive, const char *path, + uint32_t mode, uint64_t mtime); +int dat_archive_append_symlink(dat_archive_t *archive, const char *path, + const char *target, uint64_t mtime); +int dat_archive_delete(dat_archive_t *archive, const char *path, uint64_t mtime); +int dat_archive_read_entry(const dat_archive_t *archive, size_t index, + sleep_metadata_entry_t *entry); +int dat_archive_read_file(const dat_archive_t *archive, + const sleep_metadata_entry_t *entry, + uint8_t **data, size_t *length); +int dat_archive_find(const dat_archive_t *archive, const char *path, + sleep_metadata_entry_t *entry); +int dat_archive_find_at(const dat_archive_t *archive, const char *path, + uint64_t version, sleep_metadata_entry_t *entry); +int dat_checkout_init(dat_checkout_t *checkout, const dat_archive_t *archive, + uint64_t version); +int dat_checkout_find(const dat_checkout_t *checkout, const char *path, + sleep_metadata_entry_t *entry); +int dat_checkout_read(const dat_checkout_t *checkout, + const sleep_metadata_entry_t *entry, + uint8_t **data, size_t *length); +int dat_checkout_export(const dat_checkout_t *checkout, const char *archive_path, + const char *filesystem_path); +int dat_archive_changes(const dat_archive_t *archive, uint64_t from_version, + uint64_t to_version, dat_archive_change_fn callback, void *context); +int dat_archive_stat_at(const dat_archive_t *archive, uint64_t version, + const char *path, dat_path_info_t *info); +int dat_archive_readdir_at(const dat_archive_t *archive, uint64_t version, + const char *path, dat_archive_readdir_fn callback, + void *context); +int dat_archive_resolve_at(const dat_archive_t *archive, uint64_t version, + const char *path, int force_directory_listing, + dat_path_info_t *info, int *serve_directory_listing); +const char *dat_content_type(const char *path); +int dat_url_parse(const char *text, dat_url_t *url); +int dat_archive_import_path(dat_archive_t *archive, const char *filesystem_path, + const char *archive_path); +/* Imports only changed paths and records tombstones for paths removed locally. */ +int dat_archive_sync_path(dat_archive_t *archive, const char *filesystem_path, + const char *archive_path); +int dat_archive_export_path(const dat_archive_t *archive, const char *archive_path, + const char *filesystem_path); +int dat_archive_export_at(const dat_archive_t *archive, uint64_t version, + const char *archive_path, const char *filesystem_path); +int dat_key_to_url(const uint8_t key[DAT_KEY_BYTES], char url[DAT_URL_BYTES]); +int dat_key_to_compact_url(const uint8_t key[DAT_KEY_BYTES], char url[DAT_URL_BYTES]); +int dat_key_to_hex(const uint8_t key[DAT_KEY_BYTES], char hex[65]); +int dat_url_to_key(const char *url, uint8_t key[DAT_KEY_BYTES]); +int dat_link_format(const dat_link_t *link, char *out, size_t capacity); +int dat_link_format_compact(const dat_link_t *link, char *out, size_t capacity); +int dat_link_parse(const char *text, dat_link_t *link); + #endif /* DAT_H */ diff --git a/inc/dat_dns.h b/inc/dat_dns.h new file mode 100644 index 0000000..c58fe06 --- /dev/null +++ b/inc/dat_dns.h @@ -0,0 +1,27 @@ +#ifndef DAT_DNS_H +#define DAT_DNS_H + +#include +#include "dat.h" + +typedef int (*dat_dns_name_iter_fn)(const char *hostname, + const uint8_t key[DAT_KEY_BYTES], void *context); + +/* Resolve a legacy Dat DNS TXT record such as datkey=<64 hex digits>. */ +int dat_dns_resolve(const char *hostname, uint8_t key[DAT_KEY_BYTES], + uint32_t *ttl_seconds); + +/* Local dat-dns aliases are stored in $HOME/.dat/dat-dns (or USERPROFILE on + * native Windows) and override DNS TXT. */ +int dat_dns_name_set(const char *hostname, const uint8_t key[DAT_KEY_BYTES]); +int dat_dns_name_remove(const char *hostname); +int dat_dns_name_resolve(const char *hostname, uint8_t key[DAT_KEY_BYTES]); +int dat_dns_name_for_key(const uint8_t key[DAT_KEY_BYTES], char *hostname, + size_t hostname_capacity); +int dat_dns_name_foreach(dat_dns_name_iter_fn callback, void *context); + +/* Decode TXT record character strings from one DNS answer packet. */ +int dat_dns_parse_answer(const uint8_t *packet, unsigned packet_len, + uint8_t key[DAT_KEY_BYTES], uint32_t *ttl_seconds); + +#endif diff --git a/inc/dat_fetch.h b/inc/dat_fetch.h new file mode 100644 index 0000000..71d7b04 --- /dev/null +++ b/inc/dat_fetch.h @@ -0,0 +1,12 @@ +#ifndef DAT_FETCH_H +#define DAT_FETCH_H + +#include + +/* Fetch the public metadata and content feeds for a Dat key into root/.dat. + * The fetch uses the configured Cesspool network and persists verified sparse + * feed blocks so a later Dillo request can resume it. */ +int dat_fetch_archive(const char *root, const uint8_t metadata_key[32], + unsigned timeout_ms); + +#endif diff --git a/inc/dat_net.h b/inc/dat_net.h index 891e5bf..f7d39ea 100644 --- a/inc/dat_net.h +++ b/inc/dat_net.h @@ -1,33 +1,22 @@ -#ifndef DAT_NET_H -#define DAT_NET_H +#ifndef DAT_NET_COMPAT_H +#define DAT_NET_COMPAT_H -#include -#include -#include +/* Source-compatibility names for Dat/Cable code written before libcess was + * extracted. New integrations should include directly. */ +#include +#include "dat_socket.h" -#define DATNET_PORT 3282 -#define DATNET_MAX_PACKET 1400 -#define DATNET_TOPIC_BYTES 32 -#define DATNET_PEERID_BYTES 32 +typedef cess_peer_t dat_peer_t; +typedef cessnet_t datnet_t; -typedef struct { - int fd; - struct sockaddr_storage addr; - socklen_t addrlen; - uint8_t id[DATNET_PEERID_BYTES]; -} dat_peer_t; +#define DATNET_PORT CESSNET_PORT +#define DATNET_MAX_PACKET CESSNET_MAX_PACKET +#define DATNET_TOPIC_BYTES CESSNET_TOPIC_BYTES +#define DATNET_PEERID_BYTES CESSNET_PEERID_BYTES +#define datnet_init cessnet_init +#define datnet_bind cessnet_bind +#define datnet_poll cessnet_poll +#define datnet_send cessnet_send +#define datnet_close cessnet_close -typedef struct { - int sock; - uint8_t topic[DATNET_TOPIC_BYTES]; - uint8_t peerid[DATNET_PEERID_BYTES]; - void (*on_message)(dat_peer_t *peer, const uint8_t *msg, size_t len); -} datnet_t; - -int datnet_init(); -int datnet_bind(); -int datnet_poll(); -int datnet_send(); -int datnet_close(datnet_t *net); - -#endif /* DAT_NET_H */ +#endif diff --git a/inc/dat_paths.h b/inc/dat_paths.h new file mode 100644 index 0000000..6f410cd --- /dev/null +++ b/inc/dat_paths.h @@ -0,0 +1,48 @@ +#ifndef DAT_PATHS_H +#define DAT_PATHS_H + +#include +#include +#include +#ifdef _WIN32 +#include +#endif + +/* Return the user's home directory across POSIX shells and native Windows + * launches. Windows GUI starts commonly have USERPROFILE but no HOME. */ +static inline const char *dat_home_directory(void) +{ + const char *home = getenv("HOME"); + if (home && *home) return home; +#ifdef _WIN32 + home = getenv("USERPROFILE"); + if (home && *home) return home; + { + const char *drive = getenv("HOMEDRIVE"); + const char *path = getenv("HOMEPATH"); + static char combined[1024]; + int n; + if (!drive || !*drive || !path || !*path) return NULL; + n = snprintf(combined, sizeof(combined), "%s%s", drive, path); + return n >= 0 && (size_t)n < sizeof(combined) ? combined : NULL; + } +#else + return NULL; +#endif +} + +/* POSIX rename replaces an existing file; Windows rename does not. These + * state files are written to a sibling temporary file before replacement. */ +static inline int dat_replace_file(const char *temporary, const char *destination) +{ +#ifdef _WIN32 + if (MoveFileExA(temporary, destination, + MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) return 0; + errno = EIO; + return -1; +#else + return rename(temporary, destination); +#endif +} + +#endif diff --git a/inc/dat_s7.h b/inc/dat_s7.h new file mode 100644 index 0000000..1d2b5b8 --- /dev/null +++ b/inc/dat_s7.h @@ -0,0 +1,16 @@ +#ifndef DAT_S7_H +#define DAT_S7_H +#include "s7.h" +#include "dat.h" +typedef struct dat_scheme_runtime { + s7_scheme *scheme; +} dat_scheme_runtime_t; + +int dat_s7_init(s7_scheme *scheme); +int dat_scheme_runtime_init(dat_scheme_runtime_t *runtime); +void dat_scheme_runtime_close(dat_scheme_runtime_t *runtime); +int dat_scheme_write_archive_info(s7_scheme *scheme, const char *root, + const dat_archive_t *archive, + const char *title, const char *description, + int replace); +#endif diff --git a/inc/dat_session.h b/inc/dat_session.h new file mode 100644 index 0000000..4203693 --- /dev/null +++ b/inc/dat_session.h @@ -0,0 +1,151 @@ +#ifndef DAT_SESSION_H +#define DAT_SESSION_H + +#include +#include +#include "cesspool.h" +#include "dat.h" +#include "dat_wire.h" +#include "sleep_feed.h" +#include "sleep_replication.h" + +#define DAT_SESSION_MAX_PEERS 64u +#define DAT_SESSION_CAP_METADATA_ONLY (UINT64_C(1) << 63) + +enum dat_session_state { + DAT_SESSION_CLOSED = 0, + DAT_SESSION_OPEN = 1, + DAT_SESSION_DISCOVERING = 2, + DAT_SESSION_ACTIVE = 3, + DAT_SESSION_CLOSING = 4, + DAT_SESSION_FAILED = 5 +}; + +enum dat_session_peer_state { + DAT_SESSION_PEER_DISCOVERED = 1, + DAT_SESSION_PEER_SECURE = 2, + DAT_SESSION_PEER_READY = 3, + DAT_SESSION_PEER_CLOSED = 4 +}; + +enum dat_session_feed { + DAT_SESSION_METADATA = 0, + DAT_SESSION_CONTENT = 1, + DAT_SESSION_FEED_COUNT = 2 +}; + +typedef struct dat_session_peer { + uint8_t peer_id[DAT_PEER_ID_BYTES]; + uint8_t tx_nonce[24]; + uint8_t rx_nonce[24]; + uint8_t *rx_buffer; + size_t rx_buffer_len; + size_t rx_buffer_capacity; + uint64_t tx_offset; + uint64_t rx_offset; + uint8_t remote_feed_mask; + uint8_t local_feed_mask; + uint8_t remote_content_discovery[32]; + int remote_content_pending; + dat_peer_t address; + uint64_t capabilities; + uint64_t payload_bytes_sent; + uint64_t payload_bytes_received; + int state; + int transport_authenticated; + int paused; + int tx_stream_encrypted; + int rx_stream_encrypted; + sleep_replication_t replication[DAT_SESSION_FEED_COUNT]; +} dat_session_peer_t; + +struct dat_session; +typedef int (*dat_session_send_fn)(void *context, + const uint8_t peer_id[DAT_PEER_ID_BYTES], + const uint8_t *frame, size_t frame_len); +typedef int (*dat_session_request_fn)(void *context, + const uint8_t peer_id[DAT_PEER_ID_BYTES], + enum dat_session_feed feed, + const dat_request_t *request); +/* Called when discovery yields a peer; establish an authenticated transport, + * then report success with dat_session_peer_secure(). */ +typedef void (*dat_session_peer_fn)(void *context, + const dat_session_peer_t *peer); +typedef int (*dat_session_poll_fn)(void *context, int timeout_ms); + +/* + * Session protocol state is separate from connection establishment. In the + * Cesspool path, call peer_secure only after the authenticated transport has + * bound this peer ID to the topic. In legacy Hypercore V6 mode, call it once + * the discovered TCP/uTP stream is connected; V6 performs its feed-key stream + * encryption inside the protocol itself. + */ +typedef struct dat_session { + int state; + int announcing; + int looking_up; + uint16_t port; + uint8_t archive_key[DAT_KEY_BYTES]; + uint8_t discovery_topic[CESSPOOL_TOPIC_BYTES]; + uint8_t peer_id[DAT_PEER_ID_BYTES]; + uint8_t feed_keys[DAT_SESSION_FEED_COUNT][DAT_KEY_BYTES]; + int content_key_known; + cesspool_t *cesspool; + sleep_feed_t *feeds[DAT_SESSION_FEED_COUNT]; + dat_session_peer_t peers[DAT_SESSION_MAX_PEERS]; + size_t peer_count; + dat_session_send_fn send; + dat_session_request_fn on_request; + dat_session_peer_fn on_peer; + void *peer_context; + dat_session_poll_fn poll_transport; + void *poll_context; + void *context; +} dat_session_t; + +int dat_session_init(dat_session_t *session, cesspool_t *cesspool, + const uint8_t archive_key[DAT_KEY_BYTES], + const uint8_t peer_id[DAT_PEER_ID_BYTES], uint16_t port, + sleep_feed_t *metadata_feed, + const uint8_t metadata_key[DAT_KEY_BYTES], + sleep_feed_t *content_feed, + const uint8_t content_key[DAT_KEY_BYTES], + dat_session_send_fn send, void *context); +int dat_session_start(dat_session_t *session, int announce, int lookup); +int dat_session_set_peer_callback(dat_session_t *session, + dat_session_peer_fn callback, void *context); +int dat_session_set_transport_poll(dat_session_t *session, + dat_session_poll_fn poll, void *context); +int dat_session_peer_discovered(dat_session_t *session, + const cesspool_peer_t *discovered_peer); +int dat_session_peer_secure(dat_session_t *session, + const uint8_t peer_id[DAT_PEER_ID_BYTES], + int transport_authenticated); +/* For a metadata-only archive import, install the content key learned from + * the verified metadata header and announce that feed to connected peers. */ +int dat_session_set_content_key(dat_session_t *session, + const uint8_t content_key[DAT_KEY_BYTES]); +int dat_session_expect_content_key(dat_session_t *session); +int dat_session_peer_down(dat_session_t *session, + const uint8_t peer_id[DAT_PEER_ID_BYTES]); +/* Pause this client's incoming block requests without dropping the peer. */ +int dat_session_peer_pause(dat_session_t *session, + const uint8_t peer_id[DAT_PEER_ID_BYTES], int paused); +/* Accept arbitrary transport chunks. Partial and coalesced wire frames are buffered. */ +int dat_session_receive(dat_session_t *session, + const uint8_t peer_id[DAT_PEER_ID_BYTES], + const uint8_t *frame, size_t frame_len); +int dat_session_request_block(dat_session_t *session, + const uint8_t peer_id[DAT_PEER_ID_BYTES], + enum dat_session_feed feed, uint64_t index, + uint64_t bytes); +int dat_session_send_block(dat_session_t *session, + const uint8_t peer_id[DAT_PEER_ID_BYTES], + enum dat_session_feed feed, uint64_t index); +int dat_session_tick(dat_session_t *session, uint64_t now_ms); +int dat_session_poll(dat_session_t *session, int timeout_ms, uint64_t now_ms); +int dat_session_close(dat_session_t *session); +dat_session_peer_t *dat_session_find_peer(dat_session_t *session, + const uint8_t peer_id[DAT_PEER_ID_BYTES]); + +#endif /* DAT_SESSION_H */ diff --git a/inc/dat_socket.h b/inc/dat_socket.h new file mode 100644 index 0000000..fe5abff --- /dev/null +++ b/inc/dat_socket.h @@ -0,0 +1,18 @@ +#ifndef DAT_SOCKET_H +#define DAT_SOCKET_H + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include +typedef SOCKET dat_socket_t; +#define DAT_INVALID_SOCKET INVALID_SOCKET +#else +#include +typedef int dat_socket_t; +#define DAT_INVALID_SOCKET (-1) +#endif + +#endif diff --git a/inc/dat_wire.h b/inc/dat_wire.h new file mode 100644 index 0000000..4224a20 --- /dev/null +++ b/inc/dat_wire.h @@ -0,0 +1,110 @@ +#ifndef DAT_WIRE_H +#define DAT_WIRE_H +#include +#include + +enum dat_message_type { + DAT_MSG_FEED = 0, + DAT_MSG_HANDSHAKE = 1, + DAT_MSG_INFO = 2, + DAT_MSG_HAVE = 3, + DAT_MSG_UNHAVE = 4, + DAT_MSG_WANT = 5, + DAT_MSG_UNWANT = 6, + DAT_MSG_REQUEST = 7, + DAT_MSG_CANCEL = 8, + DAT_MSG_DATA = 9, + DAT_MSG_EXTENSION = 15 +}; + +typedef struct dat_feed_open { + uint8_t discovery_key[32]; + uint8_t nonce[24]; + int has_nonce; +} dat_feed_open_t; + +typedef struct dat_wire_message { + uint64_t channel; + uint64_t type; + const uint8_t *payload; + size_t payload_len; +} dat_wire_message_t; + +#define DAT_PROTOCOL_BYTES 32u +#define DAT_PEER_ID_BYTES 32u +#ifndef DAT_NETWORK_PROTOCOL +#define DAT_NETWORK_PROTOCOL "cesspool" +#endif +/* + * The exact protocol token lives in the repository-root NETWORK_PROTOCOL + * file. The Cesspool handshake carries it in `userData`; legacy Hypercore V6 + * omits that application-owned field and uses the token to select its + * discovery namespace and wire mode. This checkout defaults to `cesspool`. + */ +typedef struct dat_handshake { + uint8_t protocol[DAT_PROTOCOL_BYTES]; + uint8_t peer_id[DAT_PEER_ID_BYTES]; + uint64_t capabilities; + int has_peer_id; +} dat_handshake_t; +int dat_handshake_init(dat_handshake_t *handshake, + const uint8_t peer_id[DAT_PEER_ID_BYTES], + uint64_t capabilities); +int dat_handshake_protocol_matches(const dat_handshake_t *handshake); +typedef struct dat_range { uint64_t start; uint64_t length; } dat_range_t; +typedef struct dat_info { uint8_t uploading; uint8_t downloading; } dat_info_t; +typedef struct dat_request { uint64_t index; uint64_t bytes; uint8_t want_hash; uint8_t want_nodes; } dat_request_t; +#define DAT_DATA_MAX_NODES 128u +typedef struct dat_data_node { + uint64_t index; + uint64_t length; + uint8_t hash[32]; +} dat_data_node_t; +typedef struct dat_data { + uint64_t index; + const uint8_t *value; + size_t value_len; + const uint8_t *proof; + size_t proof_len; + dat_data_node_t nodes[DAT_DATA_MAX_NODES]; + size_t node_count; + uint8_t signature[64]; + int has_signature; +} dat_data_t; +typedef struct dat_cancel { uint64_t index; } dat_cancel_t; +typedef struct dat_extension { + uint64_t id; + const uint8_t *message; + size_t message_len; +} dat_extension_t; + +/* Hypercore frames are length-prefixed; headers pack channel and type. */ +int dat_wire_encode(const dat_wire_message_t *message, uint8_t *out, + size_t capacity, size_t *written); +int dat_wire_decode(dat_wire_message_t *message, const uint8_t *input, + size_t input_len, size_t *consumed); +int dat_handshake_encode(const dat_handshake_t *handshake, uint8_t *out, + size_t capacity, size_t *written); +int dat_handshake_decode(dat_handshake_t *handshake, const uint8_t *input, + size_t input_len); +int dat_feed_open_encode(const dat_feed_open_t *feed, uint8_t *out, + size_t capacity, size_t *written); +int dat_feed_open_decode(dat_feed_open_t *feed, const uint8_t *input, + size_t input_len); +int dat_range_encode(const dat_range_t *range, uint8_t *out, size_t capacity, size_t *written); +int dat_range_decode(dat_range_t *range, const uint8_t *input, size_t input_len); +/* WANT omits length to mean through the current end of a non-live feed. */ +int dat_want_decode(dat_range_t *range, const uint8_t *input, size_t input_len, + uint64_t feed_length); +int dat_info_encode(const dat_info_t *info, uint8_t *out, size_t capacity, + size_t *written); +int dat_info_decode(dat_info_t *info, const uint8_t *input, size_t input_len); +int dat_request_encode(const dat_request_t *request, uint8_t *out, size_t capacity, size_t *written); +int dat_request_decode(dat_request_t *request, const uint8_t *input, size_t input_len); +int dat_data_encode(const dat_data_t *data, uint8_t *out, size_t capacity, size_t *written); +int dat_data_decode(dat_data_t *data, const uint8_t *input, size_t input_len); +int dat_cancel_encode(const dat_cancel_t *cancel, uint8_t *out, size_t capacity, size_t *written); +int dat_cancel_decode(dat_cancel_t *cancel, const uint8_t *input, size_t input_len); +int dat_extension_encode(const dat_extension_t *extension, uint8_t *out, size_t capacity, size_t *written); +int dat_extension_decode(dat_extension_t *extension, const uint8_t *input, size_t input_len); +#endif diff --git a/inc/errno_compat.h b/inc/errno_compat.h new file mode 100644 index 0000000..0403f11 --- /dev/null +++ b/inc/errno_compat.h @@ -0,0 +1,12 @@ +#ifndef DATGPT_ERRNO_COMPAT_H +#define DATGPT_ERRNO_COMPAT_H + +#include + +/* EKEYREJECTED is a Linux extension. Keep the key-authentication failure + * portable while preserving a useful access-denied error on other hosts. */ +#ifndef EKEYREJECTED +#define EKEYREJECTED EACCES +#endif + +#endif diff --git a/inc/sleep.h b/inc/sleep.h index 002d09b..dcc4ec6 100644 --- a/inc/sleep.h +++ b/inc/sleep.h @@ -11,7 +11,7 @@ content.key content.signatures content.bitfield content.tree*/ -#include "key.h" +#include "keys.h" #include #include // test @@ -21,8 +21,14 @@ content.tree*/ extern "C" { #endif +int sleep_init(const char *root); + #define SLEEP_HEADER_SIZE 32u +#define SLEEP_MAGIC_BITFIELD 0x05025700u +#define SLEEP_MAGIC_SIGNATURE 0x05025701u +#define SLEEP_MAGIC_TREE 0x05025702u + // TODO MAGIC_BITFIELD, MAGIC_SIG, MAGIC_TREE #define SLEEP_ALGO_BLAKE2B "BLAKE2b" @@ -30,7 +36,7 @@ extern "C" { #define SLEEP_TREE_ENTRY_SIZE 40u #define SLEEP_SIG_ENTRY_SIZE 64u -#define SLEEP_BITFIELD_ENTRY_SIZE 3328u +#define SLEEP_BITFIELD_ENTRY_SIZE 3584u enum sleep_hash_type { SLEEP_HASH_LEAF = 0, @@ -69,11 +75,12 @@ typedef struct SLEEP_PACKED sleep_sig_entry { } sleep_sig_entry_t; // TODO assert -// 3328 bytes: tree[2048] + data[1024] + index[256] +// 3584-byte historical record. The final 256 bytes are opaque for now. typedef struct SLEEP_PACKED sleep_bitfield_entry { - uint8_t tree[2048]; uint8_t data[1024]; + uint8_t tree[2048]; uint8_t index[256]; + uint8_t reserved[256]; } sleep_bitfield_entry_t; // TODO DOTO ODOT assert it @@ -97,6 +104,22 @@ int sleep_compute_discovery_key(const uint8_t pubkey[32], uint8_t out32[32]); #endif /* SLEEP_SALT */ // function decs +uint64_t sleep_tree_index(uint64_t depth, uint64_t offset); +uint64_t sleep_tree_depth(uint64_t index); +uint64_t sleep_tree_offset(uint64_t index, uint64_t depth); +uint64_t sleep_tree_parent(uint64_t index); +int sleep_tree_left_child(uint64_t index, uint64_t *out); +int sleep_tree_right_child(uint64_t index, uint64_t *out); +int sleep_tree_sibling(uint64_t index, uint64_t *out); +uint64_t sleep_tree_left_span(uint64_t index); +uint64_t sleep_tree_right_span(uint64_t index); +void sleep_tree_spans(uint64_t index, uint64_t *left, uint64_t *right); +size_t sleep_tree_count(uint64_t index); +size_t sleep_tree_count_leaves(uint64_t index); +size_t sleep_tree_full_roots(uint64_t index, uint64_t *out, size_t capacity); +int sleep_tree_hash_block(const uint8_t *data, size_t len, uint8_t out_hash[32]); +int sleep_tree_hash_parent(const uint8_t left[32], const uint8_t right[32], + uint64_t length, uint8_t out_hash[32]); #ifdef __cplusplus } diff --git a/inc/sleep_bitfield.h b/inc/sleep_bitfield.h new file mode 100644 index 0000000..26d8c69 --- /dev/null +++ b/inc/sleep_bitfield.h @@ -0,0 +1,27 @@ +#ifndef SLEEP_BITFIELD_H +#define SLEEP_BITFIELD_H +#include +#include + +#define SLEEP_BITFIELD_DATA_BYTES 1024u +#define SLEEP_BITFIELD_TREE_BYTES 2048u +#define SLEEP_BITFIELD_INDEX_BYTES 256u +#define SLEEP_BITFIELD_RESERVED_BYTES 256u + +typedef struct sleep_bitfield { + uint8_t data[SLEEP_BITFIELD_DATA_BYTES]; + uint8_t tree[SLEEP_BITFIELD_TREE_BYTES]; + uint8_t index[SLEEP_BITFIELD_INDEX_BYTES]; + uint8_t reserved[SLEEP_BITFIELD_RESERVED_BYTES]; +} sleep_bitfield_t; + +void sleep_bitfield_clear(sleep_bitfield_t *field); +int sleep_bitfield_get_data(const sleep_bitfield_t *field, size_t bit); +int sleep_bitfield_get_tree(const sleep_bitfield_t *field, size_t bit); +int sleep_bitfield_set_data(sleep_bitfield_t *field, size_t bit, int present); +int sleep_bitfield_set_tree(sleep_bitfield_t *field, size_t bit, int present); +void sleep_bitfield_reindex(sleep_bitfield_t *field); +size_t sleep_bitfield_next_data(const sleep_bitfield_t *field, size_t start, int present); +int sleep_bitfield_encode(const sleep_bitfield_t *field, uint8_t *out, size_t capacity); +int sleep_bitfield_decode(sleep_bitfield_t *field, const uint8_t *input, size_t length); +#endif diff --git a/inc/sleep_crypto.h b/inc/sleep_crypto.h index b5b62f4..34aa17c 100644 --- a/inc/sleep_crypto.h +++ b/inc/sleep_crypto.h @@ -4,19 +4,26 @@ #include #include #include "keys.h" -//#include "sleep.h" +#define SLEEP_ROOT_RECORD_BYTES 48u + +int sleep_crypto_init(void); +int sleep_stream_xsalsa20_xor(uint8_t *out, const uint8_t *in, size_t length, + const uint8_t nonce[24], const uint8_t key[32], + uint64_t stream_offset); int sleep_gen_keypair(sleep_keypair_t *kp); -int sleep_save_pubkey(const char *path, const uint8_t pubkey[SLEEP_PUBLIC_KEY_BYTES]); -int sleep_load_pubkey(const char *path, uint8_t pubkey[SLEEP_PUBLIC_KEY_BYTES]); +int sleep_save_pubkey(const char *path, const uint8_t pubkey[SLEEP_PUB_KEY_BYTES]); +int sleep_load_pubkey(const char *path, uint8_t pubkey[SLEEP_PUB_KEY_BYTES]); +int sleep_save_keypair(const char *path, const sleep_keypair_t *keypair); +int sleep_load_keypair(const char *path, sleep_keypair_t *keypair); int sleep_blake2b(uint8_t out[SLEEP_BLAKE2B_BYTES], const uint8_t *in, size_t inlen); -int sleep_sign(uint8_t sig[SLEEP_BLAKE2B_BYTES], const uint8_t *msg, size_t msglen, const sleep_keypair_t *kp); +int sleep_sign(uint8_t sig[SLEEP_SIGNATURE_BYTES], const uint8_t *msg, size_t msglen, const sleep_keypair_t *kp); -int sleep_verify(const uint8_t sig[SLEEP_SIGNATURE_BYTES], const uint8_t *msg, size_t msglen, const uint8_t pubkey[SLEEP_PUBLIC_KEY_BYTES]); +int sleep_verify(const uint8_t sig[SLEEP_SIGNATURE_BYTES], const uint8_t *msg, size_t msglen, const uint8_t pubkey[SLEEP_PUB_KEY_BYTES]); int sleep_sign_roots(uint8_t sig[SLEEP_SIGNATURE_BYTES], const uint8_t *roots, size_t nroots, const uint64_t *indices, const uint64_t *lengths, const sleep_keypair_t *kp); -int sleep_verify_roots(const uint8_t sig[SLEEP_SIGNATURE_BYTES], const uint8_t *roots, size_t nroots, const uint64_t *indices, const uint64_t *lengths, const uint8_t pub[SLEEP_PUBLIC_KEY_BYTES]); +int sleep_verify_roots(const uint8_t sig[SLEEP_SIGNATURE_BYTES], const uint8_t *roots, size_t nroots, const uint64_t *indices, const uint64_t *lengths, const uint8_t pub[SLEEP_PUB_KEY_BYTES]); #endif /* SLEEP_CRYPTO_H */ diff --git a/inc/sleep_feed.h b/inc/sleep_feed.h new file mode 100644 index 0000000..0d36c11 --- /dev/null +++ b/inc/sleep_feed.h @@ -0,0 +1,43 @@ +#ifndef SLEEP_FEED_H +#define SLEEP_FEED_H +#include +#include +#include "keys.h" + +typedef struct sleep_feed { + char directory[1024]; + char name[64]; + uint8_t **blocks; + size_t *lengths; + uint8_t **proofs; + size_t *proof_lengths; + size_t length; + size_t capacity; + sleep_keypair_t keypair; + int writable; + int dirty; + int remote_only; + int persist_remote; +} sleep_feed_t; + +int sleep_feed_open(sleep_feed_t *feed, const char *directory, const char *name, + const sleep_keypair_t *keypair); +/* Opens an in-memory, read-only feed replica using only its public key. */ +int sleep_feed_open_remote(sleep_feed_t *feed, const char *directory, + const char *name, const uint8_t public_key[32]); +/* Merge verified cached remote blocks and persist subsequent received blocks. */ +int sleep_feed_load_remote(sleep_feed_t *feed, const uint8_t public_key[32]); +size_t sleep_feed_length(const sleep_feed_t *feed); +int sleep_feed_read(const sleep_feed_t *feed, size_t index, uint8_t **data, size_t *length); +int sleep_feed_append(sleep_feed_t *feed, const uint8_t *data, size_t length); +/* Keeps already verified remote blocks and proofs in this session's memory. */ +int sleep_feed_append_remote(sleep_feed_t *feed, size_t index, + const uint8_t *data, size_t length, + const uint8_t *proof, size_t proof_length); +int sleep_feed_truncate(sleep_feed_t *feed, size_t length); +int sleep_feed_has(const sleep_feed_t *feed, size_t index); +int sleep_feed_byte_offset(const sleep_feed_t *feed, size_t index, + uint64_t *offset); +int sleep_feed_flush(sleep_feed_t *feed); +void sleep_feed_close(sleep_feed_t *feed); +#endif diff --git a/inc/sleep_proof.h b/inc/sleep_proof.h new file mode 100644 index 0000000..b2c9921 --- /dev/null +++ b/inc/sleep_proof.h @@ -0,0 +1,43 @@ +#ifndef SLEEP_PROOF_H +#define SLEEP_PROOF_H +#include +#include +#include "sleep_feed.h" + +#define SLEEP_PROOF_HASH_BYTES 32u +#define SLEEP_PROOF_MAX_NODES 64u +typedef struct sleep_proof_node { + uint64_t index; + uint64_t length; + uint8_t hash[SLEEP_PROOF_HASH_BYTES]; +} sleep_proof_node_t; +typedef struct sleep_proof { + uint64_t block; + uint64_t block_length; + uint8_t block_hash[SLEEP_PROOF_HASH_BYTES]; + sleep_proof_node_t nodes[SLEEP_PROOF_MAX_NODES]; + size_t node_count; + sleep_proof_node_t roots[SLEEP_PROOF_MAX_NODES]; + size_t root_count; + uint8_t signature[64]; + int has_signature; +} sleep_proof_t; + +void sleep_proof_init(sleep_proof_t *proof, uint64_t block); +int sleep_proof_add_node(sleep_proof_t *proof, uint64_t index, + uint64_t length, const uint8_t hash[32]); +int sleep_proof_add_root(sleep_proof_t *proof, uint64_t index, + uint64_t length, const uint8_t hash[32]); +int sleep_proof_create(const sleep_feed_t *feed, uint64_t block, + sleep_proof_t *proof); +int sleep_proof_from_nodes(uint64_t block, const uint8_t *value, + size_t value_len, const sleep_proof_node_t *nodes, + size_t node_count, const uint8_t signature[64], + uint64_t feed_length, sleep_proof_t *proof); +int sleep_proof_encode(const sleep_proof_t *proof, uint8_t *out, + size_t capacity, size_t *written); +int sleep_proof_decode(sleep_proof_t *proof, const uint8_t *input, + size_t input_len); +int sleep_proof_verify(const sleep_proof_t *proof, const uint8_t *block, + size_t block_len, const uint8_t public_key[32]); +#endif diff --git a/inc/sleep_proto.h b/inc/sleep_proto.h new file mode 100644 index 0000000..9a11a35 --- /dev/null +++ b/inc/sleep_proto.h @@ -0,0 +1,26 @@ +#ifndef SLEEP_PROTO_H +#define SLEEP_PROTO_H +#include +#include +#define SLEEP_PROTO_KEY_BYTES 32u +typedef struct sleep_metadata_header { char type[32]; uint8_t content[32]; size_t content_len; } sleep_metadata_header_t; +enum sleep_entry_type { SLEEP_ENTRY_FILE=1, SLEEP_ENTRY_DIRECTORY=2, SLEEP_ENTRY_SYMLINK=3, SLEEP_ENTRY_DELETE=4 }; +typedef struct sleep_metadata_entry { + char path[1024]; + uint32_t type; + uint32_t mode; + uint64_t size; + uint64_t mtime; + uint64_t block_offset; + uint64_t block_count; + char linkname[1024]; +} sleep_metadata_entry_t; +size_t sleep_varint_size(uint64_t value); +int sleep_varint_encode(uint64_t value, uint8_t *out, size_t capacity, size_t *written); +int sleep_varint_decode(const uint8_t *input, size_t input_len, uint64_t *value, size_t *consumed); +int sleep_metadata_header_encode(const sleep_metadata_header_t *header, uint8_t *out, size_t capacity, size_t *written); +int sleep_metadata_header_decode(sleep_metadata_header_t *header, const uint8_t *input, size_t input_len); +int sleep_write_metadata_header(const char *path, const uint8_t content_pubkey[32]); +int sleep_metadata_entry_encode(const sleep_metadata_entry_t *entry, uint8_t *out, size_t capacity, size_t *written); +int sleep_metadata_entry_decode(sleep_metadata_entry_t *entry, const uint8_t *input, size_t input_len); +#endif diff --git a/inc/sleep_replication.h b/inc/sleep_replication.h new file mode 100644 index 0000000..35d8b49 --- /dev/null +++ b/inc/sleep_replication.h @@ -0,0 +1,44 @@ +#ifndef SLEEP_REPLICATION_H +#define SLEEP_REPLICATION_H +#include +#include +#include "sleep_feed.h" +#include "sleep_bitfield.h" +#include "sleep_proof.h" + +#define SLEEP_REPLICATION_QUEUE 128u +typedef struct sleep_replication_request { uint64_t index; uint64_t bytes; int hash; int nodes; } sleep_replication_request_t; +typedef struct sleep_replication { + sleep_feed_t *feed; + uint64_t channel; + uint64_t remote_length; + sleep_bitfield_t remote_bitfield; + sleep_replication_request_t requests[SLEEP_REPLICATION_QUEUE]; + size_t request_head, request_count; + int live, uploading, downloading; +} sleep_replication_t; + +int sleep_replication_init(sleep_replication_t *replication, + sleep_feed_t *feed, uint64_t channel); +int sleep_replication_have(sleep_replication_t *replication, + uint64_t start, uint64_t length); +int sleep_replication_unhave(sleep_replication_t *replication, + uint64_t start, uint64_t length); +int sleep_replication_want(sleep_replication_t *replication, + uint64_t start, uint64_t length); +int sleep_replication_request(sleep_replication_t *replication, + const sleep_replication_request_t *request); +int sleep_replication_next(sleep_replication_t *replication, + sleep_replication_request_t *request); +int sleep_replication_complete(sleep_replication_t *replication, + uint64_t index); +int sleep_replication_is_requested(const sleep_replication_t *replication, + uint64_t index); +int sleep_replication_unwant(sleep_replication_t *replication, + uint64_t start, uint64_t length); +void sleep_replication_clear_requests(sleep_replication_t *replication); +int sleep_replication_receive(sleep_replication_t *replication, + const uint8_t *block, size_t block_len, + const sleep_proof_t *proof, + const uint8_t feed_public_key[32]); +#endif diff --git a/inc/sleep_utp.h b/inc/sleep_utp.h new file mode 100644 index 0000000..860665d --- /dev/null +++ b/inc/sleep_utp.h @@ -0,0 +1,39 @@ +#ifndef SLEEP_UTP_H +#define SLEEP_UTP_H +#include +#include +#include "dat_socket.h" + +#define SLEEP_UTP_MTU 1200u +#define SLEEP_UTP_WINDOW 64u +enum sleep_utp_state { SLEEP_UTP_CLOSED, SLEEP_UTP_SYN_SENT, SLEEP_UTP_SYN_RECV, SLEEP_UTP_CONNECTED, SLEEP_UTP_FIN_SENT, SLEEP_UTP_RESET }; +enum sleep_utp_packet_type { SLEEP_UTP_DATA, SLEEP_UTP_FIN, SLEEP_UTP_STATE, SLEEP_UTP_RESET_PACKET, SLEEP_UTP_SYN }; +typedef struct sleep_utp_packet { uint8_t type, version; uint16_t connection_id; uint32_t timestamp, timestamp_diff, window_size; uint16_t sequence, acknowledgement; uint32_t selective_ack; int has_selective_ack; const uint8_t *payload; size_t payload_len; } sleep_utp_packet_t; +typedef struct sleep_utp_slot { uint16_t sequence; uint8_t data[SLEEP_UTP_MTU]; size_t length; uint64_t sent_at; unsigned retries; int used; } sleep_utp_slot_t; +typedef struct sleep_utp { + dat_socket_t socket; + struct sockaddr_storage remote; + socklen_t remote_len; + uint16_t send_id, receive_id, sequence, acknowledgement; + uint32_t receive_window, send_window; + enum sleep_utp_state state; + uint64_t retransmit_at; + unsigned syn_retries; + uint32_t congestion_window, slow_start_threshold; + uint32_t round_trip_time, round_trip_variance; + size_t bytes_in_flight; + sleep_utp_slot_t unacked[SLEEP_UTP_WINDOW]; + sleep_utp_slot_t reorder[SLEEP_UTP_WINDOW]; + void *context; + void (*on_data)(struct sleep_utp *, const uint8_t *, size_t); +} sleep_utp_t; +int sleep_utp_init(sleep_utp_t *utp, dat_socket_t udp_socket); +int sleep_utp_connect(sleep_utp_t *utp, const struct sockaddr *remote, socklen_t remote_len); +int sleep_utp_accept(sleep_utp_t *utp, const sleep_utp_packet_t *syn, const struct sockaddr *remote, socklen_t remote_len); +int sleep_utp_packet_encode(const sleep_utp_packet_t *packet, uint8_t *out, size_t capacity, size_t *written); +int sleep_utp_packet_decode(sleep_utp_packet_t *packet, const uint8_t *input, size_t input_len); +int sleep_utp_input(sleep_utp_t *utp, const uint8_t *packet, size_t packet_len); +int sleep_utp_send(sleep_utp_t *utp, const uint8_t *data, size_t length); +int sleep_utp_tick(sleep_utp_t *utp, uint64_t now_ms); +int sleep_utp_close(sleep_utp_t *utp); +#endif diff --git a/inc/sleep_verify.h b/inc/sleep_verify.h new file mode 100644 index 0000000..7be1419 --- /dev/null +++ b/inc/sleep_verify.h @@ -0,0 +1,6 @@ +#ifndef SLEEP_VERIFY_H +#define SLEEP_VERIFY_H + +int sleep_verify_dat(const char *dat_dir); + +#endif \ No newline at end of file diff --git a/inc/sleep_writer.h b/inc/sleep_writer.h new file mode 100644 index 0000000..474bfab --- /dev/null +++ b/inc/sleep_writer.h @@ -0,0 +1,18 @@ +#ifndef SLEEP_WRITER_H +#define SLEEP_WRITER_H +#include +#include +#include "sleep.h" +#ifdef __cplusplus +extern "C" { +#endif +int sleep_write_feed(const char *feed_dir, + const char *feed_name, + const uint8_t *const *blocks, + const size_t *lengths, + size_t nblocks, + const sleep_keypair_t *keypair); +#ifdef __cplusplus +} +#endif +#endif diff --git a/inc/win_compat.h b/inc/win_compat.h new file mode 100644 index 0000000..8ec6c94 --- /dev/null +++ b/inc/win_compat.h @@ -0,0 +1,80 @@ +#ifndef DATGPT_WIN_COMPAT_H +#define DATGPT_WIN_COMPAT_H + +/* Small POSIX spellings used by the shared C sources on native Windows. */ +#ifdef _WIN32 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef mkdir +#define mkdir(path, mode) _mkdir(path) +#endif +#ifndef fsync +#define fsync(fd) _commit(fd) +#endif +#ifndef getpid +#define getpid _getpid +#endif +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#endif +#ifndef O_NOFOLLOW +#define O_NOFOLLOW 0 +#endif +#ifndef EKEYREJECTED +#define EKEYREJECTED EACCES +#endif +#ifndef S_ISLNK +#define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK) +#endif +#ifndef S_IFLNK +#define S_IFLNK 0120000 +#endif +#ifndef fchmod +#define fchmod(fd, mode) ((void)(fd), (void)(mode), 0) +#endif +#ifndef strcasecmp +#define strcasecmp _stricmp +#endif +#ifndef strncasecmp +#define strncasecmp _strnicmp +#endif +static inline char *dat_win_getenv_compat(const char *name) +{ + char *value = getenv(name); + if (!value && name && !strcmp(name, "HOME")) value = getenv("USERPROFILE"); + return value; +} +#define getenv dat_win_getenv_compat +static inline int dat_win_lstat(const char *path, struct stat *info) +{ + DWORD attributes; + if (stat(path, info)) return -1; + attributes = GetFileAttributesA(path); + if (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_REPARSE_POINT)) + info->st_mode = (info->st_mode & ~S_IFMT) | S_IFLNK; + return 0; +} +#ifndef lstat +#define lstat dat_win_lstat +#endif +static inline int dat_win_readlink(const char *path, char *target, size_t capacity) +{ (void)path; (void)target; (void)capacity; errno = ENOTSUP; return -1; } +static inline int dat_win_symlink(const char *target, const char *path) +{ (void)target; (void)path; errno = ENOTSUP; return -1; } +#ifndef readlink +#define readlink dat_win_readlink +#endif +#ifndef symlink +#define symlink dat_win_symlink +#endif +#endif + +#endif diff --git a/inc/win_dirent.h b/inc/win_dirent.h new file mode 100644 index 0000000..de0eb3c --- /dev/null +++ b/inc/win_dirent.h @@ -0,0 +1,53 @@ +#ifndef DATGPT_WIN_DIRENT_H +#define DATGPT_WIN_DIRENT_H +#ifdef _WIN32 +#include +#include +#include +#include +#include +#define NAME_MAX MAX_PATH +typedef struct dirent { char d_name[MAX_PATH]; } struct_dirent_t; +typedef struct dat_win_dir { + HANDLE handle; + WIN32_FIND_DATAA data; + int first; + struct dirent entry; +} DIR; +static DIR *dat_opendir(const char *path) +{ + DIR *dir = (DIR *)calloc(1, sizeof(*dir)); + char pattern[MAX_PATH * 2]; + int n; + if (!dir) return NULL; + n = snprintf(pattern, sizeof(pattern), "%s\\*", path); + if (n < 0 || (size_t)n >= sizeof(pattern)) { free(dir); errno = ENAMETOOLONG; return NULL; } + dir->handle = FindFirstFileA(pattern, &dir->data); + if (dir->handle == INVALID_HANDLE_VALUE) { free(dir); errno = ENOENT; return NULL; } + dir->first = 1; + return dir; +} +static struct dirent *dat_readdir(DIR *dir) +{ + if (!dir) return NULL; + if (dir->first) dir->first = 0; + else if (!FindNextFileA(dir->handle, &dir->data)) return NULL; + strncpy(dir->entry.d_name, dir->data.cFileName, sizeof(dir->entry.d_name) - 1); + dir->entry.d_name[sizeof(dir->entry.d_name) - 1] = 0; + return &dir->entry; +} +static int dat_closedir(DIR *dir) +{ + int rc; + if (!dir) { errno = EINVAL; return -1; } + rc = FindClose(dir->handle) ? 0 : -1; + free(dir); + return rc; +} +#define opendir dat_opendir +#define readdir dat_readdir +#define closedir dat_closedir +#else +#include +#endif +#endif diff --git a/inc/win_fnmatch.h b/inc/win_fnmatch.h new file mode 100644 index 0000000..a65dd15 --- /dev/null +++ b/inc/win_fnmatch.h @@ -0,0 +1,24 @@ +#ifndef DATGPT_WIN_FNMATCH_H +#define DATGPT_WIN_FNMATCH_H +#ifdef _WIN32 +static int dat_win_fnmatch(const char *pattern, const char *text) +{ + while (*pattern) { + if (*pattern == '*') { + while (*pattern == '*') pattern++; + if (!*pattern) return 0; + do { if (dat_win_fnmatch(pattern, text) == 0) return 0; } while (*text++); + return 1; + } + if (!*text) return 1; + if (*pattern == '?') { pattern++; text++; continue; } + if (*pattern != *text) return 1; + pattern++; text++; + } + return *text ? 1 : 0; +} +#define fnmatch(pattern, text, flags) ((void)(flags), dat_win_fnmatch((pattern), (text))) +#else +#include +#endif +#endif diff --git a/libcess/Makefile b/libcess/Makefile new file mode 100644 index 0000000..675804a --- /dev/null +++ b/libcess/Makefile @@ -0,0 +1,49 @@ +CC ?= cc +AR ?= ar +RANLIB ?= ranlib +INSTALL ?= install +PREFIX ?= /usr/local +BUILD ?= build +CFLAGS ?= -O2 -std=c99 -Wall -Wextra -Wpedantic +CPPFLAGS ?= +NETWORK_PROTOCOL ?= $(shell tr -d '\r\n' < NETWORK_PROTOCOL) +SODIUM_CFLAGS ?= $(shell pkg-config --cflags libsodium 2>/dev/null) +SODIUM_LIBS ?= $(shell pkg-config --libs libsodium 2>/dev/null || echo -lsodium) +TARGET_TRIPLE = $(shell $(CC) -dumpmachine 2>/dev/null) +ifeq ($(findstring mingw,$(TARGET_TRIPLE)),mingw) +PLATFORM_LIBS = -lws2_32 +endif + +CPPFLAGS += -Iinclude -include cess_compat.h -DCESSPOOL_HAVE_SODIUM -DCESSPOOL_NETWORK_PROTOCOL=\"$(NETWORK_PROTOCOL)\" $(SODIUM_CFLAGS) +OBJECTS = $(BUILD)/cess_net.o $(BUILD)/cesspool.o $(BUILD)/cess_identity.o + +.PHONY: all clean install uninstall +all: $(BUILD)/libcess.a $(BUILD)/cesspool-bootstrap + +$(BUILD): + mkdir -p $@ + +$(BUILD)/%.o: src/%.c include/cesspool.h include/cess_net.h | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) -MMD -MP -c $< -o $@ + +$(BUILD)/libcess.a: $(OBJECTS) + $(AR) rcs $@ $(OBJECTS) + $(RANLIB) $@ + +$(BUILD)/cesspool-bootstrap: tools/cesspool-bootstrap.c $(BUILD)/libcess.a + $(CC) $(CPPFLAGS) $(CFLAGS) $< $(BUILD)/libcess.a $(SODIUM_LIBS) $(PLATFORM_LIBS) -o $@ + +-include $(OBJECTS:.o=.d) + +install: all + $(INSTALL) -d "$(DESTDIR)$(PREFIX)/lib" "$(DESTDIR)$(PREFIX)/include/libcess" "$(DESTDIR)$(PREFIX)/bin" "$(DESTDIR)$(PREFIX)/share/man/man1" + $(INSTALL) -m 644 $(BUILD)/libcess.a "$(DESTDIR)$(PREFIX)/lib/libcess.a" + $(INSTALL) -m 644 include/cesspool.h include/cess_net.h include/cess_compat.h "$(DESTDIR)$(PREFIX)/include/libcess/" + $(INSTALL) -m 755 $(BUILD)/cesspool-bootstrap "$(DESTDIR)$(PREFIX)/bin/cesspool-bootstrap" + $(INSTALL) -m 644 docs/cesspool-bootstrap.1 "$(DESTDIR)$(PREFIX)/share/man/man1/cesspool-bootstrap.1" + +uninstall: + rm -f "$(DESTDIR)$(PREFIX)/lib/libcess.a" "$(DESTDIR)$(PREFIX)/bin/cesspool-bootstrap" "$(DESTDIR)$(PREFIX)/share/man/man1/cesspool-bootstrap.1" "$(DESTDIR)$(PREFIX)/include/libcess/cesspool.h" "$(DESTDIR)$(PREFIX)/include/libcess/cess_net.h" "$(DESTDIR)$(PREFIX)/include/libcess/cess_compat.h" + +clean: + rm -rf "$(BUILD)" diff --git a/libcess/NETWORK_PROTOCOL b/libcess/NETWORK_PROTOCOL new file mode 100644 index 0000000..5434abc --- /dev/null +++ b/libcess/NETWORK_PROTOCOL @@ -0,0 +1 @@ +cesspool diff --git a/libcess/README.md b/libcess/README.md new file mode 100644 index 0000000..6564dea --- /dev/null +++ b/libcess/README.md @@ -0,0 +1,32 @@ +# libcess + +`libcess` is the Cesspool networking library used by the Dat (`ddat`) and +Cabal-compatible (`cable`) applications. It owns the UDP transport, peer +discovery/rendezvous protocol, bootstrap client/server behavior, and the +persistent Ed25519 identity used by the Cesspool network. + +Dat and Cable are applications built on this base. Archive storage remains +SLEEP; room/feed behavior remains in Cable. libcess does not depend on either +application or their storage layers. + +## Build + +Requires a C99 compiler, GNU make, and libsodium development files. + +```sh +make +make install +``` + +Build artifacts stay under `libcess/build`. Set `PREFIX` or `DESTDIR` for a +staged install. The standalone package reads `libcess/NETWORK_PROTOCOL`; +the full dat-gpt build uses the project-root `NETWORK_PROTOCOL` file so Dat +and Cable share the same Cesspool network identity. + +The bootstrap service is `cesspool-bootstrap`. It only provides discovery; +it does not relay chat or archive payloads. Supply `--advertise HOST:PORT` +for the address clients should use, and make that UDP port reachable through +the host firewall and edge router. No external address lookup is performed. + +See [the public API](include/cesspool.h), [network API](include/cess_net.h), +and [bootstrap man page](docs/cesspool-bootstrap.1). diff --git a/libcess/cess_net.h b/libcess/cess_net.h new file mode 100644 index 0000000..43901fb --- /dev/null +++ b/libcess/cess_net.h @@ -0,0 +1 @@ +#include "include/cess_net.h" diff --git a/libcess/cesspool.h b/libcess/cesspool.h new file mode 100644 index 0000000..8108d6e --- /dev/null +++ b/libcess/cesspool.h @@ -0,0 +1 @@ +#include "include/cesspool.h" diff --git a/libcess/docs/cesspool-bootstrap.1 b/libcess/docs/cesspool-bootstrap.1 new file mode 100644 index 0000000..888e1eb --- /dev/null +++ b/libcess/docs/cesspool-bootstrap.1 @@ -0,0 +1,28 @@ +.TH CESSPOOL-BOOTSTRAP 1 +.SH NAME +cesspool-bootstrap \- run a Cesspool discovery node +.SH SYNOPSIS +.B cesspool-bootstrap +[ B--bind R IPv4] [ B--port R UDP-PORT] [ B--advertise R HOST:PORT] [ B--state-dir R DIR] +.SH DESCRIPTION +Runs the Cesspool UDP bootstrap and rendezvous service in the foreground. +It helps clients discover peers and does not relay chat or archive payloads. +The persistent Ed25519 node identity is stored in the state directory. +.PP +The listening UDP port must be reachable from clients. Configure firewall and +router forwarding as needed. No public-IP lookup is performed; use +.B --advertise +to print the endpoint that clients should configure. +.SH OPTIONS +.TP +.B --bind IPv4 +Local IPv4 address to bind; defaults to 0.0.0.0. +.TP +.B --port UDP-PORT +UDP port to listen on; defaults to 3282. +.TP +.B --advertise HOST:PORT +Document the externally reachable endpoint for client configuration. +.TP +.B --state-dir DIR +Persistent identity directory. diff --git a/libcess/include/cess_compat.h b/libcess/include/cess_compat.h new file mode 100644 index 0000000..bbb8f87 --- /dev/null +++ b/libcess/include/cess_compat.h @@ -0,0 +1,19 @@ +#ifndef LIBCESS_COMPAT_H +#define LIBCESS_COMPAT_H + +/* Small operating-system spelling adjustments needed by the portable + * Cesspool sources. This header has no dependency on Dat or SLEEP. */ +#ifdef _WIN32 +#include +#include +#include +#include +#ifndef mkdir +#define mkdir(path, mode) _mkdir(path) +#endif +#ifndef EKEYREJECTED +#define EKEYREJECTED EACCES +#endif +#endif + +#endif diff --git a/libcess/include/cess_net.h b/libcess/include/cess_net.h new file mode 100644 index 0000000..7b90659 --- /dev/null +++ b/libcess/include/cess_net.h @@ -0,0 +1,50 @@ +#ifndef LIBCESS_NET_H +#define LIBCESS_NET_H + +#include +#include + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include +typedef SOCKET cess_socket_t; +#define CESS_INVALID_SOCKET INVALID_SOCKET +#else +#include +typedef int cess_socket_t; +#define CESS_INVALID_SOCKET (-1) +#endif + +#define CESSNET_PORT 3282 +#define CESSNET_MAX_PACKET 1400 +#define CESSNET_TOPIC_BYTES 32 +#define CESSNET_PEERID_BYTES 32 + +typedef struct cess_peer { + cess_socket_t fd; + struct sockaddr_storage addr; + socklen_t addrlen; + uint8_t id[CESSNET_PEERID_BYTES]; +} cess_peer_t; + +typedef struct cessnet { + cess_socket_t sock; + uint8_t topic[CESSNET_TOPIC_BYTES]; + uint8_t peerid[CESSNET_PEERID_BYTES]; + void *context; + void (*on_message)(struct cessnet *net, cess_peer_t *peer, + const uint8_t *msg, size_t len); +} cessnet_t; + +int cessnet_init(cessnet_t *net, const uint8_t topic[CESSNET_TOPIC_BYTES], + const uint8_t peerid[CESSNET_PEERID_BYTES]); +int cessnet_bind(cessnet_t *net, const char *host, uint16_t port); +int cessnet_poll(cessnet_t *net, int timeout_ms); +int cessnet_send(cessnet_t *net, const cess_peer_t *peer, + const uint8_t *message, size_t length); +int cessnet_close(cessnet_t *net); + +#endif diff --git a/libcess/include/cesspool.h b/libcess/include/cesspool.h new file mode 100644 index 0000000..c9af135 --- /dev/null +++ b/libcess/include/cesspool.h @@ -0,0 +1,129 @@ +#ifndef LIBCESS_CESSPOOL_H +#define LIBCESS_CESSPOOL_H + +#include +#include +#include "cess_net.h" + +#define CESSPOOL_TOPIC_BYTES 32u +#define CESSPOOL_PEER_BYTES 32u +#define CESSPOOL_MAX_BOOTSTRAP 16u +#define CESSPOOL_MAX_PEERS 256u +#define CESSPOOL_MAX_TOPICS 64u +#define CESSPOOL_MAX_ANNOUNCEMENTS 1024u +#define CESSPOOL_MAX_TRANSACTIONS 64u + +typedef struct cesspool_keypair { + uint8_t pubkey[32]; + uint8_t seckey[64]; +} cesspool_keypair_t; + +enum cesspool_message_type { + CESSPOOL_LOOKUP = 0, CESSPOOL_ANNOUNCE = 1, CESSPOOL_UNANNOUNCE = 2, + CESSPOOL_REPLY = 3, CESSPOOL_PING = 4, CESSPOOL_PONG = 5, + CESSPOOL_HOLEPUNCH = 6 +}; + +typedef struct cesspool_message { + uint8_t type, topic[CESSPOOL_TOPIC_BYTES], peer_id[CESSPOOL_PEER_BYTES]; + uint16_t port; + uint8_t payload[2048]; + size_t payload_len; +} cesspool_message_t; +typedef struct cesspool_request { + uint32_t type; + uint16_t port; + uint8_t local_address[6]; + size_t local_address_len; +} cesspool_request_t; +typedef struct cesspool_response { + uint8_t peers[1024]; size_t peers_len; + uint8_t local_peers[1024]; size_t local_peers_len; + uint8_t peer_info[2048]; size_t peer_info_len; +} cesspool_response_t; +typedef struct cesspool_peer { + cess_peer_t address; + uint8_t public_key[32]; + uint8_t discovery_topic[CESSPOOL_TOPIC_BYTES]; + uint64_t last_seen; + unsigned failures; + int local, discovered; +} cesspool_peer_t; +typedef struct cesspool_topic { + uint8_t key[32]; uint16_t port; int announcing, looking_up; uint64_t refresh_at; +} cesspool_topic_t; +typedef struct cesspool_transaction { + uint16_t id; uint8_t target[32], type; uint64_t expires_at; int active; +} cesspool_transaction_t; +typedef struct cesspool_announcement { + uint8_t topic[CESSPOOL_TOPIC_BYTES], peer_id[CESSPOOL_PEER_BYTES]; + cess_peer_t address; + uint64_t last_seen; +} cesspool_announcement_t; +struct cesspool; +typedef void (*cesspool_transport_packet_fn)(struct cesspool *, + const cess_peer_t *, + const uint8_t *, size_t); +typedef struct cesspool { + cessnet_t net; + cess_peer_t bootstrap_nodes[CESSPOOL_MAX_BOOTSTRAP]; + size_t bootstrap_count; + uint16_t port; + int ready, bootstrap_server; + cesspool_peer_t peers[CESSPOOL_MAX_PEERS]; size_t peer_count; + cesspool_topic_t topics[CESSPOOL_MAX_TOPICS]; size_t topic_count; + cesspool_announcement_t announcements[CESSPOOL_MAX_ANNOUNCEMENTS]; + size_t announcement_count; + uint64_t bootstrap_last_seen, bootstrap_probe_at; + int bootstrap_reachable; + cesspool_transaction_t transactions[CESSPOOL_MAX_TRANSACTIONS]; + uint16_t next_transaction; + void (*on_peer)(struct cesspool *, const cesspool_peer_t *); + void (*on_holepunch)(struct cesspool *, const cess_peer_t *); + cesspool_transport_packet_fn on_transport_packet; + void *transport_context; +} cesspool_t; + +int cesspool_identity_init(void); +int cesspool_identity_generate(cesspool_keypair_t *identity); +int cesspool_identity_save(const char *path, const cesspool_keypair_t *identity); +int cesspool_identity_load(const char *path, cesspool_keypair_t *identity); +int cesspool_discovery_key(const uint8_t public_key[32], uint8_t topic[32]); +int cesspool_encode(const cesspool_message_t *, uint8_t *, size_t, size_t *); +int cesspool_decode(cesspool_message_t *, const uint8_t *, size_t); +int cesspool_request_encode(const cesspool_request_t *, uint8_t *, size_t, size_t *); +int cesspool_request_decode(cesspool_request_t *, const uint8_t *, size_t); +int cesspool_response_encode(const cesspool_response_t *, uint8_t *, size_t, size_t *); +int cesspool_response_decode(cesspool_response_t *, const uint8_t *, size_t); +int cesspool_process_response(cesspool_t *, const cesspool_response_t *, const uint8_t[2]); +int cesspool_init(cesspool_t *, const uint8_t peer_id[32]); +int cesspool_add_bootstrap(cesspool_t *, const char *, uint16_t); +int cesspool_configure_bootstraps(cesspool_t *); +int cesspool_bootstrap_config_add(const char *); +int cesspool_bootstrap_config_set(const char *); +int cesspool_bootstrap_config_get(char *, size_t); +int cesspool_bootstrap_config_list(char *, size_t); +int cesspool_bootstrap(cesspool_t *); +int cesspool_listen(cesspool_t *, const char *, uint16_t); +int cesspool_set_bootstrap_server(cesspool_t *, int); +int cesspool_announce(cesspool_t *, const uint8_t[32], uint16_t); +int cesspool_lookup(cesspool_t *, const uint8_t[32]); +int cesspool_unannounce(cesspool_t *, const uint8_t[32], uint16_t); +int cesspool_ping(cesspool_t *, const cess_peer_t *); +int cesspool_holepunch(cesspool_t *, const cess_peer_t *); +int cesspool_poll(cesspool_t *, int); +int cesspool_add_peer(cesspool_t *, const cesspool_peer_t *); +int cesspool_remove_peer(cesspool_t *, const uint8_t[32]); +cesspool_peer_t *cesspool_find_peer(cesspool_t *, const uint8_t[32]); +int cesspool_join(cesspool_t *, const uint8_t[32], uint16_t, int, int); +int cesspool_leave(cesspool_t *, const uint8_t[32]); +int cesspool_tick(cesspool_t *, uint64_t); +size_t cesspool_closest(cesspool_t *, const uint8_t[32], cesspool_peer_t **, size_t); +size_t cesspool_topic_peers(cesspool_t *, const uint8_t[CESSPOOL_TOPIC_BYTES], cesspool_peer_t **, size_t); +size_t cesspool_announced_peer_count(const cesspool_t *); +size_t cesspool_announced_topic_count(const cesspool_t *); +int cesspool_transaction_begin(cesspool_t *, uint8_t, const uint8_t[32], uint64_t, uint16_t *); +int cesspool_transaction_finish(cesspool_t *, uint16_t); +int cesspool_destroy(cesspool_t *); + +#endif diff --git a/libcess/src/cess_identity.c b/libcess/src/cess_identity.c new file mode 100644 index 0000000..62bdea1 --- /dev/null +++ b/libcess/src/cess_identity.c @@ -0,0 +1,75 @@ +#define _POSIX_C_SOURCE 200809L +#include "cesspool.h" +#include "cess_compat.h" +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +int cesspool_identity_init(void) +{ + return sodium_init() < 0 ? -1 : 0; +} + +int cesspool_identity_generate(cesspool_keypair_t *identity) +{ + if (!identity) { errno = EINVAL; return -1; } + if (crypto_sign_keypair(identity->pubkey, identity->seckey) != 0) { + errno = EIO; + return -1; + } + return 0; +} + +int cesspool_identity_save(const char *path, const cesspool_keypair_t *identity) +{ + FILE *file; +#ifndef _WIN32 + int fd; +#endif + size_t written; + if (!path || !identity) { errno = EINVAL; return -1; } + file = fopen(path, "wb"); + if (!file) return -1; +#ifdef _WIN32 + if (_chmod(path, _S_IREAD | _S_IWRITE) != 0) { fclose(file); return -1; } +#else + fd = fileno(file); + if (fchmod(fd, S_IRUSR | S_IWUSR) != 0) { fclose(file); return -1; } +#endif + written = fwrite(identity, 1, sizeof(*identity), file); + if (fclose(file) != 0 || written != sizeof(*identity)) { + errno = EIO; + return -1; + } + return 0; +} + +int cesspool_identity_load(const char *path, cesspool_keypair_t *identity) +{ + FILE *file; + size_t count; + int extra; + if (!path || !identity) { errno = EINVAL; return -1; } + file = fopen(path, "rb"); + if (!file) return -1; + count = fread(identity, 1, sizeof(*identity), file); + extra = fgetc(file); + if (fclose(file) != 0 || count != sizeof(*identity) || extra != EOF) { + sodium_memzero(identity, sizeof(*identity)); + errno = EINVAL; + return -1; + } +#ifdef _WIN32 + if (_chmod(path, _S_IREAD | _S_IWRITE) != 0) return -1; +#else + if (chmod(path, S_IRUSR | S_IWUSR) != 0) return -1; +#endif + return 0; +} diff --git a/libcess/src/cess_net.c b/libcess/src/cess_net.c new file mode 100644 index 0000000..27f7915 --- /dev/null +++ b/libcess/src/cess_net.c @@ -0,0 +1,167 @@ +#define _POSIX_C_SOURCE 200112L +#include "cess_compat.h" +#include "cess_net.h" +#include +#include +#ifdef _WIN32 +#include +#ifndef SIO_UDP_CONNRESET +#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR, 12) +#endif +static int cessnet_winsock_ready; +#else +#include +#include +#include +#include +#include +#endif + +#ifdef _WIN32 +static int cessnet_winsock_errno(int error) +{ + switch (error) { + case WSAEINTR: return EINTR; + case WSAEACCES: return EACCES; + case WSAEADDRINUSE: return EADDRINUSE; + case WSAEADDRNOTAVAIL: return EADDRNOTAVAIL; + case WSAENETDOWN: return ENETDOWN; + case WSAENETUNREACH: return ENETUNREACH; + case WSAEHOSTUNREACH: return EHOSTUNREACH; + case WSAECONNRESET: return ECONNRESET; + case WSAECONNREFUSED: return ECONNREFUSED; + case WSAETIMEDOUT: return ETIMEDOUT; + case WSAEMSGSIZE: return EMSGSIZE; + case WSAENOBUFS: return ENOBUFS; + case WSAEWOULDBLOCK: return EAGAIN; + default: return EIO; + } +} +#endif + +int cessnet_init(cessnet_t *net, const uint8_t topic[32], const uint8_t peerid[32]) { + if (!net || !topic || !peerid) { errno = EINVAL; return -1; } +#ifdef _WIN32 + if (!cessnet_winsock_ready) { WSADATA data; if (WSAStartup(MAKEWORD(2,2), &data)) { errno = ENETDOWN; return -1; } cessnet_winsock_ready = 1; } +#endif + memset(net, 0, sizeof(*net)); net->sock = CESS_INVALID_SOCKET; memcpy(net->topic, topic, 32); memcpy(net->peerid, peerid, 32); return 0; +} +int cessnet_bind(cessnet_t *net, const char *host, uint16_t port) { + struct sockaddr_in address; int broadcast = 1; + if (!net) { errno = EINVAL; return -1; } + memset(&address, 0, sizeof(address)); address.sin_family = AF_INET; address.sin_port = htons(port ? port : CESSNET_PORT); + if (!host || !*host || !strcmp(host, "*")) address.sin_addr.s_addr = htonl(INADDR_ANY); + else { + struct addrinfo hints, *resolved = NULL; + memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_DGRAM; + if (getaddrinfo(host, NULL, &hints, &resolved) != 0 || !resolved) { + errno = EADDRNOTAVAIL; return -1; + } + address.sin_addr = ((struct sockaddr_in *)resolved->ai_addr)->sin_addr; + freeaddrinfo(resolved); + } + net->sock = socket(AF_INET, SOCK_DGRAM, 0); + if (net->sock == CESS_INVALID_SOCKET) { +#ifdef _WIN32 + errno = ENETDOWN; +#endif + return -1; + } +#ifdef _WIN32 + if (setsockopt(net->sock, SOL_SOCKET, SO_BROADCAST, (const char *)&broadcast, sizeof(broadcast))) { + int error = WSAGetLastError(); + closesocket(net->sock); net->sock = CESS_INVALID_SOCKET; + errno = cessnet_winsock_errno(error); + return -1; + } + /* A peer that is not listening can send an ICMP port-unreachable response. + * Winsock reports that asynchronously as WSAECONNRESET on a UDP socket; + * that is a peer/network condition, not a dead local socket. */ + { + BOOL disable_udp_connreset = FALSE; + DWORD returned = 0; + (void)WSAIoctl(net->sock, SIO_UDP_CONNRESET, &disable_udp_connreset, + sizeof(disable_udp_connreset), NULL, 0, &returned, NULL, NULL); + } + if (bind(net->sock, (struct sockaddr *)&address, sizeof(address))) { + int error = WSAGetLastError(); + closesocket(net->sock); net->sock = CESS_INVALID_SOCKET; + errno = error == WSAEADDRINUSE ? EADDRINUSE : + error == WSAEACCES ? EACCES : EIO; + return -1; + } +#else + if (setsockopt(net->sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast))) { + int saved = errno; + close(net->sock); net->sock = CESS_INVALID_SOCKET; errno = saved; + return -1; + } + if (bind(net->sock, (struct sockaddr *)&address, sizeof(address))) { close(net->sock); net->sock = CESS_INVALID_SOCKET; return -1; } +#endif + return 0; +} +int cessnet_poll(cessnet_t *net, int timeout_ms) { + uint8_t packet[CESSNET_MAX_PACKET], peer_id[CESSNET_PEERID_BYTES]; struct sockaddr_storage source; socklen_t source_len = sizeof(source); ssize_t n; cess_peer_t peer; + if (!net || net->sock == CESS_INVALID_SOCKET) { errno = EINVAL; return -1; } + memcpy(peer_id, net->peerid, sizeof(peer_id)); +#ifdef _WIN32 + { WSAPOLLFD pfd; int ready; pfd.fd = net->sock; pfd.events = POLLRDNORM; pfd.revents = 0; + ready = WSAPoll(&pfd, 1, timeout_ms); + if (ready == SOCKET_ERROR) { errno = cessnet_winsock_errno(WSAGetLastError()); return -1; } + if (ready == 0) return 0; + if (pfd.revents & (POLLHUP | POLLNVAL)) { errno = EIO; return -1; } + if (pfd.revents & POLLERR) { + int socket_error = 0, error_length = sizeof(socket_error); + if (getsockopt(net->sock, SOL_SOCKET, SO_ERROR, (char *)&socket_error, + &error_length) == SOCKET_ERROR) { + errno = cessnet_winsock_errno(WSAGetLastError()); + return -1; + } + if (socket_error == WSAECONNRESET || socket_error == WSAECONNREFUSED || + socket_error == WSAEHOSTUNREACH || socket_error == WSAENETUNREACH) + return 0; + if (socket_error) { errno = cessnet_winsock_errno(socket_error); return -1; } + } + if (!(pfd.revents & POLLRDNORM)) return 0; } +#else + { struct pollfd pfd; int ready; pfd.fd = net->sock; pfd.events = POLLIN; pfd.revents = 0; + ready = poll(&pfd, 1, timeout_ms); + if (ready < 0) return -1; + if (ready == 0) return 0; + if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) { errno = EIO; return -1; } + if (!(pfd.revents & POLLIN)) return 0; } +#endif +#ifdef _WIN32 + { int source_length = (int)source_len; n = recvfrom(net->sock, (char *)packet, sizeof(packet), 0, (struct sockaddr *)&source, &source_length); source_len = (socklen_t)source_length; + if (n < 0) { errno = cessnet_winsock_errno(WSAGetLastError()); return -1; } } +#else + n = recvfrom(net->sock, packet, sizeof(packet), 0, (struct sockaddr *)&source, &source_len); if (n < 0) return -1; +#endif + if (n < 0) return -1; + memset(&peer, 0, sizeof(peer)); peer.fd = net->sock; peer.addr = source; peer.addrlen = source_len; + /* Cesspool decodes discovery records here; other datagrams are passed to + * its authenticated peer-transport hook. */ + if (net->on_message) net->on_message(net, &peer, packet, (size_t)n); + /* The identity bound to this UDP socket is immutable. Keep it intact even + * if a protocol callback uses an aliased view while handling the packet. */ + memcpy(net->peerid, peer_id, sizeof(peer_id)); + return 1; +} +int cessnet_send(cessnet_t *net, const cess_peer_t *peer, const uint8_t *message, size_t length) { + ssize_t n; if (!net || !peer || net->sock == CESS_INVALID_SOCKET || (!message && length) || length > CESSNET_MAX_PACKET) { errno = EINVAL; return -1; } +#ifdef _WIN32 + n = sendto(net->sock, (const char *)message, (int)length, 0, (const struct sockaddr *)&peer->addr, (int)peer->addrlen); + if (n == SOCKET_ERROR) { errno = cessnet_winsock_errno(WSAGetLastError()); return -1; } +#else + n = sendto(net->sock, message, length, 0, (const struct sockaddr *)&peer->addr, peer->addrlen); + if (n < 0) return -1; +#endif + return n == (ssize_t)length ? 0 : -1; +} +int cessnet_close(cessnet_t *net) { int rc = 0; if (!net) { errno = EINVAL; return -1; } if (net->sock != CESS_INVALID_SOCKET) { +#ifdef _WIN32 + rc = closesocket(net->sock); +#else + rc = close(net->sock); +#endif + } net->sock = CESS_INVALID_SOCKET; return rc; } diff --git a/libcess/src/cesspool.c b/libcess/src/cesspool.c new file mode 100644 index 0000000..30e268c --- /dev/null +++ b/libcess/src/cesspool.c @@ -0,0 +1,793 @@ +#define _POSIX_C_SOURCE 200112L +#include "cess_compat.h" +#include "cesspool.h" +#include +#include +#ifdef _WIN32 +#include +#include +#include +#else +#include +#include +#include +#endif +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif +#ifdef CESSPOOL_HAVE_SODIUM +#include +#endif + +#define CESSPOOL_PACKET_BYTES 72u +#ifndef CESSPOOL_CONFIG_DIRECTORY +#define CESSPOOL_CONFIG_DIRECTORY "libcess" +#endif +#define CESSPOOL_MAX_RENDEZVOUS_PEERS 34u +#define CESSPOOL_PEER_TTL_MS 600000u +#define CESSPOOL_BOOTSTRAP_PROBE_MS 5000u +#define CESSPOOL_BOOTSTRAP_TIMEOUT_MS 15000u + +/* Cesspool owns its compact protobuf-style varint framing; it has no SLEEP + * dependency even though Dat also uses the same wire primitive. */ +static int cess_varint_encode(uint64_t value, uint8_t *out, size_t capacity, + size_t *written) +{ + size_t n = 0; + if (!out) { errno = EINVAL; return -1; } + do { + if (n >= capacity) { errno = ENOSPC; return -1; } + out[n] = (uint8_t)(value & 0x7fu); + value >>= 7; + if (value) out[n] |= 0x80u; + n++; + } while (value); + if (written) *written = n; + return 0; +} + +static int cess_varint_decode(const uint8_t *in, size_t length, + uint64_t *value, size_t *used) +{ + uint64_t result = 0; + size_t i; + if (!in || !value) { errno = EINVAL; return -1; } + for (i = 0; i < length && i < 10; i++) { + uint8_t byte = in[i]; + if (i == 9 && byte > 1) { errno = EOVERFLOW; return -1; } + result |= (uint64_t)(byte & 0x7fu) << (i * 7); + if (!(byte & 0x80u)) { + *value = result; + if (used) *used = i + 1; + return 0; + } + } + errno = length < 10 ? EAGAIN : EOVERFLOW; + return -1; +} + +static uint64_t monotonic_ms(void) +{ + struct timespec now; + if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) return 0; + return (uint64_t)now.tv_sec * 1000u + (uint64_t)now.tv_nsec / 1000000u; +} +static int pb_var(uint8_t tag,uint64_t value,uint8_t*out,size_t cap,size_t*off){size_t n;if(*off>=cap){errno=ENOSPC;return-1;}out[(*off)++]=tag;if(cess_varint_encode(value,out+*off,cap-*off,&n))return-1;*off+=n;return 0;} +static int pb_bytes(uint8_t tag,const uint8_t*data,size_t len,uint8_t*out,size_t cap,size_t*off){size_t n;if(*off>=cap){errno=ENOSPC;return-1;}out[(*off)++]=tag;if(cess_varint_encode(len,out+*off,cap-*off,&n))return-1;*off+=n;if(len>cap-*off){errno=ENOSPC;return-1;}memcpy(out+*off,data,len);*off+=len;return 0;} + +int cesspool_request_encode(const cesspool_request_t*r,uint8_t*out,size_t cap,size_t*written){size_t off=0;if(!r||!out){errno=EINVAL;return-1;}if(pb_var(0x08,r->type,out,cap,&off))return-1;if(r->port&&pb_var(0x10,r->port,out,cap,&off))return-1;if(r->local_address_len&&pb_bytes(0x1a,r->local_address,r->local_address_len,out,cap,&off))return-1;if(written)*written=off;return 0;} +int cesspool_request_decode(cesspool_request_t*r,const uint8_t*in,size_t len){size_t off=0,n;uint64_t v;if(!r||!in){errno=EINVAL;return-1;}memset(r,0,sizeof(*r));while(offtype=(uint32_t)v;else r->port=(uint16_t)v;}else if(tag==0x1a){if(cess_varint_decode(in+off,len-off,&v,&n))return-1;off+=n;if(v>sizeof(r->local_address)||v>len-off){errno=EPROTO;return-1;}memcpy(r->local_address,in+off,(size_t)v);r->local_address_len=(size_t)v;off+=(size_t)v;}else{errno=EPROTO;return-1;}}return 0;} +int cesspool_response_encode(const cesspool_response_t *r, uint8_t *out, + size_t cap, size_t *written) +{ + size_t off = 0; + if (!r || !out) { errno = EINVAL; return -1; } + if (r->peers_len && pb_bytes(0x0a, r->peers, r->peers_len, out, cap, &off)) return -1; + if (r->local_peers_len && pb_bytes(0x12, r->local_peers, r->local_peers_len, out, cap, &off)) return -1; + if (r->peer_info_len && pb_bytes(0x1a, r->peer_info, r->peer_info_len, out, cap, &off)) return -1; + if (written) *written = off; + return 0; +} +int cesspool_response_decode(cesspool_response_t *r, const uint8_t *in, size_t len) +{ + size_t off = 0, n; + uint64_t v; + if (!r || !in) { errno = EINVAL; return -1; } + memset(r, 0, sizeof(*r)); + while (off < len) { + uint8_t tag = in[off++], *dst; + size_t *dstlen, capacity; + if (cess_varint_decode(in + off, len - off, &v, &n)) return -1; + off += n; + if (v > len - off) { errno = EPROTO; return -1; } + if (tag == 0x0a) { dst = r->peers; dstlen = &r->peers_len; capacity = sizeof(r->peers); } + else if (tag == 0x12) { dst = r->local_peers; dstlen = &r->local_peers_len; capacity = sizeof(r->local_peers); } + else if (tag == 0x1a) { dst = r->peer_info; dstlen = &r->peer_info_len; capacity = sizeof(r->peer_info); } + else { off += (size_t)v; continue; } + if (v > capacity) { errno = EOVERFLOW; return -1; } + memcpy(dst, in + off, (size_t)v); + *dstlen = (size_t)v; + off += (size_t)v; + } + return 0; +} +int cesspool_process_response(cesspool_t*p,const cesspool_response_t*r,const uint8_t prefix[2]){size_t off;cesspool_peer_t peer;struct sockaddr_in*address;if(!p||!r){errno=EINVAL;return-1;}if(r->peers_len%6||r->local_peers_len%4){errno=EPROTO;return-1;}for(off=0;offpeers_len;off+=6){memset(&peer,0,sizeof(peer));address=(struct sockaddr_in*)&peer.address.addr;address->sin_family=AF_INET;memcpy(&address->sin_addr,r->peers+off,4);memcpy(&address->sin_port,r->peers+off+4,2);peer.address.addrlen=sizeof(*address);memcpy(peer.public_key,r->peers+off,6);cesspool_add_peer(p,&peer);}if(prefix)for(off=0;offlocal_peers_len;off+=4){memset(&peer,0,sizeof(peer));address=(struct sockaddr_in*)&peer.address.addr;address->sin_family=AF_INET;((uint8_t*)&address->sin_addr)[0]=prefix[0];((uint8_t*)&address->sin_addr)[1]=prefix[1];((uint8_t*)&address->sin_addr)[2]=r->local_peers[off];((uint8_t*)&address->sin_addr)[3]=r->local_peers[off+1];memcpy(&address->sin_port,r->local_peers+off+2,2);peer.address.addrlen=sizeof(*address);peer.local=1;memcpy(peer.public_key,&address->sin_addr,4);memcpy(peer.public_key+4,&address->sin_port,2);cesspool_add_peer(p,&peer);}return 0;} + +int cesspool_discovery_key(const uint8_t key[32], uint8_t topic[32]) +{ + if (!key || !topic) { errno = EINVAL; return -1; } +#ifdef CESSPOOL_HAVE_SODIUM + /* The root NETWORK_PROTOCOL token selects the discovery namespace. + * With "hypercore", this matches legacy Dat's discoveryKey derivation. */ + return crypto_generichash(topic, 32, + (const unsigned char *)CESSPOOL_NETWORK_PROTOCOL, + strlen(CESSPOOL_NETWORK_PROTOCOL), key, 32); +#else + memcpy(topic, key, 32); + return 0; +#endif +} + +int cesspool_encode(const cesspool_message_t *m, uint8_t *out, + size_t capacity, size_t *written) +{ + size_t total = CESSPOOL_PACKET_BYTES; + if (!m || !out) { errno = EINVAL; return -1; } + if (m->type > CESSPOOL_HOLEPUNCH) { errno = EINVAL; return -1; } + if (m->type == CESSPOOL_REPLY) { + if (capacity < CESSPOOL_PACKET_BYTES + 2 || + m->payload_len > UINT16_MAX || + m->payload_len > capacity - CESSPOOL_PACKET_BYTES - 2) { + errno = ENOSPC; return -1; + } + total += 2 + m->payload_len; + } + if (capacity < total) { errno = ENOSPC; return -1; } + memcpy(out, "CSP0", 4); + out[4] = m->type; + out[5] = m->type == CESSPOOL_REPLY ? 1 : 0; + out[6] = (uint8_t)(m->port >> 8); + out[7] = (uint8_t)m->port; + memcpy(out + 8, m->topic, 32); + memcpy(out + 40, m->peer_id, 32); + if (m->type == CESSPOOL_REPLY) { + out[72] = (uint8_t)(m->payload_len >> 8); + out[73] = (uint8_t)m->payload_len; + memcpy(out + 74, m->payload, m->payload_len); + } + if (written) *written = total; + return 0; +} + +int cesspool_decode(cesspool_message_t *m, const uint8_t *in, size_t len) +{ + if (!m || !in) { errno = EINVAL; return -1; } + if (len < CESSPOOL_PACKET_BYTES || memcmp(in, "CSP0", 4) || + in[4] > CESSPOOL_HOLEPUNCH) { + errno = EPROTO; + return -1; + } + memset(m, 0, sizeof(*m)); + m->type = in[4]; + m->port = ((uint16_t)in[6] << 8) | in[7]; + memcpy(m->topic, in + 8, 32); + memcpy(m->peer_id, in + 40, 32); + if (m->type == CESSPOOL_REPLY) { + if (in[5] != 1 || len < CESSPOOL_PACKET_BYTES + 2) { errno = EPROTO; return -1; } + m->payload_len = ((size_t)in[72] << 8) | in[73]; + if (m->payload_len > sizeof(m->payload) || len != CESSPOOL_PACKET_BYTES + 2 + m->payload_len) { errno = EPROTO; return -1; } + memcpy(m->payload, in + 74, m->payload_len); + } else if (in[5] != 0 || len != CESSPOOL_PACKET_BYTES) { errno = EPROTO; return -1; } + return 0; +} + +static int send_message(cesspool_t *pool,const cess_peer_t *peer,uint8_t type,const uint8_t topic[32],uint16_t port) +{ + cesspool_message_t message;uint8_t packet[CESSNET_MAX_PACKET];size_t len; + memset(&message,0,sizeof(message));message.type=type;message.port=port;if(topic)memcpy(message.topic,topic,32);memcpy(message.peer_id,pool->net.peerid,32); + if(cesspool_encode(&message,packet,sizeof(packet),&len))return-1; + return cessnet_send(&pool->net,peer,packet,len); +} +static int send_reply(cesspool_t *pool, const cess_peer_t *peer, + const uint8_t topic[32], const cesspool_response_t *response) +{ + cesspool_message_t message; + uint8_t packet[CESSNET_MAX_PACKET]; + size_t payload_len, packet_len; + memset(&message, 0, sizeof(message)); + message.type = CESSPOOL_REPLY; + memcpy(message.topic, topic, CESSPOOL_TOPIC_BYTES); + memcpy(message.peer_id, pool->net.peerid, CESSPOOL_PEER_BYTES); + if (cesspool_response_encode(response, message.payload, + sizeof(message.payload), &payload_len)) return -1; + message.payload_len = payload_len; + if (cesspool_encode(&message, packet, sizeof(packet), &packet_len)) return -1; + return cessnet_send(&pool->net, peer, packet, packet_len); +} +static int send_bootstrap(cesspool_t *pool,uint8_t type,const uint8_t topic[32],uint16_t port) +{ + static const uint16_t lan_ports[] = {CESSNET_PORT,3000,3002,3004,2001,2003,2005}; + size_t i;int sent=0,lan_sent=0; + if (!pool) { errno = EINVAL; return -1; } + for(i=0;ibootstrap_count;i++) + if(send_message(pool,&pool->bootstrap_nodes[i],type,topic,port)==0)sent++; + /* Always keep zero-configuration LAN discovery active, even when a + * rendezvous node is configured. Routers do not forward these broadcasts; + * this complements Internet discovery and lets same-LAN peers connect + * without requiring them to share bootstrap configuration. */ + for (i = 0; i < sizeof(lan_ports) / sizeof(lan_ports[0]); i++) { + cess_peer_t target; + struct sockaddr_in *address; + memset(&target, 0, sizeof(target)); + address = (struct sockaddr_in *)&target.addr; + address->sin_family = AF_INET; + address->sin_addr.s_addr = htonl(INADDR_BROADCAST); + address->sin_port = htons(lan_ports[i]); + target.addrlen = sizeof(*address); + if (send_message(pool, &target, type, topic, port) == 0) lan_sent++; + } + if (pool->bootstrap_count) return (sent || lan_sent) ? 0 : -1; + return 0; /* LAN broadcasts are best-effort, never a join failure. */ +} +static int has_topic(const cesspool_t *pool,const uint8_t topic[32],int announcing) +{ + size_t i; + for(i=0;itopic_count;i++)if(!memcmp(pool->topics[i].key,topic,32)&&(!announcing||pool->topics[i].announcing))return 1; + return 0; +} +static int same_peer_address(const cess_peer_t *a, const cess_peer_t *b) +{ + const struct sockaddr_in *aa, *bb; + if (!a || !b || a->addrlen < (socklen_t)sizeof(struct sockaddr_in) || + b->addrlen < (socklen_t)sizeof(struct sockaddr_in)) return 0; + aa = (const struct sockaddr_in *)&a->addr; + bb = (const struct sockaddr_in *)&b->addr; + return aa->sin_family == AF_INET && bb->sin_family == AF_INET && + aa->sin_addr.s_addr == bb->sin_addr.s_addr && aa->sin_port == bb->sin_port; +} + +static int announcement_add(cesspool_t *pool, const uint8_t topic[32], + const uint8_t peer_id[32], const cess_peer_t *address, + uint64_t now) +{ + size_t i; + for (i = 0; i < pool->announcement_count; i++) { + cesspool_announcement_t *entry = &pool->announcements[i]; + if (!memcmp(entry->topic, topic, 32) && !memcmp(entry->peer_id, peer_id, 32)) { + entry->address = *address; + entry->last_seen = now; + return 0; + } + } + if (pool->announcement_count >= CESSPOOL_MAX_ANNOUNCEMENTS) { + errno = ENOSPC; + return -1; + } + { + cesspool_announcement_t *entry = &pool->announcements[pool->announcement_count++]; + memcpy(entry->topic, topic, 32); + memcpy(entry->peer_id, peer_id, 32); + entry->address = *address; + entry->last_seen = now; + } + return 0; +} + +static void announcement_remove(cesspool_t *pool, const uint8_t topic[32], + const uint8_t peer_id[32]) +{ + size_t i = 0; + while (i < pool->announcement_count) { + cesspool_announcement_t *entry = &pool->announcements[i]; + if (!memcmp(entry->topic, topic, 32) && !memcmp(entry->peer_id, peer_id, 32)) { + pool->announcements[i] = pool->announcements[--pool->announcement_count]; + continue; + } + i++; + } +} + +static int peer_has_announcement(const cesspool_t *pool, const uint8_t peer_id[32]) +{ + size_t i; + for (i = 0; i < pool->announcement_count; i++) + if (!memcmp(pool->announcements[i].peer_id, peer_id, 32)) return 1; + return 0; +} + +static int send_rendezvous_reply(cesspool_t *pool, cess_peer_t *requester, + const uint8_t topic[32]) +{ + cesspool_response_t response; + size_t i; + memset(&response, 0, sizeof(response)); + for (i = 0; i < pool->announcement_count; i++) { + const cesspool_announcement_t *peer = &pool->announcements[i]; + const struct sockaddr_in *address; + uint8_t *record; + if (memcmp(peer->topic, topic, 32) || same_peer_address(&peer->address, requester) || + peer->address.addrlen < (socklen_t)sizeof(struct sockaddr_in)) continue; + address = (const struct sockaddr_in *)&peer->address.addr; + if (address->sin_family != AF_INET || + response.peer_info_len / 38 >= CESSPOOL_MAX_RENDEZVOUS_PEERS || + response.peer_info_len + 38 > sizeof(response.peer_info)) continue; + record = response.peer_info + response.peer_info_len; + memcpy(record, &address->sin_addr, 4); + memcpy(record + 4, &address->sin_port, 2); + memcpy(record + 6, peer->peer_id, 32); + response.peer_info_len += 38; + } + return send_reply(pool, requester, topic, &response); +} + +static int receive_rendezvous_reply(cesspool_t *pool, + const cesspool_message_t *message) +{ + cesspool_response_t response; + size_t i; + if (cesspool_response_decode(&response, message->payload, message->payload_len)) return -1; + if (response.peer_info_len % 38) { errno = EPROTO; return -1; } + for (i = 0; i < response.peer_info_len; i += 38) { + const uint8_t *record = response.peer_info + i; + cesspool_peer_t peer; + struct sockaddr_in *address; + memset(&peer, 0, sizeof(peer)); + address = (struct sockaddr_in *)&peer.address.addr; + address->sin_family = AF_INET; + memcpy(&address->sin_addr, record, 4); + memcpy(&address->sin_port, record + 4, 2); + peer.address.addrlen = sizeof(*address); + memcpy(peer.public_key, record + 6, 32); + memcpy(peer.discovery_topic, message->topic, 32); + peer.discovered = 1; + peer.last_seen = monotonic_ms(); + if (announcement_add(pool, message->topic, peer.public_key, + &peer.address, peer.last_seen)) continue; + if (cesspool_add_peer(pool, &peer)) return -1; + } + return 0; +} + +static void cesspool_on_message(cessnet_t *net,cess_peer_t *source,const uint8_t *packet,size_t length) +{ + cesspool_t *pool=(cesspool_t *)net->context; + cesspool_message_t message; + cesspool_peer_t peer; + if(!pool)return; + if(cesspool_decode(&message,packet,length)){ + if(pool->on_transport_packet)pool->on_transport_packet(pool,source,packet,length); + return; + } + if(message.type>CESSPOOL_HOLEPUNCH||!memcmp(message.peer_id,net->peerid,32))return; + if (message.type == CESSPOOL_REPLY) { + size_t i; + int from_bootstrap = 0; + for (i = 0; i < pool->bootstrap_count; i++) + if (same_peer_address(&pool->bootstrap_nodes[i], source)) { + from_bootstrap = 1; + break; + } + if (!from_bootstrap) return; + /* A malformed datagram is not proof that the configured rendezvous + * endpoint is speaking Cesspool, so validate before refreshing online. */ + if (receive_rendezvous_reply(pool, &message)) return; + pool->bootstrap_last_seen = monotonic_ms(); + pool->bootstrap_reachable = 1; + return; + } + if (message.type == CESSPOOL_PONG) { + size_t i; + for (i = 0; i < pool->bootstrap_count; i++) { + if (same_peer_address(&pool->bootstrap_nodes[i], source)) { + pool->bootstrap_last_seen = monotonic_ms(); + pool->bootstrap_reachable = 1; + break; + } + } + return; + } + memset(&peer,0,sizeof(peer));peer.address=*source;memcpy(peer.public_key,message.peer_id,32); + peer.last_seen = monotonic_ms(); + if (message.type == CESSPOOL_ANNOUNCE) { + peer.discovered = 1; + memcpy(peer.discovery_topic, message.topic, sizeof(peer.discovery_topic)); + if (announcement_add(pool, message.topic, message.peer_id, source, + peer.last_seen)) return; + } + cesspool_add_peer(pool,&peer); + if (message.type == CESSPOOL_UNANNOUNCE) { + cesspool_peer_t *known = cesspool_find_peer(pool, message.peer_id); + announcement_remove(pool, message.topic, message.peer_id); + if (known) known->discovered = peer_has_announcement(pool, message.peer_id); + } else if(message.type==CESSPOOL_PING)send_message(pool,source,CESSPOOL_PONG,NULL,pool->port); + else if(message.type==CESSPOOL_LOOKUP&&pool->bootstrap_server) + (void)send_rendezvous_reply(pool, source, message.topic); + else if(message.type==CESSPOOL_LOOKUP&&has_topic(pool,message.topic,1)) + send_message(pool,source,CESSPOOL_ANNOUNCE,message.topic,pool->port); + else if(message.type==CESSPOOL_HOLEPUNCH&&pool->on_holepunch)pool->on_holepunch(pool,source); +} +int cesspool_init(cesspool_t *pool,const uint8_t peer_id[32]) +{ + uint8_t empty[32]={0};if(!pool||!peer_id){errno=EINVAL;return-1;}memset(pool,0,sizeof(*pool));if(cessnet_init(&pool->net,empty,peer_id))return-1;pool->net.context=pool;pool->net.on_message=cesspool_on_message;return 0; +} +int cesspool_add_bootstrap(cesspool_t *pool,const char *host,uint16_t port) +{ + struct addrinfo hints, *resolved = NULL; + struct sockaddr_in *address; + cess_peer_t *peer; + if(!pool||!host||!port||pool->bootstrap_count>=CESSPOOL_MAX_BOOTSTRAP){errno=EINVAL;return-1;} + memset(&hints,0,sizeof(hints));hints.ai_family=AF_INET;hints.ai_socktype=SOCK_DGRAM; + if(getaddrinfo(host,NULL,&hints,&resolved)!=0||!resolved){errno=EHOSTUNREACH;return-1;} + peer=&pool->bootstrap_nodes[pool->bootstrap_count];memset(peer,0,sizeof(*peer)); + memcpy(&peer->addr,resolved->ai_addr,resolved->ai_addrlen);peer->addrlen=(socklen_t)resolved->ai_addrlen; + address=(struct sockaddr_in*)&peer->addr;address->sin_port=htons(port); + freeaddrinfo(resolved); + { + size_t i; + for (i = 0; i < pool->bootstrap_count; i++) + if (same_peer_address(&pool->bootstrap_nodes[i], peer)) return 0; + } + if (pool->bootstrap_count >= CESSPOOL_MAX_BOOTSTRAP) { errno = ENOSPC; return -1; } + pool->bootstrap_count++;return 0; +} +static int add_endpoint_list(cesspool_t *pool, const char *setting) +{ + char list[2048], *item, *next; + int added = 0; + if (!setting || !*setting) return 0; + if (strlen(setting) >= sizeof(list)) { errno = EOVERFLOW; return -1; } + strcpy(list, setting); + for (item = list; item && *item; item = next) { + char *separator, *end; + unsigned long port; + next = strpbrk(item, ",;\r\n"); + if (next) *next++ = '\0'; + separator = strrchr(item, ':'); + if (!separator || separator == item || !separator[1]) continue; + *separator++ = '\0'; + port = strtoul(separator, &end, 10); + if (*end || port == 0 || port > 65535) continue; + if (cesspool_add_bootstrap(pool, item, (uint16_t)port) != 0) return -1; + added++; + } + return added; +} + +static int bootstrap_config_path(char *path, size_t capacity, int make_dirs) +{ + const char *home = getenv("HOME"); + const char *config = getenv("XDG_CONFIG_HOME"); + char directory[1024], config_directory[1024]; + if (!home || !*home) home = getenv("USERPROFILE"); +#ifdef _WIN32 + if ((!home || !*home) && getenv("HOMEDRIVE") && getenv("HOMEPATH")) { + static char combined[1024]; + if (snprintf(combined, sizeof(combined), "%s%s", getenv("HOMEDRIVE"), getenv("HOMEPATH")) + < (int)sizeof(combined)) home = combined; + } +#endif + if (!home || !*home) home = "."; + if (!config || !*config) { + if (snprintf(config_directory, sizeof(config_directory), "%s/.config", home) >= (int)sizeof(config_directory)) { + errno = ENAMETOOLONG; return -1; + } + config = config_directory; + } + if (snprintf(directory, sizeof(directory), "%s/%s", config, + CESSPOOL_CONFIG_DIRECTORY) >= (int)sizeof(directory) || + snprintf(path, capacity, "%s/bootstrap", directory) >= (int)capacity) { + errno = ENAMETOOLONG; return -1; + } + if (make_dirs) { + char parent[1024]; + if (snprintf(parent, sizeof(parent), "%s", config) >= (int)sizeof(parent)) { errno = ENAMETOOLONG; return -1; } + if (mkdir(parent, 0700) && errno != EEXIST) return -1; + if (mkdir(directory, 0700) && errno != EEXIST) return -1; + } + return 0; +} + +int cesspool_configure_bootstraps(cesspool_t *pool) +{ + char path[1200], line[512]; + FILE *file; + int count = 0, loaded; + if (!pool) { errno = EINVAL; return -1; } + loaded = add_endpoint_list(pool, getenv("CESSPOOL_BOOTSTRAP")); + if (loaded < 0) return -1; + count += loaded; + loaded = add_endpoint_list(pool, getenv("DDAT_BOOTSTRAP")); + if (loaded < 0) return -1; + count += loaded; + loaded = add_endpoint_list(pool, getenv("CABLE_BOOTSTRAP")); + if (loaded < 0) return -1; + count += loaded; + if (bootstrap_config_path(path, sizeof(path), 0)) return count; + file = fopen(path, "r"); + if (!file) return errno == ENOENT ? count : -1; + while (fgets(line, sizeof(line), file)) { + line[strcspn(line, "\r\n#")] = '\0'; + loaded = add_endpoint_list(pool, line); + if (loaded < 0) { fclose(file); return -1; } + count += loaded; + } + if (ferror(file)) { fclose(file); return -1; } + fclose(file); + return count; +} + +int cesspool_bootstrap_config_add(const char *endpoint) +{ + char path[1200], copy[512], *separator, *end; + unsigned long port; + FILE *file; + if (!endpoint || strlen(endpoint) >= sizeof(copy)) { errno = EINVAL; return -1; } + strcpy(copy, endpoint); + separator = strrchr(copy, ':'); + if (!separator || separator == copy || !separator[1]) { errno = EINVAL; return -1; } + *separator++ = '\0'; port = strtoul(separator, &end, 10); + if (*end || port == 0 || port > 65535) { errno = EINVAL; return -1; } + if (bootstrap_config_path(path, sizeof(path), 1)) return -1; + file = fopen(path, "a"); + if (!file) return -1; + { + int failed = fprintf(file, "%s:%lu\n", copy, port) < 0; + if (fclose(file)) failed = 1; + return failed ? -1 : 0; + } +} + +int cesspool_bootstrap_config_set(const char *endpoint) +{ + char path[1200], temporary[1240], copy[512], *separator, *end; + unsigned long port; + FILE *file; + int n, failed; + if (!endpoint || strlen(endpoint) >= sizeof(copy)) { errno = EINVAL; return -1; } + strcpy(copy, endpoint); + separator = strrchr(copy, ':'); + if (!separator || separator == copy || !separator[1]) { errno = EINVAL; return -1; } + *separator++ = '\0'; + port = strtoul(separator, &end, 10); + if (*end || port == 0 || port > 65535) { errno = EINVAL; return -1; } + if (bootstrap_config_path(path, sizeof(path), 1)) return -1; + n = snprintf(temporary, sizeof(temporary), "%s.tmp", path); + if (n < 0 || (size_t)n >= sizeof(temporary)) { errno = ENAMETOOLONG; return -1; } + file = fopen(temporary, "w"); + if (!file) return -1; + failed = fprintf(file, "%s:%lu\n", copy, port) < 0; + if (fclose(file)) failed = 1; + if (failed || chmod(temporary, S_IRUSR | S_IWUSR)) { + int saved = errno; + (void)remove(temporary); + errno = saved; + return -1; + } +#ifdef _WIN32 + if (!MoveFileExA(temporary, path, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) { + (void)remove(temporary); + errno = EIO; + return -1; + } +#else + if (rename(temporary, path)) { + int saved = errno; + (void)remove(temporary); + errno = saved; + return -1; + } +#endif + return 0; +} + +int cesspool_bootstrap_config_get(char *endpoint, size_t capacity) +{ + char path[1200], line[512]; + FILE *file; + size_t length; + if (!endpoint || !capacity) { errno = EINVAL; return -1; } + endpoint[0] = '\0'; + if (bootstrap_config_path(path, sizeof(path), 0)) return -1; + file = fopen(path, "r"); + if (!file) return -1; + while (fgets(line, sizeof(line), file)) { + line[strcspn(line, "\r\n#")] = '\0'; + if (!line[0]) continue; + length = strlen(line); + if (length >= capacity) { fclose(file); errno = ENOSPC; return -1; } + memcpy(endpoint, line, length + 1); + fclose(file); + return 0; + } + fclose(file); + errno = ENOENT; + return -1; +} + +int cesspool_bootstrap_config_list(char *contents, size_t capacity) +{ + char path[1200]; + FILE *file; + size_t length; + if (!contents || !capacity) { errno = EINVAL; return -1; } + contents[0] = '\0'; + if (bootstrap_config_path(path, sizeof(path), 0)) return -1; + file = fopen(path, "r"); + if (!file) return -1; + length = fread(contents, 1, capacity - 1, file); + if (ferror(file)) { int saved = errno; fclose(file); errno = saved; return -1; } + if (!feof(file)) { fclose(file); errno = ENOSPC; return -1; } + contents[length] = '\0'; + return fclose(file) == 0 ? 0 : -1; +} +int cesspool_bootstrap(cesspool_t *pool){if(!pool||!pool->bootstrap_count){errno=EINVAL;return-1;}return send_bootstrap(pool,CESSPOOL_PING,NULL,pool->port);} +int cesspool_listen(cesspool_t *pool,const char *host,uint16_t port){ + static const uint16_t defaults[]={CESSNET_PORT,3000,3002,3004,2001,2003,2005}; + struct sockaddr_in bound;socklen_t length=sizeof(bound);size_t i,count; + if(!pool){errno=EINVAL;return-1;} + count=port?1:sizeof(defaults)/sizeof(defaults[0]); + for(i=0;inet,host,candidate)==0)break; + if(port||errno!=EADDRINUSE)return-1; + } + if(i==count){errno=EADDRINUSE;return-1;} + if(getsockname(pool->net.sock,(struct sockaddr*)&bound,&length)){cessnet_close(&pool->net);return-1;} + pool->port=ntohs(bound.sin_port);pool->ready=1;return 0; +} +int cesspool_set_bootstrap_server(cesspool_t *pool, int enabled) +{ + if (!pool || !pool->ready) { errno = EINVAL; return -1; } + pool->bootstrap_server = !!enabled; + return 0; +} +int cesspool_announce(cesspool_t *pool,const uint8_t topic[32],uint16_t port){if(!pool||!topic){errno=EINVAL;return-1;}return send_bootstrap(pool,CESSPOOL_ANNOUNCE,topic,port?port:pool->port);} +int cesspool_lookup(cesspool_t *pool,const uint8_t topic[32]){if(!pool||!topic){errno=EINVAL;return-1;}return send_bootstrap(pool,CESSPOOL_LOOKUP,topic,0);} +int cesspool_unannounce(cesspool_t *pool,const uint8_t topic[32],uint16_t port){if(!pool||!topic){errno=EINVAL;return-1;}return send_bootstrap(pool,CESSPOOL_UNANNOUNCE,topic,port);} +int cesspool_ping(cesspool_t *pool,const cess_peer_t *peer){if(!pool||!peer){errno=EINVAL;return-1;}return send_message(pool,peer,CESSPOOL_PING,NULL,pool->port);} +int cesspool_holepunch(cesspool_t *pool,const cess_peer_t *peer){if(!pool||!peer){errno=EINVAL;return-1;}return send_message(pool,peer,CESSPOOL_HOLEPUNCH,NULL,pool->port);} +int cesspool_poll(cesspool_t *pool,int timeout_ms){if(!pool){errno=EINVAL;return-1;}return cessnet_poll(&pool->net,timeout_ms);} +cesspool_peer_t *cesspool_find_peer(cesspool_t*p,const uint8_t key[32]){size_t i;if(!p||!key)return NULL;for(i=0;ipeer_count;i++)if(!memcmp(p->peers[i].public_key,key,32))return&p->peers[i];return NULL;} +int cesspool_add_peer(cesspool_t*p,const cesspool_peer_t*peer){cesspool_peer_t*old;int newly_discovered;if(!p||!peer){errno=EINVAL;return-1;}old=cesspool_find_peer(p,peer->public_key);if(old){newly_discovered=peer->discovered&&(!old->discovered||memcmp(peer->discovery_topic,old->discovery_topic,32));{cesspool_peer_t merged=*peer;if(!peer->discovered&&old->discovered){merged.discovered=1;memcpy(merged.discovery_topic,old->discovery_topic,32);}*old=merged;}if(newly_discovered&&p->on_peer)p->on_peer(p,old);return 0;}if(p->peer_count>=CESSPOOL_MAX_PEERS){errno=ENOSPC;return-1;}p->peers[p->peer_count++]=*peer;if(peer->discovered&&p->on_peer)p->on_peer(p,peer);return 0;} +int cesspool_remove_peer(cesspool_t*p,const uint8_t key[32]){size_t i;if(!p||!key){errno=EINVAL;return-1;}for(i=0;ipeer_count;i++)if(!memcmp(p->peers[i].public_key,key,32)){p->peers[i]=p->peers[--p->peer_count];return 0;}errno=ENOENT;return-1;} +int cesspool_join(cesspool_t *p, const uint8_t topic[32], uint16_t port, + int announce, int lookup) +{ + cesspool_topic_t entry; + int announced_now = 0; + if (!p || !topic || (!announce && !lookup)) { errno = EINVAL; return -1; } + if (p->topic_count >= CESSPOOL_MAX_TOPICS) { errno = ENOSPC; return -1; } + memset(&entry, 0, sizeof(entry)); + memcpy(entry.key, topic, CESSPOOL_TOPIC_BYTES); + entry.port = port ? port : p->port; + entry.announcing = !!announce; + entry.looking_up = !!lookup; + /* No configured rendezvous falls back to best-effort LAN broadcast. */ + if (announce) { + if (cesspool_announce(p, topic, entry.port)) return -1; + announced_now = 1; + } + if (lookup && cesspool_lookup(p, topic)) { + if (announced_now) (void)cesspool_unannounce(p, topic, entry.port); + return -1; + } + p->topics[p->topic_count++] = entry; + return 0; +} +int cesspool_leave(cesspool_t*p,const uint8_t topic[32]){size_t i;if(!p||!topic){errno=EINVAL;return-1;}for(i=0;itopic_count;i++)if(!memcmp(p->topics[i].key,topic,32)){if(p->topics[i].announcing)cesspool_unannounce(p,topic,p->topics[i].port);p->topics[i]=p->topics[--p->topic_count];return 0;}errno=ENOENT;return-1;} +int cesspool_tick(cesspool_t *p, uint64_t now) +{ + size_t i = 0; + uint64_t wall_now; + if (!p) { errno = EINVAL; return -1; } + for (i = 0; i < p->topic_count; i++) if (now >= p->topics[i].refresh_at) { + if (p->topics[i].announcing) (void)cesspool_announce(p, p->topics[i].key, p->topics[i].port); + if (p->topics[i].looking_up) (void)cesspool_lookup(p, p->topics[i].key); + p->topics[i].refresh_at = now + 240000u; + } + if (!p->bootstrap_server && p->bootstrap_count && now >= p->bootstrap_probe_at) { + int probed = 0; + for (i = 0; i < p->topic_count; i++) if (p->topics[i].looking_up) { + (void)cesspool_lookup(p, p->topics[i].key); + probed = 1; + } + if (!probed) (void)cesspool_bootstrap(p); + p->bootstrap_probe_at = now + CESSPOOL_BOOTSTRAP_PROBE_MS; + } + for (i = 0; i < CESSPOOL_MAX_TRANSACTIONS; i++) + if (p->transactions[i].active && now >= p->transactions[i].expires_at) + p->transactions[i].active = 0; + i = 0; + wall_now = monotonic_ms(); + if (p->bootstrap_count && p->bootstrap_last_seen && wall_now >= p->bootstrap_last_seen) + p->bootstrap_reachable = wall_now - p->bootstrap_last_seen < CESSPOOL_BOOTSTRAP_TIMEOUT_MS; + else if (p->bootstrap_count) p->bootstrap_reachable = 0; + while (i < p->announcement_count) { + cesspool_announcement_t *entry = &p->announcements[i]; + if (!entry->last_seen || (wall_now >= entry->last_seen && + wall_now - entry->last_seen >= CESSPOOL_PEER_TTL_MS)) { + p->announcements[i] = p->announcements[--p->announcement_count]; + continue; + } + i++; + } + /* Client peer rows are a view of live topic announcements. Without + * reconciling this bit after pruning, a peer remains "discovered" forever + * even though its rendezvous record has expired. */ + for (i = 0; i < p->peer_count; i++) { + cesspool_peer_t *peer = &p->peers[i]; + size_t announcement; + peer->discovered = 0; + for (announcement = 0; announcement < p->announcement_count; announcement++) { + const cesspool_announcement_t *entry = &p->announcements[announcement]; + if (memcmp(peer->public_key, entry->peer_id, CESSPOOL_PEER_BYTES)) continue; + peer->discovered = 1; + memcpy(peer->discovery_topic, entry->topic, CESSPOOL_TOPIC_BYTES); + if (entry->last_seen > peer->last_seen) peer->last_seen = entry->last_seen; + break; + } + } + if (p->bootstrap_server) { + i = 0; + while (i < p->peer_count) { + cesspool_peer_t *peer = &p->peers[i]; + if (!peer->last_seen || (wall_now >= peer->last_seen && + wall_now - peer->last_seen >= CESSPOOL_PEER_TTL_MS)) { + p->peers[i] = p->peers[--p->peer_count]; + continue; + } + i++; + } + } + return 0; +} +static int distance_less(const uint8_t*a,const uint8_t*b,const uint8_t target[32]){size_t i;for(i=0;i<32;i++){uint8_t da=a[i]^target[i],db=b[i]^target[i];if(da!=db)return dapeer_count;i++){if(npeers[i];else if(cap&&distance_less(p->peers[i].public_key,out[n-1]->public_key,target))out[n-1]=&p->peers[i];for(j=n;j>1&&distance_less(out[j-1]->public_key,out[j-2]->public_key,target);j--){cesspool_peer_t*t=out[j-1];out[j-1]=out[j-2];out[j-2]=t;}}return n;} +size_t cesspool_topic_peers(cesspool_t *p, const uint8_t topic[32], + cesspool_peer_t **out, size_t cap) +{ + size_t i, n = 0; + if (!p || !topic || (!out && cap)) return 0; + for (i = 0; i < p->announcement_count && n < cap; i++) { + cesspool_announcement_t *entry = &p->announcements[i]; + cesspool_peer_t *peer; + size_t j; + if (memcmp(entry->topic, topic, 32)) continue; + for (j = 0; j < n; j++) if (!memcmp(out[j]->public_key, entry->peer_id, 32)) break; + if (j < n) continue; + peer = cesspool_find_peer(p, entry->peer_id); + if (peer) out[n++] = peer; + } + return n; +} + +size_t cesspool_announced_peer_count(const cesspool_t *p) +{ + size_t i, j, count = 0; + if (!p) return 0; + for (i = 0; i < p->announcement_count; i++) { + for (j = 0; j < i; j++) + if (!memcmp(p->announcements[i].peer_id, p->announcements[j].peer_id, 32)) break; + if (j == i) count++; + } + return count; +} + +size_t cesspool_announced_topic_count(const cesspool_t *p) +{ + size_t i, j, count = 0; + if (!p) return 0; + for (i = 0; i < p->announcement_count; i++) { + for (j = 0; j < i; j++) + if (!memcmp(p->announcements[i].topic, p->announcements[j].topic, 32)) break; + if (j == i) count++; + } + return count; +} +int cesspool_transaction_begin(cesspool_t*p,uint8_t type,const uint8_t target[32],uint64_t expires,uint16_t*out){size_t i,j,attempt;uint16_t candidate;if(!p||!target||!out){errno=EINVAL;return-1;}for(i=0;itransactions[i].active){for(attempt=0;attemptnext_transaction;if(!candidate)candidate=++p->next_transaction;for(j=0;jtransactions[j].active&&p->transactions[j].id==candidate)break;if(j==CESSPOOL_MAX_TRANSACTIONS){cesspool_transaction_t*t=&p->transactions[i];memset(t,0,sizeof(*t));t->active=1;t->type=type;t->id=candidate;t->expires_at=expires;memcpy(t->target,target,32);*out=t->id;return 0;}}errno=ENOSPC;return-1;}errno=ENOSPC;return-1;} +int cesspool_transaction_finish(cesspool_t*p,uint16_t id){size_t i;if(!p){errno=EINVAL;return-1;}for(i=0;itransactions[i].active&&p->transactions[i].id==id){p->transactions[i].active=0;return 0;}errno=ENOENT;return-1;} +int cesspool_destroy(cesspool_t *pool){if(!pool){errno=EINVAL;return-1;}pool->ready=0;return cessnet_close(&pool->net);} diff --git a/libcess/tools/cesspool-bootstrap.c b/libcess/tools/cesspool-bootstrap.c new file mode 100644 index 0000000..7987a37 --- /dev/null +++ b/libcess/tools/cesspool-bootstrap.c @@ -0,0 +1,247 @@ +#define _POSIX_C_SOURCE 200809L +#include "cess_compat.h" +#include "cesspool.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef EKEYREJECTED +#define EKEYREJECTED EACCES +#endif +#ifdef _WIN32 +#include +#endif +#ifdef _WIN32 +#include +#include +#else +#include +#endif + +#define BOOTSTRAP_PATH_BYTES 1400u + +static volatile sig_atomic_t bootstrap_running = 1; + +static void stop_bootstrap(int signal_number) +{ + (void)signal_number; + bootstrap_running = 0; +} + +static void usage(FILE *out) +{ + fputs("cesspool-bootstrap - dedicated Cesspool public rendezvous node\n" + "usage: cesspool-bootstrap [--bind IPv4] [--port UDP-PORT]\n" + " [--advertise HOST:PORT] [--state-dir DIR]\n" + " cesspool-bootstrap --help\n" + "Runs in the foreground for a service manager. It handles peer discovery;\n" + "chat and archive payloads remain peer-to-peer.\n", out); +} + +static int parse_port(const char *text, uint16_t *port) +{ + char *end; + unsigned long value; + if (!text || !*text || !port) { errno = EINVAL; return -1; } + value = strtoul(text, &end, 10); + if (*end || value == 0 || value > 65535) { errno = EINVAL; return -1; } + *port = (uint16_t)value; + return 0; +} + +static int make_one_state_directory(const char *path, int secure_existing) +{ + struct stat info; + int created = 0; + if (!path || !*path) { errno = EINVAL; return -1; } + if (mkdir(path, 0700) == 0) created = 1; + else if (errno != EEXIST) return -1; + if (stat(path, &info) != 0) return -1; + if (!S_ISDIR(info.st_mode)) { errno = ENOTDIR; return -1; } + if ((created || secure_existing) && chmod(path, S_IRWXU) != 0) return -1; + return 0; +} + +static int make_state_directory(const char *path) +{ + char copy[BOOTSTRAP_PATH_BYTES]; + size_t length, start = 0, i; + if (!path || !*path) { errno = EINVAL; return -1; } + length = strlen(path); + if (length >= sizeof(copy)) { errno = ENAMETOOLONG; return -1; } + memcpy(copy, path, length + 1); +#ifdef _WIN32 + if (length >= 3 && copy[1] == ':') start = 3; +#else + if (copy[0] == '/') start = 1; +#endif + for (i = start; i < length; i++) { + if (copy[i] != '/' && copy[i] != '\\') continue; + if (i == 0 || (i == 2 && copy[1] == ':')) continue; + copy[i] = '\0'; + if (make_one_state_directory(copy, 0) != 0) return -1; + copy[i] = path[i]; + } + return make_one_state_directory(copy, 1); +} + +static int default_state_directory(char *out, size_t capacity) +{ + const char *base; + int n; + if (!out || capacity == 0) { errno = EINVAL; return -1; } +#ifdef _WIN32 + base = getenv("LOCALAPPDATA"); + if (!base || !*base) { errno = ENOENT; return -1; } + n = snprintf(out, capacity, "%s/dat-gpt/cesspool-bootstrap-state", base); +#else + base = getenv("XDG_STATE_HOME"); + if (base && *base) n = snprintf(out, capacity, "%s/dat-gpt/cesspool-bootstrap", base); + else { + base = getenv("HOME"); + if (!base || !*base) { errno = ENOENT; return -1; } + n = snprintf(out, capacity, "%s/.local/state/dat-gpt/cesspool-bootstrap", base); + } +#endif + if (n < 0 || (size_t)n >= capacity) { errno = ENAMETOOLONG; return -1; } + return 0; +} + +static int load_or_create_identity(const char *state_dir, cesspool_keypair_t *identity) +{ + char key_path[BOOTSTRAP_PATH_BYTES]; + uint8_t derived[32]; + int n; + if (!state_dir || !identity) { errno = EINVAL; return -1; } + n = snprintf(key_path, sizeof(key_path), "%s/%s", state_dir, "identity.key"); + if (n < 0 || (size_t)n >= sizeof(key_path)) { errno = ENAMETOOLONG; return -1; } + if (cesspool_identity_init()) return -1; + if (cesspool_identity_load(key_path, identity) == 0) { + if (crypto_sign_ed25519_sk_to_pk(derived, identity->seckey) != 0 || + sodium_memcmp(derived, identity->pubkey, sizeof(derived)) != 0) { + sodium_memzero(identity, sizeof(*identity)); + errno = EKEYREJECTED; + return -1; + } + if (chmod(key_path, S_IRUSR | S_IWUSR) != 0) return -1; + return 0; + } + if (errno != ENOENT || cesspool_identity_generate(identity) || + cesspool_identity_save(key_path, identity) != 0 || + chmod(key_path, S_IRUSR | S_IWUSR) != 0) { + int saved = errno; + sodium_memzero(identity, sizeof(*identity)); + errno = saved; + return -1; + } + return 0; +} + +static uint64_t monotonic_milliseconds(void) +{ + struct timespec now; + if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) return 0; + return (uint64_t)now.tv_sec * 1000u + (uint64_t)now.tv_nsec / 1000000u; +} + +int main(int argc, char **argv) +{ + cesspool_t pool; + cesspool_keypair_t identity; + const char *bind_host = "0.0.0.0"; + const char *state_dir = NULL; + const char *advertise = NULL; + uint16_t port = CESSNET_PORT; + uint64_t next_status = 0; + char default_state[BOOTSTRAP_PATH_BYTES]; + int i, result = 1; + memset(&pool, 0, sizeof(pool)); + pool.net.sock = CESS_INVALID_SOCKET; + memset(&identity, 0, sizeof(identity)); + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")) { + usage(stdout); + return 0; + } + if ((!strcmp(argv[i], "--bind") || !strcmp(argv[i], "--port") || + !strcmp(argv[i], "--advertise") || !strcmp(argv[i], "--state-dir")) && i + 1 >= argc) { + fprintf(stderr, "cesspool-bootstrap: %s needs a value\n", argv[i]); + return 2; + } + if (!strcmp(argv[i], "--bind")) bind_host = argv[++i]; + else if (!strcmp(argv[i], "--port")) { + if (parse_port(argv[++i], &port)) { perror("cesspool-bootstrap --port"); return 2; } + } else if (!strcmp(argv[i], "--advertise")) advertise = argv[++i]; + else if (!strcmp(argv[i], "--state-dir")) state_dir = argv[++i]; + else { + fprintf(stderr, "cesspool-bootstrap: unknown option %s\n", argv[i]); + usage(stderr); + return 2; + } + } + if (!state_dir) { + if (default_state_directory(default_state, sizeof(default_state)) != 0) { + perror("cesspool-bootstrap default state directory"); + return 1; + } + state_dir = default_state; + } + if (!bind_host[0] || strchr(bind_host, ':')) { + fputs("cesspool-bootstrap: --bind requires an IPv4 host or 0.0.0.0\n", stderr); + return 2; + } + (void)umask(077); + if (make_state_directory(state_dir) || load_or_create_identity(state_dir, &identity) || + cesspool_init(&pool, identity.pubkey) || + cesspool_listen(&pool, bind_host, port) || + cesspool_set_bootstrap_server(&pool, 1)) { + perror("cesspool-bootstrap startup"); + goto done; + } + signal(SIGINT, stop_bootstrap); + signal(SIGTERM, stop_bootstrap); + printf("Cesspool public bootstrap ready\n" + " bind: %s:%u/udp\n" + " state: persistent private identity loaded\n" + " peer discovery only; peer payloads are not relayed\n", + bind_host, pool.port); + if (advertise && *advertise) + printf(" advertised endpoint: %s/udp\n" + " client setup: dat bootstrap set %s\n" + " client setup: cable bootstrap set %s\n" + " GUI setup: Settings > Cesspool Node > %s\n", + advertise, advertise, advertise, advertise); + else printf(" advertised endpoint: not set; configure a public host:port in clients\n"); + printf(" Windows firewall (run elevated): netsh advfirewall firewall add rule name=\"Cesspool UDP %u\" dir=in action=allow protocol=UDP localport=%u\n", + pool.port, pool.port); + puts(" route the same UDP port through the edge router or cloud firewall"); + fflush(stdout); + while (bootstrap_running) { + int polled = cesspool_poll(&pool, 1000); + uint64_t now = monotonic_milliseconds(); + if (polled < 0 && errno != EINTR) { + perror("cesspool-bootstrap poll"); + goto done; + } + if (cesspool_tick(&pool, now) != 0) { + perror("cesspool-bootstrap maintenance"); + goto done; + } + if (now >= next_status) { + fprintf(stderr, "cesspool-bootstrap: peers=%lu topics=%lu\n", + (unsigned long)cesspool_announced_peer_count(&pool), + (unsigned long)cesspool_announced_topic_count(&pool)); + next_status = now + 60000u; + } + } + result = 0; +done: + if (pool.net.sock != CESS_INVALID_SOCKET) (void)cesspool_destroy(&pool); + sodium_memzero(&identity, sizeof(identity)); + return result; +} diff --git a/list.txt b/list.txt new file mode 100644 index 0000000..1db12bb --- /dev/null +++ b/list.txt @@ -0,0 +1,2 @@ +try these +cable://a99a025111e353aaff1e3bc4994b4a305985dfeee83bdfe843049613909a51fd \ No newline at end of file diff --git a/package-source.ps1 b/package-source.ps1 new file mode 100644 index 0000000..9b6fb76 --- /dev/null +++ b/package-source.ps1 @@ -0,0 +1,67 @@ +param( + [string]$OutputDirectory = 'dist\source' +) + +$ErrorActionPreference = 'Stop' +$repo = (Resolve-Path -LiteralPath $PSScriptRoot).Path +$version = (Get-Content -LiteralPath (Join-Path $repo 'VERSION') -Raw).Trim() +$output = [System.IO.Path]::GetFullPath((Join-Path $repo $OutputDirectory)) +$archive = Join-Path $output "dat-gpt-v$version-source.zip" +$excludedRoots = @('build', 'dist', 'local-data', 'test-deps', '.git') +$excludedDirectories = @('node_modules') +$excludedFiles = @( + 'config.h', 'config.mk', 'config.log', + 'dpid_comm_keys', 'identity.key', 'room-secret.key', 'secret_key', + 'Cookies.txt', 'cookies.txt', 'known_hosts', 'authorized_keys', + 'id_rsa', 'id_ed25519' +) + +if (!$output.StartsWith($repo + [System.IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase)) { + throw "Source archive output must be inside the repository: $output" +} + +New-Item -ItemType Directory -Path $output -Force | Out-Null +if (Test-Path -LiteralPath $archive) { + Remove-Item -LiteralPath $archive -Force +} + +Add-Type -AssemblyName System.IO.Compression.FileSystem +$zip = [System.IO.Compression.ZipFile]::Open($archive, [System.IO.Compression.ZipArchiveMode]::Create) +try { + $pending = [System.Collections.Generic.Stack[string]]::new() + $pending.Push($repo) + while ($pending.Count -gt 0) { + $directory = $pending.Pop() + foreach ($item in Get-ChildItem -LiteralPath $directory -Force) { + $relative = $item.FullName.Substring($repo.Length).TrimStart('\') + if (($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) { + continue + } + if ($item.PSIsContainer) { + $parts = $relative -split '[\\/]' + if ($excludedRoots -contains $parts[0] -or $excludedDirectories -contains $item.Name -or + $item.Name -eq '.git') { + continue + } + $pending.Push($item.FullName) + continue + } + if ($excludedFiles -contains $item.Name -or $item.Name -eq '.env' -or + $item.Name.StartsWith('.env.', [StringComparison]::OrdinalIgnoreCase)) { + continue + } + $entryName = "dat-gpt-v$version/" + ($relative -replace '\\', '/') + [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile( + $zip, + $item.FullName, + $entryName, + [System.IO.Compression.CompressionLevel]::Optimal + ) | Out-Null + } + } +} +finally { + $zip.Dispose() +} + +Write-Host "Source archive created: $archive" diff --git a/scheme/cable-room.scm b/scheme/cable-room.scm new file mode 100644 index 0000000..da5a617 --- /dev/null +++ b/scheme/cable-room.scm @@ -0,0 +1,14 @@ +;;; Cable room records are S-expressions signed by each peer's persistent key. +;;; C handles the authenticated Cesspool/uTP transport; S7 owns the record form. +(define (cable:room-event->wire kind timestamp nick channel text) + (list 'cable-room-event kind timestamp nick channel text)) + +(define (cable:room-wire-event? value) + (and (list? value) + (= (length value) 6) + (eq? (car value) 'cable-room-event) + (integer? (cadr value)) + (integer? (caddr value)) + (string? (list-ref value 3)) + (string? (list-ref value 4)) + (string? (list-ref value 5)))) diff --git a/scheme/dat-init.scm b/scheme/dat-init.scm new file mode 100644 index 0000000..fb83cac --- /dev/null +++ b/scheme/dat-init.scm @@ -0,0 +1,42 @@ +;;; Host-level conventions shared by the Dat Scheme application layer. +;;; S7 intentionally leaves policy and application modules to its embedding. + +(define dat:runtime-version "1") + +(define (dat:module-name->file name) + (if (not (symbol? name)) + (error 'wrong-type-arg "Dat module name must be a symbol" name) + (if (eq? name 'dat:record) + "dat-record.scm" + (if (eq? name 'dat:wire) + "dat-wire.scm" + (let* ((text (symbol->string name)) + (path (string-append text ".scm"))) + (do ((i 0 (+ i 1))) ((= i (string-length text))) + (if (char=? (string-ref text i) #\:) + (string-set! path i #\/))) + path))))) + +(define (dat:require feature) + (let ((name (if (symbol? feature) feature (string->symbol feature)))) + (if (not (provided? name)) + (begin + (load (dat:module-name->file name)) + (if (not (provided? name)) + (error 'dat-module-error "module did not declare its feature" name)))) + name)) + +(define (dat:with-archive archive procedure) + (if (not (procedure? procedure)) + (error 'wrong-type-arg "expected a procedure" procedure) + (dynamic-wind + (lambda () #f) + (lambda () (procedure archive)) + (lambda () (dat-close archive))))) + +(define (dat:call-with-archive directory procedure . create?) + (let ((archive ((if (and (pair? create?) (car create?)) dat-create dat-open) + directory))) + (dat:with-archive archive procedure))) + +(provide 'dat:runtime) diff --git a/scheme/dat-json.scm b/scheme/dat-json.scm new file mode 100644 index 0000000..a4dfcd6 --- /dev/null +++ b/scheme/dat-json.scm @@ -0,0 +1,244 @@ +;;; Strict JSON reader shared by the S7-backed Dat application layer. +;;; Objects become (json:object ("key" . value) ...), arrays become +;;; (json:array value ...), and JSON null is the symbol json:null. + +(define (json:codepoint->utf8 code) + (if (<= code #x7f) + (list (integer->char code)) + (if (<= code #x7ff) + (list + (integer->char (+ #xc0 (quotient code 64))) + (integer->char (+ #x80 (modulo code 64)))) + (if (<= code #xffff) + (list + (integer->char (+ #xe0 (quotient code 4096))) + (integer->char (+ #x80 (modulo (quotient code 64) 64))) + (integer->char (+ #x80 (modulo code 64)))) + (list + (integer->char (+ #xf0 (quotient code 262144))) + (integer->char (+ #x80 (modulo (quotient code 4096) 64))) + (integer->char (+ #x80 (modulo (quotient code 64) 64))) + (integer->char (+ #x80 (modulo code 64)))))))) + +(define (dat:json-parse source) + (if (not (string? source)) + (error 'wrong-type-arg "dat:json-parse expects a string" source)) + (let ((port (open-input-string source))) + (define (fail message) + (error 'json-parse-error message)) + (define (peek) (peek-char port)) + (define (take) (read-char port)) + (define (ws?) + (let ((c (peek))) + (and (char? c) + (or (char=? c #\space) (char=? c #\tab) + (char=? c #\newline) (char=? c #\return))))) + (define (skip-ws) + (do () ((not (ws?))) (take))) + (define (expect character) + (if (or (not (char? (peek))) (not (char=? (take) character))) + (fail (string-append "expected " (string character))))) + (define (hex-value c) + (cond ((and (char>=? c #\0) (char<=? c #\9)) + (- (char->integer c) (char->integer #\0))) + ((and (char>=? c #\a) (char<=? c #\f)) + (+ 10 (- (char->integer c) (char->integer #\a)))) + ((and (char>=? c #\A) (char<=? c #\F)) + (+ 10 (- (char->integer c) (char->integer #\A)))) + (else (fail "invalid hexadecimal escape")))) + (define (hex4) + (let loop ((left 4) (value 0)) + (if (= left 0) value + (let ((c (take))) + (if (not (char? c)) (fail "incomplete unicode escape")) + (loop (- left 1) (+ (* value 16) (hex-value c))))))) + (define (parse-string) + (expect #\") + (let loop ((reversed '())) + (let ((c (take))) + (cond ((not (char? c)) (fail "unterminated string")) + ((char=? c #\") (list->string (reverse reversed))) + ((char=? c #\\) + (let ((escape (take))) + (cond ((not (char? escape)) (fail "incomplete escape")) + ((char=? escape #\") (loop (cons #\" reversed))) + ((char=? escape #\\) (loop (cons #\\ reversed))) + ((char=? escape #\/) (loop (cons #\/ reversed))) + ((char=? escape #\b) (loop (cons (integer->char 8) reversed))) + ((char=? escape #\f) (loop (cons (integer->char 12) reversed))) + ((char=? escape #\n) (loop (cons #\newline reversed))) + ((char=? escape #\r) (loop (cons #\return reversed))) + ((char=? escape #\t) (loop (cons #\tab reversed))) + ((char=? escape #\u) + (let* ((first (hex4)) + (code + (cond ((and (>= first #xd800) (<= first #xdbff)) + (expect #\\) (expect #\u) + (let ((second (hex4))) + (if (or (< second #xdc00) (> second #xdfff)) + (fail "invalid unicode surrogate pair")) + (+ #x10000 (* (- first #xd800) #x400) + (- second #xdc00)))) + ((and (>= first #xdc00) (<= first #xdfff)) + (fail "unpaired unicode low surrogate")) + (else first)))) + (if (< code 32) (fail "invalid control character escape")) + (loop (append (reverse (json:codepoint->utf8 code)) + reversed)))) + (else (fail "invalid string escape"))))) + ((< (char->integer c) 32) (fail "unescaped control character")) + (else (loop (cons c reversed))))))) + (define (digit? c) + (and (char? c) (char>=? c #\0) (char<=? c #\9))) + (define (parse-number) + (let loop ((chars '())) + (let ((c (peek))) + (if (and (char? c) + (or (digit? c) (memv c '(#\- #\+ #\. #\e #\E)))) + (begin (take) (loop (cons c chars))) + (let* ((text (list->string (reverse chars))) + (number (string->number text))) + (if (or (string=? text "") (not number) + (not (json:number-token? text))) + (fail "invalid JSON number")) + number))))) + (define (parse-array) + (expect #\[) + (skip-ws) + (if (and (char? (peek)) (char=? (peek) #\])) + (begin (take) '(json:array)) + (let loop ((values '())) + (let ((value (parse-value))) + (skip-ws) + (cond ((and (char? (peek)) (char=? (peek) #\])) + (take) (cons 'json:array (reverse (cons value values)))) + ((and (char? (peek)) (char=? (peek) #\,)) + (take) (skip-ws) (loop (cons value values))) + (else (fail "expected comma or closing bracket"))))))) + (define (parse-object) + (expect #\{) + (skip-ws) + (if (and (char? (peek)) (char=? (peek) #\})) + (begin (take) '(json:object)) + (let loop ((fields '())) + (if (or (not (char? (peek))) (not (char=? (peek) #\"))) + (fail "object key must be a string")) + (let ((key (parse-string))) + (skip-ws) (expect #\:) (skip-ws) + (let ((field (cons key (parse-value)))) + (skip-ws) + (cond ((and (char? (peek)) (char=? (peek) #\})) + (take) (cons 'json:object (reverse (cons field fields)))) + ((and (char? (peek)) (char=? (peek) #\,)) + (take) (skip-ws) (loop (cons field fields))) + (else (fail "expected comma or closing brace")))))))) + (define (literal token value) + (for-each (lambda (expected) + (if (or (not (char? (peek))) + (not (char=? (take) expected))) + (fail "invalid JSON literal"))) + (string->list token)) + value) + (define (parse-value) + (skip-ws) + (let ((c (peek))) + (cond ((not (char? c)) (fail "expected a JSON value")) + ((char=? c #\") (parse-string)) + ((char=? c #\{) (parse-object)) + ((char=? c #\[) (parse-array)) + ((char=? c #\t) (literal "true" #t)) + ((char=? c #\f) (literal "false" #f)) + ((char=? c #\n) (literal "null" 'json:null)) + ((or (digit? c) (char=? c #\-)) (parse-number)) + (else (fail "unexpected character in JSON"))))) + (let ((value (parse-value))) + (skip-ws) + (if (not (eof-object? (peek))) (fail "trailing data after JSON value")) + value))) + +;;; Validate the RFC 8259 number grammar before string->number, which accepts +;;; Scheme-specific forms that are not valid JSON. +(define (json:number-token? text) + (let* ((n (string-length text)) (i 0)) + (define (at c) (and (< i n) (char=? (string-ref text i) c))) + (define (digits) + (let ((start i)) + (do () ((or (>= i n) + (not (char<=? #\0 (string-ref text i) #\9)))) + (set! i (+ i 1))) + (> i start))) + (if (at #\-) (set! i (+ i 1))) + (let ((integer-ok + (cond ((at #\0) (set! i (+ i 1)) (not (and (< i n) (char<=? #\0 (string-ref text i) #\9)))) + (else (and (< i n) (char<=? #\1 (string-ref text i) #\9) + (begin (set! i (+ i 1)) (digits) #t)))))) + (and integer-ok + (or (not (at #\.)) + (begin (set! i (+ i 1)) (digits))) + (or (not (or (at #\e) (at #\E))) + (begin (set! i (+ i 1)) + (if (or (at #\+) (at #\-)) (set! i (+ i 1))) + (digits))) + (= i n))))) + +(define (dat:json-object-ref object key . fallback) + (if (or (not (pair? object)) (not (eq? (car object) 'json:object))) + (error 'wrong-type-arg "expected a parsed JSON object" object) + (let ((field (assoc key (cdr object)))) + (if field (cdr field) (if (pair? fallback) (car fallback) #f))))) + +;;; Encode the same tagged values returned by dat:json-parse. Scheme records +;;; remain the native representation; this is the narrow adapter used when a +;;; compatible peer requires JSON bytes on the wire. +(define (dat:json-stringify value) + (define (write-unicode-escape n port) + (let ((digits "0123456789abcdef")) + (display "\\u" port) + (for-each + (lambda (shift) + (write-char (string-ref digits (modulo (quotient n shift) 16)) port)) + '(4096 256 16 1)))) + (define (string-encode text) + (let ((port (open-output-string))) + (write-char #\" port) + (for-each + (lambda (c) + (let ((n (char->integer c))) + (cond ((char=? c #\") (display "\\\"" port)) + ((char=? c #\\) (display "\\\\" port)) + ((char=? c #\backspace) (display "\\b" port)) + ((char=? c #\newline) (display "\\n" port)) + ((char=? c #\return) (display "\\r" port)) + ((char=? c #\tab) (display "\\t" port)) + ((< n 32) (write-unicode-escape n port)) + (else (write-char c port))))) + (string->list text)) + (write-char #\" port) + (get-output-string port))) + (define (encode item) + (cond ((string? item) (string-encode item)) + ((eq? item 'json:null) "null") + ((eq? item #t) "true") + ((eq? item #f) "false") + ((number? item) + (let ((text (number->string item))) + (if (json:number-token? text) text + (error 'json-encode-error "number is not representable in JSON" item)))) + ((and (pair? item) (eq? (car item) 'json:array)) + (string-append "[" (join "," (map encode (cdr item))) "]")) + ((and (pair? item) (eq? (car item) 'json:object)) + (string-append "{" (join "," + (map (lambda (field) + (if (or (not (pair? field)) (not (string? (car field)))) + (error 'json-encode-error "object keys must be strings" field)) + (string-append (string-encode (car field)) ":" (encode (cdr field)))) + (cdr item))) "}")) + (else (error 'json-encode-error "unsupported JSON value" item)))) + (define (join separator strings) + (if (null? strings) "" + (let loop ((rest (cdr strings)) (out (car strings))) + (if (null? rest) out + (loop (cdr rest) (string-append out separator (car rest))))))) + (encode value)) + +(provide 'dat:json) diff --git a/scheme/dat-record.scm b/scheme/dat-record.scm new file mode 100644 index 0000000..8ada6ce --- /dev/null +++ b/scheme/dat-record.scm @@ -0,0 +1,42 @@ +;;; Shared record conventions for Scheme-facing Dat APIs. +;;; A record is (tag (field . value) ...); payload bytes stay bytevectors. + +(define (dat:record? value . wanted-tag) + (and (pair? value) + (symbol? (car value)) + (or (null? wanted-tag) (eq? (car value) (car wanted-tag))) + (let loop ((fields (cdr value)) (seen '())) + (if (null? fields) + #t + (and (pair? fields) + (pair? (car fields)) + (symbol? (caar fields)) + (not (memq (caar fields) seen)) + (loop (cdr fields) (cons (caar fields) seen))))))) + +(define (dat:record-ref record field . fallback) + (if (not (dat:record? record)) + (error 'wrong-type-arg "expected a Dat record" record) + (if (not (symbol? field)) + (error 'wrong-type-arg "record field name must be a symbol" field) + (let ((entry (assq field (cdr record)))) + (if entry + (cdr entry) + (if (pair? fallback) (car fallback) #f)))))) + +(define (dat:record-set record field value) + (if (not (dat:record? record)) + (error 'wrong-type-arg "expected a Dat record" record) + (if (not (symbol? field)) + (error 'wrong-type-arg "record field name must be a symbol" field) + (let loop ((fields (cdr record)) (reversed '()) (found #f)) + (if (null? fields) + (cons (car record) + (reverse (if found reversed + (cons (cons field value) reversed)))) + (let ((entry (car fields))) + (if (eq? (car entry) field) + (loop (cdr fields) (cons (cons field value) reversed) #t) + (loop (cdr fields) (cons entry reversed) found)))))))) + +(provide 'dat:record) diff --git a/scheme/dat-wire.scm b/scheme/dat-wire.scm new file mode 100644 index 0000000..0d22f5c --- /dev/null +++ b/scheme/dat-wire.scm @@ -0,0 +1,31 @@ +;;; The C codec owns framing and protocol bytes. Scheme owns the readable view. +;;; Wire records have named fields, so optional fields can be added without +;;; changing positional call sites or the peer-visible protocol. + +(define (dat:wire-message channel type payload) + (if (not (and (integer? channel) (exact? channel) (>= channel 0))) + (error 'out-of-range "wire channel must be a non-negative exact integer" channel)) + (if (not (and (integer? type) (exact? type) (>= type 0))) + (error 'out-of-range "wire type must be a non-negative exact integer" type)) + (if (not (byte-vector? payload)) + (error 'wrong-type-arg "wire payload must be a bytevector" payload)) + (list 'dat:wire-message + (cons 'channel channel) + (cons 'type type) + (cons 'payload payload))) + +(define (dat:wire-message? value) + (and (dat:record? value 'dat:wire-message) + (integer? (dat:record-ref value 'channel #f)) + (integer? (dat:record-ref value 'type #f)) + (byte-vector? (dat:record-ref value 'payload #f)))) + +(define (dat:wire-frame->record bytes) + (dat-wire->sexp bytes)) + +(define (dat:record->wire-frame record) + (if (not (dat:wire-message? record)) + (error 'wrong-type-arg "expected a dat:wire-message record" record) + (dat-sexp->wire record))) + +(provide 'dat:wire) diff --git a/site/.dat/content.bitfield b/site/.dat/content.bitfield new file mode 100644 index 0000000000000000000000000000000000000000..5db64f6faeafac8351347174d6ff301057e8e4a4 GIT binary patch literal 3616 ycmZQ&3TI&8W55YM;FK8!qhJ&ackpkxdvFwtf>AIEM!_f;1sH${Lu3?;Bme*uT>@PI literal 0 HcmV?d00001 diff --git a/site/.dat/content.data b/site/.dat/content.data new file mode 100644 index 0000000000000000000000000000000000000000..e6880b03ac71f547871945b806d4c32935b4672f GIT binary patch literal 26226 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%0D#~@78RHE=JSd@1L?3fSEK&NSrD1f1PF=g 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 EA4<($)Bpeg literal 0 HcmV?d00001 diff --git a/site/.dat/content.key b/site/.dat/content.key new file mode 100644 index 0000000..8280944 --- /dev/null +++ b/site/.dat/content.key @@ -0,0 +1 @@ +ª�²ïý;º† ¢nIËÌÞ¼Æ�ž«N}"Õiju{ \ No newline at end of file diff --git a/site/.dat/content.signatures b/site/.dat/content.signatures new file mode 100644 index 0000000000000000000000000000000000000000..4a86db4b715d3b3229dfaeeea64aa6b94cbe0d1b GIT binary patch literal 288 zcmZQ&3TI?saA0>$F)}qZv}8aB=Ea6Q=f3&MtS$GMxjQa;)5@L)g(7Ma(-O1aTtC!Z za`L9)yT3vE`X6w{4R65z%{1kL^mg zJ3kT-wtld7-CDOvmyfYl+`rtp-1prhrbtckBx@h_z`xtV>kjFfecy2Ab?EV#Q*>vy9D)o5<2bGub&U4FijI^0~emldL znQr1UZ|2Kd_k5A=@!(y&RN<_C+^w1Brfe7fX+22M-=ui)@cCbhH!7-Mj^|U9U9R>u i=Wj_U%ddE*V>cf-xjM~x>fXPpCosiz%Y#*(2iXDA&VP~s literal 0 HcmV?d00001 diff --git a/site/.dat/content.tree b/site/.dat/content.tree new file mode 100644 index 0000000000000000000000000000000000000000..1f1230de880c8a229d2c4d47d56c8a72adff5589 GIT binary patch literal 312 zcmZQ&3TI+q&|r7+arAaIN@73<8k0<#mCm;WE-v91J z7de>v_WS$VW<0LZSvsrMwJ4J-bt8{@M|r@jzSe0BFSIxN6+qM_MjkjK6&byEb^fG{ zM)odg%<&DYpM2SUZl6Z_uA5&^`mIriseAjaz-jyC^Ws)HcH+w_bmNX!+NC62-)Xc@ z>vG0brNy%#>e7nRRxdVK*z|AuWwVuM?X!hG+-8|J*L!9;U)ZyBVuW72Wl@D?yYha}gXH*2az*8aV5eXSWp9jm0%9(MPmZO#j>|NIkqX-=rZ ZiVIzP=gqzn;?XS5vy#as2BMCo0szBAIEM!_f;1sH${Lu3?;Bme*uT>@PI literal 0 HcmV?d00001 diff --git a/site/.dat/metadata.data b/site/.dat/metadata.data new file mode 100644 index 0000000..85ffe92 --- /dev/null +++ b/site/.dat/metadata.data @@ -0,0 +1,7 @@ + + +hyperdrive ª�²ïý;º† ¢nIËÌÞ¼Æ�ž«N}"Õiju{ + dat-logo.pngÀƒ ÙÂ(Ûä꜑408 +dat.scmÀƒ ¡(µé×Ñ408 + +index.htmlÀƒ ø(‡³þœ‘408 \ No newline at end of file diff --git a/site/.dat/metadata.key b/site/.dat/metadata.key new file mode 100644 index 0000000..d3207e3 --- /dev/null +++ b/site/.dat/metadata.key @@ -0,0 +1 @@ +J“õ‚LJ9Ô…ÈJMàõc£9•N~ãñ£öiŸ*žè \ No newline at end of file diff --git a/site/.dat/metadata.signatures b/site/.dat/metadata.signatures new file mode 100644 index 0000000000000000000000000000000000000000..5aae53e2446a84f33387b59d7cb275ced5d325a4 GIT binary patch literal 288 zcmZQ&3TI?saA0>$F)}qZv}8aBG4oFSasTo2@yW~2&zz{bbmq~Tjc>QFF155avc-9V(MnLk9Q((xO@$|Ybv(0`L2`tjht>pR} kb>^?cKNn;tyjaHt0Q44%3;+NC literal 0 HcmV?d00001 diff --git a/site/.dat/metadata.tree b/site/.dat/metadata.tree new file mode 100644 index 0000000000000000000000000000000000000000..f3523cdc6577bce55b114a04e69d734464899228 GIT binary patch literal 312 zcmZQ&3TI+q&|r7+arAaIN@737^F|E=j% zTOt8fuV=UC*R;FVj{Ul<})>%Okkz4H5`^5q5dq3Qyg4H^%tw#^Hb zQJdH+c6=fWi>L&vVMO`D;4jWsT2I6)LDeb$J2(I7rh{i!rH3z;ICo*an(xoHf;lcM zAJ?6*+%HgJ$PHE3H-~vu)TBtx9e0e*yb$~7Vj`35bmIr_>EMU^!*pK16}|;kCp%Tw zxp<~VtwddD*p1+Ybzd7-?>=sMy2ZaUO5)C`$#!?3>TH=f#2Gfdx%E&XG;fJsneCPM Xx%!jS-voD@edI6yaowsAs5%7z`$Kok literal 0 HcmV?d00001 diff --git a/site/dat-logo.png b/site/dat-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..0825afb2262ea61cfd863c69cc757e4dcda48228 GIT binary patch literal 24921 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%0D#~@78RHE=JSd@1L?3fSEK&NSrD1f1PF=g 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 literal 0 HcmV?d00001 diff --git a/site/dat.scm b/site/dat.scm new file mode 100644 index 0000000..8f654cf --- /dev/null +++ b/site/dat.scm @@ -0,0 +1 @@ +((title "Dillo setup test") (description "Dillo Dat browsing and version check") (url "dat://4a93f5824c7f4a39d485c84a4de0f563a33915954e7ee3f10ea3f6699f2a9ee8")) diff --git a/site/index.html b/site/index.html new file mode 100644 index 0000000..87e0722 --- /dev/null +++ b/site/index.html @@ -0,0 +1,25 @@ + + + + + + Site is online + + + +

    + +

    The site is online

    +

    This is the current version of the Dat Dillo test archive.

    +

    The earlier “site is being set up� page remains available at its pinned archive version.

    +
    + + diff --git a/src/base32768.c b/src/base32768.c new file mode 100644 index 0000000..12aa907 --- /dev/null +++ b/src/base32768.c @@ -0,0 +1,231 @@ +/* + * Base32768 alphabet and bit-packing ported from qntm/base32768. + * Upstream implementation: https://github.com/qntm/base32768 + * Copyright (c) 2021 qntm. Used under the MIT license; see + * third_party/base32768.LICENSE. + */ +#include "base32768.h" +#include +#include + +typedef struct base_range { + uint32_t first, last, offset; +} base_range_t; + +#define BASE32768_MAX_RANGES 128u +static base_range_t repertoire[2][BASE32768_MAX_RANGES]; +static size_t repertoire_count[2]; +static uint32_t repertoire_size[2]; +static int repertoire_ready; +static int repertoire_error; + +/* The pairs encode inclusive ranges. Keep in sync with upstream's pairStrings. */ +static const char *const pair_strings[2] = { + "ҠҿԀԟڀڿݠޟ߀ߟကဟႠႿᄀᅟᆀᆟᇠሿበቿዠዿጠጿᎠá�Ÿá� á™Ÿáš á›Ÿáž€ážŸá  á¡Ÿá£€á£Ÿá¦€á¦Ÿá§ á§¿á¨ á¨¿á¯€á¯Ÿá°€á°Ÿá´€á´Ÿâ‡ â‡¿â‹€â‹Ÿâ�€â�Ÿâ�€â�Ÿâ”€â�Ÿâž€âž¿â €â¥¿â¦ â¦¿â¨ â©Ÿâª€âª¿â« â­Ÿâ°€â°Ÿâ²€â³Ÿâ´€â´Ÿâµ€âµŸâº â»Ÿã‡€ã‡Ÿã�€ä¶Ÿä·€é¾¿ê€€ê‘¿ê’ ê’¿ê”€ê—¿ê™€ê™Ÿêš ê›Ÿêœ€ê�Ÿêž€êžŸê¡€ê¡Ÿ", + "ƀƟɀʟ" +}; + +static int utf8_next(const unsigned char *s, size_t length, size_t *offset, + uint32_t *codepoint) +{ + unsigned char a, b, c, d; + if (*offset >= length) return -1; + a = s[(*offset)++]; + if (a < 0x80) { *codepoint = a; return 0; } + if (a >= 0xc2 && a <= 0xdf) { + if (*offset >= length) return -1; + b = s[(*offset)++]; + if ((b & 0xc0) != 0x80) return -1; + *codepoint = ((uint32_t)(a & 0x1f) << 6) | (b & 0x3f); + return 0; + } + if (a >= 0xe0 && a <= 0xef) { + if (length - *offset < 2) return -1; + b = s[(*offset)++]; c = s[(*offset)++]; + if ((b & 0xc0) != 0x80 || (c & 0xc0) != 0x80 || + (a == 0xe0 && b < 0xa0) || (a == 0xed && b >= 0xa0)) return -1; + *codepoint = ((uint32_t)(a & 0x0f) << 12) | + ((uint32_t)(b & 0x3f) << 6) | (c & 0x3f); + return 0; + } + if (a >= 0xf0 && a <= 0xf4) { + if (length - *offset < 3) return -1; + b = s[(*offset)++]; c = s[(*offset)++]; d = s[(*offset)++]; + if ((b & 0xc0) != 0x80 || (c & 0xc0) != 0x80 || (d & 0xc0) != 0x80 || + (a == 0xf0 && b < 0x90) || (a == 0xf4 && b >= 0x90)) return -1; + *codepoint = ((uint32_t)(a & 7) << 18) | ((uint32_t)(b & 0x3f) << 12) | + ((uint32_t)(c & 0x3f) << 6) | (d & 0x3f); + return 0; + } + return -1; +} + +static size_t utf8_put(uint32_t cp, char *out) +{ + if (cp < 0x80) { out[0] = (char)cp; return 1; } + if (cp < 0x800) { + out[0] = (char)(0xc0 | (cp >> 6)); + out[1] = (char)(0x80 | (cp & 0x3f)); return 2; + } + if (cp < 0x10000) { + out[0] = (char)(0xe0 | (cp >> 12)); + out[1] = (char)(0x80 | ((cp >> 6) & 0x3f)); + out[2] = (char)(0x80 | (cp & 0x3f)); return 3; + } + out[0] = (char)(0xf0 | (cp >> 18)); + out[1] = (char)(0x80 | ((cp >> 12) & 0x3f)); + out[2] = (char)(0x80 | ((cp >> 6) & 0x3f)); + out[3] = (char)(0x80 | (cp & 0x3f)); return 4; +} + +static int initialize_repertoire(void) +{ + size_t row; + if (repertoire_ready) return repertoire_error ? -1 : 0; + repertoire_ready = 1; + for (row = 0; row < 2; row++) { + const char *text = pair_strings[row]; + size_t length = strlen(text), offset = 0; + uint32_t points[BASE32768_MAX_RANGES * 2u]; + size_t point_count = 0, i; + while (offset < length) { + if (point_count >= sizeof(points) / sizeof(points[0]) || + utf8_next((const unsigned char *)text, length, &offset, + &points[point_count])) { + repertoire_error = 1; return -1; + } + point_count++; + } + if (point_count % 2 || point_count / 2 > BASE32768_MAX_RANGES) { + repertoire_error = 1; return -1; + } + for (i = 0; i < point_count; i += 2) { + base_range_t *range = &repertoire[row][repertoire_count[row]++]; + if (points[i] > points[i + 1]) { repertoire_error = 1; return -1; } + range->first = points[i]; range->last = points[i + 1]; + range->offset = repertoire_size[row]; + repertoire_size[row] += points[i + 1] - points[i] + 1; + } + } + if (repertoire_size[0] != 32768u || repertoire_size[1] != 128u) { + repertoire_error = 1; return -1; + } + return 0; +} + +static int index_to_codepoint(unsigned bits, uint32_t index, uint32_t *out) +{ + size_t row = bits == 15 ? 0 : 1, i; + if (initialize_repertoire()) return -1; + for (i = 0; i < repertoire_count[row]; i++) { + const base_range_t *range = &repertoire[row][i]; + uint32_t count = range->last - range->first + 1; + if (index >= range->offset && index - range->offset < count) { + *out = range->first + index - range->offset; + return 0; + } + } + return -1; +} + +int base32768_codepoint_at(unsigned bits, uint32_t index, uint32_t *codepoint) +{ + if (!codepoint || (bits == 15 && index >= 32768u) || + (bits == 7 && index >= 128u) || (bits != 15 && bits != 7)) return -1; + return index_to_codepoint(bits, index, codepoint); +} + +static int codepoint_to_index(uint32_t cp, unsigned *bits, uint32_t *index) +{ + size_t row, i; + if (initialize_repertoire()) return -1; + for (row = 0; row < 2; row++) for (i = 0; i < repertoire_count[row]; i++) { + const base_range_t *range = &repertoire[row][i]; + if (cp >= range->first && cp <= range->last) { + *bits = row == 0 ? 15u : 7u; + *index = range->offset + cp - range->first; + return 0; + } + } + return -1; +} + +int base32768_encode(const uint8_t *input, size_t input_length, + char *output, size_t output_capacity) +{ + size_t i, used = 0; + uint32_t value = 0; + unsigned value_bits = 0; + if ((!input && input_length) || !output || !output_capacity) { errno = EINVAL; return -1; } + if (initialize_repertoire()) { errno = EPROTO; return -1; } + for (i = 0; i < input_length; i++) { + int bit; + for (bit = 7; bit >= 0; bit--) { + uint32_t cp; + char encoded[4]; + size_t n; + value = (value << 1) | ((input[i] >> bit) & 1u); + if (++value_bits == 15) { + if (index_to_codepoint(15, value, &cp)) { errno = EPROTO; return -1; } + n = utf8_put(cp, encoded); + if (used + n >= output_capacity) { errno = ENOSPC; return -1; } + memcpy(output + used, encoded, n); used += n; + value = 0; value_bits = 0; + } + } + } + if (value_bits) { + unsigned target_bits = value_bits >= 8 ? 15u : 7u; + uint32_t cp; + char encoded[4]; + size_t n; + while (value_bits < target_bits) { + value = (value << 1) | 1u; + value_bits++; + } + if (index_to_codepoint(target_bits, value, &cp)) { errno = EPROTO; return -1; } + n = utf8_put(cp, encoded); + if (used + n >= output_capacity) { errno = ENOSPC; return -1; } + memcpy(output + used, encoded, n); used += n; + } + output[used] = '\0'; + return 0; +} + +int base32768_decode(const char *input, uint8_t *output, + size_t output_capacity, size_t *output_length) +{ + size_t length, offset = 0, used = 0, i = 0, total_characters = 0, scan = 0; + unsigned pending_bits = 0; + uint32_t pending = 0; + if (!input || (!output && output_capacity) || !output_length) { errno = EINVAL; return -1; } + *output_length = 0; + length = strlen(input); + if (initialize_repertoire()) { errno = EPROTO; return -1; } + while (scan < length) { + uint32_t ignored; + if (utf8_next((const unsigned char *)input, length, &scan, &ignored)) { + errno = EILSEQ; return -1; + } + total_characters++; + } + while (offset < length) { + uint32_t cp, value; + unsigned bits, bit; + if (utf8_next((const unsigned char *)input, length, &offset, &cp) || + codepoint_to_index(cp, &bits, &value)) { errno = EILSEQ; return -1; } + if (bits == 7 && i + 1 != total_characters) { errno = EINVAL; return -1; } + for (bit = bits; bit > 0; bit--) { + pending = (pending << 1) | ((value >> (bit - 1)) & 1u); + if (++pending_bits == 8) { + if (used >= output_capacity) { errno = ENOSPC; return -1; } + output[used++] = (uint8_t)pending; + pending = 0; pending_bits = 0; + } + } + i++; + } + if (pending != ((1u << pending_bits) - 1u)) { errno = EINVAL; return -1; } + *output_length = used; + return 0; +} diff --git a/src/cable.c b/src/cable.c new file mode 100644 index 0000000..8fa36ef --- /dev/null +++ b/src/cable.c @@ -0,0 +1,200 @@ +#define _POSIX_C_SOURCE 200809L +#include "cable.h" +#include "sleep_crypto.h" +#include +#include +#include +#include +#include +#include +#include +#include + +static int hex_value(char c) +{ + if(c>='0'&&c<='9')return c-'0'; + if(c>='a'&&c<='f')return c-'a'+10; + if(c>='A'&&c<='F')return c-'A'+10; + return -1; +} + +int cable_address_parse(const char *address,uint8_t key[32],enum cable_address_scheme *scheme) +{ + const char *hex=address; enum cable_address_scheme parsed=0; size_t i; + if(!address||!key){errno=EINVAL;return -1;} + if(!strncmp(hex,"cabal://",8)){parsed=CABLE_ADDRESS_CABAL;hex+=8;} + else if(!strncmp(hex,"cable://",8)){parsed=CABLE_ADDRESS_CABLE;hex+=8;} + else if(!strncmp(hex,"hyper://",8)){parsed=CABLE_ADDRESS_HYPER;hex+=8;} + if(strlen(hex)!=64){errno=EINVAL;return -1;} + for(i=0;i<32;i++){int hi=hex_value(hex[i*2]),lo=hex_value(hex[i*2+1]);if(hi<0||lo<0){errno=EINVAL;return -1;}key[i]=(uint8_t)((hi<<4)|lo);} + if(scheme)*scheme=parsed?parsed:CABLE_ADDRESS_CABAL; + return 0; +} + +int cable_address_format(const uint8_t key[32],enum cable_address_scheme scheme,char *out,size_t cap) +{ + static const char digits[]="0123456789abcdef";const char *prefix;size_t i; + if(!key||!out){errno=EINVAL;return -1;} + switch(scheme){case CABLE_ADDRESS_CABAL:prefix="cabal://";break;case CABLE_ADDRESS_CABLE:prefix="cable://";break;case CABLE_ADDRESS_HYPER:prefix="hyper://";break;default:errno=EINVAL;return -1;} + if(cap<73){errno=ENOSPC;return -1;} + memcpy(out,prefix,8);for(i=0;i<32;i++){out[8+i*2]=digits[key[i]>>4];out[9+i*2]=digits[key[i]&15];}out[72]='\0';return 0; +} + +int cable_room_address_parse(const char *address,cable_room_address_t *room) +{ + const char *query; char key_text[73]; size_t key_len; + if(!address||!room){errno=EINVAL;return -1;} + memset(room,0,sizeof(*room)); + query=strchr(address,'?'); + key_len=query?(size_t)(query-address):strlen(address); + if(key_len>=sizeof(key_text)){errno=EINVAL;return -1;} + memcpy(key_text,address,key_len);key_text[key_len]='\0'; + if(cable_address_parse(key_text,room->key,&room->scheme))return -1; + if(query){ + static const char prefix[]="?admin="; + if(strncmp(query,prefix,sizeof(prefix)-1)){errno=EINVAL;return -1;} + if(cable_address_parse(query+sizeof(prefix)-1,room->admin_key,NULL))return -1; + room->has_admin=1; + } + return 0; +} + +int cable_room_address_format(const cable_room_address_t *room,enum cable_address_scheme scheme,char *out,size_t cap) +{ + char key[73],admin[73]; + if(!room||!out){errno=EINVAL;return -1;} + if(cable_address_format(room->key,scheme,key,sizeof(key)))return -1; + if(!room->has_admin){if(capadmin_key,CABLE_ADDRESS_CABAL,admin,sizeof(admin)))return -1; + if(cap<8+64+7+64+1){errno=ENOSPC;return -1;} + memcpy(out,key,72);memcpy(out+72,"?admin=",7);memcpy(out+79,admin+8,64);out[143]='\0';return 0; +} + +int cable_user_address_format(const uint8_t key[32],enum cable_address_scheme scheme,char *out,size_t cap) +{ + return cable_address_format(key,scheme,out,cap); +} + +int cable_user_identity_path(const char *home,const uint8_t room_key[32],char *out,size_t cap) +{ + static const char digits[]="0123456789abcdef"; + char base[1024],rooms[1056],roomdir[1120];size_t i;int n; + if(!home||!home[0]||!room_key||!out){errno=EINVAL;return -1;} + n=snprintf(base,sizeof(base),"%s/.cable",home); + if(n<0||(size_t)n>=sizeof(base)){errno=ENAMETOOLONG;return -1;} + if(mkdir(base,0700)<0&&errno!=EEXIST)return -1; + n=snprintf(rooms,sizeof(rooms),"%s/rooms",base); + if(n<0||(size_t)n>=sizeof(rooms)){errno=ENAMETOOLONG;return -1;} + if(mkdir(rooms,0700)<0&&errno!=EEXIST)return -1; + if(strlen(rooms)+1+64>=sizeof(roomdir)){errno=ENAMETOOLONG;return -1;} + strcpy(roomdir,rooms);strcat(roomdir,"/"); + for(i=0;i<32;i++){roomdir[strlen(rooms)+1+i*2]=digits[room_key[i]>>4];roomdir[strlen(rooms)+2+i*2]=digits[room_key[i]&15];} + roomdir[strlen(rooms)+65]='\0'; + if(mkdir(roomdir,0700)<0&&errno!=EEXIST)return -1; + n=snprintf(out,cap,"%s/user-feed.key",roomdir); + if(n<0||(size_t)n>=cap){errno=ENAMETOOLONG;return -1;} + return 0; +} + +int cable_room_create(const char *home,uint8_t room_key[32]) +{ + sleep_keypair_t room; char feed_path[1200], secret_path[1300]; + size_t offset=0; int fd=-1, saved=0, rc=-1; ssize_t wrote; + if(!home||!room_key){errno=EINVAL;return -1;} + secret_path[0]='\0'; + memset(&room,0,sizeof(room)); + if(sleep_crypto_init()||sleep_gen_keypair(&room))goto done; + if(cable_user_identity_path(home,room.pubkey,feed_path,sizeof(feed_path)))goto done; + { + static const char suffix[]="/user-feed.key"; + size_t n=strlen(feed_path); + if(n= (int)sizeof(secret_path)) { errno=ENAMETOOLONG; goto done; } + fd=open(secret_path,O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOFOLLOW,0600); + if(fd<0)goto done; + while(offset=0)close(fd); + if(rc&&secret_path[0])unlink(secret_path); + sodium_memzero(&room,sizeof(room)); + if(rc)errno=saved; + return rc; +} + +int cable_user_identity_load_or_create(const char *path,sleep_keypair_t *identity) +{ + int fd; struct stat st; char parent[1024]; char *slash; ssize_t n; uint8_t check[32]; + if(!path||!identity||strlen(path)>=sizeof(parent)){errno=EINVAL;return -1;} + if(sleep_crypto_init())return -1; + strcpy(parent,path);slash=strrchr(parent,'/'); + if(slash){*slash='\0';if(parent[0]&&mkdir(parent,0700)<0&&errno!=EEXIST)return -1;} + fd=open(path,O_RDONLY|O_CLOEXEC|O_NOFOLLOW); + if(fd>=0){ + if(fstat(fd,&st)||!S_ISREG(st.st_mode)||st.st_size!=crypto_sign_SECRETKEYBYTES){close(fd);errno=EINVAL;return -1;} + if((st.st_mode&077)!=0&&fchmod(fd,0600)){int saved=errno;close(fd);errno=saved;return -1;} + n=read(fd,identity->seckey,crypto_sign_SECRETKEYBYTES);close(fd); + if(n!=crypto_sign_SECRETKEYBYTES||crypto_sign_ed25519_sk_to_pk(identity->pubkey,identity->seckey)!=0){errno=EIO;return -1;} + if(crypto_sign_ed25519_sk_to_pk(check,identity->seckey)!=0||sodium_memcmp(check,identity->pubkey,32)){errno=EINVAL;return -1;} + return 0; + } + if(errno!=ENOENT)return -1; + if(sleep_gen_keypair(identity))return -1; + fd=open(path,O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOFOLLOW,0600); + if(fd<0){if(errno==EEXIST)return cable_user_identity_load_or_create(path,identity);return -1;} + n=write(fd,identity->seckey,crypto_sign_SECRETKEYBYTES); + if(n!=crypto_sign_SECRETKEYBYTES){int saved=errno?errno:EIO;close(fd);unlink(path);errno=saved;return -1;} + if(fsync(fd)){int saved=errno;close(fd);unlink(path);errno=saved;return -1;} + if(close(fd)){int saved=errno;unlink(path);errno=saved;return -1;} + return 0; +} + +static void put64be(uint8_t *p,uint64_t v){int i;for(i=0;i<8;i++)p[i]=(uint8_t)(v>>(56-8*i));} +static uint64_t get64be(const uint8_t *p){uint64_t v=0;int i;for(i=0;i<8;i++)v=(v<<8)|p[i];return v;} + +int cable_chat_message_encode(const cable_chat_message_t *m,uint8_t *out,size_t cap,size_t *written) +{ + size_t total; + if(!m||!out||!written||m->text_length>CABLE_CHAT_TEXT_BYTES||strlen(m->text)!=m->text_length){errno=EINVAL;return -1;} + total=14+m->text_length;if(captimestamp_ms);out[12]=(uint8_t)(m->text_length>>8);out[13]=(uint8_t)m->text_length; + memcpy(out+14,m->text,m->text_length);*written=total;return 0; +} + +int cable_chat_message_decode(cable_chat_message_t *m,const uint8_t *in,size_t n) +{ + size_t text_len; + if(!m||!in||n<14||memcmp(in,"CBL1",4)){errno=EPROTO;return -1;} + text_len=((size_t)in[12]<<8)|in[13];if(text_len>CABLE_CHAT_TEXT_BYTES||n!=14+text_len||memchr(in+14,0,text_len)){errno=EPROTO;return -1;} + memset(m,0,sizeof(*m));m->timestamp_ms=get64be(in+4);memcpy(m->text,in+14,text_len);m->text_length=text_len;return 0; +} + +int cable_chat_seal(const uint8_t recipient[32],const uint8_t *message,size_t n,uint8_t *out,size_t cap,size_t *written) +{ + uint8_t curve[crypto_box_PUBLICKEYBYTES]; + if(!recipient||(!message&&n)||!out||!written){errno=EINVAL;return -1;} + if(cappubkey)!=0||crypto_sign_ed25519_sk_to_curve25519(curve_sk,identity->seckey)!=0){errno=EINVAL;return -1;} + if(crypto_box_seal_open(out,sealed,(unsigned long long)n,curve_pk,curve_sk)!=0){errno=EACCES;return -1;} + sodium_memzero(curve_sk,sizeof(curve_sk));*written=plain;return 0; +} diff --git a/src/cable_cli.c b/src/cable_cli.c new file mode 100644 index 0000000..1459c1f --- /dev/null +++ b/src/cable_cli.c @@ -0,0 +1,220 @@ +#define _POSIX_C_SOURCE 200809L +#include "cable.h" +#include "cesspool.h" +#include "dat_s7.h" +#include "sleep_crypto.h" +#include "dat_paths.h" +#include +#include +#include +#include +#include +#include +#include +#include + +static void usage(FILE *f) +{ + fputs("cable - C Cabal/Cable address and identity utility\n" + "The working historical Cabal chat TUI is launched with: cabal-cli \n" + "The native SDL2 Cabal/Cable chat client is cable-thang.\n" + "usage:\n" + " cable --new\n" + " cable address \n" + " cable whoami \n" + " cable nick \n" + " cable publish \n" + " cable feed \n" + " cable bootstrap add \n" + " cable bootstrap set \n" + " cable bootstrap list\n" + " cable bootstrap serve \n" + " cable --version\n",f); +} + +static const char *cable_cli_home_directory(void) +{ + return dat_home_directory(); +} + +static int command_bootstrap(int argc, char **argv) +{ + char endpoint[512], *colon, *end; + unsigned long port; + if (argc == 5 && !strcmp(argv[2], "serve")) { + cesspool_t pool; + sleep_keypair_t identity; + char *port_end; + port = strtoul(argv[4], &port_end, 10); + if (!argv[3][0] || strchr(argv[3], ':') || *port_end || !port || port > 65535) { + fprintf(stderr, "usage: cable bootstrap serve \n"); + return 2; + } + if (sleep_crypto_init() || sleep_gen_keypair(&identity) || + cesspool_init(&pool, identity.pubkey) || + cesspool_listen(&pool, "0.0.0.0", (uint16_t)port) || + cesspool_set_bootstrap_server(&pool, 1)) { + perror("cable bootstrap serve"); + return 1; + } + printf("Cesspool rendezvous listening on %s:%u (Ctrl+C to stop)\n", argv[3], pool.port); + fflush(stdout); + for (;;) if (cesspool_poll(&pool, 1000) < 0 && errno != EINTR) { + perror("cable bootstrap serve"); + return 1; + } + } + if (argc == 4 && (!strcmp(argv[2], "add") || !strcmp(argv[2], "set"))) { + int replace = !strcmp(argv[2], "set"); + if (strlen(argv[3]) >= sizeof(endpoint)) { errno = ENAMETOOLONG; return 2; } + strcpy(endpoint, argv[3]); + colon = strrchr(endpoint, ':'); + if (!colon || colon == endpoint || !colon[1]) { errno = EINVAL; return 2; } + *colon++ = '\0'; port = strtoul(colon, &end, 10); + if (*end || !port || port > 65535) { errno = EINVAL; return 2; } + if ((replace ? cesspool_bootstrap_config_set(argv[3]) : + cesspool_bootstrap_config_add(argv[3]))) { perror("cable bootstrap"); return 1; } + printf("Saved Cesspool rendezvous %s:%lu%s\n", endpoint, port, + replace ? " as the active node" : ""); + return 0; + } + if (argc == 3 && !strcmp(argv[2], "list")) { + char contents[4096]; + if (cesspool_bootstrap_config_list(contents, sizeof(contents))) { + if (errno == ENOENT) { puts("No Cesspool rendezvous configured"); return 0; } + perror("cable bootstrap list"); return 1; + } + fputs(contents, stdout); + return 0; + } + return 2; +} + +static s7_pointer json_field(s7_scheme *sc, const char *name, s7_pointer value) +{ + return s7_cons(sc, s7_make_string(sc, name), value); +} + +static char *scheme_cabal_record(int about, const char *channel_or_nick, + const char *text, unsigned long long timestamp, + size_t *length) +{ + dat_scheme_runtime_t runtime = {0}; + s7_scheme *sc; + s7_pointer content, record, fields, encoder, encoded; + char *copy = NULL; + if (dat_scheme_runtime_init(&runtime)) return NULL; + sc = runtime.scheme; + if (about) { + content = s7_cons(sc, s7_make_symbol(sc, "json:object"), + s7_list(sc, 1, json_field(sc, "name", s7_make_string(sc, channel_or_nick)))); + fields = s7_list(sc, 3, + json_field(sc, "type", s7_make_string(sc, "about")), + json_field(sc, "content", content), + json_field(sc, "timestamp", s7_make_integer(sc, (s7_int)timestamp))); + } else { + content = s7_cons(sc, s7_make_symbol(sc, "json:object"), + s7_list(sc, 2, + json_field(sc, "channel", s7_make_string(sc, channel_or_nick)), + json_field(sc, "text", s7_make_string(sc, text)))); + fields = s7_list(sc, 3, + json_field(sc, "type", s7_make_string(sc, "chat/text")), + json_field(sc, "content", content), + json_field(sc, "timestamp", s7_make_integer(sc, (s7_int)timestamp))); + } + record = s7_cons(sc, s7_make_symbol(sc, "json:object"), fields); + encoder = s7_name_to_value(sc, "dat:json-stringify"); + encoded = s7_call(sc, encoder, s7_list(sc, 1, record)); + if (s7_is_string(encoded)) { + const char *value = s7_string(encoded); + *length = strlen(value); + copy = malloc(*length ? *length : 1); + if (copy && *length) memcpy(copy, value, *length); + } + dat_scheme_runtime_close(&runtime); + return copy; +} + +static int command_publish(int argc, char **argv, int about) +{ + const char *home = cable_cli_home_directory(); + cable_room_address_t room; + sleep_keypair_t identity; + sleep_feed_t feed; + uint8_t user_key[32]; + char *record = NULL; + size_t record_length = 0, index; + struct timespec now; + unsigned long long stamp; + int rc = 1; + if ((!about && argc != 5) || (about && argc != 4) || !home || + cable_room_address_parse(argv[2], &room)) { + fputs("cable publish: invalid room address or arguments\n", stderr); return 2; + } + if (clock_gettime(CLOCK_REALTIME, &now)) goto done; + stamp = (unsigned long long)now.tv_sec * 1000u + (unsigned long long)now.tv_nsec / 1000000u; + record = scheme_cabal_record(about, argv[3], about ? NULL : argv[4], stamp, &record_length); + if (!record) { errno = EPROTO; goto done; } + if (cable_user_feed_append(home, room.key, (const uint8_t *)record, record_length, user_key)) { + perror("cable publish"); goto done; + } + if (cable_user_feed_open(home, room.key, &identity, &feed)) { perror("cable publish reopen"); goto done; } + index = sleep_feed_length(&feed) ? sleep_feed_length(&feed) - 1 : 0; + printf("cable://"); + for (size_t i = 0; i < sizeof(user_key); i++) printf("%02x", user_key[i]); + printf("@%zu\n", index); + sleep_feed_close(&feed); sodium_memzero(&identity, sizeof(identity)); rc = 0; +done: + free(record); sodium_memzero(user_key, sizeof(user_key)); return rc; +} + +static int command_feed(int argc, char **argv) +{ + const char *home = cable_cli_home_directory(); + cable_room_address_t room; + sleep_keypair_t identity; + sleep_feed_t feed; + size_t i; + if (argc != 3 || !home || cable_room_address_parse(argv[2], &room)) return 2; + if (cable_user_feed_open(home, room.key, &identity, &feed)) { perror("cable feed"); return 1; } + for (i = 0; i < sleep_feed_length(&feed); i++) { + uint8_t *block = NULL; size_t length = 0; + if (sleep_feed_read(&feed, i, &block, &length) || + (length && fwrite(block, 1, length, stdout) != length) || putchar('\n') == EOF) { + free(block); sleep_feed_close(&feed); sodium_memzero(&identity, sizeof(identity)); return 1; + } + free(block); + } + sleep_feed_close(&feed); sodium_memzero(&identity, sizeof(identity)); return 0; +} + +int main(int argc,char **argv) +{ + char out[160]; + if(argc==2&&!strcmp(argv[1],"--version")){puts("cable 0.1.0-dev");return 0;} + if(argc==2&&!strcmp(argv[1],"--help")){usage(stdout);return 0;} + if(argc>=2&&!strcmp(argv[1],"nick"))return command_publish(argc,argv,1); + if(argc>=2&&!strcmp(argv[1],"publish"))return command_publish(argc,argv,0); + if(argc>=2&&!strcmp(argv[1],"feed"))return command_feed(argc,argv); + if(argc>=2&&!strcmp(argv[1],"bootstrap"))return command_bootstrap(argc,argv); + if(argc==2&&!strcmp(argv[1],"--new")){ + const char *home=cable_cli_home_directory();uint8_t room_key[32]; + if(!home||cable_room_create(home,room_key)){perror("cable --new");return 1;} + if(cable_address_format(room_key,CABLE_ADDRESS_CABLE,out,sizeof(out))){sodium_memzero(room_key,sizeof(room_key));perror("cable --new");return 1;} + puts(out);sodium_memzero(room_key,sizeof(room_key));return 0; + } + if(argc==3&&!strcmp(argv[1],"whoami")){ + const char *home=cable_cli_home_directory();sleep_keypair_t identity;cable_room_address_t room;char path[1200]; + if(!home||cable_room_address_parse(argv[2],&room)){fputs("cable whoami: supply a valid room address\n",stderr);return 2;} + if(cable_user_identity_path(home,room.key,path,sizeof(path))||cable_user_identity_load_or_create(path,&identity)){perror("cable whoami");return 1;} + if(cable_user_address_format(identity.pubkey,CABLE_ADDRESS_CABLE,out,sizeof(out))){perror("cable whoami");return 1;} + puts(out);sodium_memzero(&identity,sizeof(identity));return 0; + } + if(argc==3&&!strcmp(argv[1],"address")){ + cable_room_address_t room; + if(cable_room_address_parse(argv[2],&room)){fputs("cable: invalid Cabal/Cable room address\n",stderr);return 2;} + if(cable_room_address_format(&room,CABLE_ADDRESS_CABLE,out,sizeof(out))){perror("cable address");return 1;} + puts(out);return 0; + } + usage(argc==1?stdout:stderr);return argc==1?0:2; +} diff --git a/src/cable_feed.c b/src/cable_feed.c new file mode 100644 index 0000000..d5c2541 --- /dev/null +++ b/src/cable_feed.c @@ -0,0 +1,205 @@ +#define _POSIX_C_SOURCE 200809L +#include "cable.h" +#include "cable_room.h" +#include "sleep_crypto.h" +#include +#include +#include +#include +#include +#include +#include + +static int ensure_link(const char *directory, const char *link_name, + const char *target) +{ + char path[1400], observed[1024]; + ssize_t length; + struct stat info; + int n = snprintf(path, sizeof(path), "%s/%s", directory, link_name); + if (n < 0 || (size_t)n >= sizeof(path)) { errno = ENAMETOOLONG; return -1; } + if (symlink(target, path) == 0) return 0; + if (errno != EEXIST) return -1; + if (lstat(path, &info) || !S_ISLNK(info.st_mode)) { errno = EEXIST; return -1; } + length = readlink(path, observed, sizeof(observed) - 1); + if (length < 0) return -1; + observed[length] = '\0'; + if (strcmp(observed, target)) { errno = EEXIST; return -1; } + return 0; +} + +static int ensure_localname(const char *directory) +{ + char path[1400], contents[16]; + FILE *file; + size_t length; + int n = snprintf(path, sizeof(path), "%s/localname", directory); + if (n < 0 || (size_t)n >= sizeof(path)) { errno = ENAMETOOLONG; return -1; } + file = fopen(path, "rb"); + if (file) { + length = fread(contents, 1, sizeof(contents), file); + if (ferror(file) || fclose(file)) return -1; + if (length != 5 || memcmp(contents, "local", 5)) { errno = EEXIST; return -1; } + return 0; + } + if (errno != ENOENT) return -1; + file = fopen(path, "wbx"); + if (!file) return -1; + if (fwrite("local", 1, 5, file) != 5 || fflush(file)) { + int saved = errno ? errno : EIO; + fclose(file); errno = saved; return -1; + } + if (fclose(file)) return -1; + return 0; +} + +int cable_user_feed_open(const char *home, const uint8_t room_key[32], + sleep_keypair_t *identity, sleep_feed_t *feed) +{ + char identity_path[1200], room_dir[1200], feed_dir[1240]; + char *slash; + int n; + if (!home || !room_key || !identity || !feed) { errno = EINVAL; return -1; } + if (cable_user_identity_path(home, room_key, identity_path, + sizeof(identity_path)) || + cable_user_identity_load_or_create(identity_path, identity)) return -1; + slash = strrchr(identity_path, '/'); + if (!slash) { errno = EINVAL; goto fail_identity; } + *slash = '\0'; + n = snprintf(room_dir, sizeof(room_dir), "%s/0", identity_path); + if (n < 0 || (size_t)n >= sizeof(room_dir)) { errno = ENAMETOOLONG; goto fail_identity; } + if (mkdir(room_dir, 0700) < 0 && errno != EEXIST) goto fail_identity; + n = snprintf(feed_dir, sizeof(feed_dir), "%s", room_dir); + if (n < 0 || (size_t)n >= sizeof(feed_dir)) { errno = ENAMETOOLONG; goto fail_identity; } + /* Kappa Core 3 / multifeed 3 stores the local writer in room/0 and + * addresses its storage by the bare Hypercore names. Keep the SLEEP + * filename convention as symlinked aliases over the same Hypercore files. */ + if (ensure_link(feed_dir, "feed.tree", "tree") || + ensure_link(feed_dir, "feed.signatures", "signatures") || + ensure_link(feed_dir, "feed.bitfield", "bitfield") || + ensure_link(feed_dir, "feed.data", "data") || + ensure_link(feed_dir, "feed.key", "key") || + ensure_link(feed_dir, "secret_key", "../user-feed.key") || + ensure_localname(feed_dir)) goto fail_identity; + if (sleep_feed_open(feed, feed_dir, "feed", identity)) goto fail_identity; + return 0; +fail_identity: + sodium_memzero(identity, sizeof(*identity)); + return -1; +} + +int cable_user_feed_append(const char *home, const uint8_t room_key[32], + const uint8_t *record, size_t record_length, + uint8_t user_key[32]) +{ + sleep_keypair_t identity; + sleep_feed_t feed; + int rc = -1; + memset(&identity, 0, sizeof(identity)); + memset(&feed, 0, sizeof(feed)); + if (!record || !record_length || !user_key) { errno = EINVAL; return -1; } + if (cable_user_feed_open(home, room_key, &identity, &feed)) return -1; + if (sleep_feed_append(&feed, record, record_length) || sleep_feed_flush(&feed)) goto done; + memcpy(user_key, identity.pubkey, sizeof(identity.pubkey)); + rc = 0; +done: + sleep_feed_close(&feed); + sodium_memzero(&identity, sizeof(identity)); + return rc; +} + +static int append_json_text(char *out, size_t capacity, size_t *offset, + const char *text) +{ + const unsigned char *p = (const unsigned char *)text; + if (*offset >= capacity) { errno = ENOSPC; return -1; } + out[(*offset)++] = '"'; + for (; *p; p++) { + const char *escaped = NULL; + char control[7]; + switch (*p) { + case '"': escaped = "\\\""; break; + case '\\': escaped = "\\\\"; break; + case '\b': escaped = "\\b"; break; + case '\f': escaped = "\\f"; break; + case '\n': escaped = "\\n"; break; + case '\r': escaped = "\\r"; break; + case '\t': escaped = "\\t"; break; + default: break; + } + if (*p < 0x20 && !escaped) { + snprintf(control, sizeof(control), "\\u%04x", *p); + escaped = control; + } + if (escaped) { + size_t n = strlen(escaped); + if (n > capacity - *offset) { errno = ENOSPC; return -1; } + memcpy(out + *offset, escaped, n); *offset += n; + } else { + if (*offset >= capacity) { errno = ENOSPC; return -1; } + out[(*offset)++] = (char)*p; + } + } + if (*offset >= capacity) { errno = ENOSPC; return -1; } + out[(*offset)++] = '"'; + return 0; +} + +int cable_user_feed_publish(const char *home, const uint8_t room_key[32], + const char *record_type, const char *nick, + const char *channel, const char *text, + uint8_t user_key[32]) +{ + char *record; + size_t capacity, offset = 0; + struct timespec now; + unsigned long long timestamp; + int n, rc = -1; + if (!home || !room_key || !record_type || !user_key || + (strcmp(record_type, "about") && strcmp(record_type, "chat/text")) || + (!strcmp(record_type, "about") && !nick) || + (!strcmp(record_type, "chat/text") && (!channel || !text))) { + errno = EINVAL; return -1; + } + if (strlen(record_type) > 16 || (nick && strlen(nick) > CABLE_CHAT_TEXT_BYTES) || + (channel && strlen(channel) > CABLE_ROOM_CHANNEL_BYTES) || + (text && strlen(text) > CABLE_CHAT_TEXT_BYTES)) { errno = EMSGSIZE; return -1; } + capacity = 512 + strlen(record_type) * 6 + (nick ? strlen(nick) * 6 : 0) + + (channel ? strlen(channel) * 6 : 0) + (text ? strlen(text) * 6 : 0); + record = malloc(capacity); + if (!record) return -1; + if (clock_gettime(CLOCK_REALTIME, &now)) goto done; + timestamp = (unsigned long long)now.tv_sec * 1000u + + (unsigned long long)now.tv_nsec / 1000000u; + n = snprintf(record, capacity, "{\"type\":"); + if (n < 0 || (size_t)n >= capacity) { errno = ENOSPC; goto done; } + offset = (size_t)n; + if (append_json_text(record, capacity, &offset, record_type)) goto done; + n = snprintf(record + offset, capacity - offset, ",\"content\":{"); + if (n < 0 || (size_t)n >= capacity - offset) { errno = ENOSPC; goto done; } + offset += (size_t)n; + if (!strcmp(record_type, "about")) { + n = snprintf(record + offset, capacity - offset, "\"name\":"); + if (n < 0 || (size_t)n >= capacity - offset) { errno = ENOSPC; goto done; } + offset += (size_t)n; + if (append_json_text(record, capacity, &offset, nick)) goto done; + } else { + n = snprintf(record + offset, capacity - offset, "\"channel\":"); + if (n < 0 || (size_t)n >= capacity - offset) { errno = ENOSPC; goto done; } + offset += (size_t)n; + if (append_json_text(record, capacity, &offset, channel)) goto done; + n = snprintf(record + offset, capacity - offset, ",\"text\":"); + if (n < 0 || (size_t)n >= capacity - offset) { errno = ENOSPC; goto done; } + offset += (size_t)n; + if (append_json_text(record, capacity, &offset, text)) goto done; + } + n = snprintf(record + offset, capacity - offset, + "},\"timestamp\":%llu}", timestamp); + if (n < 0 || (size_t)n >= capacity - offset) { errno = ENOSPC; goto done; } + offset += (size_t)n; + rc = cable_user_feed_append(home, room_key, (const uint8_t *)record, + offset, user_key); +done: + free(record); + return rc; +} diff --git a/src/cable_media.c b/src/cable_media.c new file mode 100644 index 0000000..1c7820b --- /dev/null +++ b/src/cable_media.c @@ -0,0 +1,303 @@ +#include "cable_media.h" +#include +#include +#include + +#ifdef CABLE_HAVE_FFMPEG +#include +#include +#include +#include +#include + +typedef struct cable_aac_encoder_impl { + AVCodecContext *codec; + AVFrame *frame; + AVPacket *packet; + int64_t pts; +} cable_aac_encoder_impl_t; + +typedef struct cable_aac_decoder_impl { + AVCodecContext *codec; + AVPacket *packet; + AVFrame *frame; + SwrContext *resampler; +} cable_aac_decoder_impl_t; +#endif + +static void put_u16(uint8_t *p, uint16_t value) +{ + p[0] = (uint8_t)(value >> 8); p[1] = (uint8_t)value; +} + +static uint16_t get_u16(const uint8_t *p) +{ + return (uint16_t)(((uint16_t)p[0] << 8) | p[1]); +} + +static void put_u64(uint8_t *p, uint64_t value) +{ + unsigned i; + for (i = 0; i < 8; i++) p[i] = (uint8_t)(value >> (56u - i * 8u)); +} + +static uint64_t get_u64(const uint8_t *p) +{ + uint64_t value = 0; + unsigned i; + for (i = 0; i < 8; i++) value = (value << 8) | p[i]; + return value; +} + +int cable_media_packet_write(const cable_media_packet_t *packet, + uint8_t *output, size_t capacity, size_t *written) +{ + size_t total; + if (!packet || !output || !written || !packet->kind || + packet->payload_length > CABLE_MEDIA_MAX_PACKET_BYTES - CABLE_MEDIA_HEADER_BYTES || + (!packet->payload && packet->payload_length)) { errno = EINVAL; return -1; } + total = CABLE_MEDIA_HEADER_BYTES + packet->payload_length; + if (capacity < total) { errno = ENOSPC; return -1; } + memcpy(output, "CBLM", 4); + output[4] = 1; + output[5] = packet->kind; + output[6] = output[7] = 0; + memcpy(output + 8, packet->session, CABLE_MEDIA_SESSION_BYTES); + put_u64(output + 24, packet->sequence); + put_u64(output + 32, packet->timestamp_us); + put_u16(output + 40, (uint16_t)packet->payload_length); + if (packet->payload_length) memcpy(output + CABLE_MEDIA_HEADER_BYTES, packet->payload, packet->payload_length); + *written = total; + return 0; +} + +int cable_media_packet_read(cable_media_packet_t *packet, + const uint8_t *input, size_t length) +{ + size_t payload_length; + if (!packet || !input || length < CABLE_MEDIA_HEADER_BYTES || + memcmp(input, "CBLM", 4) || input[4] != 1 || + input[5] < CABLE_MEDIA_AUDIO_START || input[5] > CABLE_MEDIA_VIDEO_STOP) { + errno = EPROTO; return -1; + } + payload_length = get_u16(input + 40); + if (length != CABLE_MEDIA_HEADER_BYTES + payload_length || + length > CABLE_MEDIA_MAX_PACKET_BYTES) { errno = EMSGSIZE; return -1; } + memset(packet, 0, sizeof(*packet)); + packet->kind = input[5]; + memcpy(packet->session, input + 8, CABLE_MEDIA_SESSION_BYTES); + packet->sequence = get_u64(input + 24); + packet->timestamp_us = get_u64(input + 32); + packet->payload = input + CABLE_MEDIA_HEADER_BYTES; + packet->payload_length = payload_length; + return 0; +} + +int cable_aac_encoder_open(cable_aac_encoder_t *encoder) +{ +#ifdef CABLE_HAVE_FFMPEG + const AVCodec *implementation; + cable_aac_encoder_impl_t *state; + if (!encoder) { errno = EINVAL; return -1; } + encoder->implementation = NULL; + implementation = avcodec_find_encoder(AV_CODEC_ID_AAC); + if (!implementation) { errno = ENOSYS; return -1; } + state = (cable_aac_encoder_impl_t *)calloc(1, sizeof(*state)); + if (!state) return -1; + state->codec = avcodec_alloc_context3(implementation); + state->frame = av_frame_alloc(); + state->packet = av_packet_alloc(); + if (!state->codec || !state->frame || !state->packet) goto fail; + state->codec->sample_rate = CABLE_MEDIA_AAC_RATE; + state->codec->bit_rate = CABLE_MEDIA_AAC_BITRATE; + state->codec->sample_fmt = AV_SAMPLE_FMT_FLTP; + state->codec->profile = AV_PROFILE_AAC_LOW; + state->codec->time_base = (AVRational){1, CABLE_MEDIA_AAC_RATE}; + av_channel_layout_default(&state->codec->ch_layout, CABLE_MEDIA_AAC_CHANNELS); + if (avcodec_open2(state->codec, implementation, NULL) < 0) goto fail; + state->frame->format = state->codec->sample_fmt; + state->frame->sample_rate = state->codec->sample_rate; + if (av_channel_layout_copy(&state->frame->ch_layout, &state->codec->ch_layout) < 0 || + av_frame_get_buffer(state->frame, 0) < 0) goto fail; + encoder->implementation = state; + return 0; +fail: + if (state->packet) av_packet_free(&state->packet); + if (state->frame) av_frame_free(&state->frame); + if (state->codec) avcodec_free_context(&state->codec); + free(state); errno = EIO; return -1; +#else + (void)encoder; errno = ENOSYS; return -1; +#endif +} + +static int adts_header(uint8_t header[7], size_t payload_length) +{ + size_t frame_length = payload_length + 7u; + if (frame_length > 0x1fffu) { errno = EMSGSIZE; return -1; } + /* AAC-LC, 48 kHz, mono, MPEG-4 ADTS, no CRC. */ + header[0] = 0xff; header[1] = 0xf1; + header[2] = (uint8_t)((1u << 6) | (3u << 2)); + header[3] = (uint8_t)((1u << 6) | ((frame_length >> 11) & 3u)); + header[4] = (uint8_t)(frame_length >> 3); + header[5] = (uint8_t)(((frame_length & 7u) << 5) | 0x1fu); + header[6] = 0xfcu; + return 0; +} + +int cable_aac_encoder_frame(cable_aac_encoder_t *encoder, + const int16_t samples[CABLE_MEDIA_AAC_SAMPLES], + cable_aac_frame_fn output, void *context) +{ +#ifdef CABLE_HAVE_FFMPEG + cable_aac_encoder_impl_t *state = encoder ? (cable_aac_encoder_impl_t *)encoder->implementation : NULL; + size_t i; + int result; + if (!state || !samples || !output) { errno = EINVAL; return -1; } + if (av_frame_make_writable(state->frame) < 0) { errno = EIO; return -1; } + for (i = 0; i < CABLE_MEDIA_AAC_SAMPLES; i++) + ((float *)state->frame->data[0])[i] = (float)samples[i] / 32768.0f; + state->frame->nb_samples = CABLE_MEDIA_AAC_SAMPLES; + state->frame->pts = state->pts; + state->pts += CABLE_MEDIA_AAC_SAMPLES; + if (avcodec_send_frame(state->codec, state->frame) < 0) { errno = EIO; return -1; } + while ((result = avcodec_receive_packet(state->codec, state->packet)) >= 0) { + uint8_t adts[CABLE_MEDIA_MAX_PACKET_BYTES]; + size_t total; + if (state->packet->size < 0 || + (size_t)state->packet->size + 7u > sizeof(adts) || + adts_header(adts, (size_t)state->packet->size)) { + av_packet_unref(state->packet); errno = EMSGSIZE; return -1; + } + memcpy(adts + 7, state->packet->data, (size_t)state->packet->size); + total = (size_t)state->packet->size + 7u; + av_packet_unref(state->packet); + if (output(context, adts, total)) return -1; + } + if (result != AVERROR(EAGAIN) && result != AVERROR_EOF) { errno = EIO; return -1; } + return 0; +#else + (void)encoder; (void)samples; (void)output; (void)context; errno = ENOSYS; return -1; +#endif +} + +void cable_aac_encoder_close(cable_aac_encoder_t *encoder) +{ +#ifdef CABLE_HAVE_FFMPEG + cable_aac_encoder_impl_t *state; + if (!encoder || !encoder->implementation) return; + state = (cable_aac_encoder_impl_t *)encoder->implementation; + av_packet_free(&state->packet); av_frame_free(&state->frame); avcodec_free_context(&state->codec); + free(state); encoder->implementation = NULL; +#else + (void)encoder; +#endif +} + +int cable_aac_decoder_open(cable_aac_decoder_t *decoder) +{ +#ifdef CABLE_HAVE_FFMPEG + const AVCodec *implementation; + cable_aac_decoder_impl_t *state; + AVChannelLayout output_layout; + if (!decoder) { errno = EINVAL; return -1; } + decoder->implementation = NULL; + implementation = avcodec_find_decoder(AV_CODEC_ID_AAC); + if (!implementation) { errno = ENOSYS; return -1; } + state = (cable_aac_decoder_impl_t *)calloc(1, sizeof(*state)); + if (!state) return -1; + state->codec = avcodec_alloc_context3(implementation); + state->packet = av_packet_alloc(); + state->frame = av_frame_alloc(); + if (!state->codec || !state->packet || !state->frame) goto fail; + state->codec->sample_rate = CABLE_MEDIA_AAC_RATE; + state->codec->extradata = (uint8_t *)av_mallocz(2 + AV_INPUT_BUFFER_PADDING_SIZE); + if (!state->codec->extradata) goto fail; + state->codec->extradata[0] = 0x11; + state->codec->extradata[1] = 0x88; /* AAC-LC, 48 kHz, mono AudioSpecificConfig. */ + state->codec->extradata_size = 2; + av_channel_layout_default(&state->codec->ch_layout, CABLE_MEDIA_AAC_CHANNELS); + if (avcodec_open2(state->codec, implementation, NULL) < 0) goto fail; + av_channel_layout_default(&output_layout, CABLE_MEDIA_AAC_CHANNELS); + if (swr_alloc_set_opts2(&state->resampler, + &output_layout, AV_SAMPLE_FMT_S16, CABLE_MEDIA_AAC_RATE, + &state->codec->ch_layout, + AV_SAMPLE_FMT_FLTP, CABLE_MEDIA_AAC_RATE, 0, NULL) < 0 || + !state->resampler || swr_init(state->resampler) < 0) { + av_channel_layout_uninit(&output_layout); + goto fail; + } + av_channel_layout_uninit(&output_layout); + decoder->implementation = state; + return 0; +fail: + if (state->resampler) swr_free(&state->resampler); + if (state->frame) av_frame_free(&state->frame); + if (state->packet) av_packet_free(&state->packet); + if (state->codec) avcodec_free_context(&state->codec); + free(state); errno = EIO; return -1; +#else + (void)decoder; errno = ENOSYS; return -1; +#endif +} + +int cable_aac_decoder_frame(cable_aac_decoder_t *decoder, + const uint8_t *adts, size_t length, + cable_pcm_samples_fn output, void *context) +{ +#ifdef CABLE_HAVE_FFMPEG + cable_aac_decoder_impl_t *state = decoder ? (cable_aac_decoder_impl_t *)decoder->implementation : NULL; + size_t header_length, frame_length; + int result; + if (!state || !adts || !output || length < 7 || adts[0] != 0xff || + (adts[1] & 0xf6) != 0xf0) { errno = EINVAL; return -1; } + header_length = (adts[1] & 1) ? 7u : 9u; + frame_length = ((size_t)(adts[3] & 3u) << 11) | ((size_t)adts[4] << 3) | (adts[5] >> 5); + if (frame_length != length || frame_length < header_length || frame_length > CABLE_MEDIA_MAX_PACKET_BYTES) { + errno = EMSGSIZE; return -1; + } + if (av_new_packet(state->packet, (int)(frame_length - header_length)) < 0) { errno = ENOMEM; return -1; } + memcpy(state->packet->data, adts + header_length, frame_length - header_length); + result = avcodec_send_packet(state->codec, state->packet); + av_packet_unref(state->packet); + if (result < 0) { errno = EPROTO; return -1; } + while ((result = avcodec_receive_frame(state->codec, state->frame)) >= 0) { + int16_t *pcm; + uint8_t *output_planes[1]; + int capacity = (int)av_rescale_rnd(swr_get_delay(state->resampler, state->codec->sample_rate) + + state->frame->nb_samples, + CABLE_MEDIA_AAC_RATE, state->codec->sample_rate, + AV_ROUND_UP); + int samples; + pcm = (int16_t *)av_malloc_array((size_t)capacity, sizeof(*pcm)); + if (!pcm) { av_frame_unref(state->frame); errno = ENOMEM; return -1; } + output_planes[0] = (uint8_t *)pcm; + samples = swr_convert(state->resampler, output_planes, capacity, + (const uint8_t **)state->frame->extended_data, + state->frame->nb_samples); + av_frame_unref(state->frame); + if (samples < 0 || output(context, pcm, (size_t)samples)) { + av_free(pcm); errno = EIO; return -1; + } + av_free(pcm); + } + if (result != AVERROR(EAGAIN) && result != AVERROR_EOF) { errno = EIO; return -1; } + return 0; +#else + (void)decoder; (void)adts; (void)length; (void)output; (void)context; errno = ENOSYS; return -1; +#endif +} + +void cable_aac_decoder_close(cable_aac_decoder_t *decoder) +{ +#ifdef CABLE_HAVE_FFMPEG + cable_aac_decoder_impl_t *state; + if (!decoder || !decoder->implementation) return; + state = (cable_aac_decoder_impl_t *)decoder->implementation; + if (state->resampler) swr_free(&state->resampler); + av_frame_free(&state->frame); av_packet_free(&state->packet); avcodec_free_context(&state->codec); + free(state); decoder->implementation = NULL; +#else + (void)decoder; +#endif +} diff --git a/src/cable_video.c b/src/cable_video.c new file mode 100644 index 0000000..6582d5f --- /dev/null +++ b/src/cable_video.c @@ -0,0 +1,270 @@ +#include "cable_video.h" + +#include +#include +#include +#include +#include + +#ifdef CABLE_HAVE_FFMPEG +#include +#include +#include +#include + +struct cable_video_source { + SDL_Thread *thread; + SDL_mutex *lock; + volatile int stop; + AVFormatContext *format; + char url[1024]; + uint8_t *pixels; + unsigned width, height; + uint64_t generation; +}; + +static int interrupted(void *opaque) +{ + return ((cable_video_source_t *)opaque)->stop; +} + +static int video_worker(void *opaque) +{ + cable_video_source_t *source = (cable_video_source_t *)opaque; + AVCodecContext *decoder = NULL; + AVPacket *packet = NULL; + AVFrame *frame = NULL, *rgb = NULL; + struct SwsContext *scale = NULL; + uint8_t *pixels = NULL; + AVFormatContext *format = avformat_alloc_context(); + int video_stream = -1, result = -1; + if (!format) return -1; + format->interrupt_callback.callback = interrupted; + format->interrupt_callback.opaque = source; + if (avformat_open_input(&format, source->url, NULL, NULL) < 0 || + avformat_find_stream_info(format, NULL) < 0) goto done; + source->format = format; + for (unsigned i = 0; i < format->nb_streams; i++) { + if (format->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + video_stream = (int)i; + break; + } + } + if (video_stream < 0) goto done; + { + const AVCodec *codec = avcodec_find_decoder(format->streams[video_stream]->codecpar->codec_id); + if (!codec || !(decoder = avcodec_alloc_context3(codec)) || + avcodec_parameters_to_context(decoder, format->streams[video_stream]->codecpar) < 0 || + avcodec_open2(decoder, codec, NULL) < 0) goto done; + } + packet = av_packet_alloc(); + frame = av_frame_alloc(); + rgb = av_frame_alloc(); + if (!packet || !frame || !rgb) goto done; + while (!source->stop && av_read_frame(format, packet) >= 0) { + if (packet->stream_index == video_stream && avcodec_send_packet(decoder, packet) >= 0) { + while (!source->stop && avcodec_receive_frame(decoder, frame) >= 0) { + unsigned width = frame->width > 640 ? 640u : (unsigned)frame->width; + unsigned height = frame->height > 360 ? 360u : (unsigned)frame->height; + size_t bytes = (size_t)width * height * 3u; + uint8_t *next_pixels; + if (!width || !height || bytes > 640u * 360u * 3u) break; + next_pixels = (uint8_t *)realloc(pixels, bytes); + if (!next_pixels) goto done; + pixels = next_pixels; + av_frame_unref(rgb); + rgb->format = AV_PIX_FMT_RGB24; + rgb->width = (int)width; + rgb->height = (int)height; + if (av_frame_get_buffer(rgb, 1) < 0) break; + scale = sws_getCachedContext(scale, frame->width, frame->height, + (enum AVPixelFormat)frame->format, (int)width, (int)height, + AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL); + if (!scale || sws_scale(scale, (const uint8_t * const *)frame->data, + frame->linesize, 0, frame->height, rgb->data, rgb->linesize) <= 0) break; + for (unsigned y = 0; y < height; y++) + memcpy(pixels + (size_t)y * width * 3u, + rgb->data[0] + (size_t)y * (size_t)rgb->linesize[0], + (size_t)width * 3u); + SDL_LockMutex(source->lock); + memcpy(source->pixels, pixels, bytes); + source->width = width; + source->height = height; + source->generation++; + SDL_UnlockMutex(source->lock); + av_frame_unref(frame); + } + } + av_packet_unref(packet); + } + result = 0; +done: + free(pixels); + sws_freeContext(scale); + av_frame_free(&rgb); + av_frame_free(&frame); + av_packet_free(&packet); + avcodec_free_context(&decoder); + if (format) avformat_close_input(&format); + source->format = NULL; + return result; +} + +cable_video_source_t *cable_video_source_open(const char *url) +{ + cable_video_source_t *source; + if (!url || strlen(url) >= 1024) return NULL; + source = (cable_video_source_t *)calloc(1, sizeof(*source)); + if (!source) return NULL; + strcpy(source->url, url); + source->lock = SDL_CreateMutex(); + source->pixels = (uint8_t *)malloc(640u * 360u * 3u); + if (!source->lock || !source->pixels) { + cable_video_source_close(source); + return NULL; + } + source->thread = SDL_CreateThread(video_worker, "cable-obs-video", source); + if (!source->thread) { + cable_video_source_close(source); + return NULL; + } + return source; +} + +void cable_video_source_close(cable_video_source_t *source) +{ + if (!source) return; + source->stop = 1; + if (source->thread) SDL_WaitThread(source->thread, NULL); + if (source->format) avformat_close_input(&source->format); + if (source->lock) SDL_DestroyMutex(source->lock); + free(source->pixels); + free(source); +} + +int cable_video_source_frame(cable_video_source_t *source, uint8_t *rgb, + size_t capacity, unsigned *width, unsigned *height, + uint64_t *generation) +{ + size_t bytes; + if (!source || !rgb || !width || !height || !generation) return -1; + SDL_LockMutex(source->lock); + bytes = (size_t)source->width * source->height * 3u; + if (!bytes || capacity < bytes) { SDL_UnlockMutex(source->lock); return 0; } + memcpy(rgb, source->pixels, bytes); + *width = source->width; + *height = source->height; + *generation = source->generation; + SDL_UnlockMutex(source->lock); + return (int)bytes; +} + +int cable_video_encode_jpeg(const uint8_t *rgb, unsigned width, unsigned height, + uint8_t *jpeg, size_t capacity, size_t *written) +{ + const AVCodec *codec; + AVCodecContext *ctx = NULL; + AVFrame *frame = NULL; + AVPacket *packet = NULL; + struct SwsContext *scale = NULL; + int result = -1; + if (!rgb || !width || !height || !jpeg || !written || width > 640 || height > 360) { + errno = EINVAL; + return -1; + } + codec = avcodec_find_encoder(AV_CODEC_ID_MJPEG); + if (!codec || !(ctx = avcodec_alloc_context3(codec)) || + !(frame = av_frame_alloc()) || !(packet = av_packet_alloc())) goto done; + ctx->width = (int)(width > 160u ? 160u : width); + ctx->height = (int)(height > 90u ? 90u : height); + ctx->width &= ~1; + ctx->height &= ~1; + if (ctx->width < 2 || ctx->height < 2) goto done; + ctx->pix_fmt = AV_PIX_FMT_YUVJ420P; + ctx->time_base = (AVRational){1, 25}; + ctx->qmin = 5; + ctx->qmax = 12; + ctx->color_range = AVCOL_RANGE_JPEG; + if (avcodec_open2(ctx, codec, NULL) < 0) goto done; + frame->format = ctx->pix_fmt; + frame->width = ctx->width; + frame->height = ctx->height; + frame->color_range = AVCOL_RANGE_JPEG; + if (av_frame_get_buffer(frame, 32) < 0 || av_frame_make_writable(frame) < 0) goto done; + scale = sws_getContext((int)width, (int)height, AV_PIX_FMT_RGB24, + ctx->width, ctx->height, AV_PIX_FMT_YUVJ420P, + SWS_BILINEAR, NULL, NULL, NULL); + if (!scale || sws_scale(scale, &rgb, (int[]){(int)width * 3}, 0, + (int)height, frame->data, frame->linesize) <= 0 || + avcodec_send_frame(ctx, frame) < 0 || avcodec_receive_packet(ctx, packet) < 0) goto done; + if ((size_t)packet->size > capacity) { errno = EMSGSIZE; goto done; } + memcpy(jpeg, packet->data, (size_t)packet->size); + *written = (size_t)packet->size; + result = 0; +done: + if (result && errno == 0) errno = EIO; + sws_freeContext(scale); + av_packet_free(&packet); + av_frame_free(&frame); + avcodec_free_context(&ctx); + return result; +} + +int cable_video_decode_jpeg(const uint8_t *jpeg, size_t length, uint8_t *rgb, + size_t capacity, unsigned *width, unsigned *height) +{ + const AVCodec *codec; + AVCodecContext *ctx = NULL; + AVFrame *frame = NULL; + AVPacket *packet = NULL; + struct SwsContext *scale = NULL; + uint8_t *planes[4]; + int linesize[4], result = -1; + size_t bytes; + if (!jpeg || !length || length > INT_MAX || !rgb || !width || !height) { + errno = EINVAL; + return -1; + } + codec = avcodec_find_decoder(AV_CODEC_ID_MJPEG); + if (!codec || !(ctx = avcodec_alloc_context3(codec)) || + !(frame = av_frame_alloc()) || !(packet = av_packet_alloc())) goto done; + if (avcodec_open2(ctx, codec, NULL) < 0 || av_new_packet(packet, (int)length) < 0) goto done; + memcpy(packet->data, jpeg, length); + if (avcodec_send_packet(ctx, packet) < 0 || avcodec_receive_frame(ctx, frame) < 0 || + frame->width <= 0 || frame->height <= 0 || frame->width > 640 || frame->height > 360) goto done; + bytes = (size_t)frame->width * (size_t)frame->height * 3u; + if (capacity < bytes) { errno = ENOSPC; goto done; } + scale = sws_getContext(frame->width, frame->height, + (enum AVPixelFormat)frame->format, frame->width, frame->height, + AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL); + if (!scale) goto done; + planes[0] = rgb; planes[1] = planes[2] = planes[3] = NULL; + linesize[0] = frame->width * 3; linesize[1] = linesize[2] = linesize[3] = 0; + if (sws_scale(scale, (const uint8_t * const *)frame->data, frame->linesize, + 0, frame->height, planes, linesize) <= 0) goto done; + *width = (unsigned)frame->width; + *height = (unsigned)frame->height; + result = (int)bytes; +done: + if (result < 0 && errno == 0) errno = EIO; + sws_freeContext(scale); + av_packet_free(&packet); + av_frame_free(&frame); + avcodec_free_context(&ctx); + return result; +} + +#else +struct cable_video_source { int unused; }; +cable_video_source_t *cable_video_source_open(const char *url) { (void)url; return NULL; } +void cable_video_source_close(cable_video_source_t *source) { free(source); } +int cable_video_source_frame(cable_video_source_t *source, uint8_t *rgb, size_t capacity, + unsigned *width, unsigned *height, uint64_t *generation) +{ (void)source; (void)rgb; (void)capacity; (void)width; (void)height; (void)generation; return 0; } +int cable_video_encode_jpeg(const uint8_t *rgb, unsigned width, unsigned height, + uint8_t *jpeg, size_t capacity, size_t *written) +{ (void)rgb; (void)width; (void)height; (void)jpeg; (void)capacity; (void)written; errno = ENOSYS; return -1; } +int cable_video_decode_jpeg(const uint8_t *jpeg, size_t length, uint8_t *rgb, + size_t capacity, unsigned *width, unsigned *height) +{ (void)jpeg; (void)length; (void)rgb; (void)capacity; (void)width; (void)height; errno = ENOSYS; return -1; } +#endif diff --git a/src/cesspool.c b/src/cesspool.c index 9980227..66c359f 100644 --- a/src/cesspool.c +++ b/src/cesspool.c @@ -1,3 +1,2 @@ -// hyperswarm dht -#include "cesspool.h" -#include "s7.h" +/* Cesspool implementation is maintained in the standalone libcess package. */ +#include "../libcess/src/cesspool.c" diff --git a/src/cesspool_bootstrap.c b/src/cesspool_bootstrap.c new file mode 100644 index 0000000..41135a8 --- /dev/null +++ b/src/cesspool_bootstrap.c @@ -0,0 +1,2 @@ +/* Bootstrap executable is maintained in the standalone libcess package. */ +#include "../libcess/tools/cesspool-bootstrap.c" diff --git a/src/dat.c b/src/dat.c new file mode 100644 index 0000000..255eedd --- /dev/null +++ b/src/dat.c @@ -0,0 +1,235 @@ +#include "dat.h" +#include "base32768.h" +#include "sleep.h" +#include "sleep_crypto.h" +#include "sleep_proto.h" +#include "sleep_writer.h" +#include "sleep_feed.h" +#include "errno_compat.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +int dat_key_to_hex(const uint8_t key[DAT_KEY_BYTES], char hex[65]) +{ + static const char digits[] = "0123456789abcdef"; + size_t i; + if (!key || !hex) { errno = EINVAL; return -1; } + for (i = 0; i < DAT_KEY_BYTES; i++) { + hex[i * 2] = digits[key[i] >> 4]; + hex[i * 2 + 1] = digits[key[i] & 15]; + } + hex[64] = '\0'; + return 0; +} + +int dat_key_to_url(const uint8_t key[DAT_KEY_BYTES], char url[DAT_URL_BYTES]) +{ + if (!key || !url) { errno = EINVAL; return -1; } + memcpy(url, "dat://", 6); + if (dat_key_to_hex(key, url + 6)) return -1; + return 0; +} +int dat_key_to_compact_url(const uint8_t key[DAT_KEY_BYTES], char url[DAT_URL_BYTES]) +{ + if (!key || !url) { errno = EINVAL; return -1; } + memcpy(url, "dat://", 6); + if (base32768_encode(key, DAT_KEY_BYTES, url + 6, DAT_URL_BYTES - 6)) return -1; + return 0; +} +static int hex_value(char c){if(c>='0'&&c<='9')return c-'0';if(c>='a'&&c<='f')return c-'a'+10;if(c>='A'&&c<='F')return c-'A'+10;return-1;} +int dat_url_to_key(const char *url, uint8_t key[DAT_KEY_BYTES]) +{ + size_t i, length, decoded_length = 0; + int hi, lo; + if (!url || !key) { errno = EINVAL; return -1; } + if (!strncmp(url, "dat://", 6)) url += 6; + length = strlen(url); + if (length == 64) { + for (i = 0; i < DAT_KEY_BYTES; i++) { + hi = hex_value(url[i * 2]); lo = hex_value(url[i * 2 + 1]); + if (hi < 0 || lo < 0) { errno = EINVAL; return -1; } + key[i] = (uint8_t)((hi << 4) | lo); + } + return 0; + } + if (base32768_decode(url, key, DAT_KEY_BYTES, &decoded_length) || + decoded_length != DAT_KEY_BYTES) { errno = EINVAL; return -1; } + return 0; +} +static int dat_link_format_with(const dat_link_t*l,char*out,size_t cap,int compact){char base[DAT_URL_BYTES];int n;if(!l||!out){errno=EINVAL;return-1;}if((compact?dat_key_to_compact_url(l->key,base):dat_key_to_url(l->key,base)))return-1;if(l->has_version)n=snprintf(out,cap,"%s+%llu%s%s",base,(unsigned long long)l->version,*l->path&&l->path[0]!='/'?"/":"",l->path);else n=snprintf(out,cap,"%s%s%s",base,*l->path&&l->path[0]!='/'?"/":"",l->path);if(n<0||(size_t)n>=cap){errno=ENOSPC;return-1;}return 0;} +int dat_link_format(const dat_link_t*l,char*out,size_t cap){return dat_link_format_with(l,out,cap,0);} +int dat_link_format_compact(const dat_link_t*l,char*out,size_t cap){return dat_link_format_with(l,out,cap,1);} +int dat_link_parse(const char *text, dat_link_t *link) +{ + const char *p, *suffix, *plus; + char encoded[DAT_URL_BYTES], version[32]; + size_t key_length, n; + if (!text || !link) { errno = EINVAL; return -1; } + memset(link, 0, sizeof(*link)); + p = !strncmp(text, "dat://", 6) ? text + 6 : text; + suffix = strpbrk(p, "/+?#"); + key_length = suffix ? (size_t)(suffix - p) : strlen(p); + if (!key_length || key_length >= sizeof(encoded)) { errno = EINVAL; return -1; } + memcpy(encoded, p, key_length); encoded[key_length] = '\0'; + if (dat_url_to_key(encoded, link->key)) return -1; + suffix = p + key_length; + plus = *suffix == '+' ? suffix : NULL; + if (plus) { + const char *end = plus + 1; + while (*end >= '0' && *end <= '9') end++; + n = (size_t)(end - (plus + 1)); + if (!n || n >= sizeof(version)) { errno = EINVAL; return -1; } + memcpy(version, plus + 1, n); version[n] = '\0'; + link->version = (uint64_t)strtoull(version, NULL, 10); + link->has_version = 1; suffix = end; + } + if (*suffix && *suffix != '/') { errno = EINVAL; return -1; } + if (*suffix == '/') suffix++; + if (strlen(suffix) >= sizeof(link->path)) { errno = ENAMETOOLONG; return -1; } + strcpy(link->path, suffix); + return 0; +} + +int dat_archive_create(dat_archive_t *archive, const char *root) +{ + sleep_keypair_t metadata_keys, content_keys; + sleep_metadata_header_t header; + uint8_t metadata_block[128], empty = 0; + const uint8_t *metadata_blocks[1], *content_blocks[1]; + size_t metadata_lengths[1], content_lengths[1] = { 0 }; + size_t metadata_len; + int n; + + if (!archive || !root || !*root) { errno = EINVAL; return -1; } + memset(archive, 0, sizeof(*archive)); + n = snprintf(archive->dat_path, sizeof(archive->dat_path), "%s/.dat", root); + if (n < 0 || (size_t)n >= sizeof(archive->dat_path)) { errno = ENAMETOOLONG; return -1; } + if (sleep_init(root) || sleep_crypto_init() || sleep_gen_keypair(&metadata_keys) || sleep_gen_keypair(&content_keys)) return -1; + + memset(&header, 0, sizeof(header)); + strcpy(header.type, "hyperdrive"); + memcpy(header.content, content_keys.pubkey, 32); header.content_len = 32; + if (sleep_metadata_header_encode(&header, metadata_block, sizeof(metadata_block), &metadata_len)) return -1; + + metadata_blocks[0] = metadata_block; metadata_lengths[0] = metadata_len; + content_blocks[0] = ∅ + if (sleep_write_feed(archive->dat_path, "metadata", metadata_blocks, metadata_lengths, 1, &metadata_keys)) return -1; + if (sleep_write_feed(archive->dat_path, "content", content_blocks, content_lengths, 1, &content_keys)) return -1; + memcpy(archive->metadata_key, metadata_keys.pubkey, 32); + memcpy(archive->content_key, content_keys.pubkey, 32); + archive->metadata_keys = metadata_keys; + archive->content_keys = content_keys; + return 0; +} + +int dat_archive_open(dat_archive_t*a,const char*root){char path[1200];int n;if(!a||!root){errno=EINVAL;return-1;}memset(a,0,sizeof(*a));n=snprintf(a->dat_path,sizeof(a->dat_path),"%s/.dat",root);if(n<0||(size_t)n>=sizeof(a->dat_path)){errno=ENAMETOOLONG;return-1;}snprintf(path,sizeof(path),"%s/metadata.key",a->dat_path);if(sleep_load_pubkey(path,a->metadata_key))return-1;snprintf(path,sizeof(path),"%s/content.key",a->dat_path);return sleep_load_pubkey(path,a->content_key);} +/* Dat Desktop's dat-secret-storage indexes each Hypercore secret by the + * discovery key and stores the raw 64-byte Ed25519 secret at that path. */ +static int feed_secret_path(char *out,size_t cap,const char *root,const uint8_t pub[32]) +{ + uint8_t disc[32]; char hex[65], shard[1200]; int n; + if(sleep_compute_discovery_key(pub,disc)||dat_key_to_hex(disc,hex))return -1; + n=snprintf(shard,sizeof(shard),"%s/%c%c",root,hex[0],hex[1]); + if(n<0||(size_t)n>=sizeof(shard)){errno=ENAMETOOLONG;return -1;} + n=snprintf(out,cap,"%s/%s",shard,hex+2); + if(n<0||(size_t)n>=cap){errno=ENAMETOOLONG;return -1;} return 0; +} + +static int load_feed_secret(const char *root,const uint8_t pub[32],sleep_keypair_t *kp) +{ + char path[1400]; FILE *f; uint8_t raw[sizeof(*kp)]; size_t n; int saved; + if(feed_secret_path(path,sizeof(path),root,pub))return -1; + f=fopen(path,"rb"); + if(f){n=fread(raw,1,sizeof(raw),f);if(ferror(f)){saved=errno;fclose(f);errno=saved;return -1;}fclose(f); + if(n==SLEEP_SECRET_KEY_BYTES){memcpy(kp->seckey,raw,n);if(crypto_sign_ed25519_sk_to_pk(kp->pubkey,kp->seckey)!=0||memcmp(kp->pubkey,pub,32)){errno=EKEYREJECTED;return -1;}return 0;} + errno=EINVAL;return -1; + } + return -1; +} + +static int save_feed_secret(const char *root,const sleep_keypair_t *kp) +{ + char path[1400], parent[1400], *slash; FILE *f; int fd, saved; + if(feed_secret_path(path,sizeof(path),root,kp->pubkey))return -1; + strcpy(parent,path); slash=strrchr(parent,'/'); if(!slash){errno=EINVAL;return -1;} *slash='\0'; + if(mkdir(parent,0700)&&errno!=EEXIST)return -1; + f=fopen(path,"rb"); if(f){uint8_t raw[sizeof(*kp)];size_t n=fread(raw,1,sizeof(raw),f);fclose(f); + if(n==SLEEP_SECRET_KEY_BYTES&&!memcmp(raw,kp->seckey,SLEEP_SECRET_KEY_BYTES))return 0; + errno=EEXIST;return -1; + } + fd=open(path,O_WRONLY|O_CREAT|O_EXCL,0600);if(fd<0){if(errno==EEXIST)return save_feed_secret(root,kp);return -1;} + if(write(fd,kp->seckey,SLEEP_SECRET_KEY_BYTES)!=(ssize_t)SLEEP_SECRET_KEY_BYTES){saved=errno;close(fd);unlink(path);errno=saved?saved:EIO;return -1;} + if(close(fd)){saved=errno;unlink(path);errno=saved;return -1;}return 0; +} + +int dat_archive_open_writable(dat_archive_t*a,const char*root,const char*secrets) +{ + if(dat_archive_open(a,root))return -1; + if(!secrets){errno=EINVAL;return -1;} + if(load_feed_secret(secrets,a->metadata_key,&a->metadata_keys)|| + load_feed_secret(secrets,a->content_key,&a->content_keys))return -1; + return 0; +} + +int dat_archive_save_secrets(const dat_archive_t*a,const char*secrets) +{ + if(!a||!secrets){errno=EINVAL;return -1;} + if(mkdir(secrets,0700)&&errno!=EEXIST)return -1; + if(save_feed_secret(secrets,&a->metadata_keys))return -1; + return save_feed_secret(secrets,&a->content_keys); +} +void dat_archive_close(dat_archive_t*a){if(a)memset(a,0,sizeof(*a));} +size_t dat_archive_entry_count(const dat_archive_t*a){sleep_feed_t f;size_t n=0;if(a&&sleep_feed_open(&f,a->dat_path,"metadata",NULL)==0){n=sleep_feed_length(&f);sleep_feed_close(&f);}return n?n-1:0;} +uint64_t dat_archive_version(const dat_archive_t*a){return (uint64_t)dat_archive_entry_count(a);} + +int dat_archive_append_file(dat_archive_t *a,const char *path,const uint8_t *data,size_t length,uint32_t mode,uint64_t mtime) +{ + sleep_feed_t content,metadata;sleep_metadata_entry_t entry;uint8_t encoded[2300];size_t encoded_len,block,off=0,count=0,part; + if(!a||!path||(!data&&length)||strlen(path)>=sizeof(entry.path)){errno=EINVAL;return-1;} + if(sleep_feed_open(&content,a->dat_path,"content",&a->content_keys))return-1; + block=sleep_feed_length(&content);do{part=length-off>DAT_BLOCK_BYTES?DAT_BLOCK_BYTES:length-off;if(sleep_feed_append(&content,data?data+off:NULL,part)){sleep_feed_close(&content);return-1;}off+=part;count++;}while(offdat_path,"metadata",&a->metadata_keys))return-1; + if(sleep_feed_append(&metadata,encoded,encoded_len)||sleep_feed_flush(&metadata)){sleep_feed_close(&metadata);return-1;}sleep_feed_close(&metadata);return 0; +} +int dat_archive_read_entry(const dat_archive_t *a,size_t index,sleep_metadata_entry_t *entry) +{ + sleep_feed_t f;uint8_t *data;size_t len;int rc;if(!a||!entry){errno=EINVAL;return-1;}if(sleep_feed_open(&f,a->dat_path,"metadata",NULL))return-1; + /* Metadata block zero is the archive header. */ rc=sleep_feed_read(&f,index+1,&data,&len);sleep_feed_close(&f);if(rc)return-1;rc=sleep_metadata_entry_decode(entry,data,len);free(data);return rc; +} +int dat_archive_read_file(const dat_archive_t *a,const sleep_metadata_entry_t *entry,uint8_t **data,size_t *length) +{ + sleep_feed_t f;uint8_t*out,*block;size_t total=0,n,i;if(!a||!entry||!data||!length||entry->type!=SLEEP_ENTRY_FILE){errno=EINVAL;return-1;}if(entry->size>SIZE_MAX){errno=EOVERFLOW;return-1;}out=malloc(entry->size?(size_t)entry->size:1);if(!out)return-1;if(sleep_feed_open(&f,a->dat_path,"content",NULL)){free(out);return-1;}for(i=0;iblock_count;i++){if(sleep_feed_read(&f,(size_t)entry->block_offset+i,&block,&n)||n>(size_t)entry->size-total){sleep_feed_close(&f);free(out);return-1;}if(n)memcpy(out+total,block,n);total+=n;free(block);}sleep_feed_close(&f);if(total!=(size_t)entry->size){free(out);errno=EPROTO;return-1;}*data=out;*length=total;return 0; +} + +static int append_metadata_only(dat_archive_t *a,const sleep_metadata_entry_t *entry) +{ + sleep_feed_t metadata;uint8_t encoded[2300];size_t len;int rc; + if(sleep_metadata_entry_encode(entry,encoded,sizeof(encoded),&len))return-1; + if(sleep_feed_open(&metadata,a->dat_path,"metadata",&a->metadata_keys))return-1; + rc=sleep_feed_append(&metadata,encoded,len);if(!rc)rc=sleep_feed_flush(&metadata);sleep_feed_close(&metadata);return rc; +} +int dat_archive_append_directory(dat_archive_t *a,const char *path,uint32_t mode,uint64_t mtime) +{ + sleep_metadata_entry_t e;if(!a||!path||strlen(path)>=sizeof(e.path)){errno=EINVAL;return-1;}memset(&e,0,sizeof(e));strcpy(e.path,path);e.type=SLEEP_ENTRY_DIRECTORY;e.mode=mode;e.mtime=mtime;return append_metadata_only(a,&e); +} +int dat_archive_append_symlink(dat_archive_t *a,const char *path,const char *target,uint64_t mtime) +{ + sleep_metadata_entry_t e;if(!a||!path||!target||strlen(path)>=sizeof(e.path)||strlen(target)>=sizeof(e.linkname)){errno=EINVAL;return-1;}memset(&e,0,sizeof(e));strcpy(e.path,path);strcpy(e.linkname,target);e.type=SLEEP_ENTRY_SYMLINK;e.mtime=mtime;return append_metadata_only(a,&e); +} +int dat_archive_delete(dat_archive_t*a,const char*path,uint64_t mtime){sleep_metadata_entry_t e;if(!a||!path||strlen(path)>=sizeof(e.path)){errno=EINVAL;return-1;}memset(&e,0,sizeof(e));strcpy(e.path,path);e.type=SLEEP_ENTRY_DELETE;e.mtime=mtime;return append_metadata_only(a,&e);} +int dat_archive_find(const dat_archive_t*a,const char*path,sleep_metadata_entry_t*entry){size_t n,i;if(!a||!path||!entry){errno=EINVAL;return-1;}n=dat_archive_entry_count(a);for(i=n;i>0;i--){if(dat_archive_read_entry(a,i-1,entry)==0&&!strcmp(entry->path,path)){if(entry->type==SLEEP_ENTRY_DELETE){errno=ENOENT;return-1;}return 0;}}errno=ENOENT;return-1;} +int dat_archive_find_at(const dat_archive_t*a,const char*path,uint64_t version,sleep_metadata_entry_t*entry){size_t n,i;if(!a||!path||!entry){errno=EINVAL;return-1;}n=dat_archive_entry_count(a);if(version>n){errno=ERANGE;return-1;}for(i=(size_t)version;i>0;i--){if(dat_archive_read_entry(a,i-1,entry)==0&&!strcmp(entry->path,path)){if(entry->type==SLEEP_ENTRY_DELETE){errno=ENOENT;return-1;}return 0;}}errno=ENOENT;return-1;} +int dat_checkout_init(dat_checkout_t*c,const dat_archive_t*a,uint64_t version){if(!c||!a||version>dat_archive_version(a)){errno=ERANGE;return-1;}c->archive=a;c->version=version;return 0;} +int dat_checkout_find(const dat_checkout_t*c,const char*path,sleep_metadata_entry_t*entry){if(!c){errno=EINVAL;return-1;}return dat_archive_find_at(c->archive,path,c->version,entry);} +int dat_checkout_read(const dat_checkout_t*c,const sleep_metadata_entry_t*entry,uint8_t**data,size_t*length){if(!c){errno=EINVAL;return-1;}return dat_archive_read_file(c->archive,entry,data,length);} +int dat_checkout_export(const dat_checkout_t*c,const char*archive_path,const char*filesystem_path){if(!c){errno=EINVAL;return-1;}return dat_archive_export_at(c->archive,c->version,archive_path,filesystem_path);} +int dat_archive_changes(const dat_archive_t*a,uint64_t from,uint64_t to,dat_archive_change_fn callback,void*context){sleep_metadata_entry_t entry;uint64_t version,last;if(!a||!callback){errno=EINVAL;return-1;}last=dat_archive_version(a);if(from>to||to>last){errno=ERANGE;return-1;}for(version=from+1;version<=to;version++){if(dat_archive_read_entry(a,(size_t)(version-1),&entry)||callback(&entry,version,context))return-1;}return 0;} diff --git a/src/dat_browse.c b/src/dat_browse.c new file mode 100644 index 0000000..9053807 --- /dev/null +++ b/src/dat_browse.c @@ -0,0 +1,314 @@ +#define _POSIX_C_SOURCE 200809L +#include "dat.h" + +#include +#include +#include +#include +#include +#include + +static int normalize_url_path(const char *src, char *dst, size_t capacity) +{ + char decoded[2048]; + size_t i = 0, decoded_len = 0, out = 1, segments[1024], count = 0; + int trailing_slash; + if (!src || !dst || capacity < 2 || strlen(src) >= sizeof(decoded)) { + errno = ENAMETOOLONG; + return -1; + } + while (src[i]) { + unsigned char c = (unsigned char)src[i++]; + if (c == '%') { + int hi, lo; + if (!src[i] || !src[i + 1]) { errno = EINVAL; return -1; } + hi = isdigit((unsigned char)src[i]) ? src[i] - '0' : + (tolower((unsigned char)src[i]) >= 'a' && tolower((unsigned char)src[i]) <= 'f' ? tolower((unsigned char)src[i]) - 'a' + 10 : -1); + lo = isdigit((unsigned char)src[i + 1]) ? src[i + 1] - '0' : + (tolower((unsigned char)src[i + 1]) >= 'a' && tolower((unsigned char)src[i + 1]) <= 'f' ? tolower((unsigned char)src[i + 1]) - 'a' + 10 : -1); + if (hi < 0 || lo < 0) { errno = EINVAL; return -1; } + c = (unsigned char)((hi << 4) | lo); i += 2; + } + if (!c || c == '\\') { errno = EINVAL; return -1; } + decoded[decoded_len++] = (char)c; + } + decoded[decoded_len] = '\0'; + trailing_slash = decoded_len == 0 || decoded[decoded_len - 1] == '/'; + dst[0] = '/'; + i = 0; + while (i < decoded_len) { + size_t start, n; + while (i < decoded_len && decoded[i] == '/') i++; + start = i; + while (i < decoded_len && decoded[i] != '/') i++; + n = i - start; + if (!n) break; + if (n == 1 && decoded[start] == '.') { trailing_slash = 1; continue; } + if (n == 2 && decoded[start] == '.' && decoded[start + 1] == '.') { + trailing_slash = 1; + if (count) out = segments[--count]; + continue; + } + trailing_slash = 0; + if (count >= sizeof(segments) / sizeof(segments[0]) || + out + n + 2 > capacity) { errno = ENAMETOOLONG; return -1; } + segments[count++] = out; + if (out > 1) dst[out++] = '/'; + memcpy(dst + out, decoded + start, n); + out += n; + } + if (trailing_slash && out > 1) dst[out++] = '/'; + if (out >= capacity) { errno = ENAMETOOLONG; return -1; } + dst[out] = '\0'; + return 0; +} + +static int parse_decimal(const char *s, uint64_t *value) +{ + char *end; + unsigned long long v; + if (!s || !*s || !value) return -1; + errno = 0; v = strtoull(s, &end, 10); + if (errno || *end) return -1; + *value = (uint64_t)v; + return 0; +} + +int dat_url_parse(const char *text, dat_url_t *url) +{ + const char *authority, *path, *query, *end; + size_t host_len, path_len; + char query_copy[512], raw_path[2048], *item, *save; + char *plus; + if (!text || !url || strncmp(text, "dat://", 6)) { errno = EINVAL; return -1; } + memset(url, 0, sizeof(*url)); + authority = text + 6; + path = strpbrk(authority, "/?"); + end = path ? path : authority + strlen(authority); + host_len = (size_t)(end - authority); + if (!host_len || host_len >= sizeof(url->host)) { errno = EINVAL; return -1; } + memcpy(url->host, authority, host_len); url->host[host_len] = 0; + plus = strchr(url->host, '+'); + if (plus) { + if (parse_decimal(plus + 1, &url->version)) { errno = EINVAL; return -1; } + *plus = 0; url->has_version = 1; + if (!url->host[0]) { errno = EINVAL; return -1; } + } + query = (*end == '?') ? end : strchr(end, '?'); + if (*end == '/') { + path_len = query ? (size_t)(query - end) : strlen(end); + if (path_len >= sizeof(raw_path)) { errno = ENAMETOOLONG; return -1; } + memcpy(raw_path, end, path_len); raw_path[path_len] = 0; + } else { strcpy(raw_path, "/"); path_len = 1; } + if (path_len >= 2 && raw_path[path_len - 1] == '/' && raw_path[path_len - 2] == '/') + url->force_directory_listing = 1; + if (normalize_url_path(raw_path, url->path, sizeof(url->path))) return -1; + if (query) { + if (strlen(query + 1) >= sizeof(query_copy)) { errno = EOVERFLOW; return -1; } + strcpy(query_copy, query + 1); + for (item = strtok_r(query_copy, "&", &save); item; item = strtok_r(NULL, "&", &save)) { + char *equals = strchr(item, '='); + if (equals) *equals++ = 0; + if (!strcmp(item, "version")) { + if (parse_decimal(equals, &url->version)) { errno = EINVAL; return -1; } + if (url->has_version && url->version != (uint64_t)strtoull(equals, NULL, 10)) { errno = EINVAL; return -1; } + url->has_version = 1; + } else if (!strcmp(item, "stat")) { + url->wants_stat = !equals || strcmp(equals, "0") != 0; + } + } + } + return 0; +} + +static int canonical_entry_path(const char *input, char *out, size_t capacity) +{ + const char *p = input; + size_t n; + while (*p == '/') p++; + n = strlen(p); + while (n && p[n - 1] == '/') n--; + if (n >= capacity) { errno = ENAMETOOLONG; return -1; } + memcpy(out, p, n); out[n] = 0; + return 0; +} + +static int find_normalized(const dat_archive_t *archive, uint64_t version, + const char *path, sleep_metadata_entry_t *entry) +{ + size_t i; + for (i = (size_t)version; i > 0; i--) { + sleep_metadata_entry_t current; + const char *candidate; + if (dat_archive_read_entry(archive, i - 1, ¤t)) return -1; + candidate = current.path; while (*candidate == '/') candidate++; + if (strcmp(candidate, path)) continue; + if (current.type == SLEEP_ENTRY_DELETE) { errno = ENOENT; return -1; } + *entry = current; + return 0; + } + errno = ENOENT; + return -1; +} + +int dat_archive_stat_at(const dat_archive_t *archive, uint64_t version, + const char *path, dat_path_info_t *info) +{ + char wanted[2048]; + size_t i; + if (!archive || !path || !info || canonical_entry_path(path, wanted, sizeof(wanted))) { errno = EINVAL; return -1; } + if (version > dat_archive_version(archive)) { errno = ERANGE; return -1; } + memset(info, 0, sizeof(*info)); + if (find_normalized(archive, version, wanted, &info->entry) == 0) { + info->exists = 1; + info->is_directory = info->entry.type == SLEEP_ENTRY_DIRECTORY; + return 0; + } + if (errno != ENOENT) return -1; + for (i = 0; i < (size_t)version; i++) { + sleep_metadata_entry_t e, latest; + const char *candidate; + size_t prefix = strlen(wanted); + if (dat_archive_read_entry(archive, i, &e)) return -1; + candidate = e.path; while (*candidate == '/') candidate++; + if (prefix && (strncmp(candidate, wanted, prefix) || candidate[prefix] != '/')) continue; + if (!prefix && !*candidate) continue; + if (find_normalized(archive, version, candidate, &latest)) { + if (errno == ENOENT) continue; + return -1; + } + if (latest.type == SLEEP_ENTRY_DELETE) continue; + memset(info, 0, sizeof(*info)); + strcpy(info->entry.path, wanted); + info->entry.type = SLEEP_ENTRY_DIRECTORY; + info->exists = 1; info->is_directory = 1; + return 0; + } + errno = ENOENT; + return -1; +} + +int dat_archive_readdir_at(const dat_archive_t *archive, uint64_t version, + const char *path, dat_archive_readdir_fn callback, + void *context) +{ + char wanted[2048]; + size_t i, prefix, seen_count = 0; + char **seen; + dat_path_info_t info; + if (!archive || !path || !callback || canonical_entry_path(path, wanted, sizeof(wanted))) { errno = EINVAL; return -1; } + if (dat_archive_stat_at(archive, version, wanted, &info) || !info.is_directory) { if (!errno) errno = ENOTDIR; return -1; } + prefix = strlen(wanted); + seen = calloc(version ? (size_t)version : 1, sizeof(*seen)); + if (!seen) return -1; + for (i = 0; i < (size_t)version; i++) { + sleep_metadata_entry_t e, latest; + const char *candidate, *relative, *slash; + char name[1024], child[2048]; + size_t child_len; + if (dat_archive_read_entry(archive, i, &e)) { free(seen); return -1; } + candidate = e.path; while (*candidate == '/') candidate++; + if (prefix) { + if (strncmp(candidate, wanted, prefix) || candidate[prefix] != '/') continue; + relative = candidate + prefix + 1; + } else relative = candidate; + if (!*relative) continue; + slash = strchr(relative, '/'); + child_len = slash ? (size_t)(slash - relative) : strlen(relative); + if (!child_len || child_len >= sizeof(name)) continue; + memcpy(name, relative, child_len); name[child_len] = 0; + { + size_t s; + for (s = 0; s < seen_count; s++) if (!strcmp(seen[s], name)) break; + if (s < seen_count) continue; + } + if (prefix) snprintf(child, sizeof(child), "%s/%s", wanted, name); + else snprintf(child, sizeof(child), "%s", name); + if (find_normalized(archive, version, child, &latest)) { + if (errno == ENOENT) { + /* A descendant may imply a directory even without an explicit record. */ + if (!slash || dat_archive_stat_at(archive, version, child, &info)) continue; + latest = info.entry; + } else { free(seen); return -1; } + } + if (latest.type == SLEEP_ENTRY_DELETE) continue; + seen[seen_count] = strdup(name); + if (!seen[seen_count]) { while (seen_count) free(seen[--seen_count]); free(seen); return -1; } + seen_count++; + if (callback(&latest, name, context)) { while (seen_count) free(seen[--seen_count]); free(seen); return -1; } + } + while (seen_count) free(seen[--seen_count]); + free(seen); + return 0; +} + +static int lookup_index(const dat_archive_t *archive, uint64_t version, + const char *path, dat_path_info_t *info) +{ + char index_path[2048]; + int n; + if (*path) n = snprintf(index_path, sizeof(index_path), "%s/index.html", path); + else n = snprintf(index_path, sizeof(index_path), "index.html"); + if (n < 0 || (size_t)n >= sizeof(index_path)) { errno = ENAMETOOLONG; return -1; } + if (dat_archive_stat_at(archive, version, index_path, info)) return -1; + return info->entry.type == SLEEP_ENTRY_FILE ? 0 : -1; +} + +int dat_archive_resolve_at(const dat_archive_t *archive, uint64_t version, + const char *path, int force_listing, + dat_path_info_t *info, int *serve_listing) +{ + char normalized[2048], html_path[2048]; + const char *p; + size_t n; + int trailing_slash, is_directory; + if (!archive || !path || !info || !serve_listing || canonical_entry_path(path, normalized, sizeof(normalized))) { errno = EINVAL; return -1; } + p = path + strlen(path); + trailing_slash = p > path && p[-1] == '/'; + *serve_listing = 0; + if (dat_archive_stat_at(archive, version, normalized, info)) { + /* Hyperdrive resolves /name to /name.html when the direct path is absent. */ + if (errno != ENOENT || force_listing) return -1; + n = strlen(normalized); + if (n + sizeof(".html") > sizeof(html_path)) { errno = ENAMETOOLONG; return -1; } + snprintf(html_path, sizeof(html_path), "%s.html", normalized); + if (!dat_archive_stat_at(archive, version, html_path, info) && + info->entry.type == SLEEP_ENTRY_FILE) return 0; + errno = ENOENT; + return -1; + } + is_directory = info->is_directory; + if (force_listing) { + if (!is_directory) { errno = ENOTDIR; return -1; } + *serve_listing = 1; return 0; + } + if (trailing_slash || is_directory) { + if (!lookup_index(archive, version, normalized, info)) return 0; + if (!is_directory) { errno = ENOENT; return -1; } + *serve_listing = 1; return 0; + } + if (info->entry.type == SLEEP_ENTRY_FILE) return 0; + n = strlen(normalized); + if (n + sizeof(".html") > sizeof(html_path)) { errno = ENAMETOOLONG; return -1; } + snprintf(html_path, sizeof(html_path), "%s.html", normalized); + if (!dat_archive_stat_at(archive, version, html_path, info) && info->entry.type == SLEEP_ENTRY_FILE) return 0; + errno = ENOENT; + return -1; +} + +const char *dat_content_type(const char *path) +{ + const char *dot = strrchr(path ? path : "", '.'); + if (!dot) return "text/plain"; + dot++; + if (!strcasecmp(dot, "html") || !strcasecmp(dot, "htm")) return "text/html"; + if (!strcasecmp(dot, "css")) return "text/css"; + if (!strcasecmp(dot, "js")) return "application/javascript"; + if (!strcasecmp(dot, "json")) return "application/json"; + if (!strcasecmp(dot, "svg")) return "image/svg+xml"; + if (!strcasecmp(dot, "png")) return "image/png"; + if (!strcasecmp(dot, "gif")) return "image/gif"; + if (!strcasecmp(dot, "jpg") || !strcasecmp(dot, "jpeg")) return "image/jpeg"; + if (!strcasecmp(dot, "webp")) return "image/webp"; + return "text/plain"; +} diff --git a/src/dat_cli.c b/src/dat_cli.c new file mode 100644 index 0000000..cb91ab1 --- /dev/null +++ b/src/dat_cli.c @@ -0,0 +1,248 @@ +#define _POSIX_C_SOURCE 200809L +#include "dat.h" +#include "dat_s7.h" +#include "dat_dns.h" +#include "dat_paths.h" +#include "cesspool.h" +#include "dat_wire.h" +#include "sleep_crypto.h" +#include "sleep_verify.h" +#include +#include +#include +#include +#include +#include + +#define DATGPT_VERSION "0.1.0-dev" +#ifndef DAT_CLI_NAME +#define DAT_CLI_NAME "ddat" +#endif + +static void usage(FILE *f) +{ + fprintf(f, "%s %s\nusage:\n" + " %s create|init [dir] [--yes] [--title text] [--description text]\n" + " %s share|sync [dir] [--no-import] [--no-watch]\n" + " %s clone [dir] [--temp]\n" + " %s pull [dir]\n" + " %s status [dir]\n" + " %s files [version] [dir]\n" + " %s log [dir|dat://key]\n" + " %s cat [dir]\n" + " %s checkout [dir]\n" + " %s url [--compact] [version] [path] [dir]\n" + " %s resolve \n" + " %s dns set \n" + " %s dns remove \n" + " %s dns list\n" + " %s verify [dir]\n" + " %s keys export [dir]\n" + " %s keys import [dir]\n" + " %s doctor [peer-key]\n" + " %s bootstrap [udp-port] | add|set | list\n" + " %s --version\n", + DAT_CLI_NAME, DATGPT_VERSION, + DAT_CLI_NAME, DAT_CLI_NAME, DAT_CLI_NAME, DAT_CLI_NAME, + DAT_CLI_NAME, + DAT_CLI_NAME, DAT_CLI_NAME, DAT_CLI_NAME, DAT_CLI_NAME, + DAT_CLI_NAME, DAT_CLI_NAME, DAT_CLI_NAME, DAT_CLI_NAME, + DAT_CLI_NAME, DAT_CLI_NAME, DAT_CLI_NAME, DAT_CLI_NAME, + DAT_CLI_NAME, DAT_CLI_NAME, DAT_CLI_NAME); +} +static int make_dir(const char*p){return mkdir(p,0700)==0||errno==EEXIST?0:-1;} +static int secret_directory(char*out,size_t cap){char base[1024];const char*home=dat_home_directory();int n;if(!home){errno=ENOENT;return-1;}n=snprintf(base,sizeof(base),"%s/.dat",home);if(n<0||(size_t)n>=sizeof(base)||make_dir(base))return-1;n=snprintf(out,cap,"%s/secret_keys",base);if(n<0||(size_t)n>=cap){errno=ENAMETOOLONG;return-1;}if(make_dir(out))return-1;return 0;} +static int create_archive(const char*dir,dat_archive_t*a){char secrets[1200];if(make_dir(dir)||dat_archive_create(a,dir)||secret_directory(secrets,sizeof(secrets))||dat_archive_save_secrets(a,secrets))return-1;return 0;} +static int open_writable(const char*dir,dat_archive_t*a){char secrets[1200];if(secret_directory(secrets,sizeof(secrets)))return-1;return dat_archive_open_writable(a,dir,secrets);} +static int print_url(const dat_archive_t*a){char url[DAT_URL_BYTES];if(dat_key_to_url(a->metadata_key,url))return-1;puts(url);return 0;} +static int write_archive_info(const char *dir, const dat_archive_t *archive, + const char *title, const char *description, int replace) +{ + dat_scheme_runtime_t runtime; + int result; + if (dat_scheme_runtime_init(&runtime)) return -1; + result = dat_scheme_write_archive_info(runtime.scheme, dir, archive, + title, description, replace); + dat_scheme_runtime_close(&runtime); + return result; +} +static int command_create(const char *dir, const char *title, + const char *description, int replace_info) +{ + dat_archive_t archive; + char metadata_path[1200]; + int n; + if (create_archive(dir, &archive)) { perror("dat create"); return 1; } + if (write_archive_info(dir, &archive, title, description, replace_info)) { + perror("dat create dat.scm"); dat_archive_close(&archive); return 1; + } + n = snprintf(metadata_path, sizeof(metadata_path), "%s%sdat.scm", dir, + dir[strlen(dir) - 1] == '/' ? "" : "/"); + if (n < 0 || (size_t)n >= sizeof(metadata_path) || + dat_archive_import_path(&archive, metadata_path, "dat.scm")) { + perror("dat create dat.scm import"); dat_archive_close(&archive); return 1; + } + print_url(&archive); + dat_archive_close(&archive); + return 0; +} +static int command_sync(const char *dir, int do_import) +{ + dat_archive_t archive; + char dat_directory[1200]; + struct stat info; + int n; + if (open_writable(dir, &archive)) { + int open_error = errno; + n = snprintf(dat_directory, sizeof(dat_directory), "%s/.dat", dir); + if (n < 0 || (size_t)n >= sizeof(dat_directory)) { errno = ENAMETOOLONG; perror("dat sync"); return 1; } + if (stat(dat_directory, &info) == 0) { + fprintf(stderr, "dat sync: existing archive could not be opened for writing (signing keys missing or invalid); left unchanged\n"); + return 1; + } + if (errno != ENOENT || open_error != ENOENT) { errno = open_error; perror("dat sync"); return 1; } + if (create_archive(dir, &archive)) { perror("dat sync"); return 1; } + } + if (write_archive_info(dir, &archive, NULL, NULL, 0)) { + perror("dat sync dat.scm"); dat_archive_close(&archive); return 1; + } + if (do_import && dat_archive_sync_path(&archive, dir, "")) { perror("dat sync"); dat_archive_close(&archive); return 1; } + print_url(&archive); dat_archive_close(&archive); return 0; +} +static int command_create_options(int argc,char**argv){const char*dir=".",*title=NULL,*description=NULL;int replace_info=0,i;for(i=2;i ":"",e.linkname);dat_archive_close(&a);return 0;} +static int parse_version(const char*s,uint64_t*v); +static int command_status(const char*dir){dat_archive_t a;sleep_metadata_entry_t e,later;size_t i,j,n,files=0,dirs=0,deleted=0;char url[DAT_URL_BYTES];if(dat_archive_open(&a,dir)){perror("dat status");return 1;}if(dat_key_to_url(a.metadata_key,url)){dat_archive_close(&a);return 1;}n=dat_archive_entry_count(&a);for(i=0;in){fprintf(stderr,"dat files: version out of range\n");dat_archive_close(&a);return 2;}for(i=0;i<(size_t)version;i++){int newest=1;if(dat_archive_read_entry(&a,i,&e))continue;for(j=i+1;j<(size_t)version;j++){if(dat_archive_read_entry(&a,j,&later))continue;if(!strcmp(e.path,later.path)){newest=0;break;}}if(newest&&e.type!=SLEEP_ENTRY_DELETE)printf("%-4s %10llu %s\n",type_name(e.type),(unsigned long long)e.size,e.path);}dat_archive_close(&a);return 0;} +static int parse_version(const char*s,uint64_t*v){char*end;unsigned long long n;errno=0;n=strtoull(s,&end,10);if(errno||!s[0]||*end)return-1;*v=(uint64_t)n;return 0;} +static int command_cat(const char*dir,const char*version_text,const char*path){dat_archive_t a;dat_checkout_t c;sleep_metadata_entry_t e;uint8_t*data;size_t length;uint64_t version;while(*path=='/')path++;if(parse_version(version_text,&version)){fprintf(stderr,"dat cat: invalid version\n");return 2;}if(dat_archive_open(&a,dir)||dat_checkout_init(&c,&a,version)||dat_checkout_find(&c,path,&e)||dat_checkout_read(&c,&e,&data,&length)){perror("dat cat");return 1;}if(length&&fwrite(data,1,length,stdout)!=length){perror("dat cat");free(data);dat_archive_close(&a);return 1;}free(data);dat_archive_close(&a);return 0;} +static int command_checkout(const char*dir,const char*version_text,const char*destination){dat_archive_t a;dat_checkout_t c;uint64_t version;if(parse_version(version_text,&version)){fprintf(stderr,"dat checkout: invalid version\n");return 2;}if(make_dir(destination)||dat_archive_open(&a,dir)||dat_checkout_init(&c,&a,version)||dat_checkout_export(&c,"",destination)){perror("dat checkout");return 1;}dat_archive_close(&a);return 0;} +static int command_url(int argc, char **argv) +{ + dat_archive_t archive; + dat_link_t link; + char text[DAT_LINK_BYTES]; + const char *directory = ".", *path; + int first = 2, compact = 0; + memset(&link, 0, sizeof(link)); + if (argc > first && !strcmp(argv[first], "--compact")) { compact = 1; first++; } + if (argc > first && parse_version(argv[first], &link.version) == 0) { + link.has_version = 1; first++; + } + if (argc > first) { + path = argv[first++]; + if (*path == '/') path++; + if (strlen(path) >= sizeof(link.path)) { errno = ENAMETOOLONG; perror("dat url"); return 1; } + strcpy(link.path, path); + } + if (argc > first) directory = argv[first++]; + if (argc > first) { usage(stderr); return 2; } + if (dat_archive_open(&archive, directory)) { perror("dat url"); return 1; } + memcpy(link.key, archive.metadata_key, DAT_KEY_BYTES); + if ((compact ? dat_link_format_compact(&link, text, sizeof(text)) : + dat_link_format(&link, text, sizeof(text)))) { + perror("dat url"); dat_archive_close(&archive); return 1; + } + puts(text); dat_archive_close(&archive); return 0; +} +static void print_hex(const uint8_t*p,size_t n){size_t i;for(i=0;i='0'&&s[i*2]<='9')?s[i*2]-'0':(s[i*2]>='a'&&s[i*2]<='f')?s[i*2]-'a'+10:(s[i*2]>='A'&&s[i*2]<='F')?s[i*2]-'A'+10:-1;lo=(s[i*2+1]>='0'&&s[i*2+1]<='9')?s[i*2+1]-'0':(s[i*2+1]>='a'&&s[i*2+1]<='f')?s[i*2+1]-'a'+10:(s[i*2+1]>='A'&&s[i*2+1]<='F')?s[i*2+1]-'A'+10:-1;if(hi<0||lo<0)return-1;out[i]=(uint8_t)((hi<<4)|lo);}return 0;} +static int command_keys_export(const char*dir){dat_archive_t a;if(open_writable(dir,&a)){perror("dat keys export");return 1;}printf("metadata: ");print_hex((const uint8_t*)&a.metadata_keys,sizeof(a.metadata_keys));printf("content: ");print_hex((const uint8_t*)&a.content_keys,sizeof(a.content_keys));dat_archive_close(&a);return 0;} +static int command_keys_import(const char*dir,const char*metadata,const char*content){dat_archive_t a;char secrets[1200];if(dat_archive_open(&a,dir)){perror("dat keys import");return 1;}if(parse_hex(metadata,(uint8_t*)&a.metadata_keys,sizeof(a.metadata_keys))||parse_hex(content,(uint8_t*)&a.content_keys,sizeof(a.content_keys))||memcmp(a.metadata_key,a.metadata_keys.pubkey,32)||memcmp(a.content_key,a.content_keys.pubkey,32)){fprintf(stderr,"dat keys import: keys do not match this archive\n");return 1;}if(secret_directory(secrets,sizeof(secrets))||dat_archive_save_secrets(&a,secrets)){perror("dat keys import");return 1;}return 0;} +static int command_doctor(const char *key_text){sleep_keypair_t key;cesspool_t pool;uint8_t topic[32];char url[DAT_URL_BYTES];memset(&key,0,sizeof(key));if(sleep_crypto_init()||(key_text?parse_hex(key_text,key.pubkey,sizeof(key.pubkey)):sleep_gen_keypair(&key))||dat_key_to_url(key.pubkey,url)){if(key_text)fprintf(stderr,"dat doctor: peer key must be 64 hexadecimal characters\n");else perror("dat doctor");return 1;}if(cesspool_init(&pool,key.pubkey)||cesspool_listen(&pool,"127.0.0.1",0)||cesspool_discovery_key(key.pubkey,topic)){perror("dat doctor Cesspool");return 1;}printf("crypto: ok\naddress: %s\ncesspool: UDP socket bound on 127.0.0.1:%u\nprotocol: %s\ndiscovery topic: ",url,pool.port,DAT_NETWORK_PROTOCOL);print_hex(topic,sizeof(topic));if(cesspool_destroy(&pool)){perror("dat doctor close");return 1;}return 0;} +static int command_bootstrap(int argc, char **argv) +{ + sleep_keypair_t identity; + cesspool_t pool; + unsigned long requested = 0; + char *end = NULL; + if (argc == 3 && !strcmp(argv[2], "list")) { + char contents[4096]; + if (cesspool_bootstrap_config_list(contents, sizeof(contents))) { + if (errno == ENOENT) { puts("No Cesspool rendezvous configured"); return 0; } + perror("dat bootstrap list"); return 1; + } + fputs(contents, stdout); + return 0; + } + if (argc == 4 && (!strcmp(argv[2], "add") || !strcmp(argv[2], "set"))) { + int replace = !strcmp(argv[2], "set"); + if ((replace ? cesspool_bootstrap_config_set(argv[3]) : + cesspool_bootstrap_config_add(argv[3]))) { perror("dat bootstrap"); return 1; } + printf("Saved Cesspool endpoint %s%s\n", argv[3], replace ? " as the active node" : ""); + return 0; + } + if (argc > 3 || (argc == 3 && !strcmp(argv[2], "add"))) { usage(stderr); return 2; } + if (argc == 3) { + requested = strtoul(argv[2], &end, 10); + if (!argv[2][0] || !end || *end || requested > 65535) { usage(stderr); return 2; } + } + memset(&identity, 0, sizeof(identity)); + if (sleep_gen_keypair(&identity) || cesspool_init(&pool, identity.pubkey) || + cesspool_listen(&pool, "0.0.0.0", (uint16_t)requested) || + cesspool_set_bootstrap_server(&pool, 1)) { + perror("dat bootstrap"); + memset(&identity, 0, sizeof(identity)); + return 1; + } + printf("Cesspool rendezvous listening on UDP :%u (NETWORK_PROTOCOL=%s)\n", + pool.port, DAT_NETWORK_PROTOCOL); + printf("Set DDAT_BOOTSTRAP=:%u on participating clients.\n", pool.port); + fflush(stdout); + memset(&identity, 0, sizeof(identity)); + for (;;) { + struct timespec now; + uint64_t now_ms; + if (cesspool_poll(&pool, 1000) < 0 && errno != EINTR) { perror("dat bootstrap poll"); break; } + if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) { + now_ms = (uint64_t)now.tv_sec * 1000u + (uint64_t)now.tv_nsec / 1000000u; + (void)cesspool_tick(&pool, now_ms); + } + } + (void)cesspool_destroy(&pool); + return 1; +} +static int command_resolve(const char*hostname){uint8_t key[DAT_KEY_BYTES];uint32_t ttl=0;char url[DAT_URL_BYTES];if(dat_dns_resolve(hostname,key,&ttl)){perror("dat resolve");return 1;}if(dat_key_to_url(key,url)){perror("dat resolve");return 1;}printf("%s\nTTL: %u seconds\n",url,ttl);return 0;} +static int print_dns_alias(const char *hostname, const uint8_t key[DAT_KEY_BYTES], void *context) +{ + char url[DAT_URL_BYTES]; + (void)context; + if (dat_key_to_url(key, url)) return 1; + printf("%s\t%s\n", hostname, url); + return 0; +} + +static int command_dns(int argc, char **argv) +{ + if (argc == 3 && !strcmp(argv[2], "list")) { + if (dat_dns_name_foreach(print_dns_alias, NULL) < 0) { perror("dat dns list"); return 1; } + return 0; + } + if (argc == 4 && !strcmp(argv[2], "remove")) { + if (dat_dns_name_remove(argv[3])) { perror("dat dns remove"); return 1; } + return 0; + } + if (argc == 5 && !strcmp(argv[2], "set")) { + uint8_t key[DAT_KEY_BYTES]; + dat_archive_t archive; + char url[DAT_URL_BYTES], hex[65]; + if (dat_url_to_key(argv[4], key)) { + if (dat_archive_open(&archive, argv[4])) { perror("dat dns set archive"); return 1; } + memcpy(key, archive.metadata_key, sizeof(key)); + dat_archive_close(&archive); + } + if (dat_dns_name_set(argv[3], key) || dat_key_to_url(key, url) || dat_key_to_hex(key, hex)) { + perror("dat dns set"); return 1; + } + printf("SITE: dat://%s\nARCHIVE: %s\nDAT-DNS /.well-known/dat:\ndat://%s\nTTL=3600\n", + argv[3], url, hex); + return 0; + } + usage(stderr); + return 2; +} +static int command_verify(const char*dir){char path[1200];int n=snprintf(path,sizeof(path),"%s/.dat",dir);if(n<0||(size_t)n>=sizeof(path)){errno=ENAMETOOLONG;perror("dat verify");return 1;}return sleep_verify_dat(path)?1:0;} +static int network_pending(const char*command){fprintf(stderr,"dat %s: command is present; network transfer is not connected to the CLI yet\n",command);return 2;} +int main(int argc,char**argv){const char*cmd,*dir=".";if(argc<2){usage(stdout);return 0;}cmd=argv[1];if(!strcmp(cmd,"-v")||!strcmp(cmd,"--version")||!strcmp(cmd,"version")){puts(DATGPT_VERSION);return 0;}if(!strcmp(cmd,"help")||!strcmp(cmd,"--help")||!strcmp(cmd,"-h")){usage(stdout);return 0;}if(!strcmp(cmd,"create")||!strcmp(cmd,"init"))return command_create_options(argc,argv);if(!strcmp(cmd,"share")||!strcmp(cmd,"sync"))return command_sync_options(argc,argv);if(!strcmp(cmd,"log")){if(argc>2)dir=argv[2];return command_log(dir);}if(!strcmp(cmd,"status")){if(argc>2)dir=argv[2];return command_status(dir);}if(!strcmp(cmd,"files")){const char*v=NULL;uint64_t parsed;if(argc>2&&parse_version(argv[2],&parsed)==0){v=argv[2];if(argc>3)dir=argv[3];}else if(argc>2)dir=argv[2];return command_files(dir,v);}if(!strcmp(cmd,"cat")){if(argc<4){usage(stderr);return 2;}if(argc>4)dir=argv[4];return command_cat(dir,argv[2],argv[3]);}if(!strcmp(cmd,"checkout")){if(argc<4){usage(stderr);return 2;}if(argc>4)dir=argv[4];return command_checkout(dir,argv[2],argv[3]);}if(!strcmp(cmd,"url"))return command_url(argc,argv);if(!strcmp(cmd,"dns"))return command_dns(argc,argv);if(!strcmp(cmd,"resolve")){if(argc!=3){usage(stderr);return 2;}return command_resolve(argv[2]);}if(!strcmp(cmd,"verify")){if(argc>2)dir=argv[2];return command_verify(dir);}if(!strcmp(cmd,"keys")){if(argc<3){usage(stderr);return 2;}if(!strcmp(argv[2],"export")){if(argc>3)dir=argv[3];return command_keys_export(dir);}if(!strcmp(argv[2],"import")&&argc>=5){if(argc>5)dir=argv[5];return command_keys_import(dir,argv[3],argv[4]);}usage(stderr);return 2;}if(!strcmp(cmd,"doctor")){if(argc>3){usage(stderr);return 2;}return command_doctor(argc==3?argv[2]:NULL);}if(!strcmp(cmd,"bootstrap"))return command_bootstrap(argc,argv);if(!strcmp(cmd,"clone")||!strcmp(cmd,"pull"))return network_pending(cmd);if(!strncmp(cmd,"dat://",6))return network_pending("clone");if(cmd[0]!='-'){char*sync_argv[3]={argv[0],"sync",argv[1]};return command_sync_options(3,sync_argv);}usage(stderr);return 2;} diff --git a/src/dat_dns.c b/src/dat_dns.c new file mode 100644 index 0000000..22a38f8 --- /dev/null +++ b/src/dat_dns.c @@ -0,0 +1,421 @@ +#define _DEFAULT_SOURCE +#include "dat_dns.h" +#include "dat_paths.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DAT_DNS_PACKET_MAX 65536 +#define DAT_DNS_TXT_MAX 4096 +#define DAT_DNS_CACHE_SIZE 32 + +typedef struct dat_dns_cache_entry { + char hostname[256]; + uint8_t key[DAT_KEY_BYTES]; + uint64_t expires_at; + int active; +} dat_dns_cache_entry_t; + +static dat_dns_cache_entry_t dns_cache[DAT_DNS_CACHE_SIZE]; +static size_t dns_cache_next; +static int curl_initialized; +static int hex_value(unsigned char c); + +typedef struct dat_dns_body { + char text[4096]; + size_t length; + int overflow; +} dat_dns_body_t; + +static size_t receive_dat_dns_body(char *data, size_t size, size_t count, void *context) +{ + dat_dns_body_t *body = (dat_dns_body_t *)context; + size_t bytes = size * count; + if (bytes > sizeof(body->text) - body->length - 1) { body->overflow = 1; return 0; } + memcpy(body->text + body->length, data, bytes); + body->length += bytes; + body->text[body->length] = '\0'; + return bytes; +} + +static int parse_wellknown_record(const char *body, size_t length, + uint8_t key[DAT_KEY_BYTES], uint32_t *ttl_out) +{ + const char *line_end, *ttl_line; + size_t key_length, i; + uint32_t ttl = 3600; + if (!body || !key) { errno = EINVAL; return -1; } + line_end = memchr(body, '\n', length); + if (!line_end) line_end = body + length; + if ((size_t)(line_end - body) < 70 || strncasecmp(body, "dat://", 6)) { errno = EPROTO; return -1; } + key_length = (size_t)(line_end - body) - 6; + if (key_length < 64) { errno = EPROTO; return -1; } + for (i = 0; i < DAT_KEY_BYTES; i++) { + int hi = hex_value((unsigned char)body[6 + i * 2]); + int lo = hex_value((unsigned char)body[7 + i * 2]); + if (hi < 0 || lo < 0) { errno = EPROTO; return -1; } + key[i] = (uint8_t)((hi << 4) | lo); + } + if (key_length > 64 && body[70] != '+' && body[70] != '/' && body[70] != '?') { errno = EPROTO; return -1; } + ttl_line = line_end < body + length ? line_end + 1 : NULL; + if (ttl_line && !strncasecmp(ttl_line, "ttl=", 4)) { + char *end; + unsigned long parsed = strtoul(ttl_line + 4, &end, 10); + if (end != ttl_line + 4 && (*end == '\n' || *end == '\r' || *end == '\0')) + ttl = parsed > 604800ul ? 604800u : (uint32_t)parsed; + } + if (ttl_out) *ttl_out = ttl; + return 0; +} + +static int hex_value(unsigned char c) +{ + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return -1; +} + +static int normalize_hostname(const char *input, char output[256]) +{ + size_t n, label_start = 0, i; + if (!input || !*input) { errno = EINVAL; return -1; } + n = strlen(input); + if (n && input[n - 1] == '.') n--; + if (!n || n > 253) { errno = EINVAL; return -1; } + for (i = 0; i < n; i++) { + unsigned char c = (unsigned char)input[i]; + if (c == '.') { + size_t label_length = i - label_start; + if (!label_length || label_length > 63 || input[label_start] == '-' || input[i - 1] == '-') { + errno = EINVAL; return -1; + } + output[i] = '.'; + label_start = i + 1; + } else if (isalnum(c) || c == '-') output[i] = (char)tolower(c); + else { errno = EINVAL; return -1; } + } + if (label_start == n || n - label_start > 63 || input[label_start] == '-' || input[n - 1] == '-') { + errno = EINVAL; return -1; + } + output[n] = '\0'; + return 0; +} + +static int names_file(char *path, size_t capacity, int create) +{ + const char *home = dat_home_directory(); + char directory[1024]; + int n; + if (!home || !*home) { errno = ENOENT; return -1; } + n = snprintf(directory, sizeof(directory), "%s/.dat", home); + if (n < 0 || (size_t)n >= sizeof(directory)) { errno = ENAMETOOLONG; return -1; } + if (create && mkdir(directory, 0700) && errno != EEXIST) return -1; + n = snprintf(path, capacity, "%s/dat-dns", directory); + if (n < 0 || (size_t)n >= capacity) { errno = ENAMETOOLONG; return -1; } + return 0; +} + +static int parse_name_line(char *line, char hostname[256], uint8_t key[DAT_KEY_BYTES]) +{ + char name[256], hex[65]; + size_t i; + if (sscanf(line, "%255s %64s", name, hex) != 2 || strlen(hex) != 64 || + normalize_hostname(name, hostname)) return -1; + for (i = 0; i < DAT_KEY_BYTES; i++) { + int hi = hex_value((unsigned char)hex[i * 2]); + int lo = hex_value((unsigned char)hex[i * 2 + 1]); + if (hi < 0 || lo < 0) return -1; + key[i] = (uint8_t)((hi << 4) | lo); + } + return 0; +} + +int dat_dns_name_foreach(dat_dns_name_iter_fn callback, void *context) +{ + char path[1200], line[400], hostname[256]; + uint8_t key[DAT_KEY_BYTES]; + FILE *file; + if (!callback || names_file(path, sizeof(path), 0)) { if (!callback) errno = EINVAL; return -1; } + file = fopen(path, "r"); + if (!file) return errno == ENOENT ? 0 : -1; + while (fgets(line, sizeof(line), file)) { + if (!parse_name_line(line, hostname, key) && callback(hostname, key, context)) { + fclose(file); return 1; + } + } + if (ferror(file)) { fclose(file); return -1; } + fclose(file); + return 0; +} + +typedef struct name_lookup_context { + const uint8_t *key; + char *hostname; + size_t capacity; + int found; +} name_lookup_context_t; + +static int find_name_for_key(const char *name, const uint8_t key[DAT_KEY_BYTES], void *context) +{ + name_lookup_context_t *lookup = (name_lookup_context_t *)context; + if (!memcmp(lookup->key, key, DAT_KEY_BYTES)) { + if (strlen(name) >= lookup->capacity) { errno = ENOSPC; return 1; } + strcpy(lookup->hostname, name); + lookup->found = 1; + } + return 0; +} + +int dat_dns_name_for_key(const uint8_t key[DAT_KEY_BYTES], char *hostname, + size_t hostname_capacity) +{ + name_lookup_context_t lookup; + if (!key || !hostname || !hostname_capacity) { errno = EINVAL; return -1; } + lookup.key = key; lookup.hostname = hostname; lookup.capacity = hostname_capacity; lookup.found = 0; + if (dat_dns_name_foreach(find_name_for_key, &lookup) < 0) return -1; + if (!lookup.found) { errno = ENOENT; return -1; } + return 0; +} + +typedef struct name_resolve_context { + const char *hostname; + uint8_t *key; + int found; +} name_resolve_context_t; + +static int find_key_for_name(const char *name, const uint8_t key[DAT_KEY_BYTES], void *context) +{ + name_resolve_context_t *lookup = (name_resolve_context_t *)context; + if (!strcasecmp(lookup->hostname, name)) { + memcpy(lookup->key, key, DAT_KEY_BYTES); + lookup->found = 1; + return 1; + } + return 0; +} + +int dat_dns_name_resolve(const char *hostname, uint8_t key[DAT_KEY_BYTES]) +{ + char normalized[256]; + name_resolve_context_t lookup; + if (!key || normalize_hostname(hostname, normalized)) { if (!key) errno = EINVAL; return -1; } + lookup.hostname = normalized; lookup.key = key; lookup.found = 0; + if (dat_dns_name_foreach(find_key_for_name, &lookup) < 0) return -1; + if (!lookup.found) { errno = ENOENT; return -1; } + return 0; +} + +static int update_name(const char *hostname, const uint8_t *key, int remove_alias) +{ + char normalized[256], path[1200], temporary[1250], line[400], old_name[256]; + uint8_t old_key[DAT_KEY_BYTES]; + FILE *input = NULL, *output = NULL; + char hex[65]; + int found = 0, n, result = -1; + if (normalize_hostname(hostname, normalized) || (!remove_alias && !key)) return -1; + if (names_file(path, sizeof(path), 1)) return -1; + n = snprintf(temporary, sizeof(temporary), "%s.tmp", path); + if (n < 0 || (size_t)n >= sizeof(temporary)) { errno = ENAMETOOLONG; return -1; } + input = fopen(path, "r"); + if (!input && errno != ENOENT) return -1; + output = fopen(temporary, "w"); + if (!output) { if (input) fclose(input); return -1; } + while (input && fgets(line, sizeof(line), input)) { + if (!parse_name_line(line, old_name, old_key) && !strcasecmp(old_name, normalized)) { + found = 1; + if (!remove_alias) { + if (dat_key_to_hex(key, hex) || fprintf(output, "%s %s\n", normalized, hex) < 0) + goto done; + } + } else if (fputs(line, output) == EOF) goto done; + } + if (input && ferror(input)) goto done; + if (!remove_alias && !found) { + if (dat_key_to_hex(key, hex) || fprintf(output, "%s %s\n", normalized, hex) < 0) goto done; + } + if (fclose(output)) { output = NULL; goto done; } + output = NULL; + if (input && fclose(input)) { input = NULL; goto done; } + input = NULL; + if (remove_alias && !found) { errno = ENOENT; goto done; } + if (dat_replace_file(temporary, path)) goto done; + result = 0; +done: + if (input) fclose(input); + if (output) fclose(output); + if (result) unlink(temporary); + return result; +} + +int dat_dns_name_set(const char *hostname, const uint8_t key[DAT_KEY_BYTES]) +{ + return update_name(hostname, key, 0); +} + +int dat_dns_name_remove(const char *hostname) +{ + return update_name(hostname, NULL, 1); +} + +static int parse_datkey(const char *txt, size_t length, uint8_t key[32]) +{ + size_t i; + const char *value = NULL; + for (i = 0; i + 7 <= length; i++) { + if ((txt[i] == 'd' || txt[i] == 'D') && + (txt[i + 1] == 'a' || txt[i + 1] == 'A') && + (txt[i + 2] == 't' || txt[i + 2] == 'T') && + (txt[i + 3] == 'k' || txt[i + 3] == 'K') && + (txt[i + 4] == 'e' || txt[i + 4] == 'E') && + (txt[i + 5] == 'y' || txt[i + 5] == 'Y') && txt[i + 6] == '=') { + value = txt + i + 7; + break; + } + } + if (!value || (size_t)(txt + length - value) < 64) return 0; + for (i = 0; i < 32; i++) { + int hi = hex_value((unsigned char)value[i * 2]); + int lo = hex_value((unsigned char)value[i * 2 + 1]); + if (hi < 0 || lo < 0) return 0; + key[i] = (uint8_t)((hi << 4) | lo); + } + if ((size_t)(txt + length - value) > 64 && value[64] != '&' && value[64] != ';' && + value[64] != ' ' && value[64] != '"' && value[64] != '\'') + return 0; + return 1; +} + +int dat_dns_parse_answer(const uint8_t *packet, unsigned packet_len, + uint8_t key[DAT_KEY_BYTES], uint32_t *ttl_seconds) +{ + unsigned offset = 12, questions, answers, record_index; + if (!packet || packet_len < 12 || !key) { errno = EINVAL; return -1; } + questions = ((unsigned)packet[4] << 8) | packet[5]; + answers = ((unsigned)packet[6] << 8) | packet[7]; + for (record_index = 0; record_index < questions; record_index++) { + unsigned hops = 0, cursor = offset; + int jumped = 0; + for (;;) { + unsigned label; + if (cursor >= packet_len || ++hops > packet_len) { errno = EPROTO; return -1; } + label = packet[cursor++]; + if ((label & 0xc0u) == 0xc0u) { + if (cursor >= packet_len) { errno = EPROTO; return -1; } + if (!jumped) offset = cursor + 1; + cursor = ((label & 0x3fu) << 8) | packet[cursor]; jumped = 1; continue; + } + if (label & 0xc0u) { errno = EPROTO; return -1; } + if (!label) { if (!jumped) offset = cursor; break; } + if (label > 63 || label > packet_len - cursor) { errno = EPROTO; return -1; } + cursor += label; + } + if (packet_len - offset < 4) { errno = EPROTO; return -1; } + offset += 4; + } + for (record_index = 0; record_index < answers; record_index++) { + unsigned hops = 0, cursor = offset, type, data_length, end; + int jumped = 0; + uint32_t ttl; + for (;;) { + unsigned label; + if (cursor >= packet_len || ++hops > packet_len) { errno = EPROTO; return -1; } + label = packet[cursor++]; + if ((label & 0xc0u) == 0xc0u) { + if (cursor >= packet_len) { errno = EPROTO; return -1; } + if (!jumped) offset = cursor + 1; + cursor = ((label & 0x3fu) << 8) | packet[cursor]; jumped = 1; continue; + } + if (label & 0xc0u) { errno = EPROTO; return -1; } + if (!label) { if (!jumped) offset = cursor; break; } + if (label > 63 || label > packet_len - cursor) { errno = EPROTO; return -1; } + cursor += label; + } + if (packet_len - offset < 10) { errno = EPROTO; return -1; } + type = ((unsigned)packet[offset] << 8) | packet[offset + 1]; + ttl = ((uint32_t)packet[offset + 4] << 24) | ((uint32_t)packet[offset + 5] << 16) | + ((uint32_t)packet[offset + 6] << 8) | packet[offset + 7]; + data_length = ((unsigned)packet[offset + 8] << 8) | packet[offset + 9]; + offset += 10; + if (data_length > packet_len - offset) { errno = EPROTO; return -1; } + end = offset + data_length; + if (type == 16) { + char text[DAT_DNS_TXT_MAX]; + size_t used = 0; + while (offset < end) { + unsigned part = packet[offset++]; + if (part > end - offset || used + part >= sizeof(text)) { errno = EPROTO; return -1; } + memcpy(text + used, packet + offset, part); used += part; offset += part; + } + if (parse_datkey(text, used, key)) { if (ttl_seconds) *ttl_seconds = ttl; return 0; } + } else offset = end; + } + errno = ENOENT; + return -1; +} + +int dat_dns_resolve(const char *hostname, uint8_t key[DAT_KEY_BYTES], + uint32_t *ttl_seconds) +{ + size_t i; + uint64_t now; + uint32_t ttl = 0; + char normalized[256], request_url[320]; + if (!hostname || !*hostname || !key) { errno = EINVAL; return -1; } + if (normalize_hostname(hostname, normalized)) return -1; + if (dat_dns_name_resolve(normalized, key) == 0) { + if (ttl_seconds) *ttl_seconds = 0; + return 0; + } + now = (uint64_t)time(NULL); + for (i = 0; i < DAT_DNS_CACHE_SIZE; i++) { + if (dns_cache[i].active && dns_cache[i].expires_at <= now) + dns_cache[i].active = 0; + if (dns_cache[i].active && !strcasecmp(normalized, dns_cache[i].hostname)) { + memcpy(key, dns_cache[i].key, DAT_KEY_BYTES); + if (ttl_seconds) *ttl_seconds = (uint32_t)(dns_cache[i].expires_at - now); + return 0; + } + } + { + CURL *curl; + CURLcode result; + long status = 0; + dat_dns_body_t body; + int url_length = snprintf(request_url, sizeof(request_url), "https://%s/.well-known/dat", normalized); + if (url_length < 0 || (size_t)url_length >= sizeof(request_url)) { errno = ENAMETOOLONG; return -1; } + if (!curl_initialized) { if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) { errno = EIO; return -1; } curl_initialized = 1; } + memset(&body, 0, sizeof(body)); + curl = curl_easy_init(); + if (!curl) { errno = ENOMEM; return -1; } + curl_easy_setopt(curl, CURLOPT_URL, request_url); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, 2000L); + curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 2000L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, receive_dat_dns_body); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &body); + result = curl_easy_perform(curl); + if (result == CURLE_OK) curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); + curl_easy_cleanup(curl); + if (result != CURLE_OK || status != 200 || body.overflow) { errno = EHOSTUNREACH; return -1; } + if (parse_wellknown_record(body.text, body.length, key, &ttl)) return -1; + } + if (ttl_seconds) *ttl_seconds = ttl; + if (ttl) { + dat_dns_cache_entry_t *entry = &dns_cache[dns_cache_next++ % DAT_DNS_CACHE_SIZE]; + strcpy(entry->hostname, hostname); + memcpy(entry->key, key, DAT_KEY_BYTES); + entry->expires_at = now + ttl; + entry->active = 1; + } + return 0; +} diff --git a/src/dat_dpi.c b/src/dat_dpi.c new file mode 100644 index 0000000..770d6b4 --- /dev/null +++ b/src/dat_dpi.c @@ -0,0 +1,758 @@ +#define _POSIX_C_SOURCE 200809L +#include "dat.h" +#include "dat_dns.h" +#include "dat_fetch.h" +#include "dat_paths.h" +#include "dat_dpi_icons.h" +#include "s7.h" +#include "sleep_feed.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TAG_CAP 8192 +#define DPI_ARCHIVES_MAX 128 + +typedef struct { + char key[65]; + char root[1200]; + char title[256]; + char description[512]; + uint64_t version; + int used; +} dpi_archive_t; + +static dpi_archive_t open_archives[DPI_ARCHIVES_MAX]; +static s7_scheme *metadata_scheme; +static int quit_requested; + +typedef struct { + char *data; + size_t length; + size_t capacity; +} body_buffer_t; + +static int body_appendf(body_buffer_t *body, const char *format, ...) +{ + va_list args, copy; + int needed; + size_t capacity; + char *data; + va_start(args, format); + va_copy(copy, args); + needed = vsnprintf(NULL, 0, format, copy); + va_end(copy); + if (needed < 0) { va_end(args); return -1; } + if ((size_t)needed > SIZE_MAX - body->length - 1) { va_end(args); errno = EOVERFLOW; return -1; } + if (body->length + (size_t)needed + 1 > body->capacity) { + capacity = body->capacity ? body->capacity : 512; + while (capacity < body->length + (size_t)needed + 1) { + if (capacity > SIZE_MAX / 2) { capacity = body->length + (size_t)needed + 1; break; } + capacity *= 2; + } + data = realloc(body->data, capacity); + if (!data) { va_end(args); return -1; } + body->data = data; + body->capacity = capacity; + } + vsnprintf(body->data + body->length, body->capacity - body->length, format, args); + va_end(args); + body->length += (size_t)needed; + return 0; +} + +static void send_page(const char *url, const char *mime, const void *body, size_t length) +{ + const char *p; + printf("\nContent-Type: %s\r\n\r\n", mime); + if (length) fwrite(body, 1, length, stdout); + fflush(stdout); +} + +static int send_file(const char *url, const char *mime, const dat_archive_t *archive, + const sleep_metadata_entry_t *entry) +{ + sleep_feed_t feed; + uint64_t block; + size_t sent = 0; + const char *p; + if (entry->size > SIZE_MAX) { errno = EOVERFLOW; return -1; } + if (sleep_feed_open(&feed, archive->dat_path, "content", NULL)) return -1; + for (block = 0; block < entry->block_count; block++) { + if (!sleep_feed_has(&feed, (size_t)entry->block_offset + (size_t)block)) { + sleep_feed_close(&feed); errno = ENOENT; return -1; + } + } + fputs("\nContent-Type: %s\r\n\r\n", mime); + fflush(stdout); + for (block = 0; block < entry->block_count; block++) { + uint8_t *data = NULL; + size_t length = 0; + if (sleep_feed_read(&feed, (size_t)entry->block_offset + (size_t)block, + &data, &length) || length > (size_t)entry->size - sent) { + free(data); sleep_feed_close(&feed); errno = EIO; return -1; + } + if (length && fwrite(data, 1, length, stdout) != length) { + free(data); sleep_feed_close(&feed); errno = EIO; return -1; + } + sent += length; + free(data); + } + sleep_feed_close(&feed); + fflush(stdout); + if (sent != (size_t)entry->size) { errno = EPROTO; return -1; } + return ferror(stdout) ? -1 : 0; +} + +static void send_status(const char *message) +{ + const char *p; + fputs("", stdout); + fflush(stdout); +} + +static void send_reload(const char *url) +{ + const char *p; + fputs("", stdout); + fflush(stdout); +} + +static int html_append(body_buffer_t *body, const char *text) +{ + const unsigned char *p = (const unsigned char *)(text ? text : ""); + for (; *p; p++) { + const char *escape = NULL; + switch (*p) { + case '&': escape = "&"; break; + case '<': escape = "<"; break; + case '>': escape = ">"; break; + case '\"': escape = """; break; + case '\'': escape = "'"; break; + default: break; + } + if (escape) { if (body_appendf(body, "%s", escape)) return -1; } + else if (body_appendf(body, "%c", *p)) return -1; + } + return 0; +} + +static int url_append_path(body_buffer_t *body, const char *path) +{ + static const char hex[] = "0123456789ABCDEF"; + const unsigned char *p = (const unsigned char *)path; + for (; *p; p++) { + if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || + (*p >= '0' && *p <= '9') || strchr("/-_.~", *p)) { + if (body_appendf(body, "%c", *p)) return -1; + } else if (body_appendf(body, "%%%c%c", hex[*p >> 4], hex[*p & 15])) return -1; + } + return 0; +} + +/* Dillo DPIP tags end in space, quote, greater-than. */ +static int read_tag(FILE *in, char *tag, size_t cap) +{ + int c, quote = 0; + size_t n = 0; + while ((c = fgetc(in)) != EOF) { + if (!n && isspace((unsigned char)c)) continue; + if (n + 1 >= cap) { errno = EOVERFLOW; return -1; } + tag[n++] = (char)c; + if (c == '\'') { + if (quote) { + c = fgetc(in); + if (c == '\'') { + if (n + 1 >= cap) { errno = EOVERFLOW; return -1; } + tag[n++] = (char)c; + continue; + } + if (c != EOF) ungetc(c, in); + } + quote = !quote; + } + if (n >= 2 && tag[n - 2] == '\'' && tag[n - 1] == '>') { + tag[n] = 0; + return 1; + } + } + return n ? -1 : 0; +} + +static char *attribute(const char *tag, const char *name) +{ + char needle[64]; + const char *p, *start, *end; + char *out; + size_t n, used = 0; + if (snprintf(needle, sizeof(needle), " %s='", name) >= (int)sizeof(needle)) return NULL; + p = strstr(tag, needle); + if (p) start = p + strlen(needle); + else if (tag[0] == '<' && !strncmp(tag + 1, needle + 1, strlen(needle) - 1)) + start = tag + strlen(needle); + else return NULL; + end = start; + while (*end) { + if (*end == '\'' && end[1] != '\'') break; + if (*end == '\'' && end[1] == '\'') end += 2; + else end++; + } + if (!*end) return NULL; + n = (size_t)(end - start); + out = malloc(n + 1); + if (!out) return NULL; + while (start < end) { + out[used++] = *start; + if (*start == '\'' && start + 1 < end && start[1] == '\'') start++; + start++; + } + out[used] = 0; + return out; +} + +static int authenticate(const char *tag) +{ + char path[1024], line[512], secret[128]; + FILE *f; + char *msg; + int ok = 0; + const char *home = dat_home_directory(); + if (!home || snprintf(path, sizeof(path), "%s/.dillo/dpid_comm_keys", home) >= (int)sizeof(path)) return 0; + f = fopen(path, "r"); + if (!f) return 0; + if (fgets(line, sizeof(line), f) && sscanf(line, "%*u %127s", secret) == 1) { + msg = attribute(tag, "msg"); + if (msg && strlen(msg) == strlen(secret)) ok = memcmp(msg, secret, strlen(secret)) == 0; + free(msg); + } + fclose(f); + return ok; +} + +static s7_pointer manifest_field(s7_pointer record, const char *name) +{ + s7_pointer fields = record; + while (s7_is_pair(fields)) { + s7_pointer field = s7_car(fields), key, values; + if (s7_is_pair(field)) { + key = s7_car(field); + values = s7_cdr(field); + if (s7_is_symbol(key) && !strcmp(s7_symbol_name(key), name) && + s7_is_pair(values)) return s7_car(values); + } + fields = s7_cdr(fields); + } + return NULL; +} + +static void read_manifest(const dat_archive_t *archive, uint64_t version, + dpi_archive_t *record) +{ + dat_path_info_t info; + int listing; + uint8_t *data = NULL; + size_t length = 0; + if (!metadata_scheme) metadata_scheme = s7_init(); + if (metadata_scheme && + dat_archive_resolve_at(archive, version, "/dat.scm", 0, &info, &listing) == 0 && + !listing && info.entry.type == SLEEP_ENTRY_FILE && + dat_archive_read_file(archive, &info.entry, &data, &length) == 0 && + length < 1024u * 1024u && !memchr(data, 0, length)) { + char *source = malloc(length + 1); + if (source) { + s7_pointer port, parsed, title, description; + memcpy(source, data, length); source[length] = '\0'; + port = s7_open_input_string(metadata_scheme, source); + parsed = port ? s7_read(metadata_scheme, port) : NULL; + if (port) s7_close_input_port(metadata_scheme, port); + title = parsed ? manifest_field(parsed, "title") : NULL; + description = parsed ? manifest_field(parsed, "description") : NULL; + if (title && s7_is_string(title)) snprintf(record->title, sizeof(record->title), "%s", s7_string(title)); + if (description && s7_is_string(description)) snprintf(record->description, sizeof(record->description), "%s", s7_string(description)); + free(source); + } + } + free(data); + if (!record->title[0] || !record->description[0]) { + uint8_t *cname = NULL; + size_t cname_length = 0; + if (dat_archive_resolve_at(archive, version, "/CNAME", 0, &info, &listing) == 0 && + !listing && info.entry.type == SLEEP_ENTRY_FILE && + dat_archive_read_file(archive, &info.entry, &cname, &cname_length) == 0 && + cname_length && cname_length < sizeof(record->title)) { + while (cname_length && (cname[cname_length - 1] == '\n' || cname[cname_length - 1] == '\r')) cname_length--; + if (!record->title[0]) { memcpy(record->title, cname, cname_length); record->title[cname_length] = '\0'; } + } + free(cname); + } +} + +static dpi_archive_t *remember_archive(const dat_archive_t *archive, const char *keyhex, + const char *root, uint64_t version) +{ + size_t i; + dpi_archive_t *record = NULL; + for (i = 0; i < DPI_ARCHIVES_MAX; i++) { + if (open_archives[i].used && !strcmp(open_archives[i].key, keyhex)) { + record = &open_archives[i]; break; + } + if (!open_archives[i].used && !record) record = &open_archives[i]; + } + if (!record) return NULL; + if (!record->used) { + memset(record, 0, sizeof(*record)); + snprintf(record->key, sizeof(record->key), "%s", keyhex); + snprintf(record->root, sizeof(record->root), "%s", root); + snprintf(record->title, sizeof(record->title), "%s", keyhex); + record->used = 1; + } + record->version = version; + read_manifest(archive, version, record); + return record; +} + +static int parse_query_value(const char *url, const char *name, char *out, size_t capacity) +{ + const char *query = strchr(url, '?'); + size_t name_length = strlen(name); + if (!query) return 0; + query++; + while (*query) { + const char *end = strchr(query, '&'); + size_t part_length = end ? (size_t)(end - query) : strlen(query); + const char *equals = memchr(query, '=', part_length); + if (equals && (size_t)(equals - query) == name_length && !memcmp(query, name, name_length)) { + size_t value_length = part_length - name_length - 1; + if (value_length >= capacity) { errno = ENAMETOOLONG; return -1; } + memcpy(out, equals + 1, value_length); out[value_length] = '\0'; + return 1; + } + if (!end) break; + query = end + 1; + } + return 0; +} + +static int error_page(const char *url, int status, const char *message) +{ + const char *p; + (void)status; + printf("\nContent-Type: text/plain\r\n\r\n%s\n", message); + fflush(stdout); + return ferror(stdout) ? -1 : 0; +} + +typedef struct { + sleep_metadata_entry_t entry; + char name[1024]; + int type; +} listing_context_t; + +typedef struct { + listing_context_t *entries; + size_t count; + size_t capacity; +} listing_accumulator_t; + +static int list_entry(const sleep_metadata_entry_t *entry, const char *name, void *context) +{ + listing_accumulator_t *list = context; + listing_context_t *next; + if (list->count == list->capacity) { + size_t capacity = list->capacity ? list->capacity * 2 : 16; + next = realloc(list->entries, capacity * sizeof(*next)); + if (!next) return -1; + list->entries = next; list->capacity = capacity; + } + next = &list->entries[list->count++]; + memset(next, 0, sizeof(*next)); + next->entry = *entry; + next->type = entry->type == SLEEP_ENTRY_DIRECTORY ? 3 : 2; + if (strlen(name) >= sizeof(next->name)) { errno = ENAMETOOLONG; return -1; } + strcpy(next->name, name); + return 0; +} + +static int compare_listing(const void *left, const void *right) +{ + const listing_context_t *a = left, *b = right; + if (a->type != b->type) return b->type - a->type; + return -strcoll(a->name, b->name); +} + +static int append_listing_row(body_buffer_t *body, const dat_url_t *url, + const listing_context_t *entry, const char *link_path) +{ + const char *icon = entry->type == 1 ? dat_dpi_icon_updir : + entry->type == 3 ? dat_dpi_icon_directory : dat_dpi_icon_file; + char date[64] = ""; + if (entry->entry.mtime) { + time_t seconds = (time_t)(entry->entry.mtime / 1000u); + struct tm *tm = gmtime(&seconds); + if (tm) strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", tm); + } + if (body_appendf(body, "\"%s\"
    type == 1 ? "up" : entry->type == 2 ? "directory" : "file", icon)) return -1; + if (html_append(body, url->host)) return -1; + if (url->has_version && body_appendf(body, "+%llu", (unsigned long long)url->version)) return -1; + if (url_append_path(body, link_path) || body_appendf(body, "\">") || + html_append(body, entry->name)) return -1; + if (entry->type == 3 && body_appendf(body, "/")) return -1; + if (body_appendf(body, "")) return -1; + if (*date && html_append(body, date)) return -1; + if (body_appendf(body, "")) return -1; + if (entry->type == 2 && body_appendf(body, "%llu", (unsigned long long)entry->entry.size)) return -1; + return body_appendf(body, "\n"); +} + +static const char *dpi_content_type(const char *pathname) +{ + const char *dot = strrchr(pathname, '.'); + if (!dot) return "text/plain"; + dot++; + if (!strcmp(dot, "htm") || !strcmp(dot, "html")) return "text/html"; + if (!strcmp(dot, "png")) return "image/png"; + if (!strcmp(dot, "gif")) return "image/gif"; + if (!strcmp(dot, "jpg")) return "image/jpeg"; + return "text/plain"; +} + +static int serve_not_found(const char *url) +{ + static const char page[] = "

    Not found

    "; + send_page(url, "text/html", page, sizeof(page) - 1); + return -1; +} + +static int serve_timeout(const char *url) +{ + static const char page[] = "

    Timed out

    "; + send_page(url, "text/html", page, sizeof(page) - 1); + return -1; +} + +static int serve_directory(const char *url_text, const dat_url_t *url, + const dat_archive_t *archive, uint64_t version) +{ + listing_accumulator_t entries = {0}; + listing_context_t parent; + body_buffer_t body = {0}; + char parent_path[2048]; + size_t i, n; + int failed = 0; + if (dat_archive_readdir_at(archive, version, url->path, list_entry, &entries)) { + free(entries.entries); + return serve_not_found(url_text); + } + qsort(entries.entries, entries.count, sizeof(*entries.entries), compare_listing); + if (body_appendf(&body, "")) failed = 1; + if (!failed && html_append(&body, url->path)) failed = 1; + if (!failed && body_appendf(&body, "

    ")) failed = 1; + if (!failed && html_append(&body, url->path)) failed = 1; + if (!failed && body_appendf(&body, "

    ")) failed = 1; + if (strcmp(url->path, "/")) { + memset(&parent, 0, sizeof(parent)); + parent.type = 1; + strcpy(parent.name, ".."); + strcpy(parent_path, url->path); + n = strlen(parent_path); + while (n && parent_path[n - 1] == '/') parent_path[--n] = '\0'; + while (n && parent_path[n - 1] != '/') parent_path[--n] = '\0'; + if (!n) strcpy(parent_path, "/"); + else parent_path[n] = '\0'; + if (!failed && append_listing_row(&body, url, &parent, parent_path)) failed = 1; + } + for (i = 0; !failed && i < entries.count; i++) { + body_buffer_t href = {0}; + const listing_context_t *entry = &entries.entries[i]; + if (body_appendf(&href, "%s", url->path) || + (href.length && href.data[href.length - 1] != '/' && body_appendf(&href, "/")) || + body_appendf(&href, "%s%s", entry->name, entry->type == 3 ? "/" : "") || + append_listing_row(&body, url, entry, href.data)) failed = 1; + free(href.data); + } + if (!failed && body_appendf(&body, "
    ")) failed = 1; + if (failed) { + free(body.data); free(entries.entries); + return error_page(url_text, 500, "Unable to build directory listing"); + } + send_page(url_text, "text/html", body.data, body.length); + free(body.data); free(entries.entries); + return 0; +} + +static int json_append_string(body_buffer_t *body, const char *text) +{ + const unsigned char *p = (const unsigned char *)(text ? text : ""); + if (body_appendf(body, "\"")) return -1; + for (; *p; p++) { + if (*p == '\"' || *p == '\\') { if (body_appendf(body, "\\%c", *p)) return -1; } + else if (*p < 0x20) { if (body_appendf(body, "\\u%04x", *p)) return -1; } + else if (body_appendf(body, "%c", *p)) return -1; + } + return body_appendf(body, "\""); +} + +static int serve(const char *url_text) +{ + dat_url_t url; + uint8_t key[DAT_KEY_BYTES]; + const char *home = dat_home_directory(); + char root[1200], keyhex[65], fetch_error[512]; + dat_archive_t archive; + dat_path_info_t info; + int listing = 0; + uint64_t version; + if (dat_url_parse(url_text, &url)) { + error_page(url_text, 400, "Invalid dat:// URL"); return -1; + } + send_status("resolving name"); + if (dat_url_to_key(url.host, key) && dat_dns_resolve(url.host, key, NULL)) { + error_page(url_text, 404, "Dat hostname has no usable datkey TXT record"); return -1; + } + if (!home) { error_page(url_text, 500, "User home directory is unavailable"); return -1; } + if (dat_key_to_hex(key, keyhex)) { error_page(url_text, 500, "Unable to format archive key"); return -1; } + if (snprintf(root, sizeof(root), "%s/.dillo/dat/archives/%.2s/%s", + home, keyhex, keyhex + 2) >= (int)sizeof(root)) { + error_page(url_text, 400, "Dillo archive cache path is too long"); return -1; + } + if (dat_archive_open(&archive, root)) { + send_status("getting archive"); + if (dat_fetch_archive(root, key, 10000u)) { + int saved = errno; + if (saved == ETIMEDOUT) return serve_timeout(url_text); + const char *reason = saved == ENETUNREACH ? "No Cesspool bootstrap is configured" : + saved == EHOSTUNREACH ? "Cesspool bootstrap did not answer" : + saved == ENOENT ? "No peer is announcing this archive key" : + strerror(saved); + snprintf(fetch_error, sizeof(fetch_error), "Cesspool archive fetch failed: %s", reason); + return error_page(url_text, 404, fetch_error); + } + if (dat_archive_open(&archive, root)) { + snprintf(fetch_error, sizeof(fetch_error), "Fetched archive cannot be opened: %s", strerror(errno)); + return error_page(url_text, 404, fetch_error); + } + } + version = url.has_version ? url.version : dat_archive_version(&archive); + if (version > dat_archive_version(&archive)) { + dat_archive_close(&archive); return serve_not_found(url_text); + } + if (dat_archive_resolve_at(&archive, version, url.path, + url.force_directory_listing, &info, &listing)) { + int saved = errno; + dat_archive_close(&archive); + if (saved == ETIMEDOUT || saved == EAGAIN) return serve_timeout(url_text); + return serve_not_found(url_text); + } + (void)remember_archive(&archive, keyhex, root, version); + send_status("serving file"); + if (url.wants_stat) { + body_buffer_t stat = {0}; + if (body_appendf(&stat, "{\n \"path\": ") || json_append_string(&stat, url.path) || + body_appendf(&stat, ",\n \"directory\": %s,\n \"size\": %llu,\n \"mtime\": %llu\n}\n", + info.is_directory ? "true" : "false", + (unsigned long long)info.entry.size, (unsigned long long)info.entry.mtime)) { + free(stat.data); dat_archive_close(&archive); return error_page(url_text, 500, "Stat response is too large"); + } + send_page(url_text, "text/plain", stat.data, stat.length); + free(stat.data); + } else if (listing) { + serve_directory(url_text, &url, &archive, version); + } else { + if (info.entry.type != SLEEP_ENTRY_FILE) { + dat_archive_close(&archive); return serve_not_found(url_text); + } + if (send_file(url_text, dpi_content_type(info.entry.path), &archive, &info.entry)) { + dat_archive_close(&archive); return error_page(url_text, 500, "File content unavailable"); + } + } + dat_archive_close(&archive); + fflush(stdout); + return ferror(stdout) ? -1 : 0; +} + +static int serve_dashboard(const char *url) +{ + char action[32], key[80], reload[2048]; + int has_action, has_key, has_reload; + body_buffer_t body = {0}; + size_t i, count = 0; + if (parse_query_value(url, "unswarm", action, sizeof(action)) > 0) { + has_key = parse_query_value(url, "key", key, sizeof(key)); + has_reload = parse_query_value(url, "reload", reload, sizeof(reload)); + if (has_key > 0) { + for (i = 0; i < DPI_ARCHIVES_MAX; i++) + if (open_archives[i].used && !strcmp(open_archives[i].key, key)) + memset(&open_archives[i], 0, sizeof(open_archives[i])); + send_status("unswarming archive"); + send_reload(has_reload > 0 ? reload : "dpi:/dat/"); + return 0; + } + } + has_action = parse_query_value(url, "quit", action, sizeof(action)); + if (has_action > 0) { + static const char quitting[] = "Quitting"; + send_page(url, "text/plain", quitting, sizeof(quitting) - 1); + quit_requested = 1; + return 0; + } + if (body_appendf(&body, "Dat")) goto fail; + if (body_appendf(&body, "

    Archives

    ")) goto fail; + for (i = 0; i < DPI_ARCHIVES_MAX; i++) if (open_archives[i].used) count++; + if (!count && body_appendf(&body, "

    None

    ")) goto fail; + for (i = 0; i < DPI_ARCHIVES_MAX; i++) { + dpi_archive_t *archive = &open_archives[i]; + if (!archive->used) continue; + if (body_appendf(&body, "

    ", archive->key) || + html_append(&body, archive->title[0] ? archive->title : archive->key) || + body_appendf(&body, "")) goto fail; + if (archive->description[0] && + (body_appendf(&body, " - ") || html_append(&body, archive->description))) goto fail; + if (body_appendf(&body, "
    version %llu

    ", + archive->key, (unsigned long long)archive->version, + (unsigned long long)archive->version) || + body_appendf(&body, "

    Peers

    None")) goto fail; + if (body_appendf(&body, "
    ", archive->key)) goto fail; + } + if (body_appendf(&body, "")) goto fail; + send_page(url, "text/html", body.data, body.length); + free(body.data); + return 0; +fail: + free(body.data); + return error_page(url, 500, "Unable to build Dat dashboard"); +} + +static int serve_internal(const char *url) +{ + const char *path = strstr(url, "://"); + const char *query; + char decoded[2048]; + size_t n; + if (!strncmp(url, "dpi:/", 5)) path = url + 4; + else if (path) path = strchr(path + 3, '/'); + if (!path) path = "/"; + query = strchr(path, '?'); + n = query ? (size_t)(query - path) : strlen(path); + if (n >= sizeof(decoded)) return error_page(url, 400, "Internal DPI path is too long"); + memcpy(decoded, path, n); decoded[n] = '\0'; + if (!strcmp(decoded, "/dat/")) return serve_dashboard(url); + return serve_not_found(url); +} + +static int serve_stdio_requests(void) +{ + char tag[TAG_CAP]; + int result = read_tag(stdin, tag, sizeof(tag)); + if (result <= 0 || !authenticate(tag)) return 1; + while (!quit_requested) { + char *cmd, *url; + result = read_tag(stdin, tag, sizeof(tag)); + if (result <= 0) break; + cmd = attribute(tag, "cmd"); + url = attribute(tag, "url"); + if (cmd && !strcmp(cmd, "DpiBye")) { free(cmd); free(url); break; } + if (cmd && !strcmp(cmd, "open_url") && url) { + if (!strncmp(url, "dat:", 4)) serve(url); + else if (!strncmp(url, "dpi:/", 5)) serve_internal(url); + else error_page(url, 404, "Not found"); + } + free(cmd); + free(url); + } + return 0; +} + +static int serve_socket_requests(void) +{ + int listener = dup(STDIN_FILENO), stopped = 0; + if (listener < 0) return -1; + while (!stopped) { + int client = accept(listener, NULL, NULL); + int saved_in, saved_out, stop_after_request = 0; + char tag[TAG_CAP]; + if (client < 0) { + if (errno == EINTR) continue; + close(listener); + return -1; + } + saved_in = dup(STDIN_FILENO); + saved_out = dup(STDOUT_FILENO); + if (saved_in < 0 || saved_out < 0 || dup2(client, STDIN_FILENO) < 0 || + dup2(client, STDOUT_FILENO) < 0) { + if (saved_in >= 0) close(saved_in); + if (saved_out >= 0) close(saved_out); + close(client); + close(listener); + return -1; + } + clearerr(stdin); + clearerr(stdout); + if (read_tag(stdin, tag, sizeof(tag)) > 0 && authenticate(tag)) { + while (!stop_after_request) { + char *cmd, *url; + int result = read_tag(stdin, tag, sizeof(tag)); + if (result <= 0) break; + cmd = attribute(tag, "cmd"); + url = attribute(tag, "url"); + if (cmd && !strcmp(cmd, "DpiBye")) { + stopped = 1; + stop_after_request = 1; + } else if (cmd && !strcmp(cmd, "open_url") && url) { + if (!strncmp(url, "dat:", 4)) serve(url); + else if (!strncmp(url, "dpi:/", 5)) serve_internal(url); + else error_page(url, 404, "Not found"); + stop_after_request = 1; + } + free(cmd); + free(url); + } + } + fflush(stdout); + /* The historical dillo-dat plugin ends the accepted socket per page. */ + dup2(saved_in, STDIN_FILENO); + dup2(saved_out, STDOUT_FILENO); + close(saved_in); + close(saved_out); + close(client); + clearerr(stdin); + clearerr(stdout); + } + close(listener); + return 0; +} + +int main(int argc, char **argv) +{ + int result; + (void)setlocale(LC_COLLATE, ""); + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + metadata_scheme = s7_init(); + if (argc == 2 && !strcmp(argv[1], "--stdio-test")) + result = serve_stdio_requests(); + else + result = serve_socket_requests(); + if (metadata_scheme) s7_free(metadata_scheme); + return result < 0 ? 1 : result; +} diff --git a/src/dat_dpi_icons.h b/src/dat_dpi_icons.h new file mode 100644 index 0000000..988d42d --- /dev/null +++ b/src/dat_dpi_icons.h @@ -0,0 +1,33 @@ +#ifndef DAT_DPI_ICONS_H +#define DAT_DPI_ICONS_H +/* PNG data URIs copied from the bundled historical dillo-dat DPI. */ +static const char dat_dpi_icon_updir[] = + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAKxJ" + "REFUeNpi/P//PwMlgImBQjAMDGBBF2BkZISz09LSwCE8a9YsuCBGoIMEkDEMJCUl/b90+QoYg9i41LNgc1ZycvL/hMQkhgcPH4H5" + "iUnJIJf9nzt3LiNBL2RkZPwPj4hk4BMUYuDh44MEFDMLQ0xsHAMrKyvIJYyEwuDLiuXLeP7+/Qv3EihcmJmZGZiYmL5gqEcPFKBi" + "AyDFjCPQ/wLVX8BrwGhSJh0ABBgAsetR5KBfw9EAAAAASUVORK5CYII=" + ; +static const char dat_dpi_icon_directory[] = + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAN1wAADdcBQiibeAAAABl0" + "RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAXdEVYdEF1dGhvcgBMYXBvIENhbGFtYW5kcmVp35EaKgAAACl0RVh0" + "RGVzY3JpcHRpb24AQmFzZWQgb2YgSmFrdWIgU3RlaW5lciBkZXNpZ26ghAVzAAABbElEQVQ4jaWQO0tDQRCFz2x2A8YHQoogaKFW" + "2qSysbATsdAIWgrWlhIFBRvLoFhZW/gb0vgPRBAStEgExZA2VR7X3Nw7MxY3BhUjCU6zMOz5zrcL/HPo/HDzREFnZMj1tgoI1FPm" + "/ePL/M2fgNxRxltaXh8xxkCEoSIQYQQdH6XHO6/T8ZePL/PFfgBLCifCqJQfesswDNBoNhAEnQQRFXLZjV+qAefiRQsAba/e27MI" + "Wl4Ta1t7SE3N9lVXEVxfnaYtyJjS0z04DCMlF8fK6jaSyRQatUpfwFhypvsEUrOze4CxiUmoAlBF4LfwXq/1DUcG3UJhRmJ0HI1a" + "9c/AzxGOAAYApEsbCiBfAMrDA5T5nwb8zYCHN/j8RABQFYAINGgYgEhUamPGKLOQiyciCFH3NABRdFsFqhoVqUJV4bebiBmjNmZd" + "8eW5kJ6bXxhUAADw9lpWY12BLrKZRWNjt0EYTA8DsM5Vw7a/9gEhN65EVGzVRQAAAABJRU5ErkJggg=" + ; +static const char dat_dpi_icon_file[] = + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAN1wAADdcBQiibeAAAABl0" + "RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAASdEVYdFRpdGxlAFBhcGVyIFNoZWV0c7mvkfkAAAAXdEVYdEF1dGhv" + "cgBMYXBvIENhbGFtYW5kcmVp35EaKgAAACd0RVh0RGVzY3JpcHRpb24Ad2l0aCBhIEhVR0UgaGVscCBmcm9tIEpha3VihlQHswAA" + "AhNJREFUOI11kstqU1EUhr91ctI2A2uTNsRaOxDEkeILiIgTL6CCAx+iUnTSgQPBRxAFSxWhA8XiBQst7aQjUV+kMWlzOaeJVZvs" + "y3JwctK0wQWLvQabb/3/v7eoKuubqzdFZMk5PwuKqqIKoAB/Qba8d8/v3b2/xfFSVVbXPpWbUUO990Pd7Xa0Uv2paxurf1Y+vnuc" + "wA87AOh0OjP5iQL7v/dptWOacZ1ao0plZ5vdepV2q8Wt67dzxanik7fvlxcGBQQAxlgAqpUK5e0KO5Ua9d2IuNlmL/pFuVwhCAKu" + "XrmWGx0Ze/pm+dXlFBAmAANAYSqPcy5p73DO4pwjE8OHzyuMZXNcvHAp9/3H1wXgWx9gjQGURi3CWjuU01S+xMkTBbxYgiCQg4OD" + "Gy9ePsvMzz1yfQUKTBTGcc7iVVHv8T5V4hhhFJExzp09z8bmesarzwIpINkaN1s454YUpCWBkC706gcysEkG+clxnPNo7y/0PsMh" + "QHoAa1CvwyFCQBAoipBcFY4eyWCtxTt/FCBAHO3h7P8tZMIMpeI0xlh8z+pABkLpVBG0J1UGVKQKVBARrDH9rAaeERq1iG63298Y" + "hiFnZmf63rWXiTEGd9wCwOmZaUTkaA8ooJfpEEBEqnEcTRcKk//1n1a73QIkMtZ0EluqzD98cCfMhoum2y2pgpI84fEZlGx2pG6M" + "mVtafP0F4B+wR1eZMTEGTgAAAABJRU5ErkJggg==" + ; +#endif diff --git a/src/dat_fetch.c b/src/dat_fetch.c new file mode 100644 index 0000000..d62c69e --- /dev/null +++ b/src/dat_fetch.c @@ -0,0 +1,617 @@ +#define _POSIX_C_SOURCE 200809L +#include "dat_fetch.h" +#include "dat.h" +#include "dat_paths.h" +#include "cesspool.h" +#include "dat_session.h" +#include "sleep_crypto.h" +#include "sleep_utp.h" + +#include +#include +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#include +#include +#else +#include +#include +#include +#include +#define mkdir(path, mode) _mkdir(path) +#define chmod(path, mode) 0 +#endif + +#define FETCH_HELLO_BYTES 132u +#define FETCH_ACK_BYTES 100u +#define FETCH_RECORD_LIMIT (4u * 1024u * 1024u) + +typedef struct fetch_client fetch_client_t; +typedef struct fetch_transport { + sleep_utp_t utp; + fetch_client_t *owner; + uint8_t peer_id[32]; + uint8_t local_nonce[32], remote_nonce[32]; + uint8_t auth[FETCH_HELLO_BYTES + FETCH_ACK_BYTES]; + size_t auth_length; + uint8_t *tx, *rx; + size_t tx_length, tx_offset, tx_capacity; + size_t rx_length, rx_capacity; + int used, authenticated, hello_sent, hello_received; + int ack_sent, ack_received, waiting_incoming; +} fetch_transport_t; + +struct fetch_client { + cesspool_t pool; + sleep_keypair_t identity; + sleep_feed_t metadata, content; + dat_session_t session; + uint8_t metadata_key[32], content_key[32], topic[32]; + fetch_transport_t transports[DAT_SESSION_MAX_PEERS]; + int pool_ready, session_ready, content_known, failed; + int failure_errno; +}; + +static uint64_t fetch_now_ms(void) +{ + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts)) return 0; + return (uint64_t)ts.tv_sec * 1000u + (uint64_t)ts.tv_nsec / 1000000u; +} + +static int ensure_dir(const char *path) +{ + if (mkdir(path, 0700) == 0 || errno == EEXIST) return 0; + return -1; +} + +static int ensure_dir_tree(const char *path) +{ + char copy[1200]; + size_t i, length; + if (!path || !*path) { errno = EINVAL; return -1; } + length = strlen(path); + if (length >= sizeof(copy)) { errno = ENAMETOOLONG; return -1; } + memcpy(copy, path, length + 1); + for (i = 0; i < length; i++) { + if (copy[i] != '/' && copy[i] != '\\') continue; + if (i == 0 || (i == 2 && copy[1] == ':')) continue; + { + char saved = copy[i]; + copy[i] = '\0'; + if (*copy && ensure_dir(copy)) return -1; + copy[i] = saved; + } + } + return ensure_dir(copy); +} + +/* Cached public keys identify the archive. Never replace a mismatched key + * when a later request reuses this cache directory. */ +static int persist_pubkey(const char *path, const uint8_t key[32]) +{ + uint8_t existing[32]; + if (sleep_load_pubkey(path, existing) == 0) { + if (memcmp(existing, key, sizeof(existing))) { errno = EACCES; return -1; } + return 0; + } + if (errno != ENOENT) return -1; + return sleep_save_pubkey(path, key); +} + +static int load_identity(sleep_keypair_t *identity) +{ + const char *home = dat_home_directory(); + char directory[1100], path[1200]; + int n; + if (!home || !*home) { errno = ENOENT; return -1; } + n = snprintf(directory, sizeof(directory), "%s/.dat", home); + if (n < 0 || (size_t)n >= sizeof(directory) || ensure_dir(directory)) return -1; + n = snprintf(path, sizeof(path), "%s/dillo-dat.network-key", directory); + if (n < 0 || (size_t)n >= sizeof(path)) { errno = ENAMETOOLONG; return -1; } + if (sleep_load_keypair(path, identity) == 0) return 0; + if (errno != ENOENT || sleep_gen_keypair(identity) || + sleep_save_keypair(path, identity) || chmod(path, 0600)) return -1; + return 0; +} + +static fetch_transport_t *transport_by_id(fetch_client_t *client, + const uint8_t peer_id[32]) +{ + size_t i; + for (i = 0; i < DAT_SESSION_MAX_PEERS; i++) + if (client->transports[i].used && + !memcmp(client->transports[i].peer_id, peer_id, 32)) + return &client->transports[i]; + return NULL; +} + +static fetch_transport_t *transport_create(fetch_client_t *client, + const uint8_t peer_id[32]) +{ + size_t i; + fetch_transport_t *transport = transport_by_id(client, peer_id); + if (transport) return transport; + for (i = 0; i < DAT_SESSION_MAX_PEERS; i++) { + transport = &client->transports[i]; + if (transport->used) continue; + memset(transport, 0, sizeof(*transport)); + if (sleep_utp_init(&transport->utp, client->pool.net.sock)) return NULL; + transport->used = 1; + transport->owner = client; + memcpy(transport->peer_id, peer_id, 32); + transport->utp.context = transport; + return transport; + } + errno = ENOSPC; + return NULL; +} + +static int flush_transport(fetch_transport_t *transport) +{ + while (transport->tx_offset < transport->tx_length && + transport->utp.state == SLEEP_UTP_CONNECTED) { + size_t amount = transport->tx_length - transport->tx_offset; + if (amount > SLEEP_UTP_MTU) amount = SLEEP_UTP_MTU; + if (sleep_utp_send(&transport->utp, transport->tx + transport->tx_offset, amount)) { + if (errno == EAGAIN || errno == ENOBUFS) return 0; + return -1; + } + transport->tx_offset += amount; + } + if (transport->tx_offset == transport->tx_length) + transport->tx_offset = transport->tx_length = 0; + return 0; +} + +static int queue_transport(fetch_transport_t *transport, const uint8_t *bytes, size_t length) +{ + size_t queued, needed, capacity; + uint8_t *next; + if (transport->tx_offset && transport->tx_length + length > transport->tx_capacity) { + memmove(transport->tx, transport->tx + transport->tx_offset, + transport->tx_length - transport->tx_offset); + transport->tx_length -= transport->tx_offset; + transport->tx_offset = 0; + } + queued = transport->tx_length - transport->tx_offset; + if (length > FETCH_RECORD_LIMIT - queued) { errno = ENOBUFS; return -1; } + needed = transport->tx_length + length; + if (needed > transport->tx_capacity) { + capacity = transport->tx_capacity ? transport->tx_capacity : 4096; + while (capacity < needed) { + if (capacity >= FETCH_RECORD_LIMIT / 2) { capacity = FETCH_RECORD_LIMIT; break; } + capacity *= 2; + } + next = realloc(transport->tx, capacity); + if (!next) return -1; + transport->tx = next; + transport->tx_capacity = capacity; + } + memcpy(transport->tx + transport->tx_length, bytes, length); + transport->tx_length += length; + return flush_transport(transport); +} + +static int send_session_frame(void *context, const uint8_t peer_id[32], + const uint8_t *frame, size_t frame_len) +{ + fetch_client_t *client = context; + fetch_transport_t *transport = transport_by_id(client, peer_id); + uint8_t *record; + size_t prefix, written; + int rc; + if (!transport || !transport->authenticated) { errno = ENOTCONN; return -1; } + if (frame_len > FETCH_RECORD_LIMIT) { errno = EMSGSIZE; return -1; } + prefix = 1 + sleep_varint_size(frame_len); + record = malloc(prefix + frame_len); + if (!record) return -1; + record[0] = 0; + if (sleep_varint_encode(frame_len, record + 1, prefix - 1, &written)) { free(record); return -1; } + memcpy(record + prefix, frame, frame_len); + rc = queue_transport(transport, record, prefix + frame_len); + free(record); + return rc; +} + +static int send_hello(fetch_transport_t *transport) +{ + uint8_t hello[FETCH_HELLO_BYTES]; + if (transport->hello_sent || transport->utp.state != SLEEP_UTP_CONNECTED) return 0; + memcpy(hello, "THH0", 4); + memcpy(hello + 4, transport->owner->identity.pubkey, 32); + randombytes_buf(transport->local_nonce, sizeof(transport->local_nonce)); + memcpy(hello + 36, transport->local_nonce, 32); + if (sleep_sign(hello + 68, hello, 68, &transport->owner->identity) || + sleep_utp_send(&transport->utp, hello, sizeof(hello))) return -1; + transport->hello_sent = 1; + return 0; +} + +static int send_ack(fetch_transport_t *transport) +{ + uint8_t ack[FETCH_ACK_BYTES], transcript[132]; + if (transport->ack_sent) return 0; + memcpy(ack, "THA0", 4); + memcpy(ack + 4, transport->remote_nonce, 32); + memcpy(transcript, ack, 36); + memcpy(transcript + 36, transport->local_nonce, 32); + memcpy(transcript + 68, transport->owner->identity.pubkey, 32); + memcpy(transcript + 100, transport->peer_id, 32); + if (sleep_sign(ack + 36, transcript, sizeof(transcript), &transport->owner->identity) || + sleep_utp_send(&transport->utp, ack, sizeof(ack))) return -1; + transport->ack_sent = 1; + return 0; +} + +static int activate_content(fetch_client_t *client) +{ + sleep_metadata_header_t header; + uint8_t *block = NULL; + size_t length = 0; + char key_path[1200]; + int n; + if (client->content_known || !sleep_feed_has(&client->metadata, 0)) return 0; + if (sleep_feed_read(&client->metadata, 0, &block, &length)) return -1; + if (sleep_metadata_header_decode(&header, block, length) || + strcmp(header.type, "hyperdrive") || header.content_len != 32) { + free(block); errno = EPROTO; return -1; + } + free(block); + if (sleep_feed_load_remote(&client->content, header.content) || + dat_session_set_content_key(&client->session, header.content)) return -1; + n = snprintf(key_path, sizeof(key_path), "%s/content.key", client->metadata.directory); + if (n < 0 || (size_t)n >= sizeof(key_path) || persist_pubkey(key_path, header.content)) return -1; + memcpy(client->content_key, header.content, 32); + client->content_known = 1; + return 0; +} + +static int receive_records(fetch_transport_t *transport, const uint8_t *bytes, size_t length) +{ + fetch_client_t *client = transport->owner; + size_t need, capacity; + uint8_t *next; + if (length > FETCH_RECORD_LIMIT - transport->rx_length) { errno = ENOBUFS; return -1; } + need = transport->rx_length + length; + if (need > transport->rx_capacity) { + capacity = transport->rx_capacity ? transport->rx_capacity : 4096; + while (capacity < need) capacity = capacity > FETCH_RECORD_LIMIT / 2 ? FETCH_RECORD_LIMIT : capacity * 2; + next = realloc(transport->rx, capacity); + if (!next) return -1; + transport->rx = next; + transport->rx_capacity = capacity; + } + if (length) memcpy(transport->rx + transport->rx_length, bytes, length); + transport->rx_length += length; + for (;;) { + uint64_t frame_len; + size_t varint_len, prefix; + int saved_errno; + if (transport->rx_length < 2) return 0; + if (transport->rx[0] != 0) { errno = EPROTO; return -1; } + errno = 0; + if (sleep_varint_decode(transport->rx + 1, transport->rx_length - 1, + &frame_len, &varint_len)) { + saved_errno = errno; + if (saved_errno == EAGAIN) return 0; + errno = saved_errno; return -1; + } + if (frame_len > FETCH_RECORD_LIMIT) { errno = EMSGSIZE; return -1; } + prefix = 1 + varint_len; + if (frame_len > transport->rx_length - prefix) return 0; + if (dat_session_receive(&client->session, transport->peer_id, + transport->rx + prefix, (size_t)frame_len) || + activate_content(client)) return -1; + memmove(transport->rx, transport->rx + prefix + (size_t)frame_len, + transport->rx_length - prefix - (size_t)frame_len); + transport->rx_length -= prefix + (size_t)frame_len; + } +} + +static void on_utp_data(sleep_utp_t *utp, const uint8_t *bytes, size_t length) +{ + fetch_transport_t *transport = utp ? utp->context : NULL; + if (!transport || !transport->used) return; + if (transport->authenticated) { + if (receive_records(transport, bytes, length)) { + transport->owner->failed = 1; + transport->owner->failure_errno = errno ? errno : EPROTO; + } + return; + } + if (length > sizeof(transport->auth) - transport->auth_length) { + transport->owner->failed = 1; transport->owner->failure_errno = EMSGSIZE; return; + } + memcpy(transport->auth + transport->auth_length, bytes, length); + transport->auth_length += length; + for (;;) { + if (!transport->hello_received && transport->auth_length >= FETCH_HELLO_BYTES) { + const uint8_t *hello = transport->auth; + if (memcmp(hello, "THH0", 4) || memcmp(hello + 4, transport->peer_id, 32) || + sleep_verify(hello + 68, hello, 68, transport->peer_id)) { + transport->owner->failed = 1; transport->owner->failure_errno = EACCES; return; + } + memcpy(transport->remote_nonce, hello + 36, 32); + transport->hello_received = 1; + memmove(transport->auth, transport->auth + FETCH_HELLO_BYTES, + transport->auth_length - FETCH_HELLO_BYTES); + transport->auth_length -= FETCH_HELLO_BYTES; + if (send_ack(transport)) { transport->owner->failed = 1; transport->owner->failure_errno = errno; return; } + continue; + } + if (transport->hello_received && !transport->ack_received && + transport->auth_length >= FETCH_ACK_BYTES) { + const uint8_t *ack = transport->auth; + uint8_t transcript[132]; + if (memcmp(ack, "THA0", 4) || memcmp(ack + 4, transport->local_nonce, 32)) { + transport->owner->failed = 1; transport->owner->failure_errno = EPROTO; return; + } + memcpy(transcript, ack, 36); + memcpy(transcript + 36, transport->remote_nonce, 32); + memcpy(transcript + 68, transport->peer_id, 32); + memcpy(transcript + 100, transport->owner->identity.pubkey, 32); + if (sleep_verify(ack + 36, transcript, sizeof(transcript), transport->peer_id)) { + transport->owner->failed = 1; transport->owner->failure_errno = EACCES; return; + } + transport->ack_received = 1; + memmove(transport->auth, transport->auth + FETCH_ACK_BYTES, + transport->auth_length - FETCH_ACK_BYTES); + transport->auth_length -= FETCH_ACK_BYTES; + transport->authenticated = 1; + if (dat_session_peer_secure(&transport->owner->session, transport->peer_id, 1)) { + transport->owner->failed = 1; transport->owner->failure_errno = errno; return; + } + continue; + } + if (transport->authenticated && transport->auth_length) { + if (receive_records(transport, transport->auth, transport->auth_length)) { + transport->owner->failed = 1; transport->owner->failure_errno = errno; return; + } + transport->auth_length = 0; + } + return; + } +} + +static int same_endpoint(const struct sockaddr_storage *a, const struct sockaddr_storage *b) +{ + const struct sockaddr_in *aa = (const struct sockaddr_in *)a; + const struct sockaddr_in *bb = (const struct sockaddr_in *)b; + return aa->sin_family == AF_INET && bb->sin_family == AF_INET && + aa->sin_port == bb->sin_port && aa->sin_addr.s_addr == bb->sin_addr.s_addr; +} + +static void send_hello_if_ready(fetch_transport_t *transport) +{ + if (transport->utp.state == SLEEP_UTP_CONNECTED && send_hello(transport)) { + transport->owner->failed = 1; + transport->owner->failure_errno = errno; + } +} + +static void transport_packet(cesspool_t *pool, const dat_peer_t *source, + const uint8_t *bytes, size_t length) +{ + fetch_client_t *client = pool ? pool->transport_context : NULL; + sleep_utp_packet_t packet; + fetch_transport_t *transport = NULL; + cesspool_peer_t *known = NULL; + size_t i; + if (!client || sleep_utp_packet_decode(&packet, bytes, length)) return; + if (packet.type == SLEEP_UTP_SYN) { + for (i = 0; i < pool->peer_count; i++) + if (pool->peers[i].discovered && + !memcmp(pool->peers[i].discovery_topic, client->topic, 32) && + same_endpoint(&pool->peers[i].address.addr, &source->addr)) { + known = &pool->peers[i]; break; + } + if (!known || memcmp(client->identity.pubkey, known->public_key, 32) < 0) return; + transport = transport_by_id(client, known->public_key); + if (!transport) transport = transport_create(client, known->public_key); + if (!transport || transport->utp.state == SLEEP_UTP_CONNECTED) return; + transport->waiting_incoming = 0; + transport->utp.on_data = on_utp_data; + if (sleep_utp_accept(&transport->utp, &packet, + (const struct sockaddr *)&source->addr, source->addrlen)) return; + send_hello_if_ready(transport); + return; + } + for (i = 0; i < DAT_SESSION_MAX_PEERS; i++) { + fetch_transport_t *candidate = &client->transports[i]; + if (candidate->used && candidate->utp.receive_id == packet.connection_id && + same_endpoint(&candidate->utp.remote, &source->addr)) { transport = candidate; break; } + } + if (!transport) return; + if (sleep_utp_input(&transport->utp, bytes, length)) return; + send_hello_if_ready(transport); +} + +static void peer_discovered(void *context, const dat_session_peer_t *peer) +{ + fetch_client_t *client = context; + fetch_transport_t *transport = transport_create(client, peer->peer_id); + if (!transport) return; + transport->utp.on_data = on_utp_data; + if (memcmp(client->identity.pubkey, peer->peer_id, 32) > 0) { + transport->waiting_incoming = 1; + return; + } + if (sleep_utp_connect(&transport->utp, + (const struct sockaddr *)&peer->address.addr, + peer->address.addrlen)) { + transport->owner->failed = 1; + transport->owner->failure_errno = errno; + } +} + +static int open_remote_feed(sleep_feed_t *feed, const char *directory, + const char *name, const uint8_t key[32]) +{ + if (sleep_feed_open(feed, directory, name, NULL) != 0) { + if (errno != ENOENT) return -1; + return sleep_feed_open_remote(feed, directory, name, key); + } + if (sleep_feed_load_remote(feed, key)) { sleep_feed_close(feed); return -1; } + feed->persist_remote = 1; + memcpy(feed->keypair.pubkey, key, 32); + return 0; +} + +static int feed_complete(const sleep_feed_t *feed, const sleep_replication_t *replication) +{ + uint64_t i; + if (!replication->remote_length || replication->remote_length > (uint64_t)SLEEP_BITFIELD_DATA_BYTES * 8u) return 0; + for (i = 0; i < replication->remote_length; i++) if (!sleep_feed_has(feed, (size_t)i)) return 0; + return 1; +} + +static int archive_complete(const fetch_client_t *client) +{ + size_t p; + for (p = 0; p < client->session.peer_count; p++) { + const dat_session_peer_t *peer = &client->session.peers[p]; + if (peer->state != DAT_SESSION_PEER_READY) continue; + if (!feed_complete(&client->metadata, &peer->replication[DAT_SESSION_METADATA])) continue; + if (!client->content_known || + !feed_complete(&client->content, &peer->replication[DAT_SESSION_CONTENT])) continue; + return 1; + } + return 0; +} + +static void cleanup(fetch_client_t *client) +{ + size_t i; + if (client->session_ready) (void)dat_session_close(&client->session); + if (client->pool_ready) (void)cesspool_destroy(&client->pool); + for (i = 0; i < DAT_SESSION_MAX_PEERS; i++) { + fetch_transport_t *transport = &client->transports[i]; + if (transport->used) (void)sleep_utp_close(&transport->utp); + free(transport->tx); free(transport->rx); + } + sleep_feed_close(&client->metadata); + sleep_feed_close(&client->content); +} + +int dat_fetch_archive(const char *root, const uint8_t metadata_key[32], + unsigned timeout_ms) +{ + fetch_client_t *client; + char datdir[1200], path[1400]; + uint8_t unknown_content[32] = {0}; + uint16_t port = DATNET_PORT; + uint64_t deadline; + int n, rc = -1, bootstrap_count; + if (!root || !metadata_key) { errno = EINVAL; return -1; } + client = calloc(1, sizeof(*client)); + if (!client) return -1; + memcpy(client->metadata_key, metadata_key, 32); + if (sleep_crypto_init() || load_identity(&client->identity)) goto done; + if (ensure_dir_tree(root)) goto done; + n = snprintf(datdir, sizeof(datdir), "%s/.dat", root); + if (n < 0 || (size_t)n >= sizeof(datdir) || ensure_dir(datdir)) goto done; + n = snprintf(path, sizeof(path), "%s/metadata.key", datdir); + if (n < 0 || (size_t)n >= sizeof(path) || persist_pubkey(path, metadata_key)) goto done; + if (open_remote_feed(&client->metadata, datdir, "metadata", metadata_key) || + sleep_feed_open_remote(&client->content, datdir, "content", unknown_content)) goto done; + n = snprintf(path, sizeof(path), "%s/content.key", datdir); + if (n < 0 || (size_t)n >= sizeof(path)) { errno = ENAMETOOLONG; goto done; } + if (sleep_load_pubkey(path, client->content_key) == 0) { + if (sleep_feed_load_remote(&client->content, client->content_key)) goto done; + client->content.persist_remote = 1; + memcpy(client->content.keypair.pubkey, client->content_key, 32); + client->content_known = 1; + } else if (errno != ENOENT) goto done; + if (cesspool_init(&client->pool, client->identity.pubkey)) goto done; + client->pool_ready = 1; + client->pool.on_transport_packet = transport_packet; + client->pool.transport_context = client; + if (cesspool_listen(&client->pool, "0.0.0.0", port)) { + if (errno != EADDRINUSE || cesspool_listen(&client->pool, "0.0.0.0", 0)) goto done; + } + bootstrap_count = cesspool_configure_bootstraps(&client->pool); + if (bootstrap_count < 0) goto done; + if (cesspool_discovery_key(metadata_key, client->topic) || + dat_session_init(&client->session, &client->pool, metadata_key, + client->identity.pubkey, client->pool.port, + &client->metadata, metadata_key, &client->content, + client->content_known ? client->content_key : unknown_content, + send_session_frame, client)) goto done; + client->session_ready = 1; + if (!client->content_known && dat_session_expect_content_key(&client->session)) goto done; + if (dat_session_set_peer_callback(&client->session, peer_discovered, client) || + dat_session_start(&client->session, 1, 1)) goto done; + deadline = fetch_now_ms() + (timeout_ms ? timeout_ms : 30000u); + while (fetch_now_ms() < deadline) { + size_t i; + if (client->failed) { errno = client->failure_errno ? client->failure_errno : EIO; goto done; } + if (dat_session_tick(&client->session, fetch_now_ms()) < 0 && errno != EINTR) goto done; + for (i = 0; i < DAT_SESSION_MAX_PEERS; i++) { + fetch_transport_t *transport = &client->transports[i]; + if (!transport->used) continue; + if (transport->waiting_incoming && transport->utp.state == SLEEP_UTP_CLOSED) continue; + if (sleep_utp_tick(&transport->utp, fetch_now_ms()) != 0 || + transport->utp.state == SLEEP_UTP_RESET || + transport->utp.state == SLEEP_UTP_CLOSED) { + transport->used = 0; + continue; + } + if (flush_transport(transport)) goto done; + } + if (archive_complete(client)) { rc = 0; goto done; } + { + struct timespec pause = {0, 20000000}; + nanosleep(&pause, NULL); + } + } + if (!bootstrap_count && !client->session.peer_count) errno = ENETUNREACH; + else if (bootstrap_count && !client->pool.bootstrap_reachable) errno = EHOSTUNREACH; + else if (!client->session.peer_count) errno = ENOENT; + else { + size_t i, secure = 0, ready = 0, active_utp = 0, auth = 0; + for (i = 0; i < client->session.peer_count; i++) { + if (client->session.peers[i].state >= DAT_SESSION_PEER_SECURE && + client->session.peers[i].state != DAT_SESSION_PEER_CLOSED) secure++; + if (client->session.peers[i].state == DAT_SESSION_PEER_READY) ready++; + } + for (i = 0; i < DAT_SESSION_MAX_PEERS; i++) if (client->transports[i].used) { + active_utp += client->transports[i].utp.state == SLEEP_UTP_CONNECTED; + auth += client->transports[i].authenticated; + } + fprintf(stderr, "dat-fetch: bootstrap reachable; peers=%lu secure=%lu ready=%lu utp=%lu authenticated=%lu\n", + (unsigned long)client->session.peer_count, (unsigned long)secure, + (unsigned long)ready, (unsigned long)active_utp, (unsigned long)auth); + for (i = 0; i < client->session.peer_count; i++) { + const dat_session_peer_t *peer = &client->session.peers[i]; + const struct sockaddr_in *address = (const struct sockaddr_in *)&peer->address.addr; + char host[INET_ADDRSTRLEN] = "?"; + char peer_hex[65]; + size_t j; + fetch_transport_t *transport = transport_by_id(client, peer->peer_id); + for (j = 0; j < 32; j++) snprintf(peer_hex + j * 2, 3, "%02x", peer->peer_id[j]); + if (address->sin_family == AF_INET) + (void)inet_ntop(AF_INET, &address->sin_addr, host, sizeof(host)); + fprintf(stderr, "dat-fetch: peer=%s state=%d endpoint=%s:%u local-id-order=%d waiting-incoming=%d utp-state=%d\n", + peer_hex, peer->state, host, (unsigned)ntohs(address->sin_port), + memcmp(client->identity.pubkey, peer->peer_id, 32), + transport ? transport->waiting_incoming : 0, + transport ? transport->utp.state : SLEEP_UTP_CLOSED); + } + errno = ETIMEDOUT; + } +done: + if (rc != 0 && !errno) errno = EIO; + { + int saved = errno; + cleanup(client); + free(client); + errno = saved; + } + return rc; +} diff --git a/src/dat_net.c b/src/dat_net.c index c60ac99..c60d1e9 100644 --- a/src/dat_net.c +++ b/src/dat_net.c @@ -1,4 +1,2 @@ -// dat_net.c -#include "sleep.h" -#include "dat_net.h" -#include "cesspool.h" +/* UDP transport is maintained in the standalone libcess package. */ +#include "../libcess/src/cess_net.c" diff --git a/src/dat_s7.c b/src/dat_s7.c new file mode 100644 index 0000000..da4ea55 --- /dev/null +++ b/src/dat_s7.c @@ -0,0 +1,195 @@ +#include "dat_s7.h" +#include "dat.h" +#include "dat_wire.h" +#include +#include +#include +#include +#include + +typedef struct dat_s7_archive { dat_archive_t archive; bool closed; } dat_s7_archive_t; +static s7_int dat_archive_type = -1; + +static s7_pointer archive_gc_free(s7_scheme *sc, s7_pointer object) +{ + dat_s7_archive_t *handle = s7_c_object_value(object); + (void)sc; + if (handle) { if (!handle->closed) dat_archive_close(&handle->archive); free(handle); } + return s7_f(sc); +} + +static dat_s7_archive_t *archive_argument(s7_scheme *sc, const char *name, + s7_pointer value, int position) +{ + dat_s7_archive_t *handle; + if (!s7_is_c_object(value) || s7_c_object_type(value) != dat_archive_type) { + s7_wrong_type_arg_error(sc, name, position, value, "a dat archive"); + return NULL; + } + handle = s7_c_object_value_checked(value, dat_archive_type); + if (!handle || handle->closed) { + s7_error(sc, s7_make_symbol(sc, "dat-closed-archive"), + s7_list(sc, 1, s7_make_string(sc, "archive is already closed"))); + return NULL; + } + return handle; +} + +static s7_pointer make_archive(s7_scheme *sc, const char *directory, int create) +{ + dat_s7_archive_t *handle = calloc(1, sizeof(*handle)); + int rc; + if (!handle) return s7_error(sc, s7_make_symbol(sc, "memory-error"), + s7_list(sc, 1, s7_make_string(sc, "could not allocate archive handle"))); + rc = create ? dat_archive_create(&handle->archive, directory) : dat_archive_open(&handle->archive, directory); + if (rc) { free(handle); return s7_error(sc, s7_make_symbol(sc, "dat-archive-error"), + s7_list(sc, 1, s7_make_string(sc, "archive could not be opened"))); } + if (create) { + char metadata_path[1200]; + int n; + if (dat_scheme_write_archive_info(sc, directory, &handle->archive, + NULL, NULL, 0) != 0) { + dat_archive_close(&handle->archive); free(handle); + return s7_error(sc, s7_make_symbol(sc, "dat-archive-error"), + s7_list(sc, 1, s7_make_string(sc, "dat.scm metadata could not be written"))); + } + n = snprintf(metadata_path, sizeof(metadata_path), "%s%sdat.scm", directory, + directory[strlen(directory) - 1] == '/' ? "" : "/"); + if (n < 0 || (size_t)n >= sizeof(metadata_path) || + dat_archive_import_path(&handle->archive, metadata_path, "dat.scm") != 0) { + dat_archive_close(&handle->archive); free(handle); + return s7_error(sc, s7_make_symbol(sc, "dat-archive-error"), + s7_list(sc, 1, s7_make_string(sc, "dat.scm metadata could not be imported"))); + } + } + return s7_make_c_object(sc, dat_archive_type, handle); +} + +static s7_pointer g_dat_create(s7_scheme *sc, s7_pointer args) +{ + s7_pointer value = s7_car(args); + if (!s7_is_string(value)) return s7_wrong_type_arg_error(sc, "dat-create", 1, value, "a directory string"); + return make_archive(sc, s7_string(value), 1); +} +static s7_pointer g_dat_open(s7_scheme *sc, s7_pointer args) +{ + s7_pointer value = s7_car(args); + if (!s7_is_string(value)) return s7_wrong_type_arg_error(sc, "dat-open", 1, value, "a directory string"); + return make_archive(sc, s7_string(value), 0); +} +static s7_pointer g_dat_close(s7_scheme *sc, s7_pointer args) +{ + s7_pointer value = s7_car(args); + dat_s7_archive_t *handle; + if (!s7_is_c_object(value) || s7_c_object_type(value) != dat_archive_type) + return s7_wrong_type_arg_error(sc, "dat-close", 1, value, "a dat archive"); + handle = s7_c_object_value_checked(value, dat_archive_type); + if (handle && !handle->closed) { dat_archive_close(&handle->archive); handle->closed = true; } + return s7_t(sc); +} +static s7_pointer g_dat_entries(s7_scheme *sc, s7_pointer args) +{ + dat_s7_archive_t *handle = archive_argument(sc, "dat-entry-count", s7_car(args), 1); + return handle ? s7_make_integer(sc, (s7_int)dat_archive_entry_count(&handle->archive)) : s7_f(sc); +} + +/* + * Scheme-facing protocol records are tagged, named-field s-expressions: + * (dat:wire-message (channel . 1) (type . 3) (payload . #u8(...))) + * The payload remains bytes. This is an application view over the existing + * wire codec, never a second serializer or a change to the protocol bytes. + */ +#define DAT_S7_WIRE_LIMIT (128u * 1024u) + +static s7_pointer wire_field(s7_scheme *sc, const char *name, s7_pointer value) +{ + return s7_cons(sc, s7_make_symbol(sc, name), value); +} + +static s7_pointer g_dat_wire_to_sexp(s7_scheme *sc, s7_pointer args) +{ + s7_pointer bytes = s7_car(args), payload, result; + dat_wire_message_t message; + uint8_t *raw; + size_t length, consumed; + if (!s7_is_byte_vector(bytes)) + return s7_wrong_type_arg_error(sc, "dat-wire->sexp", 1, bytes, + "a bytevector containing one wire frame"); + length = (size_t)s7_vector_length(bytes); + raw = s7_byte_vector_elements(bytes); + if (dat_wire_decode(&message, raw, length, &consumed) || consumed != length) + return s7_error(sc, s7_make_symbol(sc, "dat-wire-error"), + s7_list(sc, 1, s7_make_string(sc, "invalid or trailing wire frame bytes"))); + if (message.payload_len > (size_t)INT64_MAX || + message.channel > (uint64_t)INT64_MAX || message.type > (uint64_t)INT64_MAX) + return s7_out_of_range_error(sc, "dat-wire->sexp", 1, bytes, + "wire payload is too large for this S7 build"); + payload = s7_make_byte_vector(sc, (s7_int)message.payload_len, 1, NULL); + if (!payload) return s7_f(sc); + if (message.payload_len) + memcpy(s7_byte_vector_elements(payload), message.payload, message.payload_len); + result = s7_cons(sc, s7_make_symbol(sc, "dat:wire-message"), + s7_cons(sc, wire_field(sc, "channel", s7_make_integer(sc, (s7_int)message.channel)), + s7_cons(sc, wire_field(sc, "type", s7_make_integer(sc, (s7_int)message.type)), + s7_cons(sc, wire_field(sc, "payload", payload), s7_nil(sc))))); + return result; +} + +static s7_pointer record_field(s7_pointer record, const char *name) +{ + s7_pointer fields = s7_cdr(record); + while (s7_is_pair(fields)) { + s7_pointer field = s7_car(fields), key; + if (s7_is_pair(field)) { + key = s7_car(field); + if (s7_is_symbol(key) && !strcmp(s7_symbol_name(key), name)) + return s7_cdr(field); + } + fields = s7_cdr(fields); + } + return NULL; +} + +static s7_pointer g_dat_sexp_to_wire(s7_scheme *sc, s7_pointer args) +{ + s7_pointer record = s7_car(args), channel, type, payload, result; + dat_wire_message_t message; + uint8_t *out; + size_t capacity, written; + if (!s7_is_pair(record) || !s7_is_symbol(s7_car(record)) || + strcmp(s7_symbol_name(s7_car(record)), "dat:wire-message")) + return s7_wrong_type_arg_error(sc, "dat-sexp->wire", 1, record, + "a dat:wire-message s-expression"); + channel = record_field(record, "channel"); + type = record_field(record, "type"); + payload = record_field(record, "payload"); + if (!channel || !s7_is_integer(channel) || !type || !s7_is_integer(type) || + !payload || !s7_is_byte_vector(payload)) + return s7_wrong_type_arg_error(sc, "dat-sexp->wire", 1, record, + "a wire record with integer channel/type and bytevector payload fields"); + if (s7_integer(channel) < 0 || s7_integer(type) < 0 || + (uint64_t)s7_integer(channel) > UINT32_MAX || + (uint64_t)s7_integer(type) > UINT32_MAX) + return s7_out_of_range_error(sc, "dat-sexp->wire", 1, record, + "channel and type must fit unsigned 32-bit values"); + message.channel = (uint64_t)s7_integer(channel); + message.type = (uint64_t)s7_integer(type); + message.payload = s7_byte_vector_elements(payload); + message.payload_len = (size_t)s7_vector_length(payload); + if (message.payload_len > DAT_S7_WIRE_LIMIT - 32) return s7_out_of_range_error(sc, + "dat-sexp->wire", 1, record, "wire frame exceeds the session frame limit"); + capacity = message.payload_len + 32; + out = malloc(capacity); + if (!out) return s7_f(sc); + if (dat_wire_encode(&message, out, capacity, &written)) { + free(out); + return s7_error(sc, s7_make_symbol(sc, "dat-wire-error"), + s7_list(sc, 1, s7_make_string(sc, "wire message could not be encoded"))); + } + result = s7_make_byte_vector(sc, (s7_int)written, 1, NULL); + if (result) memcpy(s7_byte_vector_elements(result), out, written); + free(out); + return result ? result : s7_f(sc); +} + +int dat_s7_init(s7_scheme*sc){if(!sc)return-1;dat_archive_type=s7_make_c_type(sc,"dat-archive");s7_c_type_set_gc_free(sc,dat_archive_type,archive_gc_free);s7_define_function(sc,"dat-create",g_dat_create,1,0,false,"(dat-create directory)");s7_define_function(sc,"dat-open",g_dat_open,1,0,false,"(dat-open directory)");s7_define_function(sc,"dat-close",g_dat_close,1,0,false,"(dat-close archive); idempotently close a Dat archive");s7_define_function(sc,"dat-entry-count",g_dat_entries,1,0,false,"(dat-entry-count archive)");s7_define_function(sc,"dat-wire->sexp",g_dat_wire_to_sexp,1,0,false,"(dat-wire->sexp bytevector) returns one named-field dat:wire-message record");s7_define_function(sc,"dat-sexp->wire",g_dat_sexp_to_wire,1,0,false,"(dat-sexp->wire record) encodes a dat:wire-message record using the selected wire protocol");s7_provide(sc,"dat");return 0;} diff --git a/src/dat_scheme.c b/src/dat_scheme.c new file mode 100644 index 0000000..0241efc --- /dev/null +++ b/src/dat_scheme.c @@ -0,0 +1,181 @@ +#define _POSIX_C_SOURCE 200809L +#include "dat_s7.h" +#include "dat_paths.h" +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +#ifndef DAT_SCHEME_DIR +#define DAT_SCHEME_DIR "/usr/local/share/datgpt/scheme" +#endif + +static int directory_exists(const char *path) +{ + char probe[2048]; + FILE *file; + int n = snprintf(probe, sizeof(probe), "%s/dat-init.scm", path); + if (n < 0 || (size_t)n >= sizeof(probe)) return 0; + file = fopen(probe, "rb"); + if (!file) return 0; + fclose(file); + return 1; +} + +#ifdef _WIN32 +static int executable_scheme_dir(char *out, size_t capacity) +{ + char module[MAX_PATH * 4]; + char *separator; + DWORD length = GetModuleFileNameA(NULL, module, (DWORD)sizeof(module)); + int n; + if (!length || length >= sizeof(module)) return 0; + separator = strrchr(module, '\\'); + if (!separator) separator = strrchr(module, '/'); + if (!separator) return 0; + *separator = '\0'; + n = snprintf(out, capacity, "%s\\scheme", module); + return n >= 0 && (size_t)n < capacity && directory_exists(out); +} +#endif + +static int add_path_list(s7_scheme *sc, const char *list) +{ + const char *part, *end; + const char separator = +#ifdef _WIN32 + ';'; +#else + ':'; +#endif + char *path; + if (!list || !*list) return 0; + part = list; + while (*part) { + end = part; + while (*end && *end != separator) end++; + if (end != part) { + size_t n = (size_t)(end - part); + path = malloc(n + 1); + if (!path) return -1; + memcpy(path, part, n); path[n] = '\0'; + s7_add_to_load_path(sc, path); + free(path); + } + part = *end ? end + 1 : end; + } + return 0; +} + +int dat_scheme_runtime_init(dat_scheme_runtime_t *runtime) +{ + s7_scheme *sc; + const char *env_path = getenv("DAT_SCHEME_PATH"); + const char *env_dir = getenv("DAT_SCHEME_DIR"); + const char *support; +#ifdef _WIN32 + char exe_scheme[2048]; + int has_exe_scheme = executable_scheme_dir(exe_scheme, sizeof(exe_scheme)); +#endif + if (!runtime) return -1; + runtime->scheme = NULL; + sc = s7_init(); + if (!sc) return -1; + runtime->scheme = sc; + if (dat_s7_init(sc)) goto fail; + if (env_path && add_path_list(sc, env_path)) goto fail; + if (env_dir && *env_dir) s7_add_to_load_path(sc, env_dir); + if (directory_exists("scheme")) s7_add_to_load_path(sc, "scheme"); +#ifdef _WIN32 + if (has_exe_scheme) s7_add_to_load_path(sc, exe_scheme); +#endif + if (directory_exists(DAT_SCHEME_DIR)) s7_add_to_load_path(sc, DAT_SCHEME_DIR); + support = (env_dir && directory_exists(env_dir)) ? env_dir : + (directory_exists("scheme") ? "scheme" : +#ifdef _WIN32 + (has_exe_scheme ? exe_scheme : DAT_SCHEME_DIR)); +#else + DAT_SCHEME_DIR); +#endif + { + static const char *files[] = { + "dat-init.scm", "dat-record.scm", "dat-wire.scm", "dat-json.scm" + }; + size_t i; + for (i = 0; i < sizeof(files) / sizeof(files[0]); i++) { + char file_path[2048]; + int n = snprintf(file_path, sizeof(file_path), "%s/%s", support, files[i]); + if (n < 0 || (size_t)n >= sizeof(file_path) || !s7_load(sc, file_path)) goto fail; + } + } + return 0; +fail: + s7_free(sc); + runtime->scheme = NULL; + return -1; +} + +void dat_scheme_runtime_close(dat_scheme_runtime_t *runtime) +{ + if (runtime && runtime->scheme) { + s7_free(runtime->scheme); + runtime->scheme = NULL; + } +} + +int dat_scheme_write_archive_info(s7_scheme *sc, const char *root, + const dat_archive_t *archive, + const char *title, const char *description, + int replace) +{ + char path[1200], write_path[1240], address[DAT_URL_BYTES]; + const char *base, *serialized; + s7_pointer record, port; + FILE *file; + size_t length; + int n, result = -1, saved; + if (!sc || !root || !*root || !archive) { errno = EINVAL; return -1; } + base = strrchr(root, '/'); + base = base ? base + 1 : root; + if (!*base) base = "Dat archive"; + if (!title) title = base; + if (!description) description = ""; + if (dat_key_to_url(archive->metadata_key, address)) return -1; + record = s7_list(sc, 3, + s7_list(sc, 2, s7_make_symbol(sc, "title"), s7_make_string(sc, title)), + s7_list(sc, 2, s7_make_symbol(sc, "description"), s7_make_string(sc, description)), + s7_list(sc, 2, s7_make_symbol(sc, "url"), s7_make_string(sc, address))); + port = s7_open_output_string(sc); + if (!port || !s7_write(sc, record, port)) { errno = EIO; return -1; } + serialized = s7_get_output_string(sc, port); + if (!serialized) { errno = EIO; return -1; } + n = snprintf(path, sizeof(path), "%s%sdat.scm", root, + root[strlen(root) - 1] == '/' ? "" : "/"); + if (n < 0 || (size_t)n >= sizeof(path)) { errno = ENAMETOOLONG; return -1; } + if (replace) { + n = snprintf(write_path, sizeof(write_path), "%s.tmp.%ld", path, (long)getpid()); + if (n < 0 || (size_t)n >= sizeof(write_path)) { errno = ENAMETOOLONG; return -1; } + } else strcpy(write_path, path); + file = fopen(write_path, "wx"); + if (!file) { + if (!replace && errno == EEXIST) return 0; + return -1; + } + length = strlen(serialized); + if (fwrite(serialized, 1, length, file) != length || fputc('\n', file) == EOF || fflush(file)) { + saved = errno ? errno : EIO; + fclose(file); + unlink(write_path); + errno = saved; + return -1; + } + result = fclose(file); + if (result) { unlink(write_path); return -1; } + if (replace && dat_replace_file(write_path, path)) { saved = errno; unlink(write_path); errno = saved; return -1; } + return 0; +} diff --git a/src/dat_scheme_cli.c b/src/dat_scheme_cli.c new file mode 100644 index 0000000..e73620c --- /dev/null +++ b/src/dat_scheme_cli.c @@ -0,0 +1,58 @@ +#include "dat_s7.h" +#include +#include + +static void usage(FILE *out) +{ + fputs("usage: ddat-scheme [-I directory] [-e expression] [-l file] [--repl] [file ...]\n" + " -I, --include DIR add a Scheme module directory\n" + " -e EXPR evaluate a Scheme expression\n" + " -l, --load FILE load a Scheme source file\n" + " --repl enter the interactive REPL after other actions\n" + " -h, --help show this help\n", out); +} + +int main(int argc, char **argv) +{ + dat_scheme_runtime_t runtime = {0}; + s7_scheme *sc; + int i, result = 0, did_action = 0, enter_repl = 0; + if (dat_scheme_runtime_init(&runtime)) { + fputs("ddat-scheme: could not initialize the Dat S7 runtime; check DAT_SCHEME_DIR/DAT_SCHEME_PATH\n", stderr); + return 1; + } + sc = runtime.scheme; + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { usage(stdout); goto done; } + if (!strcmp(argv[i], "--repl")) { enter_repl = 1; continue; } + if (!strcmp(argv[i], "-I") || !strcmp(argv[i], "--include")) { + if (++i >= argc) { usage(stderr); result = 2; break; } + s7_add_to_load_path(sc, argv[i]); + continue; + } + if (!strcmp(argv[i], "-e")) { + s7_pointer value; + if (++i >= argc) { usage(stderr); result = 2; break; } + value = s7_load_c_string(sc, argv[i], (s7_int)strlen(argv[i])); + if (!value) { fprintf(stderr, "ddat-scheme: evaluation failed: %s\n", argv[i]); result = 1; break; } + did_action = 1; + continue; + } + if (!strcmp(argv[i], "-l") || !strcmp(argv[i], "--load")) { + if (++i >= argc) { usage(stderr); result = 2; break; } + } + if (!strcmp(argv[i], "--")) { + for (++i; i < argc; i++) { + if (!s7_load(sc, argv[i])) { fprintf(stderr, "ddat-scheme: could not load %s\n", argv[i]); result = 1; break; } + did_action = 1; + } + break; + } + if (!s7_load(sc, argv[i])) { fprintf(stderr, "ddat-scheme: could not load %s\n", argv[i]); result = 1; break; } + did_action = 1; + } + if (!result && (enter_repl || (!did_action && argc == 1))) s7_repl(sc); +done: + dat_scheme_runtime_close(&runtime); + return result; +} diff --git a/src/dat_session.c b/src/dat_session.c new file mode 100644 index 0000000..885dfc8 --- /dev/null +++ b/src/dat_session.c @@ -0,0 +1,884 @@ +#include "dat_session.h" +#include "errno_compat.h" + +#include "sleep.h" +#include "sleep_crypto.h" +#include "sleep_proof.h" +#include +#include +#include +#ifdef DAT_LEGACY_HYPERCORE +#include +#endif + +#define DAT_SESSION_FRAME_BYTES (128u * 1024u) + +#ifdef DAT_LEGACY_HYPERCORE +static const uint64_t feed_channels[DAT_SESSION_FEED_COUNT] = {0, 1}; +#else +static const uint64_t feed_channels[DAT_SESSION_FEED_COUNT] = {1, 2}; +#endif + +static int valid_feed(enum dat_session_feed feed) +{ + return feed >= DAT_SESSION_METADATA && feed < DAT_SESSION_FEED_COUNT; +} + +static int feed_for_channel(uint64_t channel, enum dat_session_feed *feed) +{ + size_t i; + for (i = 0; i < DAT_SESSION_FEED_COUNT; i++) if (feed_channels[i] == channel) { + *feed = (enum dat_session_feed)i; + return 0; + } + errno = EPROTO; + return -1; +} + +static int discover_known_peers(dat_session_t *session) +{ + cesspool_peer_t *peers[CESSPOOL_MAX_PEERS]; + size_t count, i; + count = cesspool_topic_peers(session->cesspool, session->discovery_topic, + peers, CESSPOOL_MAX_PEERS); + for (i = 0; i < count; i++) + if (dat_session_peer_discovered(session, peers[i])) return -1; + return 0; +} + +dat_session_peer_t *dat_session_find_peer(dat_session_t *session, + const uint8_t peer_id[DAT_PEER_ID_BYTES]) +{ + size_t i; + if (!session || !peer_id) return NULL; + for (i = 0; i < session->peer_count; i++) + if (!memcmp(session->peers[i].peer_id, peer_id, DAT_PEER_ID_BYTES)) + return &session->peers[i]; + return NULL; +} + +int dat_session_init(dat_session_t *session, cesspool_t *pool, + const uint8_t archive_key[DAT_KEY_BYTES], + const uint8_t peer_id[DAT_PEER_ID_BYTES], uint16_t port, + sleep_feed_t *metadata_feed, + const uint8_t metadata_key[DAT_KEY_BYTES], + sleep_feed_t *content_feed, + const uint8_t content_key[DAT_KEY_BYTES], + dat_session_send_fn send, void *context) +{ + if (!session || !pool || !archive_key || !peer_id || !metadata_feed || + !metadata_key || !content_feed || !content_key || !send) { + errno = EINVAL; + return -1; + } + memset(session, 0, sizeof(*session)); + memcpy(session->archive_key, archive_key, DAT_KEY_BYTES); + memcpy(session->peer_id, peer_id, DAT_PEER_ID_BYTES); + memcpy(session->feed_keys[DAT_SESSION_METADATA], metadata_key, DAT_KEY_BYTES); + memcpy(session->feed_keys[DAT_SESSION_CONTENT], content_key, DAT_KEY_BYTES); + session->content_key_known = 1; + if (cesspool_discovery_key(archive_key, session->discovery_topic)) return -1; + session->cesspool = pool; + session->feeds[DAT_SESSION_METADATA] = metadata_feed; + session->feeds[DAT_SESSION_CONTENT] = content_feed; + session->send = send; + session->context = context; + session->port = port; + if (session->port == 0) session->port = pool->port; + session->state = DAT_SESSION_OPEN; + return 0; +} + +int dat_session_start(dat_session_t *session, int announce, int lookup) +{ + if (!session || session->state != DAT_SESSION_OPEN || (!announce && !lookup)) { + errno = EINVAL; + return -1; + } + session->announcing = !!announce; + session->looking_up = !!lookup; + if (cesspool_join(session->cesspool, session->discovery_topic, + session->port, announce, lookup)) { + (void)cesspool_leave(session->cesspool, session->discovery_topic); + session->state = DAT_SESSION_FAILED; + return -1; + } + session->state = DAT_SESSION_DISCOVERING; + return discover_known_peers(session); +} + +int dat_session_peer_discovered(dat_session_t *session, + const cesspool_peer_t *discovered_peer) +{ + dat_session_peer_t *peer; + if (!session || !discovered_peer || (session->state != DAT_SESSION_DISCOVERING && + session->state != DAT_SESSION_ACTIVE)) { errno = EINVAL; return -1; } + peer = dat_session_find_peer(session, discovered_peer->public_key); + if (peer) { + int retry = peer->state == DAT_SESSION_PEER_CLOSED; + peer->address = discovered_peer->address; + if (retry) { + peer->state = DAT_SESSION_PEER_DISCOVERED; + peer->transport_authenticated = 0; + free(peer->rx_buffer); + peer->rx_buffer = NULL; + peer->rx_buffer_len = peer->rx_buffer_capacity = 0; + peer->tx_offset = peer->rx_offset = 0; + peer->remote_feed_mask = 0; + peer->local_feed_mask = 0; + peer->remote_content_pending = 0; + memset(peer->remote_content_discovery, 0, + sizeof(peer->remote_content_discovery)); + peer->tx_stream_encrypted = peer->rx_stream_encrypted = 0; + if (session->on_peer) session->on_peer(session->peer_context, peer); + } + return 0; + } + if (session->peer_count >= DAT_SESSION_MAX_PEERS) { errno = ENOSPC; return -1; } + peer = &session->peers[session->peer_count]; + memset(peer, 0, sizeof(*peer)); + memcpy(peer->peer_id, discovered_peer->public_key, DAT_PEER_ID_BYTES); + peer->address = discovered_peer->address; + peer->state = DAT_SESSION_PEER_DISCOVERED; + if (sleep_replication_init(&peer->replication[DAT_SESSION_METADATA], + session->feeds[DAT_SESSION_METADATA], + feed_channels[DAT_SESSION_METADATA]) || + sleep_replication_init(&peer->replication[DAT_SESSION_CONTENT], + session->feeds[DAT_SESSION_CONTENT], + feed_channels[DAT_SESSION_CONTENT])) return -1; + session->peer_count++; + if (session->on_peer) session->on_peer(session->peer_context, peer); + return 0; +} + +int dat_session_set_peer_callback(dat_session_t *session, + dat_session_peer_fn callback, void *context) +{ + size_t i; + if (!session || session->state == DAT_SESSION_CLOSED || + session->state == DAT_SESSION_CLOSING) { errno = EINVAL; return -1; } + session->on_peer = callback; + session->peer_context = context; + if (callback) + for (i = 0; i < session->peer_count; i++) + if (session->peers[i].state == DAT_SESSION_PEER_DISCOVERED) + callback(context, &session->peers[i]); + return 0; +} + +int dat_session_set_transport_poll(dat_session_t *session, + dat_session_poll_fn poll, void *context) +{ + if (!session || session->state == DAT_SESSION_CLOSED || + session->state == DAT_SESSION_CLOSING) { errno = EINVAL; return -1; } + session->poll_transport = poll; + session->poll_context = context; + return 0; +} + +static int send_frame(dat_session_t *session, dat_session_peer_t *peer, + uint64_t channel, uint64_t type, + const uint8_t *payload, size_t payload_len) +{ + dat_wire_message_t message; + uint8_t frame[DAT_SESSION_FRAME_BYTES]; + size_t frame_len; + int send_result; + message.channel = channel; + message.type = type; + message.payload = payload; + message.payload_len = payload_len; + if (dat_wire_encode(&message, frame, sizeof(frame), &frame_len)) return -1; +#ifdef DAT_LEGACY_HYPERCORE + if (peer->tx_stream_encrypted) { + if (sleep_stream_xsalsa20_xor(frame, frame, frame_len, + peer->tx_nonce, + session->feed_keys[DAT_SESSION_METADATA], + peer->tx_offset)) return -1; + if (peer->tx_offset > UINT64_MAX - frame_len) { errno = EOVERFLOW; return -1; } + } +#endif + send_result = session->send(session->context, peer->peer_id, frame, frame_len); +#ifdef DAT_LEGACY_HYPERCORE + if (!send_result && peer->tx_stream_encrypted) peer->tx_offset += frame_len; +#endif + return send_result; +} + +static int send_have_range(dat_session_t *session, dat_session_peer_t *peer, + enum dat_session_feed feed, uint64_t start, + uint64_t length) +{ + dat_range_t range = {start, length}; + uint8_t payload[32]; + size_t payload_len; + if (!range.length) return 0; + if (dat_range_encode(&range, payload, sizeof(payload), &payload_len)) return -1; + return send_frame(session, peer, feed_channels[feed], DAT_MSG_HAVE, + payload, payload_len); +} + +static int send_have(dat_session_t *session, dat_session_peer_t *peer, + enum dat_session_feed feed) +{ + size_t index, length = sleep_feed_length(session->feeds[feed]); + size_t run_start = 0; + int in_run = 0; + if (length > SLEEP_BITFIELD_DATA_BYTES * 8u) + length = SLEEP_BITFIELD_DATA_BYTES * 8u; + for (index = 0; index < length; index++) { + int have = sleep_feed_has(session->feeds[feed], index); + if (have && !in_run) { run_start = index; in_run = 1; } + if (!have && in_run) { + if (send_have_range(session, peer, feed, run_start, index - run_start)) return -1; + in_run = 0; + } + } + if (in_run) return send_have_range(session, peer, feed, run_start, length - run_start); + return 0; +} + +static int answer_want(dat_session_t *session, dat_session_peer_t *peer, + enum dat_session_feed feed, const dat_range_t *range) +{ + uint64_t i, end, run_start = 0; + int in_run = 0; + uint64_t feed_length = sleep_feed_length(session->feeds[feed]); + if (range->start > UINT64_MAX - range->length) { + errno = EOVERFLOW; + return -1; + } + end = range->start + range->length; + if (end > feed_length) end = feed_length; + if (end > SLEEP_BITFIELD_DATA_BYTES * 8u) end = SLEEP_BITFIELD_DATA_BYTES * 8u; + for (i = range->start; i < end; i++) { + int have = i <= SIZE_MAX && sleep_feed_has(session->feeds[feed], (size_t)i); + if (have && !in_run) { run_start = i; in_run = 1; } + if (!have && in_run) { + if (send_have_range(session, peer, feed, run_start, i - run_start)) return -1; + in_run = 0; + } + } + if (in_run && send_have_range(session, peer, feed, run_start, end - run_start)) return -1; + return 0; +} + +static int announce_received_block(dat_session_t *session, + enum dat_session_feed feed, uint64_t index) +{ + dat_range_t range = {index, 1}; + uint8_t payload[32]; + size_t payload_len, i; + if (dat_range_encode(&range, payload, sizeof(payload), &payload_len)) return -1; + for (i = 0; i < session->peer_count; i++) { + dat_session_peer_t *peer = &session->peers[i]; + if (peer->state == DAT_SESSION_PEER_READY && + (peer->remote_feed_mask & (uint8_t)(1u << feed)) && + send_frame(session, peer, feed_channels[feed], DAT_MSG_HAVE, + payload, payload_len)) return -1; + } + return 0; +} + +static int pump_replication(dat_session_t *session, dat_session_peer_t *peer, + enum dat_session_feed feed) +{ + sleep_replication_t *replication = &peer->replication[feed]; + size_t index, limit; + if (peer->state != DAT_SESSION_PEER_READY || peer->paused || !replication->uploading) return 0; + limit = replication->remote_length > SLEEP_BITFIELD_DATA_BYTES * 8u ? + SLEEP_BITFIELD_DATA_BYTES * 8u : (size_t)replication->remote_length; + for (index = 0; index < limit; index++) { + int requested, available; + if (sleep_feed_has(session->feeds[feed], index)) continue; + available = sleep_bitfield_get_data(&replication->remote_bitfield, index); + if (available <= 0) continue; + requested = sleep_replication_is_requested(replication, index); + if (requested < 0) return -1; + if (requested) continue; + if (dat_session_request_block(session, peer->peer_id, feed, index, 0)) { + if (errno == ENOSPC) return 0; + return -1; + } + } + return 0; +} + +int dat_session_peer_secure(dat_session_t *session, + const uint8_t peer_id[DAT_PEER_ID_BYTES], int authenticated) +{ + dat_session_peer_t *peer; + dat_handshake_t handshake; + dat_feed_open_t feed_open; + uint8_t payload[128]; + size_t payload_len; + if (!session || !peer_id || !authenticated) { errno = EACCES; return -1; } + peer = dat_session_find_peer(session, peer_id); + if (!peer || peer->state != DAT_SESSION_PEER_DISCOVERED) { errno = EINVAL; return -1; } + peer->transport_authenticated = 1; + peer->state = DAT_SESSION_PEER_SECURE; +#ifdef DAT_LEGACY_HYPERCORE + memset(&feed_open, 0, sizeof(feed_open)); + if (sleep_crypto_init()) goto fail; + if (sleep_compute_discovery_key(session->feed_keys[DAT_SESSION_METADATA], + feed_open.discovery_key)) goto fail; + feed_open.has_nonce = 1; + randombytes_buf(feed_open.nonce, sizeof(feed_open.nonce)); + memcpy(peer->tx_nonce, feed_open.nonce, sizeof(peer->tx_nonce)); + if (dat_feed_open_encode(&feed_open, payload, sizeof(payload), &payload_len) || + send_frame(session, peer, feed_channels[DAT_SESSION_METADATA], DAT_MSG_FEED, + payload, payload_len)) goto fail; + peer->local_feed_mask |= (uint8_t)(1u << DAT_SESSION_METADATA); + peer->tx_stream_encrypted = 1; + if (dat_handshake_init(&handshake, session->peer_id, + session->content_key_known ? 0 : DAT_SESSION_CAP_METADATA_ONLY) || + dat_handshake_encode(&handshake, payload, sizeof(payload), &payload_len) || + send_frame(session, peer, 0, DAT_MSG_HANDSHAKE, payload, payload_len)) goto fail; + memset(&feed_open, 0, sizeof(feed_open)); + if (session->content_key_known && + (sleep_compute_discovery_key(session->feed_keys[DAT_SESSION_CONTENT], + feed_open.discovery_key) || + dat_feed_open_encode(&feed_open, payload, sizeof(payload), &payload_len) || + send_frame(session, peer, feed_channels[DAT_SESSION_CONTENT], DAT_MSG_FEED, + payload, payload_len))) goto fail; + if (session->content_key_known) peer->local_feed_mask |= (uint8_t)(1u << DAT_SESSION_CONTENT); + return 0; +#else + memset(&feed_open, 0, sizeof(feed_open)); + if (sleep_compute_discovery_key(session->feed_keys[DAT_SESSION_METADATA], + feed_open.discovery_key) || + dat_feed_open_encode(&feed_open, payload, sizeof(payload), &payload_len) || + send_frame(session, peer, feed_channels[DAT_SESSION_METADATA], + DAT_MSG_FEED, payload, payload_len)) goto fail; + peer->local_feed_mask |= (uint8_t)(1u << DAT_SESSION_METADATA); + if (session->content_key_known && + (sleep_compute_discovery_key(session->feed_keys[DAT_SESSION_CONTENT], + feed_open.discovery_key) || + dat_feed_open_encode(&feed_open, payload, sizeof(payload), &payload_len) || + send_frame(session, peer, feed_channels[DAT_SESSION_CONTENT], + DAT_MSG_FEED, payload, payload_len))) goto fail; + if (session->content_key_known) peer->local_feed_mask |= (uint8_t)(1u << DAT_SESSION_CONTENT); + if (dat_handshake_init(&handshake, session->peer_id, + session->content_key_known ? 0 : DAT_SESSION_CAP_METADATA_ONLY) || + dat_handshake_encode(&handshake, payload, sizeof(payload), &payload_len) || + send_frame(session, peer, 0, DAT_MSG_HANDSHAKE, payload, payload_len)) goto fail; + return 0; +#endif + +fail: + peer->state = DAT_SESSION_PEER_CLOSED; + peer->transport_authenticated = 0; + peer->tx_stream_encrypted = peer->rx_stream_encrypted = 0; + peer->tx_offset = peer->rx_offset = 0; + free(peer->rx_buffer); + peer->rx_buffer = NULL; + peer->rx_buffer_len = peer->rx_buffer_capacity = 0; + return -1; +} + +int dat_session_expect_content_key(dat_session_t *session) +{ + if (!session || session->state != DAT_SESSION_OPEN) { errno = EINVAL; return -1; } + memset(session->feed_keys[DAT_SESSION_CONTENT], 0, DAT_KEY_BYTES); + session->content_key_known = 0; + return 0; +} + +int dat_session_set_content_key(dat_session_t *session, + const uint8_t content_key[DAT_KEY_BYTES]) +{ + uint8_t expected[32]; + size_t i; + if (!session || !content_key || session->state == DAT_SESSION_CLOSED || + session->state == DAT_SESSION_CLOSING) { errno = EINVAL; return -1; } + if (session->content_key_known) { + if (!memcmp(session->feed_keys[DAT_SESSION_CONTENT], content_key, DAT_KEY_BYTES)) return 0; + errno = EKEYREJECTED; + return -1; + } + if (sleep_compute_discovery_key(content_key, expected)) return -1; + for (i = 0; i < session->peer_count; i++) { + dat_session_peer_t *peer = &session->peers[i]; + if (peer->remote_content_pending && + memcmp(peer->remote_content_discovery, expected, sizeof(expected))) { + errno = EKEYREJECTED; + return -1; + } + } + memcpy(session->feed_keys[DAT_SESSION_CONTENT], content_key, DAT_KEY_BYTES); + memcpy(session->feeds[DAT_SESSION_CONTENT]->keypair.pubkey, content_key, DAT_KEY_BYTES); + session->content_key_known = 1; + for (i = 0; i < session->peer_count; i++) { + dat_session_peer_t *peer = &session->peers[i]; + dat_feed_open_t feed_open; + uint8_t payload[128]; + size_t payload_len; + peer->remote_content_pending = 0; + if (peer->state != DAT_SESSION_PEER_READY && peer->state != DAT_SESSION_PEER_SECURE) continue; + if (!(peer->local_feed_mask & (uint8_t)(1u << DAT_SESSION_CONTENT))) { + memset(&feed_open, 0, sizeof(feed_open)); + if (sleep_compute_discovery_key(content_key, feed_open.discovery_key) || + dat_feed_open_encode(&feed_open, payload, sizeof(payload), &payload_len) || + send_frame(session, peer, feed_channels[DAT_SESSION_CONTENT], DAT_MSG_FEED, + payload, payload_len)) return -1; + peer->local_feed_mask |= (uint8_t)(1u << DAT_SESSION_CONTENT); + } + if (peer->state == DAT_SESSION_PEER_READY && + (peer->remote_feed_mask & (uint8_t)(1u << DAT_SESSION_CONTENT)) && + send_have(session, peer, DAT_SESSION_CONTENT)) return -1; + } + return 0; +} + +static int receive_data(dat_session_t *session, dat_session_peer_t *peer, + enum dat_session_feed feed, const uint8_t *payload, + size_t payload_len) +{ + dat_data_t data; + sleep_proof_t proof; + sleep_feed_t *local; + uint8_t *old_data = NULL; + uint8_t proof_cache[8192]; + const uint8_t *cached_proof; + size_t old_len = 0; + size_t cached_proof_len; + size_t i; + if (!valid_feed(feed) || dat_data_decode(&data, payload, payload_len)) return -1; + local = session->feeds[feed]; + if (data.proof_len) { + if (sleep_proof_decode(&proof, data.proof, data.proof_len)) return -1; + } else { + sleep_proof_node_t nodes[DAT_DATA_MAX_NODES]; + uint64_t remote_length = peer->replication[feed].remote_length; + if (!data.has_signature || !remote_length || data.node_count > DAT_DATA_MAX_NODES) { + errno = EPROTO; + return -1; + } + for (i = 0; i < data.node_count; i++) { + nodes[i].index = data.nodes[i].index; + nodes[i].length = data.nodes[i].length; + memcpy(nodes[i].hash, data.nodes[i].hash, 32); + } + if (sleep_proof_from_nodes(data.index, data.value, data.value_len, + nodes, data.node_count, data.signature, + remote_length, &proof)) return -1; + } + if (proof.block != data.index || !proof.has_signature || + sleep_proof_verify(&proof, data.value, data.value_len, + session->feed_keys[feed])) { + errno = EBADMSG; + return -1; + } + cached_proof = data.proof; + cached_proof_len = data.proof_len; + if (!cached_proof_len) { + if (sleep_proof_encode(&proof, proof_cache, sizeof(proof_cache), + &cached_proof_len)) return -1; + cached_proof = proof_cache; + } + if (data.index >= SLEEP_BITFIELD_DATA_BYTES * 8u || + (peer->replication[feed].remote_length && + data.index >= peer->replication[feed].remote_length)) { + errno = ERANGE; + return -1; + } + if (peer->paused) + return sleep_replication_complete(&peer->replication[feed], data.index); + if (sleep_feed_has(local, (size_t)data.index)) { + if (sleep_feed_read(local, (size_t)data.index, &old_data, &old_len)) return -1; + if (old_len != data.value_len || memcmp(old_data, data.value, old_len)) { + free(old_data); errno = EBADMSG; return -1; + } + free(old_data); + peer->payload_bytes_received += data.value_len; + if (sleep_replication_complete(&peer->replication[feed], data.index)) return -1; + return pump_replication(session, peer, feed); + } + if (sleep_feed_append_remote(local, (size_t)data.index, data.value, + data.value_len, cached_proof, + cached_proof_len)) return -1; + peer->payload_bytes_received += data.value_len; + if (sleep_replication_complete(&peer->replication[feed], data.index)) return -1; + if (announce_received_block(session, feed, data.index)) return -1; + return pump_replication(session, peer, feed); +} + +static int dispatch_request(dat_session_t *session, dat_session_peer_t *peer, + enum dat_session_feed feed, + const dat_request_t *request) +{ + if (session->on_request) + return session->on_request(session->context, peer->peer_id, feed, request); + return dat_session_send_block(session, peer->peer_id, feed, request->index); +} + +static int dat_session_process_frame(dat_session_t *session, + dat_session_peer_t *peer, + const uint8_t *frame, size_t frame_len) +{ + dat_wire_message_t message; + size_t consumed; + if (dat_wire_decode(&message, frame, frame_len, &consumed) || consumed != frame_len) return -1; + if (message.type == DAT_MSG_FEED) { + dat_feed_open_t remote_feed; + enum dat_session_feed feed; + uint8_t expected[32]; + if ((peer->state != DAT_SESSION_PEER_SECURE && peer->state != DAT_SESSION_PEER_READY) || + feed_for_channel(message.channel, &feed) || + (peer->remote_feed_mask & (uint8_t)(1u << feed)) || + dat_feed_open_decode(&remote_feed, message.payload, message.payload_len)) { + errno = EKEYREJECTED; + return -1; + } + if (feed == DAT_SESSION_CONTENT && !session->content_key_known) { + memcpy(peer->remote_content_discovery, remote_feed.discovery_key, 32); + peer->remote_content_pending = 1; + } else if (sleep_compute_discovery_key(session->feed_keys[feed], expected) || + memcmp(remote_feed.discovery_key, expected, sizeof(expected))) { + errno = EKEYREJECTED; + return -1; + } +#ifdef DAT_LEGACY_HYPERCORE + if (feed == DAT_SESSION_METADATA && message.channel == 0) { + if (!remote_feed.has_nonce || peer->rx_stream_encrypted) { errno = EPROTO; return -1; } + memcpy(peer->rx_nonce, remote_feed.nonce, sizeof(peer->rx_nonce)); + peer->rx_offset = 0; + peer->rx_stream_encrypted = 1; + } +#endif + peer->remote_feed_mask |= (uint8_t)(1u << feed); + if (peer->state == DAT_SESSION_PEER_READY) + return send_have(session, peer, feed); + return 0; + } + if (message.type == DAT_MSG_HANDSHAKE) { + dat_handshake_t remote; + if (message.channel != 0 || peer->state != DAT_SESSION_PEER_SECURE || + !(peer->remote_feed_mask & (uint8_t)(1u << DAT_SESSION_METADATA)) || + dat_handshake_decode(&remote, message.payload, message.payload_len) || + dat_handshake_protocol_matches(&remote) != 1 +#ifndef DAT_LEGACY_HYPERCORE + || (remote.has_peer_id && memcmp(remote.peer_id, peer->peer_id, + DAT_PEER_ID_BYTES)) +#endif + ) { + errno = EPROTO; + return -1; + } + #ifndef DAT_LEGACY_HYPERCORE + if (!(remote.capabilities & DAT_SESSION_CAP_METADATA_ONLY) && + peer->remote_feed_mask != (uint8_t)((1u << DAT_SESSION_FEED_COUNT) - 1u)) { + errno = EPROTO; + return -1; + } + if ((remote.capabilities & DAT_SESSION_CAP_METADATA_ONLY) && + peer->remote_feed_mask != (uint8_t)(1u << DAT_SESSION_METADATA)) { + errno = EPROTO; + return -1; + } + #endif + peer->capabilities = remote.capabilities; + peer->state = DAT_SESSION_PEER_READY; + session->state = DAT_SESSION_ACTIVE; + for (size_t feed = 0; feed < DAT_SESSION_FEED_COUNT; feed++) + if ((peer->remote_feed_mask & (uint8_t)(1u << feed)) && + send_have(session, peer, (enum dat_session_feed)feed)) return -1; + return 0; + } + { + enum dat_session_feed feed; + if (peer->state != DAT_SESSION_PEER_READY || feed_for_channel(message.channel, &feed)) { + errno = EPROTO; + return -1; + } + if (!(peer->remote_feed_mask & (uint8_t)(1u << feed))) { + errno = EPROTO; + return -1; + } + if (message.type == DAT_MSG_INFO) { + dat_info_t info; + if (dat_info_decode(&info, message.payload, message.payload_len)) return -1; + peer->replication[feed].uploading = info.uploading; + peer->replication[feed].downloading = info.downloading; + return pump_replication(session, peer, feed); + } + if (message.type == DAT_MSG_UNHAVE) { + dat_range_t range; + if (dat_range_decode(&range, message.payload, message.payload_len)) return -1; + if (sleep_replication_unhave(&peer->replication[feed], range.start, + range.length)) return -1; + return sleep_replication_unwant(&peer->replication[feed], range.start, + range.length); + } + if (message.type == DAT_MSG_HAVE || message.type == DAT_MSG_WANT) { + dat_range_t range; + if (message.type == DAT_MSG_HAVE) { + if (dat_range_decode(&range, message.payload, message.payload_len)) return -1; + if (sleep_replication_have(&peer->replication[feed], + range.start, range.length)) return -1; + return pump_replication(session, peer, feed); + } + if (dat_want_decode(&range, message.payload, message.payload_len, + sleep_feed_length(session->feeds[feed]))) return -1; + return answer_want(session, peer, feed, &range); + } + if (message.type == DAT_MSG_UNWANT) return 0; + if (message.type == DAT_MSG_REQUEST) { + dat_request_t request; + if (dat_request_decode(&request, message.payload, message.payload_len)) return -1; + return dispatch_request(session, peer, feed, &request); + } + if (message.type == DAT_MSG_DATA) + return receive_data(session, peer, feed, message.payload, message.payload_len); + if (message.type == DAT_MSG_CANCEL) return 0; + errno = EPROTONOSUPPORT; + return -1; + } +} + +static int dat_session_receive_buffered(dat_session_t *session, + dat_session_peer_t *peer, + const uint8_t *bytes, size_t length) +{ + if (length > DAT_SESSION_FRAME_BYTES * 2u || + peer->rx_buffer_len > DAT_SESSION_FRAME_BYTES * 2u - length) { + errno = EMSGSIZE; + return -1; + } + if (peer->rx_buffer_capacity < peer->rx_buffer_len + length) { + size_t capacity = peer->rx_buffer_len + length; + uint8_t *next; + if (capacity < 4096) capacity = 4096; + next = realloc(peer->rx_buffer, capacity); + if (!next) return -1; + peer->rx_buffer = next; + peer->rx_buffer_capacity = capacity; + } +#ifdef DAT_LEGACY_HYPERCORE + if (peer->rx_stream_encrypted) { + if (sleep_stream_xsalsa20_xor(peer->rx_buffer + peer->rx_buffer_len, + bytes, length, peer->rx_nonce, + session->feed_keys[DAT_SESSION_METADATA], + peer->rx_offset)) return -1; + peer->rx_offset += length; + } else if (length) { + memcpy(peer->rx_buffer + peer->rx_buffer_len, bytes, length); + } +#else + if (length) memcpy(peer->rx_buffer + peer->rx_buffer_len, bytes, length); +#endif + peer->rx_buffer_len += length; + while (peer->rx_buffer_len) { + uint64_t body_len; + size_t prefix, total; + #ifdef DAT_LEGACY_HYPERCORE + int was_encrypted = peer->rx_stream_encrypted; + #endif + if (sleep_varint_decode(peer->rx_buffer, peer->rx_buffer_len, + &body_len, &prefix)) { + if (errno == EAGAIN) return 0; + return -1; + } + if (!body_len || body_len > DAT_SESSION_FRAME_BYTES || + body_len > SIZE_MAX - prefix) { errno = EMSGSIZE; return -1; } + total = prefix + (size_t)body_len; + if (peer->rx_buffer_len < total) return 0; + if (dat_session_process_frame(session, peer, peer->rx_buffer, total)) return -1; + peer->rx_buffer_len -= total; + if (peer->rx_buffer_len) + memmove(peer->rx_buffer, peer->rx_buffer + total, peer->rx_buffer_len); +#ifdef DAT_LEGACY_HYPERCORE + /* Feed(0) turns encryption on mid-read; cipher the buffered tail from offset zero. */ + if (!was_encrypted && peer->rx_stream_encrypted && peer->rx_buffer_len) { + if (sleep_stream_xsalsa20_xor(peer->rx_buffer, peer->rx_buffer, + peer->rx_buffer_len, peer->rx_nonce, + session->feed_keys[DAT_SESSION_METADATA], 0)) return -1; + peer->rx_offset = peer->rx_buffer_len; + } +#endif + } + return 0; +} + +int dat_session_receive(dat_session_t *session, + const uint8_t peer_id[DAT_PEER_ID_BYTES], + const uint8_t *bytes, size_t length) +{ + dat_session_peer_t *peer; + if (!session || !peer_id || (!bytes && length)) { errno = EINVAL; return -1; } + peer = dat_session_find_peer(session, peer_id); + if (!peer || !peer->transport_authenticated) { errno = EACCES; return -1; } + return dat_session_receive_buffered(session, peer, bytes, length); +} + +int dat_session_request_block(dat_session_t *session, + const uint8_t peer_id[DAT_PEER_ID_BYTES], + enum dat_session_feed feed, uint64_t index, + uint64_t bytes) +{ + dat_session_peer_t *peer; + sleep_replication_request_t queued; + dat_request_t request; + uint8_t payload[64]; + size_t payload_len; + int already_requested; + if (!session || !peer_id || !valid_feed(feed)) { errno = EINVAL; return -1; } + if (index >= SLEEP_BITFIELD_DATA_BYTES * 8u) { errno = ERANGE; return -1; } + peer = dat_session_find_peer(session, peer_id); + if (!peer || peer->state != DAT_SESSION_PEER_READY || + !(peer->remote_feed_mask & (uint8_t)(1u << feed))) { errno = ENOTCONN; return -1; } + if (!peer->replication[feed].uploading) { errno = EACCES; return -1; } + already_requested = sleep_replication_is_requested(&peer->replication[feed], index); + if (already_requested < 0) return -1; + if (already_requested) return 0; + queued.index = index; queued.bytes = bytes; queued.hash = 1; queued.nodes = 1; + if (sleep_replication_request(&peer->replication[feed], &queued)) return -1; + request.index = index; request.bytes = bytes; request.want_hash = 1; request.want_nodes = 1; + if (dat_request_encode(&request, payload, sizeof(payload), &payload_len) || + send_frame(session, peer, feed_channels[feed], DAT_MSG_REQUEST, payload, payload_len)) { + (void)sleep_replication_complete(&peer->replication[feed], index); + return -1; + } + return 0; +} + +int dat_session_send_block(dat_session_t *session, + const uint8_t peer_id[DAT_PEER_ID_BYTES], + enum dat_session_feed feed, uint64_t index) +{ + dat_session_peer_t *peer; + sleep_proof_t proof; + dat_data_t data; + uint8_t *block = NULL, *proof_bytes = NULL, *payload = NULL; + size_t block_len = 0, proof_len = 0, payload_len = 0; + int rc = -1; + if (!session || !peer_id || !valid_feed(feed)) { errno = EINVAL; return -1; } + peer = dat_session_find_peer(session, peer_id); + if (!peer || peer->state != DAT_SESSION_PEER_READY || + !(peer->remote_feed_mask & (uint8_t)(1u << feed))) { errno = ENOTCONN; return -1; } + if (!peer->replication[feed].downloading) { errno = EACCES; return -1; } + if (sleep_feed_read(session->feeds[feed], (size_t)index, &block, &block_len) || + sleep_proof_create(session->feeds[feed], index, &proof)) goto done; + proof_bytes = malloc(51 + (proof.node_count + proof.root_count) * 48 + 64); + if (!proof_bytes) goto done; + if (sleep_proof_encode(&proof, proof_bytes, + 51 + (proof.node_count + proof.root_count) * 48 + 64, + &proof_len)) goto done; + data.index = index; + data.value = block; + data.value_len = block_len; + data.proof = proof_bytes; + data.proof_len = proof_len; + data.node_count = 0; + data.has_signature = proof.has_signature; + memcpy(data.signature, proof.signature, sizeof(data.signature)); + { + size_t i; + for (i = 0; i < proof.node_count + proof.root_count; i++) { + const sleep_proof_node_t *node = i < proof.node_count ? + &proof.nodes[i] : &proof.roots[i - proof.node_count]; + data.nodes[i].index = node->index; + data.nodes[i].length = node->length; + memcpy(data.nodes[i].hash, node->hash, 32); + } + data.node_count = proof.node_count + proof.root_count; + } + if (block_len > SIZE_MAX - proof_len - data.node_count * 64 - 256) { + errno = EOVERFLOW; + goto done; + } + payload = malloc(block_len + proof_len + data.node_count * 64 + 256); + if (!payload) goto done; + if (dat_data_encode(&data, payload, + block_len + proof_len + data.node_count * 64 + 256, + &payload_len)) goto done; + rc = send_frame(session, peer, feed_channels[feed], DAT_MSG_DATA, payload, payload_len); + if (!rc) peer->payload_bytes_sent += block_len; +done: + free(block); free(proof_bytes); free(payload); + return rc; +} + +int dat_session_peer_down(dat_session_t *session, const uint8_t peer_id[DAT_PEER_ID_BYTES]) +{ + dat_session_peer_t *peer; + size_t i; + int active = 0; + if (!session || !peer_id) { errno = EINVAL; return -1; } + peer = dat_session_find_peer(session, peer_id); + if (!peer) { errno = ENOENT; return -1; } + peer->state = DAT_SESSION_PEER_CLOSED; + peer->transport_authenticated = 0; + free(peer->rx_buffer); + peer->rx_buffer = NULL; + peer->rx_buffer_len = peer->rx_buffer_capacity = 0; + for (i = 0; i < DAT_SESSION_FEED_COUNT; i++) + sleep_replication_clear_requests(&peer->replication[i]); + for (i = 0; i < session->peer_count; i++) + if (session->peers[i].state == DAT_SESSION_PEER_READY) active++; + if (!active) session->state = DAT_SESSION_DISCOVERING; + return 0; +} + +int dat_session_peer_pause(dat_session_t *session, + const uint8_t peer_id[DAT_PEER_ID_BYTES], int paused) +{ + dat_session_peer_t *peer; + size_t feed; + if (!session || !peer_id) { errno = EINVAL; return -1; } + peer = dat_session_find_peer(session, peer_id); + if (!peer || peer->state != DAT_SESSION_PEER_READY) { errno = ENOTCONN; return -1; } + peer->paused = !!paused; + if (peer->paused) { + for (feed = 0; feed < DAT_SESSION_FEED_COUNT; feed++) + sleep_replication_clear_requests(&peer->replication[feed]); + return 0; + } + for (feed = 0; feed < DAT_SESSION_FEED_COUNT; feed++) + if (pump_replication(session, peer, (enum dat_session_feed)feed)) return -1; + return 0; +} + +int dat_session_tick(dat_session_t *session, uint64_t now_ms) +{ + return dat_session_poll(session, 0, now_ms); +} + +int dat_session_poll(dat_session_t *session, int timeout_ms, uint64_t now_ms) +{ + int received, wait_ms; + if (!session || (session->state != DAT_SESSION_DISCOVERING && + session->state != DAT_SESSION_ACTIVE) || timeout_ms < 0) { errno = EINVAL; return -1; } + if (session->poll_transport && + session->poll_transport(session->poll_context, timeout_ms) < 0) return -1; + wait_ms = session->poll_transport ? 0 : timeout_ms; + received = cesspool_poll(session->cesspool, wait_ms); + if (received < 0) return -1; + while (received > 0) { + received = cesspool_poll(session->cesspool, 0); + if (received < 0) return -1; + } + if (cesspool_tick(session->cesspool, now_ms)) return -1; + if (discover_known_peers(session)) return -1; + return 0; +} + +int dat_session_close(dat_session_t *session) +{ + size_t i; + int rc = 0; + if (!session || session->state == DAT_SESSION_CLOSED) { errno = EINVAL; return -1; } + session->state = DAT_SESSION_CLOSING; + for (i = 0; i < session->peer_count; i++) { + session->peers[i].state = DAT_SESSION_PEER_CLOSED; + session->peers[i].transport_authenticated = 0; + free(session->peers[i].rx_buffer); + session->peers[i].rx_buffer = NULL; + session->peers[i].rx_buffer_len = session->peers[i].rx_buffer_capacity = 0; + } + if (session->announcing || session->looking_up) { + if (cesspool_leave(session->cesspool, session->discovery_topic)) rc = -1; + } + session->state = DAT_SESSION_CLOSED; + return rc; +} diff --git a/src/dat_thang_icons.h b/src/dat_thang_icons.h new file mode 100644 index 0000000..9ccf1b5 --- /dev/null +++ b/src/dat_thang_icons.h @@ -0,0 +1,114 @@ +/* Original dat-thang mockup icons, cropped and palette/RLE encoded. */ +#ifndef DAT_THANG_ICONS_H +#define DAT_THANG_ICONS_H +#include +#define DAT_THANG_DOLPHIN_W 68 +#define DAT_THANG_DOLPHIN_H 68 +static const uint8_t datthang_dolphin_palette[10][3] = { + {120,105,196}, + {85,160,73}, + {191,206,114}, + {0,0,0}, + {64,49,141}, + {255,255,255}, + {184,105,98}, + {80,80,80}, + {136,57,50}, + {139,84,41}, +}; +static const uint8_t datthang_dolphin_rle[][2] = { + {255,1}, {47,1}, {12,2}, {51,1}, {10,2}, {2,9}, {10,2}, {46,1}, + {10,2}, {2,9}, {10,2}, {43,1}, {4,2}, {1,9}, {18,2}, {1,9}, + {4,2}, {37,1}, {11,2}, {12,4}, {11,2}, {33,1}, {7,2}, {2,3}, + {5,4}, {4,0}, {4,4}, {1,0}, {5,4}, {1,3}, {7,2}, {32,1}, + {7,2}, {1,3}, {4,4}, {15,0}, {2,4}, {7,2}, {31,1}, {5,2}, + {3,3}, {4,4}, {18,0}, {3,4}, {5,2}, {27,1}, {3,2}, {1,9}, + {1,2}, {6,3}, {3,4}, {21,0}, {4,4}, {1,2}, {1,9}, {3,2}, + {23,1}, {5,2}, {6,3}, {3,4}, {23,0}, {4,4}, {5,2}, {22,1}, + {5,2}, {6,3}, {3,4}, {25,0}, {2,4}, {5,2}, {20,1}, {6,2}, + {7,3}, {2,4}, {28,0}, {1,4}, {6,2}, {10,1}, {10,2}, {1,9}, + {1,2}, {8,3}, {3,4}, {4,0}, {1,5}, {24,0}, {2,4}, {4,2}, + {9,1}, {12,2}, {9,3}, {2,4}, {4,0}, {1,5}, {25,0}, {3,4}, + {1,2}, {2,9}, {1,2}, {8,1}, {12,2}, {7,3}, {4,4}, {4,0}, + {1,5}, {26,0}, {2,4}, {1,2}, {2,9}, {1,2}, {15,1}, {4,2}, + {2,3}, {10,4}, {2,0}, {2,5}, {28,0}, {2,4}, {4,2}, {12,1}, + {4,2}, {4,3}, {6,4}, {1,0}, {1,4}, {1,5}, {2,4}, {32,0}, + {3,4}, {4,2}, {10,1}, {4,2}, {3,3}, {5,4}, {1,5}, {2,0}, + {1,5}, {2,0}, {3,4}, {16,0}, {1,5}, {16,0}, {4,2}, {10,1}, + {4,2}, {3,3}, {4,4}, {2,0}, {1,5}, {6,0}, {3,4}, {14,0}, + {1,5}, {16,0}, {4,2}, {9,1}, {4,2}, {4,3}, {3,4}, {12,0}, + {3,4}, {2,0}, {3,4}, {25,0}, {4,2}, {8,1}, {4,2}, {4,3}, + {4,4}, {1,0}, {1,5}, {11,0}, {6,4}, {4,0}, {1,5}, {20,0}, + {4,2}, {7,1}, {4,2}, {5,3}, {2,5}, {2,4}, {1,5}, {18,0}, + {1,4}, {25,0}, {4,2}, {6,1}, {4,2}, {5,3}, {2,5}, {5,4}, + {31,0}, {3,4}, {8,0}, {4,2}, {6,1}, {1,2}, {1,9}, {2,2}, + {4,3}, {2,5}, {1,3}, {2,5}, {1,4}, {2,5}, {1,4}, {16,0}, + {1,5}, {15,0}, {3,4}, {6,0}, {2,2}, {1,9}, {1,2}, {6,1}, + {4,2}, {4,3}, {1,5}, {1,3}, {2,5}, {1,3}, {1,5}, {4,4}, + {33,0}, {3,4}, {4,0}, {4,2}, {6,1}, {4,2}, {5,3}, {2,5}, + {4,3}, {4,4}, {2,3}, {9,0}, {1,5}, {2,0}, {1,5}, {12,0}, + {3,4}, {2,0}, {3,4}, {4,0}, {4,2}, {6,1}, {4,2}, {5,3}, + {1,5}, {1,3}, {1,5}, {4,3}, {3,4}, {4,3}, {7,0}, {1,5}, + {2,0}, {1,5}, {5,7}, {6,0}, {1,4}, {2,7}, {1,5}, {1,4}, + {3,0}, {1,4}, {4,0}, {4,2}, {4,1}, {4,2}, {15,3}, {2,4}, + {2,7}, {6,3}, {5,7}, {1,5}, {6,7}, {6,0}, {1,4}, {2,7}, + {1,5}, {1,4}, {3,0}, {1,4}, {6,0}, {3,2}, {3,1}, {4,2}, + {14,3}, {3,4}, {1,3}, {2,7}, {2,3}, {7,7}, {1,5}, {4,7}, + {9,0}, {1,4}, {3,7}, {1,4}, {10,0}, {3,2}, {3,1}, {4,2}, + {14,3}, {3,4}, {6,7}, {4,8}, {3,7}, {14,0}, {3,4}, {11,0}, + {3,2}, {3,1}, {4,2}, {14,3}, {2,4}, {2,3}, {2,7}, {8,8}, + {30,0}, {3,2}, {3,1}, {4,2}, {13,3}, {3,4}, {3,7}, {10,8}, + {13,0}, {1,5}, {15,0}, {3,2}, {3,1}, {2,2}, {1,9}, {1,2}, + {13,3}, {2,4}, {3,7}, {3,8}, {3,6}, {2,5}, {1,8}, {1,5}, + {1,8}, {12,0}, {1,5}, {16,0}, {1,2}, {1,9}, {1,2}, {3,1}, + {4,2}, {12,3}, {3,4}, {1,7}, {2,3}, {2,8}, {6,6}, {1,5}, + {12,0}, {1,5}, {18,0}, {3,2}, {3,1}, {4,2}, {8,3}, {1,0}, + {3,3}, {3,4}, {2,3}, {1,8}, {8,6}, {1,5}, {30,0}, {1,3}, + {3,2}, {3,1}, {4,2}, {8,3}, {2,0}, {3,3}, {1,4}, {3,3}, + {1,8}, {7,6}, {1,5}, {12,0}, {1,5}, {18,0}, {1,3}, {3,2}, + {3,1}, {4,2}, {2,0}, {1,3}, {5,0}, {1,3}, {3,0}, {4,3}, + {2,8}, {4,6}, {2,5}, {1,0}, {2,5}, {9,0}, {1,5}, {20,0}, + {1,3}, {3,2}, {3,1}, {4,2}, {1,3}, {3,0}, {8,3}, {1,4}, + {2,3}, {2,8}, {5,6}, {1,5}, {1,0}, {2,5}, {31,0}, {1,3}, + {3,2}, {3,1}, {4,2}, {11,3}, {2,4}, {1,3}, {2,8}, {6,6}, + {1,5}, {1,0}, {2,5}, {30,0}, {2,3}, {3,2}, {5,1}, {4,2}, + {9,3}, {1,4}, {1,3}, {2,8}, {5,6}, {2,5}, {1,0}, {3,5}, + {11,0}, {1,5}, {18,0}, {4,2}, {6,1}, {4,2}, {8,3}, {1,4}, + {1,0}, {2,8}, {6,6}, {2,5}, {2,0}, {1,5}, {8,0}, {2,4}, + {21,0}, {4,2}, {6,1}, {1,2}, {1,9}, {2,2}, {6,3}, {2,4}, + {2,0}, {2,8}, {5,6}, {2,5}, {1,0}, {2,5}, {7,0}, {3,4}, + {1,5}, {1,4}, {1,5}, {18,0}, {1,3}, {2,2}, {1,9}, {1,2}, + {6,1}, {1,2}, {1,9}, {2,2}, {5,3}, {2,4}, {2,0}, {2,3}, + {1,0}, {3,6}, {2,0}, {2,5}, {1,0}, {2,5}, {4,0}, {7,4}, + {1,5}, {1,0}, {1,5}, {17,0}, {1,3}, {2,2}, {1,9}, {1,2}, + {6,1}, {4,2}, {5,3}, {1,4}, {3,0}, {2,3}, {5,0}, {2,5}, + {2,0}, {1,5}, {2,0}, {8,4}, {2,0}, {1,5}, {18,0}, {2,3}, + {4,2}, {7,1}, {4,2}, {4,3}, {1,4}, {7,0}, {1,5}, {1,0}, + {3,5}, {1,0}, {2,5}, {7,4}, {2,0}, {1,5}, {1,0}, {2,5}, + {1,0}, {1,5}, {15,0}, {2,3}, {4,2}, {8,1}, {4,2}, {4,3}, + {1,4}, {7,0}, {1,5}, {1,0}, {2,5}, {1,0}, {2,5}, {5,4}, + {5,0}, {1,5}, {20,0}, {2,3}, {4,2}, {8,1}, {4,2}, {4,3}, + {1,4}, {8,0}, {3,5}, {1,4}, {2,5}, {3,4}, {8,0}, {1,5}, + {18,0}, {3,3}, {4,2}, {9,1}, {4,2}, {3,3}, {2,4}, {6,0}, + {1,4}, {2,5}, {1,4}, {3,5}, {5,4}, {6,0}, {1,5}, {1,0}, + {1,5}, {14,0}, {4,3}, {4,2}, {10,1}, {4,2}, {4,3}, {7,4}, + {3,5}, {1,3}, {2,5}, {3,3}, {5,4}, {3,0}, {2,5}, {1,0}, + {1,5}, {11,0}, {7,3}, {4,2}, {12,1}, {1,2}, {1,9}, {2,2}, + {9,3}, {2,5}, {1,3}, {3,5}, {4,3}, {5,4}, {2,0}, {2,5}, + {7,0}, {11,3}, {2,2}, {1,9}, {1,2}, {14,1}, {1,2}, {1,9}, + {2,2}, {9,3}, {2,5}, {1,3}, {2,5}, {1,3}, {1,5}, {10,3}, + {1,5}, {1,0}, {1,5}, {2,0}, {15,3}, {2,2}, {1,9}, {1,2}, + {15,1}, {4,2}, {10,3}, {3,5}, {31,3}, {4,2}, {11,1}, {10,2}, + {36,3}, {4,0}, {2,3}, {4,2}, {10,1}, {14,2}, {16,3}, {3,4}, + {10,3}, {2,4}, {1,3}, {5,0}, {1,3}, {6,2}, {10,1}, {14,2}, + {18,3}, {8,4}, {3,0}, {1,4}, {1,3}, {5,0}, {2,3}, {6,2}, + {20,1}, {2,2}, {2,9}, {1,2}, {20,3}, {13,0}, {3,3}, {1,2}, + {2,9}, {2,2}, {23,1}, {5,2}, {6,3}, {1,4}, {12,3}, {11,0}, + {4,3}, {5,2}, {27,1}, {5,2}, {3,3}, {3,4}, {17,3}, {2,0}, + {3,3}, {5,2}, {30,1}, {5,2}, {6,3}, {10,4}, {12,3}, {5,2}, + {31,1}, {7,2}, {22,3}, {7,2}, {33,1}, {3,2}, {1,9}, {7,2}, + {12,3}, {11,2}, {37,1}, {24,2}, {1,9}, {3,2}, {40,1}, {24,2}, + {1,9}, {3,2}, {43,1}, {10,2}, {2,9}, {10,2}, {21,1}, +}; +#define DAT_THANG_DOLPHIN_RUNS (sizeof(datthang_dolphin_rle)/sizeof(datthang_dolphin_rle[0])) +#endif diff --git a/src/dat_walk.c b/src/dat_walk.c new file mode 100644 index 0000000..71407f4 --- /dev/null +++ b/src/dat_walk.c @@ -0,0 +1,207 @@ +#define _POSIX_C_SOURCE 200809L +#include "dat.h" +#include "win_dirent.h" +#include +#include "win_fnmatch.h" +#include +#include +#include +#include +#include +#include +#include + +static int join_path(char*out,size_t cap,const char*a,const char*b){int n=snprintf(out,cap,"%s%s%s",a,*a&&a[strlen(a)-1]!='/'?"/":"",b);if(n<0||(size_t)n>=cap){errno=ENAMETOOLONG;return-1;}return 0;} +static int matches_archive_path(const char *path, const char *root); +typedef struct sync_context { char patterns[128][256]; size_t pattern_count; } sync_context_t; +static uint64_t stat_mtime_ms(const struct stat *st){ +#if defined(_WIN32) + if(st->st_mtime<0)return 0; + return (uint64_t)st->st_mtime*1000u; +#elif defined(__APPLE__) + if(st->st_mtimespec.tv_sec<0)return 0; + return (uint64_t)st->st_mtimespec.tv_sec*1000u+(uint64_t)st->st_mtimespec.tv_nsec/1000000u; +#else + if(st->st_mtim.tv_sec<0)return 0; + return (uint64_t)st->st_mtim.tv_sec*1000u+(uint64_t)st->st_mtim.tv_nsec/1000000u; +#endif +} +static uint64_t now_ms(void){struct timespec ts;if(clock_gettime(CLOCK_REALTIME,&ts))return (uint64_t)time(NULL)*1000u;return (uint64_t)ts.tv_sec*1000u+(uint64_t)ts.tv_nsec/1000000u;} +static int import_file(dat_archive_t*a,const char*disk,const char*name,const struct stat*st){FILE*f;uint8_t*data;size_t len=(size_t)st->st_size;if(st->st_size<0||(uint64_t)st->st_size>SIZE_MAX){errno=EOVERFLOW;return-1;}data=malloc(len?len:1);if(!data)return-1;f=fopen(disk,"rb");if(!f){free(data);return-1;}if(len&&fread(data,1,len,f)!=len){fclose(f);free(data);errno=EIO;return-1;}fclose(f);if(dat_archive_append_file(a,name,data,len,(uint32_t)st->st_mode,stat_mtime_ms(st))){free(data);return-1;}free(data);return 0;} +static int import_one(dat_archive_t*a,const char*disk,const char*name){struct stat st;if(lstat(disk,&st))return-1;if(S_ISREG(st.st_mode))return import_file(a,disk,name,&st);if(S_ISLNK(st.st_mode)){char target[1024];ssize_t n=readlink(disk,target,sizeof(target)-1);if(n<0)return-1;target[n]=0;return dat_archive_append_symlink(a,name,target,stat_mtime_ms(&st));}if(S_ISDIR(st.st_mode)){DIR*dir;struct dirent*item;int rc=0;if(*name&&dat_archive_append_directory(a,name,(uint32_t)st.st_mode,stat_mtime_ms(&st)))return-1;dir=opendir(disk);if(!dir)return-1;while((item=readdir(dir))){char child_disk[2048],child_name[2048];if(!strcmp(item->d_name,".")||!strcmp(item->d_name,"..")||!strcmp(item->d_name,".dat"))continue;if(join_path(child_disk,sizeof(child_disk),disk,item->d_name)||join_path(child_name,sizeof(child_name),name,item->d_name)||import_one(a,child_disk,child_name)){rc=-1;break;}}closedir(dir);return rc;}return 0;} +int dat_archive_import_path(dat_archive_t*a,const char*filesystem_path,const char*archive_path){if(!a||!filesystem_path||!archive_path){errno=EINVAL;return-1;}while(*archive_path=='/')archive_path++;return import_one(a,filesystem_path,archive_path);} + +static int same_file(const dat_archive_t *a, const char *disk, + const struct stat *st, const sleep_metadata_entry_t *entry) +{ + FILE *f; + uint8_t *old, buffer[8192]; + size_t old_len, n, off = 0; + if (entry->type != SLEEP_ENTRY_FILE || entry->size != (uint64_t)st->st_size || + entry->mode != (uint32_t)st->st_mode || entry->mtime != stat_mtime_ms(st)) + return 0; + if (dat_archive_read_file(a, entry, &old, &old_len)) return -1; + f = fopen(disk, "rb"); + if (!f) { free(old); return -1; } + while ((n = fread(buffer, 1, sizeof(buffer), f)) != 0) { + if (n > old_len - off || memcmp(buffer, old + off, n)) { + fclose(f); free(old); return 0; + } + off += n; + } + if (ferror(f)) { fclose(f); free(old); errno = EIO; return -1; } + fclose(f); + free(old); + return off == old_len; +} + +static int ignored_path(const sync_context_t *context, const char *name, const char *basename) +{ + size_t i; + if (*name && basename[0] == '.') return 1; + for (i = 0; i < context->pattern_count; i++) { + const char *pattern = context->patterns[i]; + size_t n = strlen(pattern); + if (pattern[0] == '/') pattern++; + if (n && context->patterns[i][n - 1] == '/' && !strncmp(name, pattern, strlen(pattern))) return 1; + if (!fnmatch(pattern, name, 0)) return 1; + } + return 0; +} + +static int sync_one(dat_archive_t *a, const char *disk, const char *name, const sync_context_t *context) +{ + struct stat st; + sleep_metadata_entry_t previous; + int found; + if (lstat(disk, &st)) return -1; + found = dat_archive_find(a, name, &previous) == 0; + if (!found && errno != ENOENT) return -1; + if (S_ISREG(st.st_mode)) { + int equal = found ? same_file(a, disk, &st, &previous) : 0; + if (equal < 0) return -1; + return equal ? 0 : import_file(a, disk, name, &st); + } + if (S_ISLNK(st.st_mode)) { + char target[1024]; ssize_t n = readlink(disk, target, sizeof(target) - 1); + if (n < 0) return -1; + target[n] = 0; + if (found && previous.type == SLEEP_ENTRY_SYMLINK && previous.mtime == stat_mtime_ms(&st) && !strcmp(previous.linkname, target)) return 0; + return dat_archive_append_symlink(a, name, target, stat_mtime_ms(&st)); + } + if (S_ISDIR(st.st_mode)) { + DIR *dir; struct dirent *item; + if (*name && (!found || previous.type != SLEEP_ENTRY_DIRECTORY || previous.mode != (uint32_t)st.st_mode || previous.mtime != stat_mtime_ms(&st)) && dat_archive_append_directory(a, name, (uint32_t)st.st_mode, stat_mtime_ms(&st))) return -1; + dir = opendir(disk); + if (!dir) return -1; + while ((item = readdir(dir))) { + char child_disk[2048], child_name[2048]; + if (!strcmp(item->d_name, ".") || !strcmp(item->d_name, "..")) continue; + if (join_path(child_disk, sizeof(child_disk), disk, item->d_name) || join_path(child_name, sizeof(child_name), name, item->d_name)) { closedir(dir); return -1; } + if (ignored_path(context, child_name, item->d_name)) continue; + if (sync_one(a, child_disk, child_name, context)) { closedir(dir); return -1; } + } + closedir(dir); + } + return 0; +} + +static int load_ignore_file(const char *root, sync_context_t *context) +{ + char path[1200], line[256]; + FILE *f; + if (join_path(path, sizeof(path), root, ".datignore")) return -1; + f = fopen(path, "r"); + if (!f) return errno == ENOENT ? 0 : -1; + while (fgets(line, sizeof(line), f)) { + size_t n = strcspn(line, "\r\n"); + line[n] = 0; + if (!line[0] || line[0] == '#') continue; + if (context->pattern_count >= sizeof(context->patterns) / sizeof(context->patterns[0])) { fclose(f); errno = EOVERFLOW; return -1; } + strcpy(context->patterns[context->pattern_count++], line); + } + if (ferror(f)) { fclose(f); errno = EIO; return -1; } + return fclose(f); +} + +static int path_missing(const char *filesystem_path, const char *archive_path, const char *entry_path) +{ + char disk[4096]; const char *relative = entry_path; size_t prefix; struct stat st; + while (*relative == '/') relative++; + prefix = strlen(archive_path); + if (prefix) { if (!strcmp(relative, archive_path)) relative = ""; else relative += prefix + 1; } + if (join_path(disk, sizeof(disk), filesystem_path, relative)) return -1; + if (!lstat(disk, &st)) return 0; + return errno == ENOENT ? 1 : -1; +} + +int dat_archive_sync_path(dat_archive_t *a, const char *filesystem_path, const char *archive_path) +{ + size_t count, i, j; + sync_context_t context; + if (!a || !filesystem_path || !archive_path) { errno = EINVAL; return -1; } + while (*archive_path == '/') archive_path++; + memset(&context, 0, sizeof(context)); + if (load_ignore_file(filesystem_path, &context) || sync_one(a, filesystem_path, archive_path, &context)) return -1; + count = dat_archive_entry_count(a); + for (i = 0; i < count; i++) { + sleep_metadata_entry_t entry, later; int newest = 1, missing; + if (dat_archive_read_entry(a, i, &entry)) return -1; + if (!matches_archive_path(entry.path, archive_path)) continue; + for (j = i + 1; j < count; j++) { if (dat_archive_read_entry(a, j, &later)) return -1; if (!strcmp(entry.path, later.path)) { newest = 0; break; } } + if (!newest || entry.type == SLEEP_ENTRY_DELETE) continue; + missing = path_missing(filesystem_path, archive_path, entry.path); + if (missing < 0) return -1; + if (missing && dat_archive_delete(a, entry.path, now_ms())) return -1; + } + return 0; +} + +static int export_entry(const dat_archive_t*a,const sleep_metadata_entry_t*e,const char*path){if(e->type==SLEEP_ENTRY_DIRECTORY){if(mkdir(path,(mode_t)e->mode)&&errno!=EEXIST)return-1;return 0;}if(e->type==SLEEP_ENTRY_SYMLINK)return symlink(e->linkname,path);if(e->type==SLEEP_ENTRY_FILE){uint8_t*data;size_t len;FILE*f;if(dat_archive_read_file(a,e,&data,&len))return-1;f=fopen(path,"wb");if(!f){free(data);return-1;}if(len&&fwrite(data,1,len,f)!=len){fclose(f);free(data);errno=EIO;return-1;}free(data);return fclose(f);}return 0;} +static int matches_archive_path(const char *path, const char *root) +{ + size_t n; + while (*path == '/') path++; + while (*root == '/') root++; + n = strlen(root); + return !n || !strcmp(path, root) || (!strncmp(path, root, n) && path[n] == '/'); +} + +int dat_archive_export_at(const dat_archive_t*a,uint64_t version,const char*archive_path,const char*filesystem_path) +{ + sleep_metadata_entry_t e, later; + size_t count,i,j,prefix; + if(!a||!archive_path||!filesystem_path){errno=EINVAL;return-1;} + while(*archive_path=='/')archive_path++; + count=dat_archive_entry_count(a); + if(version>count){errno=ERANGE;return-1;} + prefix=strlen(archive_path); + for(i=0;i<(size_t)version;i++){ + char suffix[2048],disk[4096]; + int current=1; + if(dat_archive_read_entry(a,i,&e))return-1; + if(!matches_archive_path(e.path,archive_path))continue; + for(j=i+1;j<(size_t)version;j++){ + if(dat_archive_read_entry(a,j,&later))return-1; + if(!strcmp(later.path,e.path)){current=0;break;} + } + if(!current||e.type==SLEEP_ENTRY_DELETE)continue; + { + const char *entry_path=e.path; + while(*entry_path=='/')entry_path++; + if(!prefix)sprintf(suffix,"%s",entry_path); + else if(!strcmp(entry_path,archive_path))suffix[0]=0; + else strcpy(suffix,entry_path+prefix+1); + } + if(*suffix){if(join_path(disk,sizeof(disk),filesystem_path,suffix))return-1;} + else{if(strlen(filesystem_path)>=sizeof(disk)){errno=ENAMETOOLONG;return-1;}strcpy(disk,filesystem_path);} + if(export_entry(a,&e,disk))return-1; + } + return 0; +} + +int dat_archive_export_path(const dat_archive_t*a,const char*archive_path,const char*filesystem_path) +{ + if(!a){errno=EINVAL;return-1;} + return dat_archive_export_at(a,dat_archive_version(a),archive_path,filesystem_path); +} diff --git a/src/dat_wire.c b/src/dat_wire.c new file mode 100644 index 0000000..5cc4433 --- /dev/null +++ b/src/dat_wire.c @@ -0,0 +1,421 @@ +#include "dat_wire.h" +#include "sleep_proto.h" +#include +#include + +static int put_varint(uint64_t value, uint8_t *out, size_t cap, size_t *off) +{ + size_t n; + if (*off > cap || sleep_varint_encode(value, out + *off, cap - *off, &n)) return -1; + *off += n; + return 0; +} + +static int put_bytes(uint32_t field, const uint8_t *data, size_t len, + uint8_t *out, size_t cap, size_t *off) +{ + if ((!data && len) || put_varint(((uint64_t)field << 3) | 2, out, cap, off) || + put_varint(len, out, cap, off)) return -1; + if (len > cap - *off) { errno = ENOSPC; return -1; } + if (len) memcpy(out + *off, data, len); + *off += len; + return 0; +} + +static int put_uint(uint32_t field, uint64_t value, + uint8_t *out, size_t cap, size_t *off) +{ + return put_varint((uint64_t)field << 3, out, cap, off) || + put_varint(value, out, cap, off) ? -1 : 0; +} + +/* Returns one field. Unknown protobuf wire types are skipped by read_field. */ +typedef struct proto_field { + uint32_t number; + uint8_t wire; + uint64_t value; + const uint8_t *bytes; + size_t length; +} proto_field_t; + +static int read_field(const uint8_t *in, size_t len, size_t *off, + proto_field_t *field) +{ + uint64_t tag, value; + size_t n; + if (*off >= len) return 0; + if (sleep_varint_decode(in + *off, len - *off, &tag, &n)) return -1; + *off += n; + field->number = (uint32_t)(tag >> 3); + field->wire = (uint8_t)(tag & 7); + field->bytes = NULL; + field->length = 0; + field->value = 0; + if (!field->number) { errno = EPROTO; return -1; } + if (field->wire == 0) { + if (sleep_varint_decode(in + *off, len - *off, &value, &n)) return -1; + *off += n; + field->value = value; + } else if (field->wire == 2) { + if (sleep_varint_decode(in + *off, len - *off, &value, &n)) return -1; + *off += n; + if (value > len - *off) { errno = EPROTO; return -1; } + field->bytes = in + *off; + field->length = (size_t)value; + *off += (size_t)value; + } else if (field->wire == 1 || field->wire == 5) { + size_t skip = field->wire == 1 ? 8 : 4; + if (skip > len - *off) { errno = EPROTO; return -1; } + *off += skip; + } else { errno = EPROTO; return -1; } + return 1; +} + +int dat_wire_encode(const dat_wire_message_t *m, uint8_t *out, size_t cap, + size_t *written) +{ + uint8_t header[10]; + size_t hlen, prefix, total; + uint64_t packed; + if (!m || !out || (!m->payload && m->payload_len) || m->type > 15 || + m->channel > (UINT64_MAX >> 4)) { errno = EINVAL; return -1; } + packed = (m->channel << 4) | m->type; + if (sleep_varint_encode(packed, header, sizeof(header), &hlen)) return -1; + if (m->payload_len > SIZE_MAX - hlen) { errno = EOVERFLOW; return -1; } + total = hlen + m->payload_len; + if (sleep_varint_encode(total, out, cap, &prefix)) return -1; + if (total > cap - prefix) { errno = ENOSPC; return -1; } + memcpy(out + prefix, header, hlen); + if (m->payload_len) memcpy(out + prefix + hlen, m->payload, m->payload_len); + if (written) *written = prefix + total; + return 0; +} + +int dat_wire_decode(dat_wire_message_t *m, const uint8_t *in, size_t len, + size_t *consumed) +{ + uint64_t frame, packed; + size_t prefix, hlen; + if (!m || !in) { errno = EINVAL; return -1; } + if (sleep_varint_decode(in, len, &frame, &prefix)) return -1; + if (frame > len - prefix || frame > SIZE_MAX) { errno = EAGAIN; return -1; } + if (!frame || sleep_varint_decode(in + prefix, (size_t)frame, &packed, &hlen)) return -1; + m->channel = packed >> 4; + m->type = packed & 15; + m->payload = in + prefix + hlen; + m->payload_len = (size_t)frame - hlen; + if (consumed) *consumed = prefix + (size_t)frame; + return 0; +} + +int dat_handshake_init(dat_handshake_t *h, const uint8_t peer_id[32], + uint64_t capabilities) +{ + size_t n; + if (!h || !peer_id) { errno = EINVAL; return -1; } + n = strlen(DAT_NETWORK_PROTOCOL); + if (!n || n > DAT_PROTOCOL_BYTES) { errno = EINVAL; return -1; } + memset(h, 0, sizeof(*h)); + memcpy(h->protocol, DAT_NETWORK_PROTOCOL, n); + memcpy(h->peer_id, peer_id, 32); + h->capabilities = capabilities; + h->has_peer_id = 1; + return 0; +} + +int dat_handshake_protocol_matches(const dat_handshake_t *h) +{ + size_t n, i; + if (!h) { errno = EINVAL; return -1; } + n = strlen(DAT_NETWORK_PROTOCOL); + if (!n || n > DAT_PROTOCOL_BYTES) { errno = EINVAL; return -1; } + if (memcmp(h->protocol, DAT_NETWORK_PROTOCOL, n)) { errno = EPROTONOSUPPORT; return 0; } + for (i = n; i < DAT_PROTOCOL_BYTES; i++) if (h->protocol[i]) { + errno = EPROTONOSUPPORT; + return 0; + } + return 1; +} + +int dat_handshake_encode(const dat_handshake_t *h, uint8_t *out, size_t cap, + size_t *written) +{ + size_t off = 0; +#ifndef DAT_LEGACY_HYPERCORE + size_t protocol_len = strlen(DAT_NETWORK_PROTOCOL); +#endif + if (!h || !out || dat_handshake_protocol_matches(h) != 1) { errno = EINVAL; return -1; } + if ((h->has_peer_id && put_bytes(1, h->peer_id, 32, out, cap, &off)) || + (h->capabilities && put_uint(2, h->capabilities, out, cap, &off))) return -1; +#ifndef DAT_LEGACY_HYPERCORE + if (put_bytes(3, (const uint8_t *)DAT_NETWORK_PROTOCOL, protocol_len, + out, cap, &off)) return -1; +#endif + if (written) *written = off; + return 0; +} + +int dat_handshake_decode(dat_handshake_t *h, const uint8_t *in, size_t len) +{ + size_t off = 0; + proto_field_t f; + int rc; + if (!h || !in) { errno = EINVAL; return -1; } + memset(h, 0, sizeof(*h)); + while ((rc = read_field(in, len, &off, &f)) > 0) { + if (f.number == 1 && f.wire == 2) { + if (f.length == sizeof(h->peer_id)) { + memcpy(h->peer_id, f.bytes, f.length); + h->has_peer_id = 1; + } + } else if (f.number == 2 && f.wire == 0) h->capabilities = f.value; +#ifndef DAT_LEGACY_HYPERCORE + else if (f.number == 3 && f.wire == 2) { + if (f.length > sizeof(h->protocol)) { errno = EPROTO; return -1; } + memcpy(h->protocol, f.bytes, f.length); + } +#endif + } + if (rc < 0) { errno = EPROTO; return -1; } + /* Hypercore v6 userData is application data, not a protocol identity. */ + memcpy(h->protocol, DAT_NETWORK_PROTOCOL, strlen(DAT_NETWORK_PROTOCOL)); + return 0; +} + +int dat_feed_open_encode(const dat_feed_open_t *feed, uint8_t *out, + size_t cap, size_t *written) +{ + size_t off = 0; + if (!feed || !out) { errno = EINVAL; return -1; } + if (put_bytes(1, feed->discovery_key, 32, out, cap, &off) || + (feed->has_nonce && put_bytes(2, feed->nonce, sizeof(feed->nonce), out, cap, &off))) return -1; + if (written) *written = off; + return 0; +} + +int dat_feed_open_decode(dat_feed_open_t *feed, const uint8_t *in, size_t len) +{ + size_t off = 0; + proto_field_t f; + int rc, found = 0; + if (!feed || !in) { errno = EINVAL; return -1; } + memset(feed, 0, sizeof(*feed)); + while ((rc = read_field(in, len, &off, &f)) > 0) { + if ((f.number == 1 || f.number == 2) && f.wire == 2) { + if ((f.number == 1 && f.length != 32) || + (f.number == 2 && f.length != sizeof(feed->nonce))) { errno = EPROTO; return -1; } + if (f.number == 1) { memcpy(feed->discovery_key, f.bytes, 32); found = 1; } + else { memcpy(feed->nonce, f.bytes, sizeof(feed->nonce)); feed->has_nonce = 1; } + } + } + if (rc < 0 || !found) { errno = EPROTO; return -1; } + return 0; +} + +int dat_range_encode(const dat_range_t *r, uint8_t *out, size_t cap, size_t *written) +{ + size_t off = 0; + if (!r || !out) { errno = EINVAL; return -1; } + if (put_uint(1, r->start, out, cap, &off) || put_uint(2, r->length, out, cap, &off)) return -1; + if (written) *written = off; + return 0; +} + +int dat_range_decode(dat_range_t *r, const uint8_t *in, size_t len) +{ + size_t off = 0; + proto_field_t f; + int rc, start = 0; + if (!r || !in) { errno = EINVAL; return -1; } + r->start = 0; r->length = 1; + while ((rc = read_field(in, len, &off, &f)) > 0) if (f.wire == 0) { + if (f.number == 1) { r->start = f.value; start = 1; } + if (f.number == 2) r->length = f.value; + } + if (rc < 0 || !start) { errno = EPROTO; return -1; } + return 0; +} + +int dat_want_decode(dat_range_t *r, const uint8_t *in, size_t len, + uint64_t feed_length) +{ + size_t off = 0; + proto_field_t f; + int rc, have_start = 0, have_length = 0; + if (!r || !in) { errno = EINVAL; return -1; } + r->start = 0; + r->length = 0; + while ((rc = read_field(in, len, &off, &f)) > 0) if (f.wire == 0) { + if (f.number == 1) { r->start = f.value; have_start = 1; } + else if (f.number == 2) { r->length = f.value; have_length = 1; } + } + if (rc < 0 || !have_start) { errno = EPROTO; return -1; } + if (!have_length && r->start < feed_length) r->length = feed_length - r->start; + return 0; +} + +int dat_info_encode(const dat_info_t *info, uint8_t *out, size_t cap, + size_t *written) +{ + size_t off = 0; + if (!info || !out) { errno = EINVAL; return -1; } + if ((!info->uploading && put_uint(1, 0, out, cap, &off)) || + (!info->downloading && put_uint(2, 0, out, cap, &off))) return -1; + if (written) *written = off; + return 0; +} + +int dat_info_decode(dat_info_t *info, const uint8_t *in, size_t len) +{ + size_t off = 0; + proto_field_t f; + int rc; + if (!info || (!in && len)) { errno = EINVAL; return -1; } + info->uploading = 1; + info->downloading = 1; + while ((rc = read_field(in, len, &off, &f)) > 0) if (f.wire == 0) { + if (f.number == 1) info->uploading = !!f.value; + else if (f.number == 2) info->downloading = !!f.value; + } + if (rc < 0) { errno = EPROTO; return -1; } + return 0; +} + +int dat_request_encode(const dat_request_t *r, uint8_t *out, size_t cap, size_t *written) +{ + size_t off = 0; + if (!r || !out) { errno = EINVAL; return -1; } + if (put_uint(1, r->index, out, cap, &off) || + (r->bytes && put_uint(2, r->bytes, out, cap, &off)) || + (r->want_hash && put_uint(3, 1, out, cap, &off)) || + (r->want_nodes && put_uint(4, 64, out, cap, &off))) return -1; + if (written) *written = off; + return 0; +} + +int dat_request_decode(dat_request_t *r, const uint8_t *in, size_t len) +{ + size_t off = 0; + proto_field_t f; + int rc, found = 0; + if (!r || !in) { errno = EINVAL; return -1; } + memset(r, 0, sizeof(*r)); + while ((rc = read_field(in, len, &off, &f)) > 0) if (f.wire == 0) { + if (f.number == 1) { r->index = f.value; found = 1; } + else if (f.number == 2) r->bytes = f.value; + else if (f.number == 3) r->want_hash = !!f.value; + else if (f.number == 4) r->want_nodes = !!f.value; + } + if (rc < 0 || !found) { errno = EPROTO; return -1; } + return 0; +} + +static int encode_node(const dat_data_node_t *node, uint8_t *out, + size_t cap, size_t *written) +{ + size_t off = 0; + if (put_uint(1, node->index, out, cap, &off) || + put_bytes(2, node->hash, 32, out, cap, &off) || + put_uint(3, node->length, out, cap, &off)) return -1; + *written = off; + return 0; +} + +static int decode_node(dat_data_node_t *node, const uint8_t *in, size_t len) +{ + size_t off = 0; + proto_field_t f; + int rc, have_index = 0, have_hash = 0, have_length = 0; + while ((rc = read_field(in, len, &off, &f)) > 0) { + if (f.number == 1 && f.wire == 0) { node->index = f.value; have_index = 1; } + else if (f.number == 2 && f.wire == 2 && f.length == 32) { memcpy(node->hash, f.bytes, 32); have_hash = 1; } + else if (f.number == 3 && f.wire == 0) { node->length = f.value; have_length = 1; } + } + if (rc < 0 || !have_index || !have_hash || !have_length) { errno = EPROTO; return -1; } + return 0; +} + +int dat_data_encode(const dat_data_t *d, uint8_t *out, size_t cap, size_t *written) +{ + size_t off = 0, i, node_len; + uint8_t node[64]; + if (!d || !out || (!d->value && d->value_len) || (!d->proof && d->proof_len) || + d->node_count > DAT_DATA_MAX_NODES) { errno = EINVAL; return -1; } + if (put_uint(1, d->index, out, cap, &off) || + put_bytes(2, d->value, d->value_len, out, cap, &off)) return -1; + for (i = 0; i < d->node_count; i++) { + if (encode_node(&d->nodes[i], node, sizeof(node), &node_len) || + put_bytes(3, node, node_len, out, cap, &off)) return -1; + } + if (d->has_signature && put_bytes(4, d->signature, sizeof(d->signature), out, cap, &off)) return -1; + /* Field 5 carries C's complete multi-root proof; older protobuf readers ignore it. */ + if (d->proof_len && put_bytes(5, d->proof, d->proof_len, out, cap, &off)) return -1; + if (written) *written = off; + return 0; +} + +int dat_data_decode(dat_data_t *d, const uint8_t *in, size_t len) +{ + size_t off = 0; + proto_field_t f; + int rc, have_index = 0; + if (!d || !in) { errno = EINVAL; return -1; } + memset(d, 0, sizeof(*d)); + while ((rc = read_field(in, len, &off, &f)) > 0) { + if (f.number == 1 && f.wire == 0) { d->index = f.value; have_index = 1; } + else if (f.number == 2 && f.wire == 2) { d->value = f.bytes; d->value_len = f.length; } + else if (f.number == 3 && f.wire == 2) { + if (d->node_count >= DAT_DATA_MAX_NODES || + decode_node(&d->nodes[d->node_count], f.bytes, f.length)) return -1; + d->node_count++; + } else if (f.number == 4 && f.wire == 2) { + if (f.length != 64) { errno = EPROTO; return -1; } + memcpy(d->signature, f.bytes, 64); d->has_signature = 1; + } else if (f.number == 5 && f.wire == 2) { d->proof = f.bytes; d->proof_len = f.length; } + } + if (rc < 0 || !have_index) { errno = EPROTO; return -1; } + return 0; +} + +int dat_cancel_encode(const dat_cancel_t *c, uint8_t *out, size_t cap, size_t *written) +{ + size_t off = 0; + if (!c || !out) { errno = EINVAL; return -1; } + if (put_uint(1, c->index, out, cap, &off)) return -1; + if (written) *written = off; + return 0; +} + +int dat_cancel_decode(dat_cancel_t *c, const uint8_t *in, size_t len) +{ + dat_request_t r; + if (!c || dat_request_decode(&r, in, len)) return -1; + c->index = r.index; + return 0; +} + +int dat_extension_encode(const dat_extension_t *e, uint8_t *out, size_t cap, + size_t *written) +{ + size_t off = 0; + if (!e || !out || (!e->message && e->message_len)) { errno = EINVAL; return -1; } + if (put_varint(e->id, out, cap, &off)) return -1; + if (e->message_len > cap - off) { errno = ENOSPC; return -1; } + if (e->message_len) memcpy(out + off, e->message, e->message_len); + off += e->message_len; + if (written) *written = off; + return 0; +} + +int dat_extension_decode(dat_extension_t *e, const uint8_t *in, size_t len) +{ + uint64_t id; + size_t n, off = 0; + if (!e || !in || sleep_varint_decode(in, len, &id, &n)) return -1; + off = n; + memset(e, 0, sizeof(*e)); + e->id = id; + e->message = in + off; + e->message_len = len - off; + return 0; +} diff --git a/src/ddat_gui.c b/src/ddat_gui.c new file mode 100644 index 0000000..d185ef5 --- /dev/null +++ b/src/ddat_gui.c @@ -0,0 +1,5455 @@ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 700 +#endif +#include +#include +#include +#include "dat.h" +#include "sleep.h" +#include "base32768.h" +#include "cable.h" +#include "cable_media.h" +#include "cable_video.h" +#include "cable_room.h" +#include "dat_dns.h" +#include "dat_s7.h" +#include "dat_thang_icons.h" +#include "dat_paths.h" +#include "cesspool.h" +#include "dat_session.h" +#include "sleep_crypto.h" +#include "sleep_utp.h" +#include "win_dirent.h" +#include +#include +#include +#ifndef _WIN32 +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +#define GUI_TRANSPORT_MAX DAT_SESSION_MAX_PEERS +#define GUI_SHARED_MAX 24u +#define GUI_AUTH_HELLO_BYTES 132u +#define GUI_AUTH_ACK_BYTES 100u +#define GUI_CHAT_LINES 48u +#define GUI_CABLE_CHANNELS 24u +#define GUI_CABLE_AUDIO_RING 16384u +#define GUI_CABLE_WAVE_SAMPLES 4096u +#define GUI_CONTEXT_ITEMS 5u +#ifndef DAT_THANG_VERSION +#define DAT_THANG_VERSION "v0.0.002" +#endif +#ifndef DAT_BUILD_DIR +#define DAT_BUILD_DIR "build" +#endif +#define DAT_THANG_RELEASE_STATUS "prototype, pre-alpha" + +#ifndef DAT_FONT_PATH +#define DAT_FONT_PATH "/usr/local/share/datgpt/fonts/MorePerfectDOSVGA.ttf" +#endif +#ifndef DAT_SPEAKER_PATH +#define DAT_SPEAKER_PATH "/usr/local/share/datgpt/icons/datthang-speaker.bmp" +#endif +#ifndef DAT_UNIFONT_PATH +#define DAT_UNIFONT_PATH "/usr/local/share/datgpt/fonts/Unifont-18.0.01.otf" +#endif + +typedef struct { + SDL_Texture *texture; + int advance, width, height; +} gui_font_glyph_t; + +static gui_font_glyph_t gui_font_glyphs[95]; +static TTF_Font *gui_font; +static TTF_Font *gui_unicode_font; +typedef struct { + Uint32 codepoint; + gui_font_glyph_t glyph; +} gui_unicode_glyph_t; +static gui_unicode_glyph_t gui_unicode_glyphs[256]; +static size_t gui_unicode_glyph_count; +static int gui_font_cell_width; +static int gui_font_ready; +#define GUI_CABLE_LINES 96u +#define GUI_CABLE_PEERS 128u +typedef struct gui_chat_line { + uint8_t peer_id[32]; + uint64_t timestamp_ms; + char text[CABLE_CHAT_TEXT_BYTES + 1u]; + int outgoing; +} gui_chat_line_t; +typedef struct gui_transport_peer { + sleep_utp_t utp; + void *owner; + uint8_t peer_id[DAT_PEER_ID_BYTES]; + uint8_t local_nonce[32]; + uint8_t remote_nonce[32]; + uint8_t auth_buffer[GUI_AUTH_HELLO_BYTES + GUI_AUTH_ACK_BYTES]; + size_t auth_length; + uint8_t *tx_buffer; + size_t tx_length, tx_offset, tx_capacity; + uint8_t *rx_app_buffer; + size_t rx_app_length, rx_app_capacity; + int used; + int hello_sent; + int hello_received; + int ack_sent; + int ack_received; + int authenticated; + int waiting_incoming; + char remote_nick[CABLE_ROOM_NICK_BYTES + 1u]; +} gui_transport_peer_t; + +typedef struct gui_cable_line { + cable_room_event_t event; +} gui_cable_line_t; +typedef struct gui_cable_peer { + char key[65]; + char nick[CABLE_ROOM_NICK_BYTES + 1u]; + int connected; +} gui_cable_peer_t; + +typedef struct gui_archive_label { + char path[1024]; + char title[64]; + char description[128]; + int loaded; +} gui_archive_label_t; + +typedef struct gui_state { + int cable_thang_mode; + uint8_t cable_room_key[32]; + char cable_room_address[160]; + char cable_home[1024]; + char cable_room_dir[1100]; + char cable_nick[CABLE_ROOM_NICK_BYTES + 1u]; + char cable_channels[GUI_CABLE_CHANNELS][CABLE_ROOM_CHANNEL_BYTES + 1u]; + size_t cable_channel_count; + size_t cable_channel_index; + char cable_input[CABLE_CHAT_TEXT_BYTES + 1u]; + char cable_status[128]; + gui_cable_line_t cable_lines[GUI_CABLE_LINES]; + size_t cable_line_count; + gui_cable_peer_t cable_peers[GUI_CABLE_PEERS]; + size_t cable_peer_count; + int cable_network_active; + int cable_audio_broadcasting; + int cable_audio_receiving; + int cable_video_active; + int cable_video_receiving; + char cable_video_status[64]; + cable_video_source_t *cable_video_source; + SDL_Texture *cable_video_texture; + uint8_t cable_video_pixels[640u * 360u * 3u]; + uint64_t cable_video_generation; + uint64_t cable_video_texture_generation; + uint64_t cable_video_source_generation; + uint64_t cable_video_sent_generation; + Uint32 cable_video_last_sent_at; + unsigned cable_video_width, cable_video_height; + uint8_t cable_video_session[CABLE_MEDIA_SESSION_BYTES]; + uint8_t cable_video_sender_session[CABLE_MEDIA_SESSION_BYTES]; + uint8_t cable_video_rx_peer[32]; + uint8_t cable_video_rx[256u * 1024u]; + size_t cable_video_rx_length, cable_video_rx_frame_length; + uint16_t cable_video_rx_parts, cable_video_rx_next_part; + uint32_t cable_video_rx_frame_id, cable_video_frame_id; + int cable_video_rx_active; + char cable_live_status[64]; + uint8_t cable_live_session[CABLE_MEDIA_SESSION_BYTES]; + uint8_t cable_live_sender_session[CABLE_MEDIA_SESSION_BYTES]; + uint64_t cable_live_sequence; + SDL_AudioDeviceID cable_audio_capture; + SDL_AudioDeviceID cable_audio_playback; + volatile Uint32 cable_capture_head; + volatile Uint32 cable_capture_tail; + int16_t cable_capture_ring[GUI_CABLE_AUDIO_RING]; + cable_aac_encoder_t cable_aac_encoder; + cable_aac_decoder_t cable_aac_decoder; + int16_t cable_live_wave[GUI_CABLE_WAVE_SAMPLES]; + size_t cable_live_wave_head; + size_t cable_live_wave_count; + char archive_path[1024]; + char archive_loaded_path[1024]; + int transfer_widths[6]; + int transfer_scroll; + int transfer_row_scroll; + int browser_scroll; + int browser_drag_y; + int browser_drag_scroll; + int browser_drag; + int address_compact; + uint64_t selected_version; + uint64_t remote_selected_version; + int resize_column; + int transfer_drag_x; + int transfer_scroll_drag; + int context_menu; + int context_menu_kind; + int context_menu_rows; + char context_menu_items[GUI_CONTEXT_ITEMS][48]; + int context_menu_actions[GUI_CONTEXT_ITEMS]; + int context_peer_index; + int transfer_paused[DAT_SESSION_MAX_PEERS]; + int transfer_cancelled[DAT_SESSION_MAX_PEERS]; + int context_x, context_y; + int chat_open; + uint8_t chat_peer_id[32]; + char chat_input[CABLE_CHAT_TEXT_BYTES + 1u]; + char chat_status[80]; + gui_chat_line_t chat_lines[GUI_CHAT_LINES]; + size_t chat_line_count; + int archive_switcher_visible; + char remote_address[512]; + char remote_site[256]; + char remote_archive[256]; + char remote_version[32]; + char remote_path[2048]; + uint8_t remote_metadata_key[DAT_KEY_BYTES]; + int remote_key_valid; + int remote_import_mode; + int remote_address_editing; + char remote_address_before_edit[512]; + char shared_paths[GUI_SHARED_MAX][1024]; + size_t shared_path_count; + gui_archive_label_t archive_labels[GUI_SHARED_MAX]; + size_t archive_label_count; + dat_scheme_runtime_t metadata_scheme; + char site_name[256]; + int site_name_explicit; + uint8_t site_name_archive_key[DAT_KEY_BYTES]; + int site_name_key_valid; + char site_name_before_edit[256]; + int site_name_explicit_before_edit; + int site_name_editing; + int site_record_modal; + char site_dns_txt[80]; + char selected_filename[256]; + char archive_key[65]; + uint8_t archive_key_bytes[DAT_KEY_BYTES]; + char site_state[64]; + uint64_t version; + size_t entry_count; + size_t connected_peers; + double upload_kbps; + double download_kbps; + int archive_open; + int online; + int network_requested; + int identity_valid; + int pool_initialized; + int topic_joined; + int help_visible; + int menu_visible; + int settings_visible; + int menu_selection; + int settings_selection; + int audio_enabled; + int auto_connect; + int help_count; + int help_scroll; + char help_lines[128][160]; + SDL_AudioDeviceID audio_device; + struct gui_fm_voice *fm_voice_ptr; + SDL_Texture *dolphin_icon; + SDL_Texture *speaker_icon; + sleep_keypair_t identity; + dat_session_t session; + sleep_feed_t metadata_feed; + sleep_feed_t content_feed; + gui_transport_peer_t transports[GUI_TRANSPORT_MAX]; + uint64_t bytes_sent; + uint64_t bytes_received; + uint64_t sampled_bytes_sent; + uint64_t sampled_bytes_received; + Uint32 rate_sample_at; + Uint32 upload_activity_until; + Uint32 download_activity_until; + cesspool_t pool; + uint8_t discovery_topic[CESSPOOL_TOPIC_BYTES]; + int speaker_sonar_active; + Uint32 speaker_sonar_until; + size_t speaker_sonar_sent_count; + uint8_t speaker_sonar_sent[CESSPOOL_MAX_PEERS][32]; + char network_status[64]; + int session_initialized; + int bootstrap_editing; + char bootstrap_endpoint[256]; + char bootstrap_endpoint_before_edit[256]; +} gui_state_t; + +enum gui_context_action { + GUI_CTX_LOAD_ARCHIVE = 1, GUI_CTX_CREATE_ARCHIVE, GUI_CTX_COPY_ARCHIVE, + GUI_CTX_EDIT_SITE, GUI_CTX_COPY_SITE_RECORD, + GUI_CTX_CLEAR_SITE, GUI_CTX_TOGGLE_FORMAT, GUI_CTX_EDIT_REMOTE, + GUI_CTX_PASTE_REMOTE, GUI_CTX_IMPORT_REMOTE, GUI_CTX_CHAT_PEER, + GUI_CTX_COPY_PEER, GUI_CTX_PAUSE_TRANSFER, GUI_CTX_CANCEL_TRANSFER, + GUI_CTX_REVEAL_DESTINATION, GUI_CTX_SELECT_SHARED, + GUI_CTX_COPY_REMOTE_SITE, GUI_CTX_CLEAR_REMOTE, + GUI_CTX_CANCEL_IMPORT, GUI_CTX_COPY_IMPORT_LINK +}; + +static int cable_room_broadcast(gui_state_t *state, uint8_t type, + const char *channel, const char *text); +static void network_announce_shared_paths(gui_state_t *state); +static void cable_room_append_line(gui_state_t *state, const cable_room_event_t *event); +static int cable_peer_index(gui_state_t *state, const char *key, int create); +static void cable_peer_refresh(gui_state_t *state); +static int cable_live_send_session(gui_state_t *state, + const uint8_t session[CABLE_MEDIA_SESSION_BYTES], uint8_t kind, + const uint8_t *payload, size_t payload_length, + gui_transport_peer_t *only_peer); +static void cable_audio_stop(gui_state_t *state, int announce); +static void cable_video_toggle(gui_state_t *state); +static void cable_live_receive_wire(gui_state_t *state, const uint8_t peer_id[32], + const uint8_t *wire, size_t length); +static const char *gui_home_directory(void); + +typedef struct gui_fm_voice { + uint64_t sample; + int active; + int sample_rate; + int dolphin; + size_t wave_head; + size_t wave_count; + int16_t wave_samples[4096]; +} gui_fm_voice_t; + +static void chat_append_line(gui_state_t *state,const uint8_t peer_id[32], + const cable_chat_message_t *message,int outgoing); + +static const char *gui_home_directory(void) +{ + return dat_home_directory(); +} + +#define GUI_PI 3.14159265358979323846 + +static double audio_wave_sample(int dolphin, double time) +{ + double envelope; + if (dolphin) { + /* Classic chip-sound polyphony trick: rapidly cycle the notes of a + * bright major chord, with a square pulse and a short per-step gate. + * The 25 ms steps are deliberately much faster than the old melody. */ + static const double notes[] = {1046.50, 1318.51, 1567.98, 2093.00}; + const double step_seconds = 0.025; + double step = fmod(time, step_seconds); + double note = notes[((unsigned)(time / step_seconds)) % + (sizeof(notes) / sizeof(notes[0]))]; + double gate = step < 0.0015 ? step / 0.0015 : + (step > 0.021 ? (step_seconds - step) / 0.004 : 1.0); + envelope = time < 0.006 ? time / 0.006 : 1.0; + if (time > 0.88) envelope *= fmax(0.0, (1.0 - time) / 0.12); + return (sin(2.0 * GUI_PI * note * time) >= 0.0 ? 1.0 : -1.0) * + gate * envelope * 8500.0; + } + if (time < 0.06) envelope = time / 0.06; + else if (time < 0.28) envelope = 1.0 - 0.52 * (time - 0.06) / 0.22; + else if (time > 1.55) envelope = 0.48 * (2.0 - time) / 0.45; + else envelope = 0.48; + return sin(2.0 * GUI_PI * 82.4069 * time) >= 0.0 ? envelope * 6500.0 : -envelope * 6500.0; +} + +/* Two-operator phase modulation with a small ADSR envelope: an OPL-like cue, + * intentionally independent of the archive transport and safe to mix inline. */ +static void fm_audio_callback(void *userdata, Uint8 *stream, int length) +{ + gui_fm_voice_t *voice = (gui_fm_voice_t *)userdata; + Sint16 *samples = (Sint16 *)stream; + int count = length / (int)sizeof(*samples), i; + for (i = 0; i < count; i++) { + double time, value; + if (!voice->active) { samples[i] = 0; goto keep_sample; } + time = (double)voice->sample / voice->sample_rate; + if (time >= (voice->dolphin ? 1.0 : 2.0)) { voice->active = 0; samples[i] = 0; goto keep_sample; } + value = audio_wave_sample(voice->dolphin, time); + if (value > 32767.0) value = 32767.0; + if (value < -32768.0) value = -32768.0; + samples[i] = (Sint16)value; + voice->sample++; + keep_sample: + voice->wave_samples[voice->wave_head] = samples[i]; + voice->wave_head = (voice->wave_head + 1) % (sizeof(voice->wave_samples) / sizeof(voice->wave_samples[0])); + if (voice->wave_count < sizeof(voice->wave_samples) / sizeof(voice->wave_samples[0])) voice->wave_count++; + } +} + +static void fm_audio_start(gui_state_t *state) +{ + SDL_AudioSpec wanted, obtained; + gui_fm_voice_t *voice = calloc(1, sizeof(*voice)); + if (!voice) return; + memset(&wanted, 0, sizeof(wanted)); + wanted.freq = 44100; + wanted.format = AUDIO_S16SYS; + wanted.channels = 1; + wanted.samples = 512; + wanted.callback = fm_audio_callback; + wanted.userdata = voice; + state->audio_device = SDL_OpenAudioDevice(NULL, 0, &wanted, &obtained, 0); + if (!state->audio_device) { free(voice); return; } + voice->sample_rate = obtained.freq; + state->fm_voice_ptr = (struct gui_fm_voice *)voice; + SDL_PauseAudioDevice(state->audio_device, 0); +} + +static void fm_audio_play(gui_state_t *state) +{ + gui_fm_voice_t *voice = (gui_fm_voice_t *)state->fm_voice_ptr; + if (!state->audio_enabled || !state->audio_device || !voice) return; + SDL_LockAudioDevice(state->audio_device); + voice->sample = 0; + voice->dolphin = 0; + voice->wave_head = voice->wave_count = 0; + voice->active = 1; + SDL_UnlockAudioDevice(state->audio_device); +} + +static void fm_audio_play_dolphin(gui_state_t *state) +{ + gui_fm_voice_t *voice = (gui_fm_voice_t *)state->fm_voice_ptr; + if (!state->audio_enabled || !state->audio_device || !voice) return; + SDL_LockAudioDevice(state->audio_device); + voice->sample = 0; + voice->dolphin = 1; + voice->wave_head = voice->wave_count = 0; + voice->active = 1; + SDL_UnlockAudioDevice(state->audio_device); +} + +static void fm_audio_stop(gui_state_t *state) +{ + if (state->audio_device) SDL_CloseAudioDevice(state->audio_device); + state->audio_device = 0; + free(state->fm_voice_ptr); + state->fm_voice_ptr = NULL; +} + +static void compiled_ddate(char *out, size_t capacity) +{ + static const char *months[] = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"}; + static const char *weekdays[] = {"Sweetmorn","Boomtime","Pungenday","Prickle-Prickle","Setting Orange"}; + static const char *seasons[] = {"Chaos","Discord","Confusion","Bureaucracy","The Aftermath"}; + static const int month_days[] = {31,28,31,30,31,30,31,31,30,31,30,31}; + char mon[4] = {0}; + int day = 0, year = 0, month = 0, ordinal = 0, leap, i; + if (sscanf(__DATE__, "%3s %d %d", mon, &day, &year) != 3) { + snprintf(out, capacity, "BUILD DATE UNKNOWN"); + return; + } + for (i = 0; i < 12; i++) if (!strcmp(mon, months[i])) { month = i; break; } + leap = ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0); + for (i = 0; i < month; i++) ordinal += month_days[i] + (i == 1 && leap); + ordinal += day; + if (leap && month == 1 && day == 29) { + snprintf(out, capacity, "ST TIB'S DAY, %d YOLD", year + 1166); + return; + } + if (leap && ordinal > 60) ordinal--; + ordinal--; + snprintf(out, capacity, "%s, %s %d, %d YOLD", + weekdays[ordinal % 5], seasons[ordinal / 73], ordinal % 73 + 1, + year + 1166); +} + +static void archive_key_address(const unsigned char key[DAT_KEY_BYTES], char out[65]) +{ + char url[DAT_URL_BYTES]; + if (dat_key_to_url(key, url)) { out[0] = '\0'; return; } + snprintf(out, 65, "%s", url + 6); +} + +static void remember_shared_path(gui_state_t *state, const char *path); +static void network_stop(gui_state_t *state); +static void network_start(gui_state_t *state); +static void transport_on_data(sleep_utp_t *utp, const uint8_t *bytes, size_t length); + +static void refresh_archive_at(gui_state_t *state, const char *path) +{ + dat_archive_t archive; + char stable_path[sizeof(state->archive_loaded_path)]; + char old_key[sizeof(state->archive_key)]; + int was_open = state->archive_open; + if (path && *path) { + if (snprintf(stable_path, sizeof(stable_path), "%s", path) >= (int)sizeof(stable_path)) { + path = NULL; + } else path = stable_path; + } + strcpy(old_key, state->archive_key); + state->archive_open = 0; + state->archive_loaded_path[0] = '\0'; + state->version = 0; + state->entry_count = 0; + state->archive_key[0] = '\0'; + memset(state->archive_key_bytes, 0, sizeof(state->archive_key_bytes)); + if (!state->site_name_explicit && !state->site_name_editing) state->site_name[0] = '\0'; + if (!path || !*path || dat_archive_open(&archive, path)) { + state->selected_version = 0; + return; + } + snprintf(state->archive_loaded_path, sizeof(state->archive_loaded_path), "%s", path); + memcpy(state->archive_key_bytes, archive.metadata_key, sizeof(state->archive_key_bytes)); + archive_key_address(archive.metadata_key, state->archive_key); + if (!state->site_name_editing) { + if (state->site_name_explicit && state->site_name_key_valid && + memcmp(state->site_name_archive_key, archive.metadata_key, DAT_KEY_BYTES)) + state->site_name_explicit = 0; + if (!state->site_name_explicit) { + state->site_name[0] = '\0'; + (void)dat_dns_name_for_key(archive.metadata_key, state->site_name, + sizeof(state->site_name)); + } + memcpy(state->site_name_archive_key, archive.metadata_key, DAT_KEY_BYTES); + state->site_name_key_valid = 1; + } + state->version = dat_archive_version(&archive); + if (!was_open || strcmp(old_key, state->archive_key) || state->selected_version > state->version) + state->selected_version = state->version; + state->entry_count = dat_archive_entry_count(&archive); + state->archive_open = 1; + dat_archive_close(&archive); +} + +static void refresh_archive(gui_state_t *state) +{ + refresh_archive_at(state, state->archive_path); +} + +/* Selecting an archive already in the shared list must not promote/reorder it: + * the transfer table and archive switcher both use this stable row ordering. */ +static int select_shared_archive(gui_state_t *state, size_t index) +{ + dat_archive_t archive; + if (!state || index >= state->shared_path_count) { errno = EINVAL; return -1; } + if (dat_archive_open(&archive, state->shared_paths[index])) return -1; + dat_archive_close(&archive); + snprintf(state->archive_path, sizeof(state->archive_path), "%s", state->shared_paths[index]); + strcpy(state->selected_filename, "(directory)"); + refresh_archive(state); + state->archive_switcher_visible = 0; + return state->archive_open ? 0 : -1; +} + +static void select_archive_version(gui_state_t *state, int direction) +{ + if (!state || !state->archive_open) return; + if (direction < 0) { + if (state->selected_version > 0) state->selected_version--; + } else if (direction > 0 && state->selected_version < state->version) { + state->selected_version++; + } +} + +static int click_archive_version_bar(gui_state_t *state, int x, int y, int field_x) +{ + if (!state || !state->archive_open || y < 74 || y >= 94 || + x < field_x + 126 || x >= field_x + 158) return 0; + select_archive_version(state, x < field_x + 142 ? -1 : 1); + return 1; +} + +static int save_site_name(gui_state_t *state) +{ + char hex[65]; + if (!state || !state->archive_open || !state->site_name[0]) { + errno = EINVAL; + return -1; + } + if (dat_dns_name_set(state->site_name, state->archive_key_bytes) || + dat_key_to_hex(state->archive_key_bytes, hex)) return -1; + memcpy(state->site_name_archive_key, state->archive_key_bytes, DAT_KEY_BYTES); + state->site_name_key_valid = 1; + snprintf(state->site_dns_txt, sizeof(state->site_dns_txt), "dat://%s\nTTL=3600", hex); + state->site_name_explicit = 0; + state->site_record_modal = 1; + /* Headless archive/SITE tests do not initialize SDL's video subsystem. + * In the live client it is active, so keep the convenient copy behavior. */ + if (SDL_WasInit(SDL_INIT_VIDEO)) + (void)SDL_SetClipboardText(state->site_dns_txt); + snprintf(state->network_status, sizeof(state->network_status), "SITE ALIAS SAVED - DAT-DNS RECORD COPIED"); + return 0; +} + +static void begin_bootstrap_edit(gui_state_t *state) +{ + snprintf(state->bootstrap_endpoint_before_edit, sizeof(state->bootstrap_endpoint_before_edit), + "%s", state->bootstrap_endpoint); + state->bootstrap_editing = 1; + state->bootstrap_endpoint[0] = '\0'; + SDL_StartTextInput(); + snprintf(state->network_status, sizeof(state->network_status), "TYPE CESSPOOL NODE HOST:PORT - ENTER SAVES"); +} + +static int save_bootstrap_edit(gui_state_t *state) +{ + int restart = state->network_requested; + if (cesspool_bootstrap_config_set(state->bootstrap_endpoint)) return -1; + state->bootstrap_editing = 0; + SDL_StopTextInput(); + if (restart) { + network_stop(state); + network_start(state); + } else snprintf(state->network_status, sizeof(state->network_status), "CESSPOOL NODE SAVED"); + return 0; +} + +static void context_menu_add(gui_state_t *state, const char *label, int action) +{ + int row = state->context_menu_rows; + if (row < 0 || row >= (int)GUI_CONTEXT_ITEMS) return; + snprintf(state->context_menu_items[row], sizeof(state->context_menu_items[row]), "%s", label); + state->context_menu_actions[row] = action; + state->context_menu_rows++; +} + +static void context_menu_prepare(gui_state_t *state, int kind) +{ + state->context_menu_kind = kind; + state->context_menu_rows = 0; + if (kind == 2) { + int index = state->context_peer_index; + context_menu_add(state, index >= 0 && index < (int)DAT_SESSION_MAX_PEERS && + state->transfer_paused[index] ? "RESUME TRANSFER" : "PAUSE TRANSFER", + GUI_CTX_PAUSE_TRANSFER); + context_menu_add(state, "CANCEL TRANSFER", GUI_CTX_CANCEL_TRANSFER); + context_menu_add(state, "REVEAL DESTINATION", GUI_CTX_REVEAL_DESTINATION); + } else if (kind == 3) { + context_menu_add(state, "EDIT DAT-DNS SITE NAME", GUI_CTX_EDIT_SITE); + context_menu_add(state, "COPY DAT-DNS RECORD", GUI_CTX_COPY_SITE_RECORD); + context_menu_add(state, "CLEAR LOCAL SITE ALIAS", GUI_CTX_CLEAR_SITE); + } else if (kind == 4) { + context_menu_add(state, state->address_compact ? "USE LEGACY HEX DISPLAY" : "USE COMPACT DISPLAY", GUI_CTX_TOGGLE_FORMAT); + context_menu_add(state, "COPY SELECTED VERSION LINK", GUI_CTX_COPY_ARCHIVE); + } else if (kind == 5) { + context_menu_add(state, "TYPE DAT LINK", GUI_CTX_EDIT_REMOTE); + context_menu_add(state, "PASTE DAT LINK", GUI_CTX_PASTE_REMOTE); + context_menu_add(state, "IMPORT THIS ARCHIVE", GUI_CTX_IMPORT_REMOTE); + } else if (kind == 8) { + context_menu_add(state, "COPY REMOTE SITE NAME", GUI_CTX_COPY_REMOTE_SITE); + context_menu_add(state, "CLEAR REMOTE ADDRESS", GUI_CTX_CLEAR_REMOTE); + } else if (kind == 6) { + context_menu_add(state, "OPEN PRIVATE CHAT", GUI_CTX_CHAT_PEER); + context_menu_add(state, "COPY PEER KEY", GUI_CTX_COPY_PEER); + } else if (kind == 7) { + context_menu_add(state, "SELECT THIS ARCHIVE", GUI_CTX_SELECT_SHARED); + context_menu_add(state, "COPY SELECTED VERSION LINK", GUI_CTX_COPY_ARCHIVE); + } else if (kind == 9) { + context_menu_add(state, "CANCEL THIS IMPORT", GUI_CTX_CANCEL_IMPORT); + context_menu_add(state, "COPY REMOTE DAT LINK", GUI_CTX_COPY_IMPORT_LINK); + } else if (kind == 1) { + context_menu_add(state, "F9 LOAD / SHARE THIS DIRECTORY", GUI_CTX_LOAD_ARCHIVE); + context_menu_add(state, "CREATE NEW DAT ARCHIVE", GUI_CTX_CREATE_ARCHIVE); + dat_archive_t archive; + if (!dat_archive_open(&archive, state->archive_path)) { + context_menu_add(state, "COPY DAT LINK HERE", GUI_CTX_COPY_ARCHIVE); + dat_archive_close(&archive); + } + } +} + +static int copy_selected_archive_link(gui_state_t *state) +{ + dat_link_t link; + char address[DAT_URL_BYTES + 128]; + if (!state->archive_open) { errno = ENOENT; return -1; } + memset(&link, 0, sizeof(link)); + memcpy(link.key, state->archive_key_bytes, sizeof(link.key)); + link.has_version = 1; + link.version = state->selected_version; + if ((state->address_compact ? dat_link_format_compact(&link, address, sizeof(address)) : + dat_link_format(&link, address, sizeof(address))) || SDL_SetClipboardText(address)) return -1; + snprintf(state->network_status, sizeof(state->network_status), "SELECTED VERSION LINK COPIED"); + return 0; +} + +static int copy_archive_path_link(gui_state_t *state, const char *path) +{ + dat_archive_t archive; + dat_link_t link; + char address[DAT_URL_BYTES + 128]; + if (!path || dat_archive_open(&archive, path)) return -1; + memset(&link, 0, sizeof(link)); + memcpy(link.key, archive.metadata_key, sizeof(link.key)); + link.has_version = 1; + link.version = !strcmp(path, state->archive_loaded_path) ? state->selected_version : + dat_archive_version(&archive); + dat_archive_close(&archive); + if ((state->address_compact ? dat_link_format_compact(&link, address, sizeof(address)) : + dat_link_format(&link, address, sizeof(address))) || SDL_SetClipboardText(address)) return -1; + snprintf(state->network_status, sizeof(state->network_status), "ARCHIVE VERSION LINK COPIED"); + return 0; +} + +static int reveal_directory_in_file_browser(const char *path) +{ + char absolute[2048], url[6200]; + static const char hex[] = "0123456789ABCDEF"; + size_t used = 0; + const unsigned char *p; + if (!path || !*path) { errno = EINVAL; return -1; } +#ifdef _WIN32 + if (!_fullpath(absolute, path, sizeof(absolute))) return -1; + memcpy(url, "file:///", 8); used = 8; +#else + if (!realpath(path, absolute)) return -1; + memcpy(url, "file://", 7); used = 7; +#endif + for (p = (const unsigned char *)absolute; *p; p++) { + int safe = (*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || + (*p >= '0' && *p <= '9') || *p == '/' || *p == '\\' || + *p == ':' || *p == '-' || *p == '_' || *p == '.' || *p == '~'; + size_t need = safe ? 1u : 3u; + if (used + need >= sizeof(url)) { errno = ENAMETOOLONG; return -1; } + if (safe) url[used++] = *p == '\\' ? '/' : (char)*p; + else { + url[used++] = '%'; + url[used++] = hex[*p >> 4]; + url[used++] = hex[*p & 15]; + } + } + url[used] = '\0'; + return SDL_OpenURL(url); +} + +static int open_existing_archive(gui_state_t *state) +{ + dat_archive_t archive; + if (!state->archive_path[0] || dat_archive_open(&archive, state->archive_path)) { + snprintf(state->network_status, sizeof(state->network_status), "NO ARCHIVE HERE - USE CREATE NEW DAT"); + return -1; + } + dat_archive_close(&archive); + refresh_archive(state); + if (!state->archive_open) return -1; + remember_shared_path(state, state->archive_path); + snprintf(state->network_status, sizeof(state->network_status), "ARCHIVE LOADED - OFFLINE"); + return 0; +} + +static int make_directory(const char *path) +{ + return mkdir(path, 0700) == 0 || errno == EEXIST ? 0 : -1; +} + +static int preferences_path(char *path, size_t capacity, int create_parent) +{ + const char *home = gui_home_directory(); + char directory[1024]; + int n; + if (!home || !*home) { errno = ENOENT; return -1; } + n = snprintf(directory, sizeof(directory), "%s/.dat", home); + if (n < 0 || (size_t)n >= sizeof(directory) || (create_parent && make_directory(directory))) return -1; + n = snprintf(path, capacity, "%s/dat-thang.conf", directory); + if (n < 0 || (size_t)n >= capacity) { errno = ENAMETOOLONG; return -1; } + return 0; +} + +static void load_preferences(gui_state_t *state) +{ + char path[1200], line[96]; + FILE *file; + int value; + state->address_compact = 0; + state->audio_enabled = 1; + state->auto_connect = 1; + if (preferences_path(path, sizeof(path), 0) || !(file = fopen(path, "r"))) return; + while (fgets(line, sizeof(line), file)) { + if (sscanf(line, "compact=%d", &value) == 1) state->address_compact = !!value; + else if (sscanf(line, "audio=%d", &value) == 1) state->audio_enabled = !!value; + else if (sscanf(line, "auto_connect=%d", &value) == 1) state->auto_connect = !!value; + } + fclose(file); +} + +static int save_preferences(const gui_state_t *state) +{ + char path[1200], temporary[1240]; + FILE *file; + int n; + if (preferences_path(path, sizeof(path), 1)) return -1; + n = snprintf(temporary, sizeof(temporary), "%s.tmp", path); + if (n < 0 || (size_t)n >= sizeof(temporary)) { errno = ENAMETOOLONG; return -1; } + file = fopen(temporary, "w"); + if (!file) return -1; + if (fprintf(file, "compact=%d\naudio=%d\nauto_connect=%d\n", + !!state->address_compact, !!state->audio_enabled, !!state->auto_connect) < 0) { + int saved = errno; + (void)fclose(file); + (void)unlink(temporary); + errno = saved; + return -1; + } + if (fclose(file) != 0 || chmod(temporary, S_IRUSR | S_IWUSR) || + dat_replace_file(temporary, path)) { + int saved = errno; + (void)unlink(temporary); + errno = saved; + return -1; + } + return 0; +} + +static void change_preference(gui_state_t *state, int direction) +{ + switch (state->settings_selection) { + case 0: state->address_compact = !state->address_compact; break; + case 1: state->audio_enabled = !state->audio_enabled; break; + case 2: state->auto_connect = !state->auto_connect; break; + default: return; + } + if (save_preferences(state)) + snprintf(state->network_status, sizeof(state->network_status), "PREFERENCE CHANGED - SAVE FAILED"); + else if (state->settings_selection == 2) + snprintf(state->network_status, sizeof(state->network_status), "STARTUP NETWORK PREFERENCE SAVED"); + else snprintf(state->network_status, sizeof(state->network_status), "PREFERENCE SAVED"); + (void)direction; +} + +static void activate_main_menu(gui_state_t *state, int *running) +{ + if (state->menu_selection == 0) { + state->settings_visible = 1; + state->settings_selection = 0; + } else if (state->menu_selection == 1) { + state->menu_visible = 0; + } else if (state->menu_selection == 2) { + *running = 0; + } +} + +/* Network presence belongs to this client, not to any archive's feed key. */ +static int load_client_identity(gui_state_t *state) +{ + const char *home = gui_home_directory(); + char directory[1024], path[1200]; + int n; + if (!home || !*home) { errno = ENOENT; return -1; } + if (sleep_crypto_init()) return -1; + n = snprintf(directory, sizeof(directory), "%s/.dat", home); + if (n < 0 || (size_t)n >= sizeof(directory) || make_directory(directory)) return -1; + n = snprintf(path, sizeof(path), "%s/dat-thang.network-key", directory); + if (n < 0 || (size_t)n >= sizeof(path)) { errno = ENAMETOOLONG; return -1; } + if (sleep_load_keypair(path, &state->identity) == 0) { + state->identity_valid = 1; + return 0; + } + if (errno != ENOENT || sleep_gen_keypair(&state->identity) || + sleep_save_keypair(path, &state->identity) || chmod(path, S_IRUSR | S_IWUSR)) return -1; + state->identity_valid = 1; + return 0; +} + +/* Cable rooms keep their Cabal-compatible per-room identity. Dat archives + * use the independent client identity loaded by load_client_identity(). */ +static int load_room_identity(gui_state_t *state, const uint8_t room_key[32]) +{ + const char *home = gui_home_directory(); + char path[1200]; + if (!home || cable_user_identity_path(home, room_key, path, sizeof(path)) || + cable_user_identity_load_or_create(path, &state->identity)) { + state->identity_valid = 0; + return -1; + } + state->identity_valid = 1; + return 0; +} + +static int shared_paths_file(char *out, size_t capacity) +{ + const char *home = gui_home_directory(); + char base[1024]; + int n; + if (!home) { errno = ENOENT; return -1; } + n = snprintf(base, sizeof(base), "%s/.dat", home); + if (n < 0 || (size_t)n >= sizeof(base) || make_directory(base)) return -1; + n = snprintf(out, capacity, "%s/dat-thang-shares", base); + if (n < 0 || (size_t)n >= capacity) { errno = ENAMETOOLONG; return -1; } + return 0; +} + +static void remember_shared_path(gui_state_t *state, const char *path) +{ + char filename[1200]; + FILE *file; + dat_archive_t archive; + size_t i; + int restart_session = 0; + if (!path || !*path || strlen(path) >= sizeof(state->shared_paths[0])) return; + if (dat_archive_open(&archive, path)) return; + if (state->pool_initialized && state->network_requested && !state->remote_import_mode && + state->archive_open && !strcmp(path, state->archive_loaded_path) && + (!state->session_initialized || memcmp(state->session.archive_key, + state->archive_key_bytes, DAT_KEY_BYTES))) + restart_session = 1; + dat_archive_close(&archive); + for (i = 0; i < state->shared_path_count; i++) + if (!strcmp(state->shared_paths[i], path)) break; + if (i == state->shared_path_count) { + if (state->shared_path_count == GUI_SHARED_MAX) { + memmove(state->shared_paths, state->shared_paths + 1, + (GUI_SHARED_MAX - 1) * sizeof(state->shared_paths[0])); + state->shared_path_count--; + } + strcpy(state->shared_paths[state->shared_path_count++], path); + } else if (i + 1 < state->shared_path_count) { + char selected[1024]; + strcpy(selected, state->shared_paths[i]); + memmove(state->shared_paths[i], state->shared_paths[i + 1], + (state->shared_path_count - i - 1) * sizeof(state->shared_paths[0])); + strcpy(state->shared_paths[--state->shared_path_count], selected); + } + if (restart_session) { + network_stop(state); + network_start(state); + } else if (state->pool_initialized) network_announce_shared_paths(state); + if (shared_paths_file(filename, sizeof(filename))) return; + file = fopen(filename, "w"); + if (!file) return; + for (i = 0; i < state->shared_path_count; i++) fprintf(file, "%s\n", state->shared_paths[i]); + fclose(file); +} + +static void load_shared_paths(gui_state_t *state) +{ + char filename[1200], line[1200]; + FILE *file; + if (shared_paths_file(filename, sizeof(filename)) || !(file = fopen(filename, "r"))) return; + while (state->shared_path_count < GUI_SHARED_MAX && fgets(line, sizeof(line), file)) { + size_t length = strcspn(line, "\r\n"); + line[length] = '\0'; + if (length && length < sizeof(state->shared_paths[0])) { + dat_archive_t archive; + if (!dat_archive_open(&archive, line)) { + strcpy(state->shared_paths[state->shared_path_count++], line); + dat_archive_close(&archive); + } + } + } + fclose(file); +} + +/* dat.scm is archive presentation metadata: one readable S-expression alist, + * for example ((title "name") (description "about") (url "dat://…")). + * Read it as data through S7; never evaluate archive-provided forms. */ +static s7_pointer archive_scm_field(s7_pointer record, const char *name) +{ + s7_pointer fields = record; + while (s7_is_pair(fields)) { + s7_pointer field = s7_car(fields), key, value; + if (s7_is_pair(field)) { + key = s7_car(field); + value = s7_cdr(field); + if (s7_is_symbol(key) && !strcmp(s7_symbol_name(key), name) && + s7_is_pair(value)) return s7_car(value); + } + fields = s7_cdr(fields); + } + return NULL; +} + +static gui_archive_label_t *archive_label(gui_state_t *state, const char *path, + const uint8_t key[DAT_KEY_BYTES]) +{ + gui_archive_label_t *label = NULL; + char metadata_path[1200], key_hex[65]; + const char *base = strrchr(path, '/'); + s7_scheme *sc = state->metadata_scheme.scheme; + size_t i; + for (i = 0; i < state->archive_label_count; i++) + if (!strcmp(state->archive_labels[i].path, path)) return &state->archive_labels[i]; + if (state->archive_label_count >= GUI_SHARED_MAX) return NULL; + label = &state->archive_labels[state->archive_label_count++]; + memset(label, 0, sizeof(*label)); + snprintf(label->path, sizeof(label->path), "%s", path); + base = base ? base + 1 : path; + snprintf(label->title, sizeof(label->title), "%.60s", *base ? base : path); + if (!dat_key_to_hex(key, key_hex)) + snprintf(label->description, sizeof(label->description), "DAT://%.16s...", key_hex); + if (!sc) { label->loaded = 1; return label; } + i = (size_t)snprintf(metadata_path, sizeof(metadata_path), "%s%sdat.scm", + path, path[strlen(path) - 1] == '/' ? "" : "/"); + if (i < sizeof(metadata_path)) { + s7_pointer port = s7_open_input_file(sc, metadata_path, "r"); + if (port) { + s7_pointer record = s7_read(sc, port); + s7_pointer title = archive_scm_field(record, "title"); + s7_pointer description = archive_scm_field(record, "description"); + if (title && s7_is_string(title)) + snprintf(label->title, sizeof(label->title), "%s", s7_string(title)); + if (description && s7_is_string(description)) + snprintf(label->description, sizeof(label->description), "%s", s7_string(description)); + s7_close_input_port(sc, port); + } + } + label->loaded = 1; + return label; +} + +/* ON belongs to the client. Keep every loaded archive topic joined while the + * filesystem pane selects a different local archive. */ +static void network_announce_shared_paths(gui_state_t *state) +{ + size_t i; + if (!state->pool_initialized) return; + for (i = 0; i < state->shared_path_count; i++) { + dat_archive_t archive; + uint8_t topic[CESSPOOL_TOPIC_BYTES]; + size_t t; + int joined = 0; + if (dat_archive_open(&archive, state->shared_paths[i])) continue; + if (!cesspool_discovery_key(archive.metadata_key, topic)) { + for (t = 0; t < state->pool.topic_count; t++) + if (!memcmp(state->pool.topics[t].key, topic, sizeof(topic))) { joined = 1; break; } + if (!joined && (!state->archive_open || + memcmp(topic, state->discovery_topic, sizeof(topic)))) + (void)cesspool_join(&state->pool, topic, state->pool.port, 1, 1); + } + dat_archive_close(&archive); + } +} + +static int parse_remote_address(gui_state_t *state) +{ + dat_url_t url; + state->remote_key_valid = 0; + state->remote_site[0] = state->remote_archive[0] = state->remote_version[0] = '\0'; + state->remote_path[0] = '\0'; + if (!state->remote_address[0] || dat_url_parse(state->remote_address, &url)) return -1; + snprintf(state->remote_site, sizeof(state->remote_site), "%s", url.host); + /* dat_url_to_key accepts both canonical 64-digit hex and optional + * Base32768 hosts. Keep DAT-DNS names as the fallback only after decoding + * the address payload directly. */ + if (dat_url_to_key(url.host, state->remote_metadata_key) == 0) { + snprintf(state->remote_archive, sizeof(state->remote_archive), "%s", url.host); + state->remote_key_valid = 1; + } else if (dat_dns_resolve(url.host, state->remote_metadata_key, NULL) == 0) { + snprintf(state->remote_archive, sizeof(state->remote_archive), "DAT-DNS NAME"); + state->remote_key_valid = 1; + } else { + state->remote_key_valid = 0; + snprintf(state->remote_archive, sizeof(state->remote_archive), "UNRESOLVED NAME"); + } + snprintf(state->remote_path, sizeof(state->remote_path), "%s", url.path); + if (url.has_version) { + state->remote_selected_version = url.version; + snprintf(state->remote_version, sizeof(state->remote_version), "%llu", + (unsigned long long)url.version); + } else { + state->remote_selected_version = UINT64_MAX; + snprintf(state->remote_version, sizeof(state->remote_version), "LATEST"); + } + return 0; +} + +static int archive_secret_directory(char *out, size_t capacity) +{ + const char *home = gui_home_directory(); + char base[1024]; + int n; + if (!home) { errno = ENOENT; return -1; } + n = snprintf(base, sizeof(base), "%s/.dat", home); + if (n < 0 || (size_t)n >= sizeof(base) || make_directory(base)) return -1; + n = snprintf(out, capacity, "%s/secret_keys", base); + if (n < 0 || (size_t)n >= capacity) { errno = ENAMETOOLONG; return -1; } + return make_directory(out); +} + +/* Opening ON makes the selected local directory the archive's source tree. */ +static int prepare_archive(gui_state_t *state) +{ + dat_archive_t archive; + char secrets[1200]; + if (dat_archive_open(&archive, state->archive_path) == 0) { + if (archive_secret_directory(secrets, sizeof(secrets)) == 0 && + dat_archive_open_writable(&archive, state->archive_path, secrets) == 0) { + if (!state->metadata_scheme.scheme || + dat_scheme_write_archive_info(state->metadata_scheme.scheme, + state->archive_path, &archive, NULL, NULL, 0) != 0 || + dat_archive_sync_path(&archive, state->archive_path, "") != 0) { + dat_archive_close(&archive); + return -1; + } + } + dat_archive_close(&archive); + refresh_archive(state); + return state->archive_open ? 0 : -1; + } + if (errno != ENOENT || dat_archive_create(&archive, state->archive_path) != 0) + return -1; + if (archive_secret_directory(secrets, sizeof(secrets)) != 0 || + dat_archive_save_secrets(&archive, secrets) != 0 || + !state->metadata_scheme.scheme || + dat_scheme_write_archive_info(state->metadata_scheme.scheme, + state->archive_path, &archive, NULL, NULL, 0) != 0 || + dat_archive_sync_path(&archive, state->archive_path, "") != 0) { + dat_archive_close(&archive); + return -1; + } + dat_archive_close(&archive); + refresh_archive(state); + remember_shared_path(state, state->archive_path); + return state->archive_open ? 0 : -1; +} + +static int same_endpoint(const struct sockaddr_storage *a, + const struct sockaddr_storage *b) +{ + const struct sockaddr_in *aa = (const struct sockaddr_in *)a; + const struct sockaddr_in *bb = (const struct sockaddr_in *)b; + return aa->sin_family == AF_INET && bb->sin_family == AF_INET && + aa->sin_port == bb->sin_port && aa->sin_addr.s_addr == bb->sin_addr.s_addr; +} + +static gui_transport_peer_t *transport_by_id(gui_state_t *state, + const uint8_t peer_id[32]) +{ + size_t i; + for (i = 0; i < GUI_TRANSPORT_MAX; i++) + if (state->transports[i].used && + !memcmp(state->transports[i].peer_id, peer_id, 32)) + return &state->transports[i]; + return NULL; +} + +static gui_transport_peer_t *transport_create(gui_state_t *state, + const uint8_t peer_id[32]) +{ + size_t i; + gui_transport_peer_t *transport = transport_by_id(state, peer_id); + if (transport) return transport; + for (i = 0; i < GUI_TRANSPORT_MAX; i++) if (!state->transports[i].used) { + transport = &state->transports[i]; + free(transport->tx_buffer); + free(transport->rx_app_buffer); + memset(transport, 0, sizeof(*transport)); + if (sleep_utp_init(&transport->utp, state->pool.net.sock)) return NULL; + transport->used = 1; + transport->owner = state; + memcpy(transport->peer_id, peer_id, 32); + transport->utp.context = transport; + return transport; + } + errno = ENOSPC; + return NULL; +} + +static int transport_reinitialize(gui_state_t *state, + gui_transport_peer_t *transport) +{ + uint8_t peer_id[32]; + if (!state || !transport || !state->pool_initialized) { errno = EINVAL; return -1; } + memcpy(peer_id, transport->peer_id, sizeof(peer_id)); + free(transport->tx_buffer); + free(transport->rx_app_buffer); + memset(transport, 0, sizeof(*transport)); + if (sleep_utp_init(&transport->utp, state->pool.net.sock)) return -1; + transport->used = 1; + transport->owner = state; + memcpy(transport->peer_id, peer_id, sizeof(peer_id)); + transport->utp.context = transport; + transport->utp.on_data = transport_on_data; + return 0; +} + +static int transport_queue_flush(gui_state_t *state, + gui_transport_peer_t *transport) +{ + while (transport->tx_offset < transport->tx_length && + transport->utp.state == SLEEP_UTP_CONNECTED) { + size_t remaining = transport->tx_length - transport->tx_offset; + size_t amount = remaining > SLEEP_UTP_MTU ? SLEEP_UTP_MTU : remaining; + if (sleep_utp_send(&transport->utp, + transport->tx_buffer + transport->tx_offset, amount)) { + if (errno == EAGAIN || errno == ENOBUFS) return 0; + return -1; + } + transport->tx_offset += amount; + } + if (transport->tx_offset == transport->tx_length) + transport->tx_offset = transport->tx_length = 0; + else if (transport->tx_offset && transport->tx_offset >= transport->tx_capacity / 2) { + memmove(transport->tx_buffer, + transport->tx_buffer + transport->tx_offset, + transport->tx_length - transport->tx_offset); + transport->tx_length -= transport->tx_offset; + transport->tx_offset = 0; + } + (void)state; + return 0; +} + +static int transport_queue_append(gui_state_t *state, + gui_transport_peer_t *transport, + const uint8_t *frame, size_t frame_len) +{ + size_t queued, needed, capacity; + uint8_t *next; + if (transport->tx_offset && + (transport->tx_length + frame_len > transport->tx_capacity || + transport->tx_offset >= transport->tx_capacity / 2)) { + memmove(transport->tx_buffer, + transport->tx_buffer + transport->tx_offset, + transport->tx_length - transport->tx_offset); + transport->tx_length -= transport->tx_offset; + transport->tx_offset = 0; + } + queued = transport->tx_length - transport->tx_offset; + if (frame_len > 4u * 1024u * 1024u - queued) { errno = ENOBUFS; return -1; } + needed = transport->tx_length + frame_len; + if (needed > transport->tx_capacity) { + capacity = transport->tx_capacity ? transport->tx_capacity : 4096; + while (capacity < needed) { + if (capacity >= 4u * 1024u * 1024u / 2) { + capacity = 4u * 1024u * 1024u; + break; + } + capacity *= 2; + } + next = realloc(transport->tx_buffer, capacity); + if (!next) return -1; + transport->tx_buffer = next; + transport->tx_capacity = capacity; + } + memcpy(transport->tx_buffer + transport->tx_length, frame, frame_len); + transport->tx_length += frame_len; + state->bytes_sent += frame_len; + state->upload_activity_until = SDL_GetTicks() + 500; + return transport_queue_flush(state, transport); +} + +static int remote_import_activate_content(gui_state_t *state) +{ + sleep_metadata_header_t header; + uint8_t *block = NULL; + size_t length = 0; + char path[1200]; + int n; + if (!state->remote_import_mode || !state->session_initialized || + state->session.content_key_known || !sleep_feed_has(&state->metadata_feed, 0)) return 0; + if (sleep_feed_read(&state->metadata_feed, 0, &block, &length)) return -1; + if (sleep_metadata_header_decode(&header, block, length) || + strcmp(header.type, "hyperdrive") || header.content_len != DAT_KEY_BYTES) { + free(block); errno = EPROTO; return -1; + } + free(block); + if (sleep_feed_load_remote(&state->content_feed, header.content) || + dat_session_set_content_key(&state->session, header.content)) return -1; + n = snprintf(path, sizeof(path), "%s/.dat/content.key", state->archive_path); + if (n < 0 || (size_t)n >= sizeof(path) || sleep_save_pubkey(path, header.content)) return -1; + refresh_archive(state); + if (state->archive_open && state->remote_selected_version != UINT64_MAX && + state->remote_selected_version <= state->version) + state->selected_version = state->remote_selected_version; + if (state->archive_open) remember_shared_path(state, state->archive_path); + state->remote_import_mode = 0; + snprintf(state->network_status, sizeof(state->network_status), "REMOTE FEEDS CONNECTED"); + return 0; +} + +static int session_send_frame(void *context, const uint8_t peer_id[32], + const uint8_t *frame, size_t frame_len) +{ + gui_state_t *state = (gui_state_t *)context; + gui_transport_peer_t *transport = transport_by_id(state, peer_id); + uint8_t *record; + size_t prefix, written; + int result; + if (!transport || !transport->authenticated) { errno = ENOTCONN; return -1; } + if (frame_len > 4u * 1024u * 1024u) { errno = EMSGSIZE; return -1; } + prefix = 1 + sleep_varint_size(frame_len); + record = malloc(prefix + frame_len); + if (!record) return -1; + record[0] = 0; /* framed feed/session record */ + if (sleep_varint_encode(frame_len, record + 1, prefix - 1, &written)) { + free(record); return -1; + } + memcpy(record + prefix, frame, frame_len); + result = transport_queue_append(state, transport, record, prefix + frame_len); + free(record); + return result; +} + +static int transport_receive_records(gui_state_t *state, + gui_transport_peer_t *transport, + const uint8_t *bytes, size_t length) +{ + size_t need, cap; + uint8_t *next; + if (length > 4u * 1024u * 1024u - transport->rx_app_length) { errno = ENOBUFS; return -1; } + need = transport->rx_app_length + length; + if (need > transport->rx_app_capacity) { + cap = transport->rx_app_capacity ? transport->rx_app_capacity : 4096; + while (cap < need) cap = cap > 2u * 1024u * 1024u ? 4u * 1024u * 1024u : cap * 2; + next = realloc(transport->rx_app_buffer, cap); + if (!next) return -1; + transport->rx_app_buffer = next; + transport->rx_app_capacity = cap; + } + if (length) memcpy(transport->rx_app_buffer + transport->rx_app_length, bytes, length); + transport->rx_app_length += length; + for (;;) { + uint8_t kind; + uint64_t record_len; + size_t varint_len, prefix; + int saved_errno; + if (transport->rx_app_length < 2) return 0; + kind = transport->rx_app_buffer[0]; + errno = 0; + if (sleep_varint_decode(transport->rx_app_buffer + 1, + transport->rx_app_length - 1, &record_len, &varint_len)) { + saved_errno = errno; + if (saved_errno == EAGAIN) return 0; + errno = saved_errno; + return -1; + } + if (record_len > 4u * 1024u * 1024u) { errno = EMSGSIZE; return -1; } + prefix = 1 + varint_len; + if (record_len > transport->rx_app_length - prefix) return 0; + if (kind == 0) { + if (!state->session_initialized) { errno = EPROTO; return -1; } + if (dat_session_receive(&state->session, transport->peer_id, + transport->rx_app_buffer + prefix, (size_t)record_len)) return -1; + if (remote_import_activate_content(state)) return -1; + state->bytes_received += (size_t)record_len; + state->download_activity_until = SDL_GetTicks() + 500; + } else if (kind == 1 && record_len == 1 && + (transport->rx_app_buffer[prefix] == 1 || + transport->rx_app_buffer[prefix] == 2 || + transport->rx_app_buffer[prefix] == 3)) { + /* Authenticated Dat-thang sonar cues; never forwarded to Dat peers. */ + uint8_t cue = transport->rx_app_buffer[prefix]; + if (cue == 1 || cue == 3) { + fm_audio_play_dolphin(state); + snprintf(state->network_status, sizeof(state->network_status), cue == 3 ? + "SONAR ECHO RECEIVED - PEER FOUND" : "DOLPHIN CHIRP RECEIVED"); + } else { + uint8_t reply[3] = {1, 1, 3}; + fm_audio_play(state); + snprintf(state->network_status, sizeof(state->network_status), "SONAR RECEIVED - SENDING DOLPHIN ECHO"); + (void)transport_queue_append(state, transport, reply, sizeof(reply)); + } + } else if (kind == 2) { + uint8_t plain[CABLE_CHAT_WIRE_BYTES]; size_t plain_n; + cable_chat_message_t message; + if (cable_chat_open(&state->identity,transport->rx_app_buffer+prefix, + (size_t)record_len,plain,sizeof(plain),&plain_n)|| + cable_chat_message_decode(&message,plain,plain_n)) return -1; + chat_append_line(state,transport->peer_id,&message,0); + if(!memcmp(state->chat_peer_id,transport->peer_id,32)) + snprintf(state->chat_status,sizeof(state->chat_status),"SEALED MESSAGE RECEIVED"); + } else if (kind == 3) { + const uint8_t *body = transport->rx_app_buffer + prefix; + const uint8_t *sexp; + size_t sexp_length; + char source_key[65]; + s7_scheme *sc = state->metadata_scheme.scheme; + s7_pointer port, form, validator, valid, item; + cable_room_event_t event; + int event_type; + if (record_len < 96 || !sc || memcmp(body, transport->peer_id, 32)) { errno = EPROTO; return -1; } + sexp = body + 96; + sexp_length = (size_t)record_len - 96; + if (sexp_length > 4095 || sleep_verify(body + 32, sexp, sexp_length, transport->peer_id)) return -1; + { + char text_buffer[4096]; + memcpy(text_buffer, sexp, sexp_length); + text_buffer[sexp_length] = '\0'; + port = s7_open_input_string(sc, text_buffer); + form = s7_read(sc, port); + s7_close_input_port(sc, port); + } + validator = s7_name_to_value(sc, "cable:room-wire-event?"); + valid = s7_call(sc, validator, s7_cons(sc, form, s7_nil(sc))); + if (!s7_boolean(sc, valid)) { errno = EPROTO; return -1; } + item = s7_list_ref(sc, form, 1); + if (!s7_is_integer(item)) { errno = EPROTO; return -1; } + event_type = (int)s7_integer(item); + if (event_type < CABLE_ROOM_PEER_JOIN || event_type > CABLE_ROOM_TEXT) { errno = EPROTO; return -1; } + memset(&event, 0, sizeof(event)); + event.type = (uint8_t)event_type; + item = s7_list_ref(sc, form, 2); + event.timestamp_ms = (uint64_t)s7_integer(item); + item = s7_list_ref(sc, form, 3); + if (strlen(s7_string(item)) > CABLE_ROOM_NICK_BYTES) { errno = EMSGSIZE; return -1; } + snprintf(event.nick, sizeof(event.nick), "%s", s7_string(item)); + item = s7_list_ref(sc, form, 4); + if (strlen(s7_string(item)) > CABLE_ROOM_CHANNEL_BYTES) { errno = EMSGSIZE; return -1; } + snprintf(event.channel, sizeof(event.channel), "%s", s7_string(item)); + item = s7_list_ref(sc, form, 5); + if (strlen(s7_string(item)) > CABLE_CHAT_TEXT_BYTES) { errno = EMSGSIZE; return -1; } + event.text_length = strlen(s7_string(item)); + memcpy(event.text, s7_string(item), event.text_length + 1); + memcpy(event.sender, transport->peer_id, sizeof(event.sender)); + if (dat_key_to_hex(transport->peer_id, source_key)) return -1; + { + int peer = cable_peer_index(state, source_key, 1); + if (peer >= 0) { + state->cable_peers[peer].connected = 1; + if (event.nick[0]) snprintf(state->cable_peers[peer].nick, + sizeof(state->cable_peers[peer].nick), "%s", event.nick); + } + } + cable_room_append_line(state, &event); + } else if (kind == 4) { + cable_live_receive_wire(state, transport->peer_id, + transport->rx_app_buffer + prefix, (size_t)record_len); + } else { errno = EPROTO; return -1; } + memmove(transport->rx_app_buffer, + transport->rx_app_buffer + prefix + (size_t)record_len, + transport->rx_app_length - prefix - (size_t)record_len); + transport->rx_app_length -= prefix + (size_t)record_len; + } +} + +static void audio_cue_broadcast(gui_state_t *state, uint8_t cue) +{ + uint8_t record[3] = {1, 1, cue}; /* authenticated Dat-thang audio cue */ + size_t i; + if (cue == 1) { + size_t sent = 0; + fm_audio_play_dolphin(state); + for (i = 0; i < GUI_TRANSPORT_MAX; i++) { + gui_transport_peer_t *transport = &state->transports[i]; + if (transport->used && transport->authenticated && + transport_queue_append(state, transport, record, sizeof(record)) == 0) sent++; + } + if (sent) snprintf(state->network_status, sizeof(state->network_status), "DOLPHIN SONAR SENT TO %lu CONNECTED PEER(S)", (unsigned long)sent); + else snprintf(state->network_status, sizeof(state->network_status), "DOLPHIN SONAR - NO CONNECTED PEERS"); + return; + } + fm_audio_play(state); + state->speaker_sonar_active = 1; + state->speaker_sonar_until = SDL_GetTicks() + 10000; + state->speaker_sonar_sent_count = 0; + if (state->pool_initialized && state->online && state->topic_joined) { + if (cesspool_lookup(&state->pool, state->discovery_topic) == 0) + snprintf(state->network_status, sizeof(state->network_status), "SPEAKER SONAR SENT - ASKING DISCOVERED PEERS"); + else snprintf(state->network_status, sizeof(state->network_status), "SONAR ACTIVE - PEER DISCOVERY REFRESH FAILED"); + } else { + state->speaker_sonar_active = 0; + snprintf(state->network_status, sizeof(state->network_status), "SONAR NEEDS ONLINE ARCHIVE DISCOVERY"); + } +} + +static void speaker_sonar_tick(gui_state_t *state, Uint32 now) +{ + size_t p, sent; + uint8_t record[3] = {1, 1, 2}; + if (!state->speaker_sonar_active) return; + if (SDL_TICKS_PASSED(now, state->speaker_sonar_until)) { + state->speaker_sonar_active = 0; + if (state->speaker_sonar_sent_count) + snprintf(state->network_status, sizeof(state->network_status), "SONAR REACHED %lu DISCOVERED PEER(S)", + (unsigned long)state->speaker_sonar_sent_count); + return; + } + for (p = 0; p < state->pool.peer_count; p++) { + cesspool_peer_t *peer = &state->pool.peers[p]; + gui_transport_peer_t *transport; + if (!peer->discovered) continue; + for (sent = 0; sent < state->speaker_sonar_sent_count; sent++) + if (!memcmp(state->speaker_sonar_sent[sent], peer->public_key, 32)) break; + if (sent < state->speaker_sonar_sent_count) continue; + transport = transport_by_id(state, peer->public_key); + if (!transport || !transport->authenticated) continue; + if (transport_queue_append(state, transport, record, sizeof(record)) == 0 && + state->speaker_sonar_sent_count < CESSPOOL_MAX_PEERS) { + memcpy(state->speaker_sonar_sent[state->speaker_sonar_sent_count++], peer->public_key, 32); + snprintf(state->network_status, sizeof(state->network_status), "SONAR PING SENT - WAITING FOR PEER ECHO"); + } + } +} + +static void transport_send_hello(gui_state_t *state, + gui_transport_peer_t *transport) +{ + uint8_t hello[GUI_AUTH_HELLO_BYTES]; + if (transport->hello_sent || transport->utp.state != SLEEP_UTP_CONNECTED) return; + memcpy(hello, "THH0", 4); + memcpy(hello + 4, state->identity.pubkey, 32); + randombytes_buf(transport->local_nonce, sizeof(transport->local_nonce)); + memcpy(hello + 36, transport->local_nonce, sizeof(transport->local_nonce)); + if (sleep_sign(hello + 68, hello, 68, &state->identity) || + sleep_utp_send(&transport->utp, hello, sizeof(hello))) return; + transport->hello_sent = 1; +} + +static int transport_send_ack(gui_state_t *state, + gui_transport_peer_t *transport) +{ + uint8_t ack[GUI_AUTH_ACK_BYTES], transcript[132]; + if (transport->ack_sent) return 0; + memcpy(ack, "THA0", 4); + memcpy(ack + 4, transport->remote_nonce, 32); + memcpy(transcript, ack, 36); + memcpy(transcript + 36, transport->local_nonce, 32); + memcpy(transcript + 68, state->identity.pubkey, 32); + memcpy(transcript + 100, transport->peer_id, 32); + if (sleep_sign(ack + 36, transcript, sizeof(transcript), &state->identity) || + sleep_utp_send(&transport->utp, ack, sizeof(ack))) return -1; + transport->ack_sent = 1; + return 0; +} + +static void transport_on_data(sleep_utp_t *utp, const uint8_t *bytes, size_t length) +{ + gui_transport_peer_t *transport = (gui_transport_peer_t *)utp->context; + gui_state_t *state; + if (!transport || !transport->used) return; + state = (gui_state_t *)transport->owner; + if (!state) return; + if (transport->authenticated) { + if (transport_receive_records(state, transport, bytes, length)) { + if (state->session_initialized) + (void)dat_session_peer_down(&state->session, transport->peer_id); + (void)sleep_utp_close(&transport->utp); + transport->used = 0; + transport->authenticated = 0; + } + return; + } + if (length > sizeof(transport->auth_buffer) - transport->auth_length) { + transport->used = 0; + if (state->session_initialized) + (void)dat_session_peer_down(&state->session, transport->peer_id); + return; + } + memcpy(transport->auth_buffer + transport->auth_length, bytes, length); + transport->auth_length += length; + for (;;) { + if (!transport->hello_received && transport->auth_length >= GUI_AUTH_HELLO_BYTES) { + const uint8_t *hello = transport->auth_buffer; + if (memcmp(hello, "THH0", 4) || memcmp(hello + 4, transport->peer_id, 32) || + sleep_verify(hello + 68, hello, 68, transport->peer_id)) { + transport->used = 0; + if (state->session_initialized) + (void)dat_session_peer_down(&state->session, transport->peer_id); + return; + } + memcpy(transport->remote_nonce, hello + 36, 32); + transport->hello_received = 1; + memmove(transport->auth_buffer, + transport->auth_buffer + GUI_AUTH_HELLO_BYTES, + transport->auth_length - GUI_AUTH_HELLO_BYTES); + transport->auth_length -= GUI_AUTH_HELLO_BYTES; + if (transport_send_ack(state, transport)) return; + continue; + } + if (transport->hello_received && !transport->ack_received && + transport->auth_length >= GUI_AUTH_ACK_BYTES) { + const uint8_t *ack = transport->auth_buffer; + uint8_t transcript[132]; + if (memcmp(ack, "THA0", 4) || memcmp(ack + 4, transport->local_nonce, 32)) { + transport->used = 0; + if (state->session_initialized) + (void)dat_session_peer_down(&state->session, transport->peer_id); + return; + } + memcpy(transcript, ack, 36); + memcpy(transcript + 36, transport->remote_nonce, 32); + memcpy(transcript + 68, transport->peer_id, 32); + memcpy(transcript + 100, state->identity.pubkey, 32); + if (sleep_verify(ack + 36, transcript, sizeof(transcript), + transport->peer_id)) { + transport->used = 0; + if (state->session_initialized) + (void)dat_session_peer_down(&state->session, transport->peer_id); + return; + } + transport->ack_received = 1; + memmove(transport->auth_buffer, + transport->auth_buffer + GUI_AUTH_ACK_BYTES, + transport->auth_length - GUI_AUTH_ACK_BYTES); + transport->auth_length -= GUI_AUTH_ACK_BYTES; + transport->authenticated = 1; + if (state->session_initialized && + dat_session_peer_secure(&state->session, transport->peer_id, 1)) { + transport->authenticated = 0; + transport->used = 0; + return; + } + if (state->cable_thang_mode) { + (void)cable_room_broadcast(state, CABLE_ROOM_PEER_JOIN, + state->cable_channels[state->cable_channel_index], NULL); + (void)cable_room_broadcast(state, CABLE_ROOM_NICK, + state->cable_channels[state->cable_channel_index], NULL); + (void)cable_room_broadcast(state, CABLE_ROOM_CHANNEL_JOIN, + state->cable_channels[state->cable_channel_index], NULL); + if (state->cable_audio_broadcasting) { + static const uint8_t audio_descriptor[] = "AAC-LC;RATE=48000;CHANNELS=1;BITRATE=96000"; + (void)cable_live_send_session(state, state->cable_live_session, + CABLE_MEDIA_AUDIO_START, audio_descriptor, + sizeof(audio_descriptor) - 1, transport); + } + if (state->cable_video_active) { + static const uint8_t video_descriptor[] = "JPEG;MAX=160x90;FRAG=4000"; + (void)cable_live_send_session(state, state->cable_video_session, + CABLE_MEDIA_VIDEO_START, video_descriptor, + sizeof(video_descriptor) - 1, transport); + } + } + continue; + } + if (transport->authenticated && transport->auth_length) { + if (transport_receive_records(state, transport, transport->auth_buffer, + transport->auth_length)) return; + transport->auth_length = 0; + } + break; + } +} + +static void transport_packet_received(cesspool_t *pool, const dat_peer_t *source, + const uint8_t *bytes, size_t length) +{ + gui_state_t *state = (gui_state_t *)pool->transport_context; + sleep_utp_packet_t packet; + gui_transport_peer_t *transport = NULL; + cesspool_peer_t *known = NULL; + size_t i; + if (!state || (!state->session_initialized && !state->cable_thang_mode) || + sleep_utp_packet_decode(&packet, bytes, length)) return; + if (packet.type == SLEEP_UTP_SYN) { + int restored_closed_peer = 0; + for (i = 0; i < pool->peer_count; i++) + if (pool->peers[i].discovered && + !memcmp(pool->peers[i].discovery_topic, state->discovery_topic, 32) && + same_endpoint(&pool->peers[i].address.addr, &source->addr)) { + known = &pool->peers[i]; + break; + } + if (!known) return; + if (state->cable_thang_mode) { + transport = transport_by_id(state, known->public_key); + if (!transport) transport = transport_create(state, known->public_key); + if (!transport || transport->utp.state == SLEEP_UTP_CONNECTED || + transport->utp.state == SLEEP_UTP_SYN_SENT) return; + transport->waiting_incoming = 0; + transport->utp.on_data = transport_on_data; + if (sleep_utp_accept(&transport->utp, &packet, + (const struct sockaddr *)&source->addr, + source->addrlen)) return; + transport_send_hello(state, transport); + return; + } + { + dat_session_peer_t *session_peer = dat_session_find_peer(&state->session, + known->public_key); + if (!session_peer || session_peer->state == DAT_SESSION_PEER_CLOSED) { + restored_closed_peer = 1; + dat_session_peer_fn callback = state->session.on_peer; + void *callback_context = state->session.peer_context; + /* This SYN is already the designated side's connection attempt. + * Restore session state without racing it with a new outbound SYN. */ + state->session.on_peer = NULL; + if (dat_session_peer_discovered(&state->session, known)) { + state->session.on_peer = callback; + return; + } + state->session.on_peer = callback; + state->session.peer_context = callback_context; + } + } + transport = transport_by_id(state, known->public_key); + /* A peer can send its SYN while the session record is being restored + * from discovery. The inbound connection owns this transition, so make + * sure a transport exists even when the peer callback was suppressed + * above to prevent a crossed outbound SYN. */ + if (!transport) transport = transport_create(state, known->public_key); + if (!transport) return; + /* The lower identity owns the outbound attempt. Ignore crossed SYNs + * from a stale peer session instead of replacing a live connection. */ + if (memcmp(state->identity.pubkey, known->public_key, 32) < 0) return; + if (transport->utp.state == SLEEP_UTP_CONNECTED && + transport->authenticated && !restored_closed_peer) return; + if (restored_closed_peer || transport->utp.state != SLEEP_UTP_CLOSED || + transport->hello_sent || transport->hello_received || transport->ack_sent || + transport->ack_received || transport->auth_length || transport->tx_length || + transport->rx_app_length) { + if (transport_reinitialize(state, transport)) return; + } + transport->utp.on_data = transport_on_data; + transport->waiting_incoming = 0; + if (sleep_utp_accept(&transport->utp, &packet, + (const struct sockaddr *)&source->addr, + source->addrlen)) return; + transport_send_hello(state, transport); + return; + } + for (i = 0; i < GUI_TRANSPORT_MAX; i++) { + gui_transport_peer_t *candidate = &state->transports[i]; + if (candidate->used && candidate->utp.receive_id == packet.connection_id && + same_endpoint(&candidate->utp.remote, &source->addr)) { + transport = candidate; + break; + } + } + if (!transport) return; + if (sleep_utp_input(&transport->utp, bytes, length)) return; + if (transport->utp.state == SLEEP_UTP_CONNECTED) + transport_send_hello(state, transport); + else if (transport->utp.state == SLEEP_UTP_RESET || + transport->utp.state == SLEEP_UTP_CLOSED) { + if (transport->authenticated) + (void)dat_session_peer_down(&state->session, transport->peer_id); + transport->used = 0; + } +} + +static void update_connection_count(gui_state_t *state) +{ + size_t i; + state->connected_peers = 0; + if (!state->session_initialized) return; + for (i = 0; i < state->session.peer_count; i++) + if (state->session.peers[i].state == DAT_SESSION_PEER_SECURE || + state->session.peers[i].state == DAT_SESSION_PEER_READY) + state->connected_peers++; +} + +static void update_transfer_rates(gui_state_t *state, Uint32 now) +{ + Uint32 elapsed = now - state->rate_sample_at; + if (elapsed < 500) return; + state->upload_kbps = (double)(state->bytes_sent - state->sampled_bytes_sent) * 8.0 / elapsed; + state->download_kbps = (double)(state->bytes_received - state->sampled_bytes_received) * 8.0 / elapsed; + state->sampled_bytes_sent = state->bytes_sent; + state->sampled_bytes_received = state->bytes_received; + state->rate_sample_at = now; +} + +static void session_peer_discovered(void *context, + const dat_session_peer_t *peer) +{ + gui_state_t *state = (gui_state_t *)context; + ptrdiff_t index = peer - state->session.peers; + if (index >= 0 && index < (ptrdiff_t)DAT_SESSION_MAX_PEERS && + state->transfer_cancelled[index]) return; + gui_transport_peer_t *transport = transport_create(state, peer->peer_id); + if (transport && !transport->waiting_incoming && + (transport->utp.state != SLEEP_UTP_CLOSED || transport->hello_sent || + transport->hello_received || transport->ack_sent || transport->ack_received || + transport->auth_length || transport->tx_length || transport->rx_app_length)) { + if (transport_reinitialize(state, transport)) return; + } + if (!transport || transport->utp.state == SLEEP_UTP_CONNECTED || + transport->utp.state == SLEEP_UTP_SYN_SENT || transport->waiting_incoming) return; + transport->utp.on_data = transport_on_data; + if (memcmp(state->identity.pubkey, peer->peer_id, 32) > 0) { + transport->waiting_incoming = 1; + return; + } + if (sleep_utp_connect(&transport->utp, + (const struct sockaddr *)&peer->address.addr, + peer->address.addrlen)) transport->used = 0; +} + +static void cable_connect_discovered(gui_state_t *state) +{ + cesspool_peer_t *peers[CESSPOOL_MAX_PEERS]; + size_t i, count; + if (!state->cable_thang_mode || !state->pool_initialized || !state->topic_joined) return; + count = cesspool_topic_peers(&state->pool, state->discovery_topic, + peers, CESSPOOL_MAX_PEERS); + for (i = 0; i < count; i++) { + gui_transport_peer_t *transport; + if (!memcmp(peers[i]->public_key, state->identity.pubkey, 32)) continue; + transport = transport_by_id(state, peers[i]->public_key); + if (!transport) transport = transport_create(state, peers[i]->public_key); + if (!transport || transport->authenticated || transport->waiting_incoming || + transport->utp.state == SLEEP_UTP_CONNECTED || + transport->utp.state == SLEEP_UTP_SYN_SENT) continue; + transport->utp.on_data = transport_on_data; + if (memcmp(state->identity.pubkey, peers[i]->public_key, 32) > 0) { + transport->waiting_incoming = 1; + continue; + } + if (sleep_utp_connect(&transport->utp, + (const struct sockaddr *)&peers[i]->address.addr, + peers[i]->address.addrlen)) transport->used = 0; + } +} + +static void transfer_disconnect(gui_state_t *state, size_t index, int cancel) +{ + dat_session_peer_t *peer; + gui_transport_peer_t *transport; + if (!state->session_initialized || index >= state->session.peer_count) return; + peer = &state->session.peers[index]; + if (!cancel && dat_session_peer_pause(&state->session, peer->peer_id, 1) == 0) { + state->transfer_paused[index] = 1; + return; + } + transport = transport_by_id(state, peer->peer_id); + if (transport) { + if (transport->used) (void)sleep_utp_close(&transport->utp); + free(transport->tx_buffer); + free(transport->rx_app_buffer); + memset(transport, 0, sizeof(*transport)); + } + (void)dat_session_peer_down(&state->session, peer->peer_id); + state->transfer_paused[index] = !cancel; + if (cancel) state->transfer_cancelled[index] = 1; +} + +static void transfer_resume(gui_state_t *state, size_t index) +{ + dat_session_peer_t *peer; + cesspool_peer_t discovered; + if (!state->session_initialized || index >= state->session.peer_count || + state->transfer_cancelled[index]) return; + peer = &state->session.peers[index]; + if (peer->state == DAT_SESSION_PEER_READY && + dat_session_peer_pause(&state->session, peer->peer_id, 0) == 0) { + state->transfer_paused[index] = 0; + return; + } + if (peer->state != DAT_SESSION_PEER_CLOSED) return; + memset(&discovered, 0, sizeof(discovered)); + memcpy(discovered.public_key, peer->peer_id, sizeof(discovered.public_key)); + discovered.address = peer->address; + memcpy(discovered.discovery_topic, state->discovery_topic, sizeof(discovered.discovery_topic)); + discovered.discovered = 1; + state->transfer_paused[index] = 0; + (void)dat_session_peer_discovered(&state->session, &discovered); +} + +static SDL_Rect browser_box(int w, int h) +{ + const int margin = 22, top = 104, bottom = 52, gap = 20; + int left_w = w * 18 / 100, right_w = w * 17 / 100; + int center_x = margin + left_w + gap, right_x = w - margin - right_w; + int center_w = right_x - center_x - gap; + int body_h = h - top - bottom - 44, upper_h = body_h * 53 / 100; + int graph_w = center_w * 52 / 100; + return (SDL_Rect){center_x + graph_w + gap, top + 2, + center_w - graph_w - gap, upper_h - 8}; +} + +static int browser_path_separator(char c) +{ + return c == '/' || c == '\\'; +} + +static size_t browser_path_root_length(const char *path) +{ + size_t n = strlen(path); +#ifdef _WIN32 + if (n >= 3 && ((path[0] >= 'A' && path[0] <= 'Z') || + (path[0] >= 'a' && path[0] <= 'z')) && path[1] == ':' && + browser_path_separator(path[2])) return 3; + if (n >= 2 && browser_path_separator(path[0]) && browser_path_separator(path[1])) { + size_t i = 2; + while (i < n && !browser_path_separator(path[i])) i++; + while (i < n && browser_path_separator(path[i])) i++; + while (i < n && !browser_path_separator(path[i])) i++; + return i; + } +#endif + return n && browser_path_separator(path[0]) ? 1u : 0u; +} + +static int browser_make_absolute(char *path, size_t capacity) +{ + char resolved[4096]; + if (!path || !*path) { errno = EINVAL; return -1; } +#ifdef _WIN32 + if (!_fullpath(resolved, path, sizeof(resolved))) return -1; +#else + if (!realpath(path, resolved)) return -1; +#endif + if (strlen(resolved) >= capacity) { errno = ENAMETOOLONG; return -1; } + strcpy(path, resolved); + return 0; +} + +static int parent_directory(char *path, size_t capacity) +{ + size_t n, root_length; + char *slash = NULL; + if (!path || !*path) return 0; + if (browser_make_absolute(path, capacity)) return 0; + n = strlen(path); + root_length = browser_path_root_length(path); + while (n > root_length && browser_path_separator(path[n - 1])) path[--n] = '\0'; + if (n <= root_length) return 0; + for (size_t i = 0; i < n; i++) + if (browser_path_separator(path[i])) slash = path + i; + if (!slash) return 0; + if ((size_t)(slash - path) <= root_length) path[root_length] = '\0'; + else *slash = '\0'; + return 1; +} + +static int browser_item(gui_state_t *state, int row) +{ + DIR *dir; + struct dirent *item; + int index = 0, parent = 0; + char target[1024]; + struct stat info; + if (row == 0) { + parent = parent_directory(state->archive_path, sizeof(state->archive_path)); + if (parent <= 0) return 0; + state->browser_scroll = 0; + strcpy(state->selected_filename, "(directory)"); + return 1; + } + row--; + dir = opendir(state->archive_path); + if (!dir) return 0; + while ((item = readdir(dir)) != NULL) { + if (!strcmp(item->d_name, ".") || !strcmp(item->d_name, "..") || + !strcmp(item->d_name, ".dat")) continue; + if (index++ != row) continue; + if (snprintf(target, sizeof(target), "%s%s%s", state->archive_path, + state->archive_path[strlen(state->archive_path) - 1] == '/' ? "" : "/", + item->d_name) >= (int)sizeof(target)) break; + if (stat(target, &info) == 0 && S_ISDIR(info.st_mode)) { + snprintf(state->archive_path, sizeof(state->archive_path), "%s", target); + state->browser_scroll = 0; + strcpy(state->selected_filename, "(directory)"); + } else snprintf(state->selected_filename, sizeof(state->selected_filename), "%.255s", item->d_name); + closedir(dir); + return 1; + } + closedir(dir); + return 0; +} + +static int browser_row_count(const char *path) +{ + DIR *dir = opendir(path); + struct dirent *item; + char parent[1024]; + int count = 0; + if (strlen(path) < sizeof(parent)) { + strcpy(parent, path); + if (parent_directory(parent, sizeof(parent))) count++; + } + if (!dir) return count; + while ((item = readdir(dir)) != NULL) + if (strcmp(item->d_name, ".") && strcmp(item->d_name, "..") && strcmp(item->d_name, ".dat")) count++; + closedir(dir); + return count; +} + +static void load_manpage(gui_state_t *state) +{ + char exe_manpage[2048], local_manpage[64], local_install_manpage[128]; + char system_manpage[128]; + char *base_path = SDL_GetBasePath(); + const char *page = state->cable_thang_mode ? "cable-thang.1" : "dat-thang.1"; + const char *environment = state->cable_thang_mode ? "CABLE_THANG_MANPAGE" : "DAT_THANG_MANPAGE"; + const char *paths[5]; + FILE *file = NULL; + char line[512]; + int p; + snprintf(local_manpage, sizeof(local_manpage), "docs/%s", page); + snprintf(local_install_manpage, sizeof(local_install_manpage), + "/usr/local/share/man/man1/%s", page); + paths[0] = NULL; + paths[1] = NULL; + paths[2] = local_manpage; + paths[3] = local_install_manpage; + snprintf(system_manpage, sizeof(system_manpage), "/usr/share/man/man1/%s", page); + paths[4] = system_manpage; + if (base_path) { + int n = snprintf(exe_manpage, sizeof(exe_manpage), "%sdocs/%s", base_path, page); + if (n >= 0 && (size_t)n < sizeof(exe_manpage)) paths[1] = exe_manpage; + } + state->help_count = state->help_scroll = 0; + paths[0] = getenv(environment); + for (p = 0; p < (int)(sizeof(paths) / sizeof(paths[0])); p++) { + const char *path = paths[p]; + if (path && *path && (file = fopen(path, "r")) != NULL) break; + } + if (base_path) SDL_free(base_path); + if (!file) { + snprintf(state->help_lines[0], sizeof(state->help_lines[0]), "DAT-THANG MANPAGE NOT FOUND"); + state->help_count = 1; + return; + } + while (fgets(line, sizeof(line), file) && state->help_count < 128) { + char *source = line, *dest; + size_t used = 0; + while (*source == ' ' || *source == '\t') source++; + if (*source == '.') { + if (!strncmp(source, ".TH", 3) || !strncmp(source, ".so", 3)) continue; + if (!strncmp(source, ".SH", 3) || !strncmp(source, ".SS", 3)) { + source += 3; + while (*source == ' ' || *source == '\t' || *source == '"') source++; + } else if (!strncmp(source, ".PP", 3) || !strncmp(source, ".P", 2) || + !strncmp(source, ".TP", 3) || !strncmp(source, ".br", 3)) continue; + else if (!strncmp(source, ".B ", 3) || !strncmp(source, ".I ", 3) || + !strncmp(source, ".RI ", 4) || !strncmp(source, ".IR ", 4) || + !strncmp(source, ".RB ", 4) || !strncmp(source, ".BR ", 4)) + source += source[2] == ' ' ? 3 : 4; + else continue; + } + dest = state->help_lines[state->help_count]; + while (*source && *source != '\n' && *source != '\r' && used + 1 < sizeof(state->help_lines[0])) { + if (*source == '"') { source++; continue; } + if (*source == '\\' && source[1]) source++; + dest[used++] = *source++; + } + while (used && dest[used - 1] == ' ') used--; + dest[used] = '\0'; + if (used) state->help_count++; + } + fclose(file); +} + +static int configure_bootstraps(cesspool_t *pool) +{ + return cesspool_configure_bootstraps(pool); +} + +static int open_session_feed(sleep_feed_t *feed, const char *directory, + const char *name, const uint8_t public_key[32]) +{ + if (sleep_feed_open(feed, directory, name, NULL) != 0) { + if (errno != ENOENT) return -1; + return sleep_feed_open_remote(feed, directory, name, public_key); + } + if (sleep_feed_load_remote(feed, public_key)) { + sleep_feed_close(feed); + return -1; + } + feed->persist_remote = 1; + memcpy(feed->keypair.pubkey, public_key, 32); + return 0; +} + +static uint16_t configured_port(const gui_state_t *state) +{ + const char *setting = getenv("DDAT_PORT"); + char *end; + unsigned long port; + uint16_t fallback = state && state->cable_thang_mode ? 3000u : DATNET_PORT; + /* Cesspool LAN discovery sends to stable UDP ports. Keep the two GUI + * clients distinct so dat-thang and cable-thang can run side by side. */ + if (!setting || !*setting) return fallback; + port = strtoul(setting, &end, 10); + if (*end || !port || port > 65535) return fallback; + return (uint16_t)port; +} + +static int listen_client_pool(gui_state_t *state) +{ + const char *override = getenv("DDAT_PORT"); + if (cesspool_listen(&state->pool, "0.0.0.0", configured_port(state)) == 0) + return 0; + if ((!override || !*override) && errno == EADDRINUSE) + return cesspool_listen(&state->pool, "0.0.0.0", 0); + return -1; +} + +static void network_stop(gui_state_t *state) +{ + size_t i; + if (state->cable_thang_mode) { + if (state->cable_audio_broadcasting) cable_audio_stop(state, 1); + if (state->cable_video_source) cable_video_toggle(state); + state->cable_network_active = 0; + state->cable_peer_count = 0; + state->connected_peers = 0; + } + if (state->session_initialized) { + (void)dat_session_close(&state->session); + state->session_initialized = 0; + state->topic_joined = 0; + } else if (state->topic_joined) + (void)cesspool_leave(&state->pool, state->discovery_topic); + state->topic_joined = 0; + if (state->pool_initialized) { + while (state->pool.topic_count) + (void)cesspool_leave(&state->pool, + state->pool.topics[state->pool.topic_count - 1].key); + } + for (i = 0; i < GUI_TRANSPORT_MAX; i++) { + if (state->transports[i].used) + (void)sleep_utp_close(&state->transports[i].utp); + free(state->transports[i].tx_buffer); + free(state->transports[i].rx_app_buffer); + memset(&state->transports[i], 0, sizeof(state->transports[i])); + } + sleep_feed_close(&state->metadata_feed); + sleep_feed_close(&state->content_feed); + if (state->pool_initialized) { + (void)cesspool_destroy(&state->pool); + /* Drop retained DHT identities too; OFF means no peer state survives. */ + memset(&state->pool, 0, sizeof(state->pool)); + } + state->pool_initialized = 0; + state->connected_peers = 0; + state->remote_import_mode = 0; + state->online = 0; + state->network_requested = 0; + snprintf(state->network_status, sizeof(state->network_status), "OFFLINE"); +} + +static void network_start(gui_state_t *state) +{ + int bootstraps; + const char *session_path = state->archive_open ? state->archive_loaded_path : + (state->shared_path_count ? state->shared_paths[0] : NULL); + if (state->pool_initialized) return; + if (load_client_identity(state)) { + snprintf(state->network_status, sizeof(state->network_status), "CLIENT IDENTITY ERROR"); + return; + } + if (cesspool_init(&state->pool, state->identity.pubkey)) { + snprintf(state->network_status, sizeof(state->network_status), "NETWORK ERROR"); + return; + } + state->pool_initialized = 1; + state->pool.on_transport_packet = transport_packet_received; + state->pool.transport_context = state; + if (listen_client_pool(state)) { + network_stop(state); + snprintf(state->network_status, sizeof(state->network_status), "NETWORK ERROR"); + return; + } + state->network_requested = 1; + if (session_path) { + dat_archive_t archive; + if (dat_archive_open(&archive, session_path) != 0 || + cesspool_discovery_key(archive.metadata_key, state->discovery_topic)) { + if (state->pool_initialized) network_stop(state); + snprintf(state->network_status, sizeof(state->network_status), "ARCHIVE ERROR"); + return; + } + dat_archive_close(&archive); + } + bootstraps = configure_bootstraps(&state->pool); + if (bootstraps < 0) { + network_stop(state); + snprintf(state->network_status, sizeof(state->network_status), "CESSPOOL NODE CONFIG ERROR"); + return; + } + if (bootstraps > 0) { + network_announce_shared_paths(state); + } + if (session_path) { + dat_archive_t archive; + if (dat_archive_open(&archive, session_path)) { + network_stop(state); + snprintf(state->network_status, sizeof(state->network_status), "ARCHIVE ERROR"); + return; + } + if (open_session_feed(&state->metadata_feed, archive.dat_path, + "metadata", archive.metadata_key) || + open_session_feed(&state->content_feed, archive.dat_path, + "content", archive.content_key) || + dat_session_init(&state->session, &state->pool, archive.metadata_key, + state->identity.pubkey, state->pool.port, + &state->metadata_feed, archive.metadata_key, + &state->content_feed, archive.content_key, + session_send_frame, state)) { + dat_archive_close(&archive); + network_stop(state); + snprintf(state->network_status, sizeof(state->network_status), "SESSION ERROR"); + return; + } + dat_archive_close(&archive); + state->session_initialized = 1; + if (dat_session_set_peer_callback(&state->session, + session_peer_discovered, state) || + dat_session_start(&state->session, 1, 1)) { + network_stop(state); + snprintf(state->network_status, sizeof(state->network_status), "SESSION ERROR"); + return; + } + state->topic_joined = 1; + snprintf(state->network_status, sizeof(state->network_status), + bootstraps > 0 ? "DISCOVERING VIA CESSPOOL" : "LAN PEER SEARCH"); + network_announce_shared_paths(state); + } else if (bootstraps <= 0) { + snprintf(state->network_status, sizeof(state->network_status), "LAN SEARCH - NODE UNSET"); + } else { + snprintf(state->network_status, sizeof(state->network_status), "WAITING FOR NODE"); + } + state->online = state->pool.bootstrap_reachable || state->connected_peers > 0; + state->rate_sample_at = SDL_GetTicks(); +} + +static void network_start_remote_import(gui_state_t *state) +{ + char datdir[1100], keyfile[1200]; + uint8_t unknown_content[DAT_KEY_BYTES] = {0}, existing_key[DAT_KEY_BYTES]; + struct stat info; + int bootstraps, n; + if (state->pool_initialized) return; + if (!state->remote_key_valid) { + snprintf(state->network_status, sizeof(state->network_status), "INVALID REMOTE DAT URI"); + return; + } + if (stat(state->archive_path, &info) || !S_ISDIR(info.st_mode)) { + snprintf(state->network_status, sizeof(state->network_status), "CHOOSE DESTINATION DIRECTORY"); + return; + } + n = snprintf(datdir, sizeof(datdir), "%s/.dat", state->archive_path); + if (n < 0 || (size_t)n >= sizeof(datdir) || make_directory(datdir)) { + snprintf(state->network_status, sizeof(state->network_status), "DESTINATION ERROR"); + return; + } + n = snprintf(keyfile, sizeof(keyfile), "%s/metadata.key", datdir); + if (n < 0 || (size_t)n >= sizeof(keyfile)) return; + if (sleep_load_pubkey(keyfile, existing_key) == 0) { + if (memcmp(existing_key, state->remote_metadata_key, DAT_KEY_BYTES)) { + snprintf(state->network_status, sizeof(state->network_status), "DESTINATION HAS ANOTHER DAT"); + return; + } + } else if (errno == ENOENT) { + if (sleep_save_pubkey(keyfile, state->remote_metadata_key)) return; + } else return; + if (dat_archive_open(&(dat_archive_t){0}, state->archive_path) == 0) { + refresh_archive(state); + network_start(state); + return; + } + if (open_session_feed(&state->metadata_feed, datdir, "metadata", + state->remote_metadata_key) || + sleep_feed_open_remote(&state->content_feed, datdir, "content", unknown_content)) { + network_stop(state); + snprintf(state->network_status, sizeof(state->network_status), "REMOTE CACHE ERROR"); + return; + } + if (load_client_identity(state) || + cesspool_init(&state->pool, state->identity.pubkey)) { + network_stop(state); + snprintf(state->network_status, sizeof(state->network_status), "NETWORK ERROR"); + return; + } + state->pool_initialized = 1; + state->pool.on_transport_packet = transport_packet_received; + state->pool.transport_context = state; + if (listen_client_pool(state)) { + network_stop(state); + snprintf(state->network_status, sizeof(state->network_status), "NETWORK ERROR"); + return; + } + state->network_requested = 1; + bootstraps = configure_bootstraps(&state->pool); + if (bootstraps < 0 || cesspool_discovery_key(state->remote_metadata_key, + state->discovery_topic) || + dat_session_init(&state->session, &state->pool, state->remote_metadata_key, + state->identity.pubkey, state->pool.port, + &state->metadata_feed, state->remote_metadata_key, + &state->content_feed, unknown_content, + session_send_frame, state) || + dat_session_expect_content_key(&state->session)) { + network_stop(state); + snprintf(state->network_status, sizeof(state->network_status), "REMOTE SESSION ERROR"); + return; + } + state->session_initialized = 1; + state->remote_import_mode = 1; + if (dat_session_set_peer_callback(&state->session, session_peer_discovered, state) || + dat_session_start(&state->session, 1, 1)) { + network_stop(state); + snprintf(state->network_status, sizeof(state->network_status), "REMOTE DISCOVERY ERROR"); + return; + } + state->topic_joined = 1; + state->online = state->pool.bootstrap_reachable || state->connected_peers > 0; + state->rate_sample_at = SDL_GetTicks(); + snprintf(state->network_status, sizeof(state->network_status), + bootstraps ? "WAITING FOR NODE" : "REMOTE LAN SEARCH"); +} + +static int begin_remote_import(gui_state_t *state) +{ + char keyfile[1200]; + uint8_t existing_key[DAT_KEY_BYTES]; + int n, key_result, key_error; + if (!state->remote_address[0] || parse_remote_address(state) || !state->remote_key_valid) { + snprintf(state->network_status, sizeof(state->network_status), "INVALID OR UNRESOLVED DAT LINK"); + return -1; + } + n = snprintf(keyfile, sizeof(keyfile), "%s/.dat/metadata.key", state->archive_path); + if (n < 0 || (size_t)n >= sizeof(keyfile)) { + snprintf(state->network_status, sizeof(state->network_status), "DESTINATION PATH TOO LONG"); + return -1; + } + key_result = sleep_load_pubkey(keyfile, existing_key); + key_error = errno; + if (key_result == 0 && memcmp(existing_key, state->remote_metadata_key, sizeof(existing_key))) { + snprintf(state->network_status, sizeof(state->network_status), "CHOOSE AN EMPTY DESTINATION DIRECTORY"); + return -1; + } else if (key_result != 0 && key_error != ENOENT) { + snprintf(state->network_status, sizeof(state->network_status), "DESTINATION KEY CHECK FAILED"); + return -1; + } + if (state->pool_initialized) network_stop(state); + network_start_remote_import(state); + return state->remote_import_mode || + (state->archive_open && !memcmp(state->archive_key_bytes, state->remote_metadata_key, DAT_KEY_BYTES)) ? 0 : -1; +} + +static int paste_remote_address(gui_state_t *state) +{ + char *clipboard = SDL_GetClipboardText(); + size_t length; + if (!clipboard) return -1; + length = strcspn(clipboard, "\r\n\t "); + if (!length || length >= sizeof(state->remote_address)) { + SDL_free(clipboard); + errno = EINVAL; + return -1; + } + memcpy(state->remote_address, clipboard, length); + state->remote_address[length] = '\0'; + SDL_free(clipboard); + return 0; +} + +static int paste_chat_text(gui_state_t *state) +{ + char *clipboard = SDL_GetClipboardText(); + size_t used = strlen(state->chat_input), added; + if (!clipboard) return -1; + added = strcspn(clipboard, "\r\n"); + if (!added || added > CABLE_CHAT_TEXT_BYTES - used) { + SDL_free(clipboard); + errno = added ? EMSGSIZE : EINVAL; + return -1; + } + memcpy(state->chat_input + used, clipboard, added); + state->chat_input[used + added] = '\0'; + SDL_free(clipboard); + return 0; +} + +static void network_pump(gui_state_t *state, Uint32 now) +{ + size_t i; + int poll_result = 0, poll_error = 0; + if (state->cable_thang_mode && !state->cable_network_active) return; + if (!state->pool_initialized) return; + if (state->session_initialized) poll_result = dat_session_tick(&state->session, now); + else { + poll_result = cesspool_poll(&state->pool, 0); + if (poll_result >= 0 && cesspool_tick(&state->pool, now)) poll_result = -1; + } + if (poll_result < 0 && errno != EINTR) { + poll_error = errno ? errno : EIO; + network_stop(state); + if (state->cable_thang_mode) + snprintf(state->cable_status, sizeof(state->cable_status), "NETWORK SOCKET ERROR: %.28s", strerror(poll_error)); + else + snprintf(state->network_status, sizeof(state->network_status), "NETWORK SOCKET ERROR: %.27s", strerror(poll_error)); + return; + } + cable_connect_discovered(state); + if (state->cable_thang_mode) cable_peer_refresh(state); + for (i = 0; i < GUI_TRANSPORT_MAX; i++) if (state->transports[i].used) { + gui_transport_peer_t *transport = &state->transports[i]; + if (transport->waiting_incoming && transport->utp.state == SLEEP_UTP_CLOSED) + continue; + if (sleep_utp_tick(&transport->utp, now) != 0 || + transport->utp.state == SLEEP_UTP_RESET || + transport->utp.state == SLEEP_UTP_CLOSED) { + if (transport->authenticated && state->session_initialized) + (void)dat_session_peer_down(&state->session, transport->peer_id); + transport->authenticated = 0; + transport->used = 0; + } else if (transport->authenticated && + transport_queue_flush(state, transport)) { + if (state->session_initialized) + (void)dat_session_peer_down(&state->session, transport->peer_id); + transport->authenticated = 0; + transport->used = 0; + } + } + speaker_sonar_tick(state, now); + update_connection_count(state); + update_transfer_rates(state, now); + if (state->cable_thang_mode) { + int reachable = state->pool.bootstrap_reachable || state->connected_peers > 0; + if (reachable != state->online) { + state->online = reachable; + if (reachable) { + snprintf(state->cable_status, sizeof(state->cable_status), "ONLINE - NETWORK RESPONSE RECEIVED"); + } else { + snprintf(state->cable_status, sizeof(state->cable_status), + "OFFLINE - NO REACHABLE BOOTSTRAP OR PEERS"); + } + } + } else { + int reachable = state->pool.bootstrap_reachable || state->connected_peers > 0; + state->online = reachable; + if (state->network_requested && !reachable && state->pool.bootstrap_count == 0 && + strcmp(state->network_status, "LAN SEARCH - NODE UNSET")) + snprintf(state->network_status, sizeof(state->network_status), "LAN SEARCH - NODE UNSET"); + else if (state->network_requested && !reachable && state->pool.bootstrap_count > 0 && + !state->remote_import_mode && !state->session_initialized) + snprintf(state->network_status, sizeof(state->network_status), "WAITING FOR NODE"); + else if (reachable && !state->remote_import_mode) + snprintf(state->network_status, sizeof(state->network_status), + state->pool.bootstrap_reachable ? "CESSPOOL ONLINE" : "PEER CONNECTED"); + } +} + +static void cable_room_append_line(gui_state_t *state, const cable_room_event_t *event) +{ + gui_cable_line_t *line; + if (state->cable_line_count == GUI_CABLE_LINES) { + memmove(state->cable_lines, state->cable_lines + 1, + (GUI_CABLE_LINES - 1) * sizeof(state->cable_lines[0])); + state->cable_line_count--; + } + line = &state->cable_lines[state->cable_line_count++]; + line->event = *event; +} + +static void cable_channels_sort(gui_state_t *state) +{ + size_t i, j; + for (i = 1; i < state->cable_channel_count; i++) { + char value[CABLE_ROOM_CHANNEL_BYTES + 1u]; + strcpy(value, state->cable_channels[i]); + j = i; + while (j > 1 && strcmp(state->cable_channels[j - 1], value) > 0) { + strcpy(state->cable_channels[j], state->cable_channels[j - 1]); + j--; + } + strcpy(state->cable_channels[j], value); + } +} + +static int cable_room_add_channel(gui_state_t *state, const char *channel) +{ + size_t i, length; + if (!channel || !*channel || channel[0] == '!' || + (length = strlen(channel)) > CABLE_ROOM_CHANNEL_BYTES) { errno = EINVAL; return -1; } + for (i = 0; i < length; i++) + if ((unsigned char)channel[i] <= ' ' || channel[i] == '/') { errno = EINVAL; return -1; } + for (i = 0; i < state->cable_channel_count; i++) + if (!strcmp(state->cable_channels[i], channel)) return 0; + if (state->cable_channel_count == GUI_CABLE_CHANNELS) { errno = ENOSPC; return -1; } + strcpy(state->cable_channels[state->cable_channel_count++], channel); + cable_channels_sort(state); + for (i = 0; i < state->cable_channel_count; i++) + if (!strcmp(state->cable_channels[i], channel)) state->cable_channel_index = i; + return 1; +} + +static int cable_write_all(int fd, const void *buffer, size_t length) +{ + const uint8_t *bytes = (const uint8_t *)buffer; + while (length) { + ssize_t written = write(fd, bytes, length); + if (written < 0 && errno == EINTR) continue; + if (written <= 0) { if (!errno) errno = EIO; return -1; } + bytes += (size_t)written; + length -= (size_t)written; + } + return 0; +} + +static int cable_room_save_profile(gui_state_t *state) +{ + char path[1200], temp[1240], contents[4096]; + int fd, n; + size_t i, offset = 0; + n = snprintf(path, sizeof(path), "%s/profile", state->cable_room_dir); + if (n < 0 || (size_t)n >= sizeof(path)) { errno = ENAMETOOLONG; return -1; } + n = snprintf(temp, sizeof(temp), "%s.tmp.%ld", path, (long)getpid()); + if (n < 0 || (size_t)n >= sizeof(temp)) { errno = ENAMETOOLONG; return -1; } + n = snprintf(contents, sizeof(contents), "%s\n", state->cable_nick); + if (n < 0 || (size_t)n >= sizeof(contents)) { errno = ENOSPC; return -1; } + offset = (size_t)n; + for (i = 0; i < state->cable_channel_count; i++) { + n = snprintf(contents + offset, sizeof(contents) - offset, "%s\n", state->cable_channels[i]); + if (n < 0 || (size_t)n >= sizeof(contents) - offset) { errno = ENOSPC; return -1; } + offset += (size_t)n; + } + fd = open(temp, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0600); + if (fd < 0) return -1; + if (cable_write_all(fd, contents, offset) || fsync(fd)) { + int saved = errno ? errno : EIO; + (void)close(fd); (void)unlink(temp); errno = saved; return -1; + } + if (close(fd)) { int saved = errno; (void)unlink(temp); errno = saved; return -1; } + if (dat_replace_file(temp, path)) { int saved = errno; (void)unlink(temp); errno = saved; return -1; } + return 0; +} + +static void cable_room_load_profile(gui_state_t *state) +{ + char path[1200], line[CABLE_ROOM_CHANNEL_BYTES + 2u]; + FILE *file; + int first = 1; + if (snprintf(path, sizeof(path), "%s/profile", state->cable_room_dir) < 0) return; + file = fopen(path, "r"); + if (!file) return; + while (fgets(line, sizeof(line), file)) { + size_t length = strlen(line); + while (length && (line[length - 1] == '\n' || line[length - 1] == '\r')) line[--length] = '\0'; + if (first) { + if (length && length <= CABLE_ROOM_NICK_BYTES) strcpy(state->cable_nick, line); + first = 0; + } else if (length) (void)cable_room_add_channel(state, line); + } + fclose(file); +} + +static int cable_thang_room_init(gui_state_t *state, const char *address) +{ + const char *home = gui_home_directory(); + cable_room_address_t room; + char identity_path[1200]; + if (!home || strlen(home) >= sizeof(state->cable_home) || sleep_crypto_init()) return -1; + strcpy(state->cable_home, home); + memset(&room, 0, sizeof(room)); + if (cable_room_address_parse(address, &room)) return -1; + memcpy(state->cable_room_key, room.key, sizeof(state->cable_room_key)); + if (cable_room_address_format(&room, CABLE_ADDRESS_CABLE, + state->cable_room_address, sizeof(state->cable_room_address)) || + cable_user_identity_path(home, room.key, identity_path, sizeof(identity_path))) return -1; + if (strlen(identity_path) >= sizeof(state->cable_room_dir)) return -1; + strcpy(state->cable_room_dir, identity_path); + state->cable_room_dir[strlen(state->cable_room_dir) - strlen("/user-feed.key")] = '\0'; + if (load_room_identity(state, room.key)) return -1; + { + static const char hex[] = "0123456789abcdef"; + snprintf(state->cable_nick, sizeof(state->cable_nick), "user-"); + for (size_t i = 0; i < 4; i++) { + state->cable_nick[5 + i * 2] = hex[state->identity.pubkey[i] >> 4]; + state->cable_nick[6 + i * 2] = hex[state->identity.pubkey[i] & 15]; + } + state->cable_nick[13] = '\0'; + } + state->cable_channel_count = 1; + strcpy(state->cable_channels[0], "default"); + cable_room_load_profile(state); + if (cable_room_add_channel(state, "default") < 0) return -1; + state->cable_channel_index = 0; + (void)cable_room_save_profile(state); + return 0; +} + +static int cable_thang_network_start(gui_state_t *state) +{ + int bootstraps; + if (state->cable_network_active) { errno = EALREADY; return -1; } + if (cesspool_init(&state->pool, state->identity.pubkey)) return -1; + state->pool_initialized = 1; + state->pool.on_transport_packet = transport_packet_received; + state->pool.transport_context = state; + if (listen_client_pool(state) || + cesspool_discovery_key(state->cable_room_key, state->discovery_topic)) { + int saved = errno; + network_stop(state); + errno = saved; + return -1; + } + bootstraps = configure_bootstraps(&state->pool); + if (bootstraps < 0 || cesspool_join(&state->pool, state->discovery_topic, + state->pool.port, 1, 1)) { + int saved = errno; + network_stop(state); + errno = saved; + return -1; + } + state->topic_joined = 1; + state->cable_network_active = 1; + state->online = 0; + state->rate_sample_at = SDL_GetTicks(); + snprintf(state->network_status, sizeof(state->network_status), bootstraps ? + "CESSPOOL DISCOVERING" : "LAN DISCOVERY"); + snprintf(state->cable_status, sizeof(state->cable_status), bootstraps ? + "WAITING FOR BOOTSTRAP RESPONSE" : "LOOKING FOR LOCAL NETWORK PEERS"); + return 0; +} + +static int cable_thang_scheme_init(gui_state_t *state) +{ + const char *configured = getenv("DAT_SCHEME_DIR"); + const char *dirs[5]; + char *base_path; + char exe_scheme[1200]; + char path[1200]; + int i, n; + dirs[0] = configured; + dirs[1] = "scheme"; + dirs[2] = "build/windows/scheme"; + base_path = SDL_GetBasePath(); + dirs[3] = NULL; + if (base_path) { + n = snprintf(exe_scheme, sizeof(exe_scheme), "%sscheme", base_path); + if (n >= 0 && (size_t)n < sizeof(exe_scheme)) dirs[3] = exe_scheme; + } + dirs[4] = "/usr/local/share/datgpt/scheme"; + if (dat_scheme_runtime_init(&state->metadata_scheme)) { + if (base_path) SDL_free(base_path); + return -1; + } + for (i = 0; i < 5; i++) { + if (!dirs[i] || !*dirs[i]) continue; + n = snprintf(path, sizeof(path), "%s/cable-room.scm", dirs[i]); + if (n < 0 || (size_t)n >= sizeof(path)) continue; + if (access(path, R_OK) == 0 && s7_load(state->metadata_scheme.scheme, path)) { + if (base_path) SDL_free(base_path); + return 0; + } + } + if (base_path) SDL_free(base_path); + dat_scheme_runtime_close(&state->metadata_scheme); + errno = ENOENT; + return -1; +} + +static int cable_peer_index(gui_state_t *state, const char *key, int create) +{ + size_t i; + for (i = 0; i < state->cable_peer_count; i++) + if (!strcmp(state->cable_peers[i].key, key)) return (int)i; + if (!create || state->cable_peer_count >= GUI_CABLE_PEERS || strlen(key) != 64) return -1; + i = state->cable_peer_count++; + memset(&state->cable_peers[i], 0, sizeof(state->cable_peers[i])); + strcpy(state->cable_peers[i].key, key); + return (int)i; +} + +static void cable_peer_refresh(gui_state_t *state) +{ + cesspool_peer_t *discovered[CESSPOOL_MAX_PEERS]; + size_t i, count; + state->connected_peers = 0; + for (i = 0; i < state->cable_peer_count; i++) state->cable_peers[i].connected = 0; + count = cesspool_topic_peers(&state->pool, state->discovery_topic, + discovered, CESSPOOL_MAX_PEERS); + for (i = 0; i < count; i++) { + char key[65]; + int index; + gui_transport_peer_t *transport; + if (!memcmp(discovered[i]->public_key, state->identity.pubkey, 32) || + dat_key_to_hex(discovered[i]->public_key, key)) continue; + index = cable_peer_index(state, key, 1); + if (index < 0) continue; + transport = transport_by_id(state, discovered[i]->public_key); + state->cable_peers[index].connected = transport && transport->authenticated; + if (state->cable_peers[index].connected) state->connected_peers++; + } +} + +static void cable_live_wave_push(gui_state_t *state, const int16_t *samples, size_t count) +{ + size_t i; + for (i = 0; i < count; i++) { + state->cable_live_wave[state->cable_live_wave_head] = samples[i]; + state->cable_live_wave_head = (state->cable_live_wave_head + 1) % GUI_CABLE_WAVE_SAMPLES; + if (state->cable_live_wave_count < GUI_CABLE_WAVE_SAMPLES) state->cable_live_wave_count++; + } +} + +static void cable_audio_capture_callback(void *context, Uint8 *stream, int length) +{ + gui_state_t *state = (gui_state_t *)context; + const int16_t *samples = (const int16_t *)stream; + Uint32 head = state->cable_capture_head; + Uint32 tail = state->cable_capture_tail; + int count = length / (int)sizeof(*samples), i; + for (i = 0; i < count; i++) { + Uint32 next = (head + 1u) % GUI_CABLE_AUDIO_RING; + if (next == tail) break; + state->cable_capture_ring[head] = samples[i]; + head = next; + } + state->cable_capture_head = head; +} + +static int cable_live_send_session(gui_state_t *state, + const uint8_t session[CABLE_MEDIA_SESSION_BYTES], uint8_t kind, + const uint8_t *payload, size_t payload_length, + gui_transport_peer_t *only_peer) +{ + cable_media_packet_t packet; + uint8_t wire[CABLE_MEDIA_MAX_PACKET_BYTES]; + size_t written; + if (!state->cable_network_active) { errno = ENOTCONN; return -1; } + memset(&packet, 0, sizeof(packet)); + packet.kind = kind; + memcpy(packet.session, session, sizeof(packet.session)); + packet.sequence = state->cable_live_sequence++; + packet.timestamp_us = (uint64_t)SDL_GetTicks() * 1000u; + packet.payload = payload; + packet.payload_length = payload_length; + if (cable_media_packet_write(&packet, wire, sizeof(wire), &written)) return -1; + { + uint8_t record[CABLE_MEDIA_MAX_PACKET_BYTES + 8u]; + size_t varint_length, prefix; + int queued = 0; + record[0] = 4; + if (sleep_varint_encode(written, record + 1, sizeof(record) - 1, &varint_length)) return -1; + prefix = 1 + varint_length; + memcpy(record + prefix, wire, written); + if (only_peer) { + if (only_peer->used && only_peer->authenticated && + transport_queue_append(state, only_peer, record, prefix + written) == 0) queued++; + } else for (size_t i = 0; i < GUI_TRANSPORT_MAX; i++) { + gui_transport_peer_t *peer = &state->transports[i]; + if (peer->used && peer->authenticated && + transport_queue_append(state, peer, record, prefix + written) == 0) queued++; + } + return queued; + } +} + +static int cable_live_send(gui_state_t *state, uint8_t kind, + const uint8_t *payload, size_t payload_length) +{ + return cable_live_send_session(state, state->cable_live_session, kind, + payload, payload_length, NULL); +} + +static int cable_audio_send_encoded(void *context, const uint8_t *adts, size_t length) +{ + return cable_live_send((gui_state_t *)context, CABLE_MEDIA_AUDIO_FRAME, adts, length); +} + +static int cable_audio_start(gui_state_t *state) +{ + SDL_AudioSpec wanted, obtained; + static const uint8_t aac_descriptor[] = "AAC-LC;RATE=48000;CHANNELS=1;BITRATE=96000"; + if (state->cable_audio_broadcasting) return 0; + if (!state->cable_network_active) { + snprintf(state->cable_live_status, sizeof(state->cable_live_status), "JOIN A ROOM FIRST"); + return -1; + } + if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0 || cable_aac_encoder_open(&state->cable_aac_encoder)) { + snprintf(state->cable_live_status, sizeof(state->cable_live_status), "AAC ENCODER UNAVAILABLE"); + return -1; + } + memset(&wanted, 0, sizeof(wanted)); + wanted.freq = CABLE_MEDIA_AAC_RATE; + wanted.format = AUDIO_S16SYS; + wanted.channels = CABLE_MEDIA_AAC_CHANNELS; + wanted.samples = CABLE_MEDIA_AAC_SAMPLES; + wanted.callback = cable_audio_capture_callback; + wanted.userdata = state; + state->cable_audio_capture = SDL_OpenAudioDevice(NULL, 1, &wanted, &obtained, 0); + if (!state->cable_audio_capture || obtained.freq != CABLE_MEDIA_AAC_RATE || + obtained.format != AUDIO_S16SYS || obtained.channels != CABLE_MEDIA_AAC_CHANNELS) { + if (state->cable_audio_capture) SDL_CloseAudioDevice(state->cable_audio_capture); + state->cable_audio_capture = 0; + cable_aac_encoder_close(&state->cable_aac_encoder); + snprintf(state->cable_live_status, sizeof(state->cable_live_status), "MIC NEEDS 48K MONO S16"); + return -1; + } + randombytes_buf(state->cable_live_session, sizeof(state->cable_live_session)); + state->cable_live_sequence = 0; + state->cable_capture_head = state->cable_capture_tail = 0; + state->cable_live_wave_head = state->cable_live_wave_count = 0; + if (cable_live_send(state, CABLE_MEDIA_AUDIO_START, aac_descriptor, sizeof(aac_descriptor) - 1)) { + SDL_CloseAudioDevice(state->cable_audio_capture); state->cable_audio_capture = 0; + cable_aac_encoder_close(&state->cable_aac_encoder); + snprintf(state->cable_live_status, sizeof(state->cable_live_status), "STREAM PEER LINK FAILED"); + return -1; + } + state->cable_audio_broadcasting = 1; + snprintf(state->cable_live_status, sizeof(state->cable_live_status), "BROADCASTING AAC 96K"); + SDL_PauseAudioDevice(state->cable_audio_capture, 0); + return 0; +} + +static void cable_audio_stop(gui_state_t *state, int announce) +{ + if (state->cable_audio_broadcasting && announce) + (void)cable_live_send(state, CABLE_MEDIA_AUDIO_STOP, NULL, 0); + if (state->cable_audio_capture) SDL_CloseAudioDevice(state->cable_audio_capture); + state->cable_audio_capture = 0; + state->cable_audio_broadcasting = 0; + cable_aac_encoder_close(&state->cable_aac_encoder); + if (state->cable_audio_playback) SDL_ClearQueuedAudio(state->cable_audio_playback); +} + +static int cable_audio_play_samples(void *context, const int16_t *samples, size_t count) +{ + gui_state_t *state = (gui_state_t *)context; + if (!state->cable_audio_playback) { + SDL_AudioSpec wanted; + memset(&wanted, 0, sizeof(wanted)); + wanted.freq = CABLE_MEDIA_AAC_RATE; + wanted.format = AUDIO_S16SYS; + wanted.channels = CABLE_MEDIA_AAC_CHANNELS; + wanted.samples = CABLE_MEDIA_AAC_SAMPLES; + state->cable_audio_playback = SDL_OpenAudioDevice(NULL, 0, &wanted, NULL, 0); + if (!state->cable_audio_playback) return -1; + } + if (SDL_GetQueuedAudioSize(state->cable_audio_playback) > CABLE_MEDIA_AAC_RATE * sizeof(int16_t) / 2u) + SDL_ClearQueuedAudio(state->cable_audio_playback); + cable_live_wave_push(state, samples, count); + return SDL_QueueAudio(state->cable_audio_playback, samples, + (Uint32)(count * sizeof(*samples))); +} + +static void cable_live_receive_wire(gui_state_t *state, const uint8_t peer_id[32], + const uint8_t *wire, size_t wire_length) +{ + cable_media_packet_t packet; + if (!peer_id || !wire || cable_media_packet_read(&packet, wire, wire_length)) return; + if (packet.kind == CABLE_MEDIA_AUDIO_START) { + static const char expected[] = "AAC-LC;RATE=48000;CHANNELS=1;BITRATE=96000"; + if (packet.payload_length != sizeof(expected) - 1 || + memcmp(packet.payload, expected, sizeof(expected) - 1)) return; + if (state->cable_audio_broadcasting && + sodium_memcmp(packet.session, state->cable_live_session, sizeof(packet.session)) == 0) return; + cable_aac_decoder_close(&state->cable_aac_decoder); + if (cable_aac_decoder_open(&state->cable_aac_decoder)) { + snprintf(state->cable_live_status, sizeof(state->cable_live_status), "AAC DECODER UNAVAILABLE"); + return; + } + memcpy(state->cable_live_sender_session, packet.session, sizeof(packet.session)); + state->cable_audio_receiving = 1; + state->cable_live_wave_head = state->cable_live_wave_count = 0; + snprintf(state->cable_live_status, sizeof(state->cable_live_status), "RECEIVING AAC 96K"); + } else if (packet.kind == CABLE_MEDIA_AUDIO_FRAME && state->cable_audio_receiving && + sodium_memcmp(packet.session, state->cable_live_sender_session, sizeof(packet.session)) == 0) { + if (cable_aac_decoder_frame(&state->cable_aac_decoder, packet.payload, + packet.payload_length, cable_audio_play_samples, state)) + snprintf(state->cable_live_status, sizeof(state->cable_live_status), "AAC FRAME ERROR"); + } else if (packet.kind == CABLE_MEDIA_AUDIO_STOP && state->cable_audio_receiving && + sodium_memcmp(packet.session, state->cable_live_sender_session, sizeof(packet.session)) == 0) { + state->cable_audio_receiving = 0; + cable_aac_decoder_close(&state->cable_aac_decoder); + if (state->cable_audio_playback) SDL_ClearQueuedAudio(state->cable_audio_playback); + snprintf(state->cable_live_status, sizeof(state->cable_live_status), "AUDIO STREAM ENDED"); + } else if (packet.kind == CABLE_MEDIA_VIDEO_START) { + static const char expected[] = "JPEG;MAX=160x90;FRAG=4000"; + if (packet.payload_length != sizeof(expected) - 1 || + memcmp(packet.payload, expected, sizeof(expected) - 1)) return; + memcpy(state->cable_video_sender_session, packet.session, sizeof(packet.session)); + memcpy(state->cable_video_rx_peer, peer_id, sizeof(state->cable_video_rx_peer)); + state->cable_video_receiving = 1; + state->cable_video_rx_active = 0; + state->cable_video_rx_length = 0; + snprintf(state->cable_video_status, sizeof(state->cable_video_status), "RECEIVING REMOTE VIDEO"); + } else if (packet.kind == CABLE_MEDIA_VIDEO_FRAME && state->cable_video_receiving && + sodium_memcmp(packet.session, state->cable_video_sender_session, + sizeof(packet.session)) == 0 && packet.payload_length > 12 && + !memcmp(peer_id, state->cable_video_rx_peer, sizeof(state->cable_video_rx_peer))) { + const uint8_t *fragment = packet.payload; + uint32_t frame_id = ((uint32_t)fragment[0] << 24) | ((uint32_t)fragment[1] << 16) | + ((uint32_t)fragment[2] << 8) | fragment[3]; + uint16_t part = (uint16_t)(((uint16_t)fragment[4] << 8) | fragment[5]); + uint16_t parts = (uint16_t)(((uint16_t)fragment[6] << 8) | fragment[7]); + uint32_t frame_length = ((uint32_t)fragment[8] << 24) | ((uint32_t)fragment[9] << 16) | + ((uint32_t)fragment[10] << 8) | fragment[11]; + size_t fragment_length = packet.payload_length - 12; + if (!frame_id || !parts || part >= parts || !frame_length || + frame_length > sizeof(state->cable_video_rx)) return; + if (parts != (frame_length + 3999u) / 4000u || fragment_length > 4000u) return; + if (part == 0) { + state->cable_video_rx_active = 1; + state->cable_video_rx_frame_id = frame_id; + state->cable_video_rx_frame_length = frame_length; + state->cable_video_rx_parts = parts; + state->cable_video_rx_next_part = 0; + state->cable_video_rx_length = 0; + } + if (!state->cable_video_rx_active || state->cable_video_rx_frame_id != frame_id || + state->cable_video_rx_parts != parts || state->cable_video_rx_next_part != part || + state->cable_video_rx_length > state->cable_video_rx_frame_length || + fragment_length != (state->cable_video_rx_frame_length - state->cable_video_rx_length > 4000u ? + 4000u : state->cable_video_rx_frame_length - state->cable_video_rx_length)) { + state->cable_video_rx_active = 0; + return; + } + memcpy(state->cable_video_rx + state->cable_video_rx_length, fragment + 12, fragment_length); + state->cable_video_rx_length += fragment_length; + state->cable_video_rx_next_part++; + if (state->cable_video_rx_next_part == state->cable_video_rx_parts) { + int decoded = cable_video_decode_jpeg(state->cable_video_rx, + state->cable_video_rx_length, state->cable_video_pixels, + sizeof(state->cable_video_pixels), &state->cable_video_width, + &state->cable_video_height); + state->cable_video_rx_active = 0; + if (decoded > 0) { + state->cable_video_generation++; + snprintf(state->cable_video_status, sizeof(state->cable_video_status), + "REMOTE VIDEO %ux%u", state->cable_video_width, state->cable_video_height); + } + } + } else if (packet.kind == CABLE_MEDIA_VIDEO_STOP && state->cable_video_receiving && + sodium_memcmp(packet.session, state->cable_video_sender_session, + sizeof(packet.session)) == 0 && + !memcmp(peer_id, state->cable_video_rx_peer, sizeof(state->cable_video_rx_peer))) { + state->cable_video_receiving = 0; + state->cable_video_rx_active = 0; + snprintf(state->cable_video_status, sizeof(state->cable_video_status), "REMOTE VIDEO ENDED"); + } +} + +static void cable_audio_pump(gui_state_t *state) +{ + unsigned frames = 0; + if (!state->cable_audio_broadcasting || !state->cable_audio_capture) return; + while (frames++ < 3) { + int16_t samples[CABLE_MEDIA_AAC_SAMPLES]; + Uint32 available, tail; + unsigned i; + SDL_LockAudioDevice(state->cable_audio_capture); + tail = state->cable_capture_tail; + available = (state->cable_capture_head + GUI_CABLE_AUDIO_RING - tail) % GUI_CABLE_AUDIO_RING; + if (available < CABLE_MEDIA_AAC_SAMPLES) { + SDL_UnlockAudioDevice(state->cable_audio_capture); + break; + } + for (i = 0; i < CABLE_MEDIA_AAC_SAMPLES; i++) + samples[i] = state->cable_capture_ring[(tail + i) % GUI_CABLE_AUDIO_RING]; + state->cable_capture_tail = (tail + CABLE_MEDIA_AAC_SAMPLES) % GUI_CABLE_AUDIO_RING; + SDL_UnlockAudioDevice(state->cable_audio_capture); + cable_live_wave_push(state, samples, CABLE_MEDIA_AAC_SAMPLES); + if (cable_aac_encoder_frame(&state->cable_aac_encoder, samples, + cable_audio_send_encoded, state)) { + snprintf(state->cable_live_status, sizeof(state->cable_live_status), "AAC STREAM ERROR"); + cable_audio_stop(state, 1); + break; + } + } +} + +static void cable_video_toggle(gui_state_t *state) +{ + if (state->cable_video_source) { + (void)cable_live_send_session(state, state->cable_video_session, + CABLE_MEDIA_VIDEO_STOP, NULL, 0, NULL); + cable_video_source_close(state->cable_video_source); + state->cable_video_source = NULL; + state->cable_video_active = 0; + if (state->cable_video_texture) SDL_DestroyTexture(state->cable_video_texture); + state->cable_video_texture = NULL; + state->cable_video_generation = 0; + state->cable_video_source_generation = 0; + snprintf(state->cable_video_status, sizeof(state->cable_video_status), "OBS INPUT STOPPED"); + return; + } + if (!state->cable_network_active) { + snprintf(state->cable_video_status, sizeof(state->cable_video_status), "JOIN ROOM BEFORE VIDEO"); + return; + } + state->cable_video_source = cable_video_source_open( + "udp://127.0.0.1:5000?fifo_size=1000000&overrun_nonfatal=1"); + state->cable_video_active = state->cable_video_source != NULL; + if (state->cable_video_active) { + static const uint8_t descriptor[] = "JPEG;MAX=160x90;FRAG=4000"; + randombytes_buf(state->cable_video_session, sizeof(state->cable_video_session)); + state->cable_video_sent_generation = 0; + state->cable_video_last_sent_at = 0; + (void)cable_live_send_session(state, state->cable_video_session, + CABLE_MEDIA_VIDEO_START, descriptor, sizeof(descriptor) - 1, NULL); + } + snprintf(state->cable_video_status, sizeof(state->cable_video_status), "%s", + state->cable_video_active ? "OBS UDP :5000 - SHARING VIDEO" : "VIDEO INPUT UNAVAILABLE"); +} + +static void cable_video_pump(gui_state_t *state, SDL_Renderer *renderer) +{ + if (state->cable_video_source) { + unsigned width = 0, height = 0; + uint64_t source_generation = state->cable_video_source_generation; + int result = cable_video_source_frame(state->cable_video_source, state->cable_video_pixels, + sizeof(state->cable_video_pixels), &width, &height, + &source_generation); + if (result > 0 && source_generation != state->cable_video_source_generation) { + state->cable_video_source_generation = source_generation; + state->cable_video_width = width; + state->cable_video_height = height; + state->cable_video_generation++; + } + } + if (state->cable_video_source && state->cable_video_generation != state->cable_video_sent_generation && + (!state->cable_video_last_sent_at || + SDL_GetTicks() - state->cable_video_last_sent_at >= 200u)) { + uint8_t jpeg[256u * 1024u]; + uint8_t fragment[4012]; + size_t jpeg_length = 0, offset = 0; + if (cable_video_encode_jpeg(state->cable_video_pixels, state->cable_video_width, + state->cable_video_height, jpeg, sizeof(jpeg), &jpeg_length) == 0 && + jpeg_length && jpeg_length <= sizeof(state->cable_video_rx)) { + uint32_t frame_id = ++state->cable_video_frame_id; + uint16_t parts = (uint16_t)((jpeg_length + 3999u) / 4000u); + uint16_t part; + if (!frame_id) frame_id = ++state->cable_video_frame_id; + for (part = 0; part < parts; part++) { + size_t chunk = jpeg_length - offset; + if (chunk > 4000u) chunk = 4000u; + fragment[0] = (uint8_t)(frame_id >> 24); + fragment[1] = (uint8_t)(frame_id >> 16); + fragment[2] = (uint8_t)(frame_id >> 8); + fragment[3] = (uint8_t)frame_id; + fragment[4] = (uint8_t)(part >> 8); fragment[5] = (uint8_t)part; + fragment[6] = (uint8_t)(parts >> 8); fragment[7] = (uint8_t)parts; + fragment[8] = (uint8_t)(jpeg_length >> 24); + fragment[9] = (uint8_t)(jpeg_length >> 16); + fragment[10] = (uint8_t)(jpeg_length >> 8); + fragment[11] = (uint8_t)jpeg_length; + memcpy(fragment + 12, jpeg + offset, chunk); + (void)cable_live_send_session(state, state->cable_video_session, + CABLE_MEDIA_VIDEO_FRAME, fragment, chunk + 12, NULL); + offset += chunk; + } + state->cable_video_sent_generation = state->cable_video_generation; + state->cable_video_last_sent_at = SDL_GetTicks(); + } + } + if (!state->cable_video_generation || !state->cable_video_width || !state->cable_video_height || + state->cable_video_generation == state->cable_video_texture_generation) return; + if (state->cable_video_texture) SDL_DestroyTexture(state->cable_video_texture); + state->cable_video_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB24, + SDL_TEXTUREACCESS_STREAMING, (int)state->cable_video_width, (int)state->cable_video_height); + if (!state->cable_video_texture) { + snprintf(state->cable_video_status, sizeof(state->cable_video_status), "VIDEO TEXTURE FAILED"); + return; + } + SDL_UpdateTexture(state->cable_video_texture, NULL, state->cable_video_pixels, + (int)state->cable_video_width * 3); + state->cable_video_texture_generation = state->cable_video_generation; +} + +static int cable_room_wire_send(gui_state_t *state, const cable_room_event_t *event) +{ + s7_scheme *sc = state->metadata_scheme.scheme; + s7_pointer function, args, form, port; + const char *sexp; + uint8_t body[4096], record[4200]; + size_t sexp_length, body_length, prefix, written; + int sent = 0; + if (!sc) { errno = EINVAL; return -1; } + function = s7_name_to_value(sc, "cable:room-event->wire"); + args = s7_nil(sc); + args = s7_cons(sc, s7_make_string(sc, event->text), args); + args = s7_cons(sc, s7_make_string(sc, event->channel), args); + args = s7_cons(sc, s7_make_string(sc, event->nick), args); + args = s7_cons(sc, s7_make_integer(sc, (s7_int)event->timestamp_ms), args); + args = s7_cons(sc, s7_make_integer(sc, (s7_int)event->type), args); + form = s7_call(sc, function, args); + port = s7_open_output_string(sc); + if (!port) { errno = ENOMEM; return -1; } + (void)s7_write(sc, form, port); + sexp = s7_get_output_string(sc, port); + if (!sexp) { errno = EIO; return -1; } + sexp_length = strlen(sexp); + if (sexp_length > sizeof(body) - 96) { errno = EMSGSIZE; return -1; } + memcpy(body, state->identity.pubkey, 32); + if (sleep_sign(body + 32, (const uint8_t *)sexp, sexp_length, &state->identity)) return -1; + memcpy(body + 96, sexp, sexp_length); + body_length = 96 + sexp_length; + record[0] = 3; + if (sleep_varint_encode(body_length, record + 1, sizeof(record) - 1, &written)) return -1; + prefix = 1 + written; + memcpy(record + prefix, body, body_length); + for (size_t i = 0; i < GUI_TRANSPORT_MAX; i++) { + gui_transport_peer_t *peer = &state->transports[i]; + if (!peer->used || !peer->authenticated) continue; + if (transport_queue_append(state, peer, record, prefix + body_length)) sent++; + } + return sent; +} + +static int cable_room_broadcast(gui_state_t *state, uint8_t type, + const char *channel, const char *text_value) +{ + cable_room_event_t event; + int result = 0; + memset(&event, 0, sizeof(event)); + event.type = type; + event.timestamp_ms = (uint64_t)time(NULL) * 1000u; + memcpy(event.sender, state->identity.pubkey, sizeof(event.sender)); + snprintf(event.nick, sizeof(event.nick), "%s", state->cable_nick); + snprintf(event.channel, sizeof(event.channel), "%s", + channel ? channel : state->cable_channels[state->cable_channel_index]); + if (text_value) { + event.text_length = strlen(text_value); + if (event.text_length > CABLE_CHAT_TEXT_BYTES) { errno = EMSGSIZE; return -1; } + memcpy(event.text, text_value, event.text_length + 1); + } + if (!state->cable_network_active && type != CABLE_ROOM_PEER_JOIN) { errno = ENOTCONN; return -1; } + if (state->cable_thang_mode) { + result = cable_room_wire_send(state, &event); + if (result < 0) return -1; + } + if (result < 0) return -1; + cable_room_append_line(state, &event); + return 0; +} + +static int cable_room_submit(gui_state_t *state) +{ + char input[CABLE_CHAT_TEXT_BYTES + 1u]; + char *arg; + size_t i; + int recipients; + if (!state->cable_input[0]) return 0; + strcpy(input, state->cable_input); + state->cable_input[0] = '\0'; + if (input[0] == '/') { + arg = input + 1; + while (*arg && *arg != ' ') arg++; + if (*arg) *arg++ = '\0'; + while (*arg == ' ') arg++; + if (!strcmp(input + 1, "nick") && *arg) { + for (i = 0; arg[i]; i++) { + if ((unsigned char)arg[i] < 32u || (unsigned char)arg[i] == 127u) { + snprintf(state->cable_status, sizeof(state->cable_status), "NICK HAS CONTROL CHARACTERS"); return -1; + } + } + if (strlen(arg) > CABLE_ROOM_NICK_BYTES) { snprintf(state->cable_status, sizeof(state->cable_status), "NICK TOO LONG"); return -1; } + snprintf(state->cable_nick, sizeof(state->cable_nick), "%s", arg); + (void)cable_room_broadcast(state, CABLE_ROOM_NICK, + state->cable_channels[state->cable_channel_index], NULL); + (void)cable_room_save_profile(state); + snprintf(state->cable_status, sizeof(state->cable_status), "NICK CHANGED"); + return 0; + } + if (!strcmp(input + 1, "join") && *arg) { + int added = cable_room_add_channel(state, arg); + if (added < 0) { snprintf(state->cable_status, sizeof(state->cable_status), "INVALID CHANNEL NAME"); return -1; } + if (added) (void)cable_room_broadcast(state, CABLE_ROOM_CHANNEL_JOIN, arg, NULL); + (void)cable_room_save_profile(state); + snprintf(state->cable_status, sizeof(state->cable_status), "JOINED CHANNEL %s", arg); + return 0; + } + if (!strcmp(input + 1, "channels")) { + snprintf(state->cable_status, sizeof(state->cable_status), "%lu JOINED CHANNELS", (unsigned long)state->cable_channel_count); + return 0; + } + if (!strcmp(input + 1, "help")) { + snprintf(state->cable_status, sizeof(state->cable_status), "/JOIN NAME /NICK NAME CTRL+N/P CHANNEL ENTER SEND"); + return 0; + } + snprintf(state->cable_status, sizeof(state->cable_status), "UNKNOWN COMMAND - /HELP"); + return -1; + } + for (i = 0; input[i]; i++) + if ((unsigned char)input[i] < 32u) { snprintf(state->cable_status, sizeof(state->cable_status), "MESSAGE HAS CONTROL CHARACTERS"); return -1; } + recipients = cable_room_broadcast(state, CABLE_ROOM_TEXT, + state->cable_channels[state->cable_channel_index], input); + if (recipients < 0) { + snprintf(state->cable_status, sizeof(state->cable_status), "SEND FAILED"); + return -1; + } + snprintf(state->cable_status, sizeof(state->cable_status), recipients ? + "SENT TO %d CONNECTED PEER%s" : "NO CONNECTED PEERS - MESSAGE KEPT LOCAL", + recipients, recipients == 1 ? "" : "S"); + return 0; +} + +static void color(SDL_Renderer *r, unsigned char red, unsigned char green, + unsigned char blue) +{ + SDL_SetRenderDrawColor(r, red, green, blue, 255); +} + +static void fill(SDL_Renderer *r, int x, int y, int w, int h, + unsigned char red, unsigned char green, unsigned char blue) +{ + SDL_Rect box = {x, y, w, h}; + color(r, red, green, blue); + SDL_RenderFillRect(r, &box); +} + +static void outline(SDL_Renderer *r, SDL_Rect box, unsigned char red, + unsigned char green, unsigned char blue) +{ + color(r, red, green, blue); + SDL_RenderDrawRect(r, &box); + color(r, 112, 112, 112); + SDL_RenderDrawLine(r, box.x, box.y, box.x, box.y + box.h - 1); + SDL_RenderDrawLine(r, box.x, box.y, box.x + box.w - 1, box.y); +} + +static void text(SDL_Renderer *r, int x, int y, const char *value, int scale, + unsigned char red, unsigned char green, unsigned char blue) +{ + if (!gui_font_ready) return; + while (*value) { + Uint32 codepoint; + gui_font_glyph_t *glyph; + const unsigned char *s = (const unsigned char *)value; + if (s[0] < 0x80) { codepoint = s[0]; value++; } + else if (s[0] >= 0xc2 && s[0] <= 0xdf && (s[1] & 0xc0) == 0x80) { + codepoint = ((Uint32)(s[0] & 0x1f) << 6) | (s[1] & 0x3f); value += 2; + } else if (s[0] >= 0xe0 && s[0] <= 0xef && (s[1] & 0xc0) == 0x80 && (s[2] & 0xc0) == 0x80) { + codepoint = ((Uint32)(s[0] & 0x0f) << 12) | ((Uint32)(s[1] & 0x3f) << 6) | (s[2] & 0x3f); value += 3; + } else if (s[0] >= 0xf0 && s[0] <= 0xf4 && (s[1] & 0xc0) == 0x80 && (s[2] & 0xc0) == 0x80 && (s[3] & 0xc0) == 0x80) { + codepoint = ((Uint32)(s[0] & 7) << 18) | ((Uint32)(s[1] & 0x3f) << 12) | ((Uint32)(s[2] & 0x3f) << 6) | (s[3] & 0x3f); value += 4; + } else { codepoint = '?'; value++; } + if (codepoint >= 32 && codepoint <= 126) + glyph = &gui_font_glyphs[codepoint - 32]; + else if (codepoint > 126 && gui_unicode_font) { + size_t i; + gui_unicode_glyph_t *cached = NULL; + for (i = 0; i < gui_unicode_glyph_count; i++) + if (gui_unicode_glyphs[i].codepoint == codepoint) { cached = &gui_unicode_glyphs[i]; break; } + if (!cached && TTF_GlyphIsProvided32(gui_unicode_font, codepoint)) { + int minx, maxx, miny, maxy, advance; + SDL_Color white = {255,255,255,255}; + SDL_Surface *surface; + gui_unicode_glyph_t temporary; + memset(&temporary, 0, sizeof(temporary)); + temporary.codepoint = codepoint; + if (TTF_GlyphMetrics32(gui_unicode_font, codepoint, &minx, &maxx, &miny, &maxy, &advance) == 0) { + temporary.glyph.advance = advance > 0 ? advance : gui_font_cell_width * 2; + surface = TTF_RenderGlyph32_Solid(gui_unicode_font, codepoint, white); + if (surface) { + temporary.glyph.texture = SDL_CreateTextureFromSurface(r, surface); + temporary.glyph.width = surface->w; temporary.glyph.height = surface->h; + SDL_FreeSurface(surface); + if (temporary.glyph.texture) SDL_SetTextureBlendMode(temporary.glyph.texture, SDL_BLENDMODE_BLEND); + } + } + if (gui_unicode_glyph_count < sizeof(gui_unicode_glyphs) / sizeof(gui_unicode_glyphs[0])) { + cached = &gui_unicode_glyphs[gui_unicode_glyph_count++]; *cached = temporary; + } else if (temporary.glyph.texture) SDL_DestroyTexture(temporary.glyph.texture); + } + glyph = cached ? &cached->glyph : &gui_font_glyphs['?' - 32]; + } else glyph = &gui_font_glyphs['?' - 32]; + if (glyph->texture) { + SDL_Rect destination = {x, y, glyph->width * scale, + glyph->height * scale}; + SDL_SetTextureColorMod(glyph->texture, red, green, blue); + SDL_RenderCopy(r, glyph->texture, NULL, &destination); + } + x += glyph->advance * scale; + } +} + +static int gui_unicode_font_init(void) +{ + char exe_font[2048]; + char *base_path = SDL_GetBasePath(); + const char *paths[3] = {"assets/fonts/Unifont-18.0.01.otf", DAT_UNIFONT_PATH, NULL}; + size_t path_count = 2, i; + if (base_path) { + int n = snprintf(exe_font, sizeof(exe_font), "%sassets/fonts/Unifont-18.0.01.otf", base_path); + if (n >= 0 && (size_t)n < sizeof(exe_font)) paths[path_count++] = exe_font; + } + for (i = 0; i < path_count; i++) { + if (!paths[i] || !paths[i][0]) continue; + gui_unicode_font = TTF_OpenFont(paths[i], 14); + if (gui_unicode_font) break; + } + if (base_path) SDL_free(base_path); + if (!gui_unicode_font) return -1; + TTF_SetFontHinting(gui_unicode_font, TTF_HINTING_MONO); + TTF_SetFontKerning(gui_unicode_font, 0); + { + unsigned row; + for (row = 0; row < 2; row++) { + uint32_t count = row == 0 ? 32768u : 128u, index; + unsigned bits = row == 0 ? 15u : 7u; + for (index = 0; index < count; index++) { + uint32_t codepoint; + if (base32768_codepoint_at(bits, index, &codepoint)) return -1; + if (!TTF_GlyphIsProvided32(gui_unicode_font, codepoint)) { + SDL_SetError("Unifont is missing Base32768 code point U+%04X", (unsigned)codepoint); + return -1; + } + } + } + } + return 0; +} + +static int gui_font_init(SDL_Renderer *renderer) +{ + char exe_font[2048]; + char *base_path = SDL_GetBasePath(); + const char *paths[3] = {"assets/fonts/MorePerfectDOSVGA.ttf", DAT_FONT_PATH, NULL}; + SDL_Color white = {255, 255, 255, 255}; + size_t i, path_count = 2; + int minx, maxx, miny, maxy, advance; + if (base_path) { + int n = snprintf(exe_font, sizeof(exe_font), "%sassets/fonts/MorePerfectDOSVGA.ttf", base_path); + if (n >= 0 && (size_t)n < sizeof(exe_font)) paths[path_count++] = exe_font; + } + if (TTF_Init() != 0) { if (base_path) SDL_free(base_path); return -1; } + for (i = 0; i < path_count; i++) { + if (paths[i] && paths[i][0]) { + gui_font = TTF_OpenFont(paths[i], 14); + if (gui_font) { + const char *family = TTF_FontFaceFamilyName(gui_font); + if (family && !strcmp(family, "More Perfect DOS VGA")) break; + TTF_CloseFont(gui_font); + gui_font = NULL; + } + } + } + if (base_path) SDL_free(base_path); + if (!gui_font) { TTF_Quit(); return -1; } + TTF_SetFontHinting(gui_font, TTF_HINTING_MONO); + TTF_SetFontKerning(gui_font, 0); + if (TTF_GlyphMetrics32(gui_font, 'M', &minx, &maxx, &miny, &maxy, &advance) != 0 || advance <= 0) { + TTF_CloseFont(gui_font); gui_font = NULL; TTF_Quit(); return -1; + } + gui_font_cell_width = advance; + for (i = 0; i < 95; i++) { + Uint32 codepoint = (Uint32)i + 32u; + gui_font_glyph_t *glyph = &gui_font_glyphs[i]; + SDL_Surface *surface; + if (TTF_GlyphMetrics32(gui_font, codepoint, &minx, &maxx, &miny, &maxy, &advance) != 0) + continue; + glyph->advance = advance > 0 ? advance : gui_font_cell_width; + /* VGA glyphs are a hard pixel grid; blended edges make the face look wrong. */ + surface = TTF_RenderGlyph32_Solid(gui_font, codepoint, white); + if (!surface) continue; + glyph->texture = SDL_CreateTextureFromSurface(renderer, surface); + glyph->width = surface->w; + glyph->height = surface->h; + SDL_FreeSurface(surface); + if (glyph->texture) SDL_SetTextureBlendMode(glyph->texture, SDL_BLENDMODE_BLEND); + } + if (!gui_font_glyphs['?' - 32].texture) { + for (i = 0; i < 95; i++) if (gui_font_glyphs[i].texture) SDL_DestroyTexture(gui_font_glyphs[i].texture); + memset(gui_font_glyphs, 0, sizeof(gui_font_glyphs)); + TTF_CloseFont(gui_font); gui_font = NULL; TTF_Quit(); return -1; + } + gui_font_ready = 1; + return 0; +} + +static void gui_font_close(void) +{ + size_t i; + for (i = 0; i < 95; i++) { + if (gui_font_glyphs[i].texture) SDL_DestroyTexture(gui_font_glyphs[i].texture); + memset(&gui_font_glyphs[i], 0, sizeof(gui_font_glyphs[i])); + } + for (i = 0; i < gui_unicode_glyph_count; i++) + if (gui_unicode_glyphs[i].glyph.texture) SDL_DestroyTexture(gui_unicode_glyphs[i].glyph.texture); + memset(gui_unicode_glyphs, 0, sizeof(gui_unicode_glyphs)); + gui_unicode_glyph_count = 0; + gui_font_ready = 0; + if (gui_unicode_font) TTF_CloseFont(gui_unicode_font); + gui_unicode_font = NULL; + if (gui_font) TTF_CloseFont(gui_font); + gui_font = NULL; + TTF_Quit(); +} + +static void text_clipped(SDL_Renderer *r, SDL_Rect clip, int x, int y, + const char *value, int scale, unsigned char red, + unsigned char green, unsigned char blue) +{ + SDL_Rect old, intersection; + SDL_bool had_clip = SDL_RenderIsClipEnabled(r); + SDL_RenderGetClipRect(r, &old); + if (had_clip) { + int left = old.x > clip.x ? old.x : clip.x; + int top = old.y > clip.y ? old.y : clip.y; + int right = old.x + old.w < clip.x + clip.w ? old.x + old.w : clip.x + clip.w; + int bottom = old.y + old.h < clip.y + clip.h ? old.y + old.h : clip.y + clip.h; + intersection = (SDL_Rect){left, top, right - left, bottom - top}; + SDL_RenderSetClipRect(r, &intersection); + } else SDL_RenderSetClipRect(r, &clip); + text(r, x, y, value, scale, red, green, blue); + if (had_clip) SDL_RenderSetClipRect(r, &old); + else SDL_RenderSetClipRect(r, NULL); +} + +static size_t discovered_peer_count(const gui_state_t *state) +{ + size_t i, count = 0; + if (state->cable_thang_mode) return state->cable_peer_count; + if (!state->pool_initialized) return 0; + for (i = 0; i < state->pool.peer_count; i++) + if (state->pool.peers[i].discovered) count++; + return count; +} + +static void chat_append_line(gui_state_t *state,const uint8_t peer_id[32], + const cable_chat_message_t *message,int outgoing) +{ + gui_chat_line_t *line; + if(state->chat_line_count==GUI_CHAT_LINES){ + memmove(state->chat_lines,state->chat_lines+1,(GUI_CHAT_LINES-1)*sizeof(state->chat_lines[0])); + state->chat_line_count--; + } + line=&state->chat_lines[state->chat_line_count++];memset(line,0,sizeof(*line)); + memcpy(line->peer_id,peer_id,32);line->timestamp_ms=message->timestamp_ms; + memcpy(line->text,message->text,message->text_length);line->outgoing=outgoing; +} + +static int cable_chat_send(gui_state_t *state) +{ + gui_transport_peer_t *transport; cable_chat_message_t message; uint8_t plain[CABLE_CHAT_WIRE_BYTES]; + uint8_t sealed[CABLE_CHAT_SEAL_BYTES],record[CABLE_CHAT_SEAL_BYTES+8];size_t plain_n,sealed_n,prefix,var_n; + if(!state->chat_input[0])return 0; + transport=transport_by_id(state,state->chat_peer_id); + if(!transport||!transport->authenticated){snprintf(state->chat_status,sizeof(state->chat_status),"PEER NOT CONNECTED - NOT SENT");return -1;} + memset(&message,0,sizeof(message));message.timestamp_ms=(uint64_t)time(NULL)*1000u; + message.text_length=strlen(state->chat_input);memcpy(message.text,state->chat_input,message.text_length+1); + if(cable_chat_message_encode(&message,plain,sizeof(plain),&plain_n)|| + cable_chat_seal(state->chat_peer_id,plain,plain_n,sealed,sizeof(sealed),&sealed_n))return -1; + record[0]=2; + if(sleep_varint_encode(sealed_n,record+1,sizeof(record)-1,&var_n))return -1; + prefix=1+var_n;memcpy(record+prefix,sealed,sealed_n); + if(transport_queue_append(state,transport,record,prefix+sealed_n)){snprintf(state->chat_status,sizeof(state->chat_status),"MESSAGE QUEUE FAILED");return -1;} + chat_append_line(state,state->chat_peer_id,&message,1);state->chat_input[0]='\0'; + snprintf(state->chat_status,sizeof(state->chat_status),"SEALED CABLE MESSAGE QUEUED");return 0; +} + + +static void format_speed(char *out, size_t capacity, double kbps) +{ + if (kbps >= 1000.0) + snprintf(out, capacity, "%.1f MBPS", kbps / 1000.0); + else snprintf(out, capacity, "%.1f KBPS", kbps); +} + +static unsigned session_progress_percent(const dat_session_peer_t *peer) +{ + uint64_t total = 0, present = 0; + size_t feed_index; + if (!peer) return 0; + for (feed_index = 0; feed_index < DAT_SESSION_FEED_COUNT; feed_index++) { + const sleep_replication_t *rep = &peer->replication[feed_index]; + uint64_t block, length = rep->remote_length; + /* The negotiated bitfield covers 8192 data blocks. */ + if (length > SLEEP_BITFIELD_DATA_BYTES * 8u) + length = SLEEP_BITFIELD_DATA_BYTES * 8u; + total += length; + if (!rep->feed) continue; + for (block = 0; block < length; block++) + if (sleep_feed_has(rep->feed, (size_t)block)) present++; + } + return total ? (unsigned)(present * 100u / total) : 0; +} + +static void draw_progress_bar(SDL_Renderer *r, int x, int y, int width, + int height, unsigned percent) +{ + SDL_Rect frame = {x, y, width, height}; + int inner = width - 2, filled; + if (inner < 1 || height < 3) return; + if (percent > 100) percent = 100; + fill(r, x, y, width, height, 28, 28, 32); + filled = inner * (int)percent / 100; + if (filled > 0) fill(r, x + 1, y + 1, filled, height - 2, 153, 45, 160); + outline(r, frame, 210, 210, 205); +} + +static void draw_diamond(SDL_Renderer *r, int cx, int cy, int active) +{ + int dy; + color(r, active ? 240 : 126, active ? 245 : 148, active ? 255 : 115); + for (dy = -7; dy <= 7; dy++) { + int half = 7 - (dy < 0 ? -dy : dy); + if (active) { + SDL_RenderDrawLine(r, cx - half, cy + dy, cx + half, cy + dy); + } else { + SDL_RenderDrawPoint(r, cx - half, cy + dy); + SDL_RenderDrawPoint(r, cx + half, cy + dy); + } + } +} + +static void draw_online_hexagon(SDL_Renderer *r, int cx, int cy, int online, + int connected) +{ + int y; + unsigned char red = !online ? 236 : connected ? 72 : 210; + unsigned char green = !online ? 64 : connected ? 232 : 174; + unsigned char blue = !online ? 64 : connected ? 92 : 52; + for (y = -7; y <= 7; y++) { + int distance = abs(y); + int half = 4 + (4 * (7 - distance) + 3) / 7; + color(r, red, green, blue); + SDL_RenderDrawLine(r, cx - half, cy + y, cx + half, cy + y); + } +} + +static SDL_Texture *icon_texture_from_rle(SDL_Renderer *renderer, int width, int height, + const uint8_t palette[][3], size_t palette_count, + const uint8_t rle[][2], size_t run_count) +{ + SDL_Surface *surface = SDL_CreateRGBSurfaceWithFormat(0, width, height, 8, SDL_PIXELFORMAT_INDEX8); + SDL_Texture *texture; + SDL_Color colors[16]; + size_t i, pixel = 0, total = (size_t)width * (size_t)height; + if (!surface || palette_count > sizeof(colors) / sizeof(colors[0])) { + if (surface) SDL_FreeSurface(surface); + return NULL; + } + for (i = 0; i < palette_count; i++) { + colors[i].r = palette[i][0]; colors[i].g = palette[i][1]; colors[i].b = palette[i][2]; colors[i].a = 255; + } + if (SDL_SetPaletteColors(surface->format->palette, colors, 0, (int)palette_count) != 0) { + SDL_FreeSurface(surface); + return NULL; + } + for (i = 0; i < run_count && pixel < total; i++) { + size_t j, amount = rle[i][0]; + if (amount > total - pixel) amount = total - pixel; + for (j = 0; j < amount; j++, pixel++) + ((uint8_t *)surface->pixels)[(pixel / (size_t)width) * (size_t)surface->pitch + pixel % (size_t)width] = rle[i][1]; + } + if (pixel != total) { SDL_FreeSurface(surface); return NULL; } + SDL_SetColorKey(surface, SDL_TRUE, SDL_MapRGB(surface->format, 83, 158, 70)); + texture = SDL_CreateTextureFromSurface(renderer, surface); + SDL_FreeSurface(surface); + if (texture) SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND); + return texture; +} + +static SDL_Texture *speaker_texture_from_mockup(SDL_Renderer *renderer) +{ + char exe_icon[2048]; + char *base_path = SDL_GetBasePath(); + const char *paths[3] = {"assets/datthang-speaker.bmp", DAT_SPEAKER_PATH, NULL}; + SDL_Surface *surface = NULL; + SDL_Texture *texture; + size_t i, path_count = 2; + if (base_path) { + int n = snprintf(exe_icon, sizeof(exe_icon), "%sassets/datthang-speaker.bmp", base_path); + if (n >= 0 && (size_t)n < sizeof(exe_icon)) paths[path_count++] = exe_icon; + } + for (i = 0; i < path_count; i++) { + if (!paths[i] || !paths[i][0]) continue; + surface = SDL_LoadBMP(paths[i]); + if (surface) break; + } + if (base_path) SDL_free(base_path); + if (!surface) return NULL; + SDL_SetColorKey(surface, SDL_TRUE, + SDL_MapRGB(surface->format, 85, 160, 73)); + texture = SDL_CreateTextureFromSurface(renderer, surface); + SDL_FreeSurface(surface); + if (texture) SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND); + return texture; +} + +static void draw_dolphin_porthole(SDL_Renderer *r, int cx, int cy, SDL_Texture *icon) +{ + if (icon) { + SDL_Rect destination = {cx - DAT_THANG_DOLPHIN_W / 2, cy - DAT_THANG_DOLPHIN_H / 2, + DAT_THANG_DOLPHIN_W, DAT_THANG_DOLPHIN_H}; + SDL_RenderCopy(r, icon, NULL, &destination); + } +} + +static SDL_Rect speaker_bounds(int w, int h) +{ + const int margin = 22, top = 104, bottom = 52; + int left_w = w * 18 / 100; + int body_h = h - top - bottom - 44; + int gutter_top = top + 2 + body_h - 24; + int gutter_bottom = h - 47; + int gutter_h = gutter_bottom - gutter_top; + /* Keep an inner safety margin so the full porthole stays clear of pane/footer edges. */ + int size = gutter_h - 2, cx, cy; + SDL_Rect bounds = {0, 0, 0, 0}; + if (size > 64) size = 64; + if (size > w - margin * 2) size = w - margin * 2; + if (size < 16) return bounds; + cx = (margin + left_w + w * 64 / 100 + w - margin) / 2; + cy = (gutter_top + gutter_bottom) / 2; + if (cx < margin + size / 2) cx = margin + size / 2; + if (cx > w - margin - size / 2) cx = w - margin - size / 2; + if (cy < gutter_top + size / 2) cy = gutter_top + size / 2; + if (cy > gutter_bottom - size / 2) cy = gutter_bottom - size / 2; + bounds = (SDL_Rect){cx - size / 2, cy - size / 2, size, size}; + return bounds; +} + +static void draw_speaker(SDL_Renderer *r, int w, int h, SDL_Texture *icon) +{ + SDL_Rect destination = speaker_bounds(w, h); + SDL_Rect previous_clip; + SDL_bool had_clip = SDL_RenderIsClipEnabled(r); + SDL_RenderGetClipRect(r, &previous_clip); + if (!icon || destination.w <= 0 || destination.h <= 0) return; + /* This icon sits outside the pane clips; preserve the full speaker/radar art. */ + SDL_RenderSetClipRect(r, NULL); + SDL_RenderCopy(r, icon, NULL, &destination); + SDL_RenderSetClipRect(r, had_clip ? &previous_clip : NULL); +} + +static void draw_sound_wave(SDL_Renderer *r, int x, int y, int width, int height, + const gui_state_t *state) +{ + int i, center = y + height / 2, previous_y = center; + int16_t samples[4096]; + size_t sample_count = 0, start; + gui_fm_voice_t *voice = (gui_fm_voice_t *)state->fm_voice_ptr; + fill(r, x, y, width, height, 0,0,0); + if (state->audio_device && voice) { + SDL_LockAudioDevice(state->audio_device); + sample_count = voice->wave_count; + if (sample_count > sizeof(samples) / sizeof(samples[0])) sample_count = sizeof(samples) / sizeof(samples[0]); + start = (voice->wave_head + sizeof(voice->wave_samples) / sizeof(voice->wave_samples[0]) - sample_count) % + (sizeof(voice->wave_samples) / sizeof(voice->wave_samples[0])); + for (i = 0; i < (int)sample_count; i++) + samples[i] = voice->wave_samples[(start + (size_t)i) % + (sizeof(voice->wave_samples) / sizeof(voice->wave_samples[0]))]; + SDL_UnlockAudioDevice(state->audio_device); + } + color(r, 178, 55, 186); + for (i = 0; i < width && sample_count; i++) { + size_t sample_index = width > 1 ? (size_t)i * (sample_count - 1) / (size_t)(width - 1) : sample_count - 1; + double value = (double)samples[sample_index] / 32768.0; + int current_y = center - (int)(value * (height * 0.44)); + if (i) SDL_RenderDrawLine(r, x + i - 1, previous_y, x + i, current_y); + previous_y = current_y; + } + outline(r, (SDL_Rect){x, y, width, height}, 236, 236, 220); +} + +static void panel(SDL_Renderer *r, SDL_Rect box, const char *title) +{ + fill(r, box.x, box.y, box.w, box.h, 0, 0, 0); + outline(r, box, 236, 236, 220); + if (title) text(r, box.x + 8, box.y + 6, title, 1, 224, 224, 180); +} + +#define DAT_MERKLE_GRAPH_BLOCKS 16u + +static size_t merkle_version_block_count(size_t feed_entries, + uint64_t selected_version) +{ + uint64_t max_version = (uint64_t)feed_entries; + uint64_t version = selected_version > max_version ? max_version : selected_version; + /* SLEEP feed block zero is the feed header; archive version zero includes it. */ + return (size_t)(version + 1); +} + +static void draw_merkle_node(SDL_Renderer *r, SDL_Rect box, uint64_t index, + size_t first_leaf, size_t leaf_count, + size_t visible_leaves, uint64_t root_depth) +{ + uint64_t depth = sleep_tree_depth(index); + int left = box.x + 10, usable = box.w - 20; + int x = left + (int)(((uint64_t)(2 * first_leaf + leaf_count) * + (uint64_t)usable) / (2 * visible_leaves)); + int top = box.y + 59, bottom = box.y + box.h - 28; + int y = bottom - (int)((root_depth == 0 ? 0 : depth) * + (uint64_t)(bottom - top) / (root_depth ? root_depth : 1)); + SDL_Rect node = {x - 4, y - 4, 8, 8}; + + if (depth != 0 && leaf_count >= 2) { + uint64_t left_index, right_index; + size_t half = leaf_count / 2; + if (sleep_tree_left_child(index, &left_index) == 0 && + sleep_tree_right_child(index, &right_index) == 0) { + int child_x1 = left + (int)(((uint64_t)(2 * first_leaf + half) * + (uint64_t)usable) / (2 * visible_leaves)); + int child_x2 = left + (int)(((uint64_t)(2 * (first_leaf + half) + half) * + (uint64_t)usable) / (2 * visible_leaves)); + int child_y = y + (int)((bottom - top) / (root_depth ? root_depth : 1)); + color(r, 120,150,112); + SDL_RenderDrawLine(r, x, y + 4, child_x1, child_y - 4); + SDL_RenderDrawLine(r, x, y + 4, child_x2, child_y - 4); + draw_merkle_node(r, box, left_index, first_leaf, half, + visible_leaves, root_depth); + draw_merkle_node(r, box, right_index, first_leaf + half, half, + visible_leaves, root_depth); + } + } + outline(r, node, 210,210,170); +} + +static size_t merkle_draw_roots(SDL_Renderer *r, SDL_Rect box, size_t blocks) +{ + uint64_t roots[64]; + size_t count, i, first_leaf = 0; + char root_ids[128] = "VISIBLE ROOT IDS: "; + size_t used = strlen(root_ids); + if (!blocks) return 0; + count = sleep_tree_full_roots((uint64_t)blocks * 2, roots, + sizeof(roots) / sizeof(roots[0])); + if (count > sizeof(roots) / sizeof(roots[0])) count = sizeof(roots) / sizeof(roots[0]); + for (i = 0; i < count; i++) { + uint64_t depth = sleep_tree_depth(roots[i]); + size_t leaves = (size_t)1 << depth; + if (first_leaf + leaves > blocks) break; + if (used < sizeof(root_ids) - 1) { + int n = snprintf(root_ids + used, sizeof(root_ids) - used, + "%s%llu", used > strlen("VISIBLE ROOT IDS: ") ? "," : "", + (unsigned long long)roots[i]); + if (n > 0 && (size_t)n < sizeof(root_ids) - used) used += (size_t)n; + } + draw_merkle_node(r, box, roots[i], first_leaf, leaves, blocks, depth); + first_leaf += leaves; + } + text_clipped(r, (SDL_Rect){box.x + 5, box.y + 47, box.w - 10, 14}, + box.x + 8, box.y + 49, root_ids, 1, 150,170,145); + return count; +} + +static void draw_merkle_shape(SDL_Renderer *r, SDL_Rect box, size_t feed_entries, + uint64_t selected_version) +{ + size_t blocks = merkle_version_block_count(feed_entries, selected_version); + size_t shown = blocks > DAT_MERKLE_GRAPH_BLOCKS ? DAT_MERKLE_GRAPH_BLOCKS : blocks; + uint64_t root_nodes[64]; + size_t visible_roots = merkle_draw_roots(r, box, shown); + size_t total_roots = blocks ? sleep_tree_full_roots((uint64_t)blocks * 2, + root_nodes, sizeof(root_nodes) / sizeof(root_nodes[0])) : 0; + char count[72]; + text(r, box.x + 8, box.y + 22, "SLEEP METADATA TREE", 1, 180,180,140); + snprintf(count, sizeof(count), "VERSION %llu BLOCKS %lu ROOTS %lu/%lu", + (unsigned long long)(blocks - 1), (unsigned long)blocks, + (unsigned long)visible_roots, (unsigned long)total_roots); + text_clipped(r, (SDL_Rect){box.x + 5, box.y + 34, box.w - 10, 16}, + box.x + 8, box.y + 36, count, 1, 160,160,160); + if (shown) { + size_t i; + for (i = 0; i < shown; i++) { + char label[8]; + int x = box.x + 10 + (int)(((2 * i + 1) * (size_t)(box.w - 20)) / (2 * shown)); + snprintf(label, sizeof(label), "%lu", (unsigned long)i); + text(r, x - 3, box.y + box.h - 16, label, 1, 140,140,140); + } + } +} + +static void draw(SDL_Renderer *r, int w, int h, Uint32 now, const gui_state_t *state) +{ + const int margin = 22, top = 104, bottom = 52, gap = 20; + int left_w = w * 18 / 100, right_w = w * 17 / 100; + int center_x = margin + left_w + gap, right_x = w - margin - right_w; + int center_w = right_x - center_x - gap, body_h = h - top - bottom - 44; + int upper_h = body_h * 53 / 100, table_y = top + upper_h + 34; + int table_h = h - table_y - bottom - 12, field_x = margin + 62; + int site_field_w = w * 25 / 100, archive_field_w = w * 29 / 100; + int remote_label_x = w * 67 / 100, remote_field_x = w * 72 / 100; + int remote_field_w = w * 18 / 100; + int archive_controls_right = field_x + archive_field_w + 5 + 60 + 48; + int icon_x = w / 2, wave_x, wave_w; + char build_date[96], build_display[96], version[80], remote_line[48], remote_site_line[32], archive_line[160], address_url[160]; + int chat_i; + if (icon_x < archive_controls_right + 14) icon_x = archive_controls_right + 14; + wave_x = w * 55 / 100; + if (wave_x < icon_x + 18) wave_x = icon_x + 18; + wave_w = remote_label_x - wave_x; + if (wave_w < 30) wave_w = 30; + compiled_ddate(build_date, sizeof(build_date)); + fill(r, 0, 0, w, h, 83, 158, 70); + fill(r, 10, 5, 74, 20, 0,0,0); + outline(r, (SDL_Rect){10,5,74,20}, 210,210,190); + if (state->network_requested) fill(r, 47, 6, 36, 18, + state->online ? 34 : 120, state->online ? 92 : 83, state->online ? 40 : 30); + else fill(r, 11, 6, 35, 18, 108,42,35); + text(r, 14, 11, "OFF", 1, state->network_requested ? 130 : 255, state->network_requested ? 130 : 170, state->network_requested ? 130 : 150); + text(r, 53, 11, "ON", 1, state->network_requested ? (state->online ? 170 : 235) : 130, + state->network_requested ? (state->online ? 255 : 185) : 130, + state->network_requested ? (state->online ? 170 : 105) : 130); + snprintf(build_display, sizeof(build_display), "DAT-THANG BUILT %.77s", build_date); + text_clipped(r, (SDL_Rect){w * 32 / 100, 2, w * 56 / 100, 17}, + w * 32 / 100, 2, build_display, 1, 0,0,0); + text(r, margin, 32, "SITE", 1, 0,0,0); + fill(r, field_x, 26, site_field_w, 20, 0,0,0); + outline(r, (SDL_Rect){field_x, 26, site_field_w, 20}, 220,220,200); + text_clipped(r, (SDL_Rect){field_x + 3, 27, site_field_w - 6, 18}, field_x + 6, 29, + state->site_name[0] ? state->site_name : + state->site_name_editing ? "(TYPE HOSTNAME, ENTER SAVES)" : "(NO DAT-DNS NAME)", + 1, 230,230,220); + text(r, margin, 56, "ARCHIVE", 1, 0,0,0); + fill(r, field_x, 50, archive_field_w, 20, 0,0,0); + outline(r, (SDL_Rect){field_x, 50, archive_field_w, 20}, 220,220,200); + if (state->archive_open && state->address_compact) { + if (dat_key_to_compact_url(state->archive_key_bytes, address_url)) address_url[0] = '\0'; + snprintf(archive_line, sizeof(archive_line), "%s", address_url[0] ? address_url : "(COMPACT ADDRESS)"); + } else if (state->archive_open) + snprintf(archive_line, sizeof(archive_line), "%s", state->archive_key); + else if (state->shared_path_count) + snprintf(archive_line, sizeof(archive_line), "(%lu LOADED ARCHIVES)", + (unsigned long)state->shared_path_count); + else snprintf(archive_line, sizeof(archive_line), "(NO ARCHIVE LOADED)"); + text_clipped(r, (SDL_Rect){field_x + 5, 51, archive_field_w - 10, 18}, + field_x + 6, 53, archive_line, 1, 230,230,220); + { + SDL_Rect format_box = {field_x + archive_field_w + 5, 50, 56, 20}; + SDL_Rect copy_box = {format_box.x + 60, 50, 48, 20}; + fill(r, format_box.x, format_box.y, format_box.w, format_box.h, 0,0,0); + outline(r, format_box, 220,220,190); + text_clipped(r, (SDL_Rect){format_box.x + 2, format_box.y + 2, format_box.w - 4, format_box.h - 4}, + format_box.x + 5, format_box.y + 3, state->address_compact ? "32768" : "HEX", 1, 230,230,210); + fill(r, copy_box.x, copy_box.y, copy_box.w, copy_box.h, 0,0,0); + outline(r, copy_box, 220,220,190); + text(r, copy_box.x + 5, copy_box.y + 3, "COPY", 1, 230,230,210); + } + text(r, margin, 80, "VERSION", 1, 0,0,0); + fill(r, field_x, 74, 158, 20, 0,0,0); + outline(r, (SDL_Rect){field_x, 74, 158, 20}, 220,220,200); + if (state->archive_open) { + snprintf(version, sizeof(version), "%llu / %llu", (unsigned long long)state->selected_version, + (unsigned long long)state->version); + text(r, field_x + 6, 77, version, 1, 230,230,220); + } else text(r, field_x + 6, 77, "N/A", 1, 160,160,160); + text(r, field_x + 128, 77, "< >", 1, 180,220,170); + text(r, remote_label_x, 32, "SITE", 1, 0,0,0); + fill(r, remote_field_x, 26, remote_field_w, 20, 0,0,0); + outline(r, (SDL_Rect){remote_field_x, 26, remote_field_w, 20}, 220,220,200); + snprintf(remote_site_line, sizeof(remote_site_line), "%.26s", + state->remote_site[0] ? state->remote_site : "(DAT-DNS NAME)"); + text_clipped(r, (SDL_Rect){remote_field_x + 3, 27, remote_field_w - 6, 18}, remote_field_x + 5, 29, + remote_site_line, 1, 230,230,220); + text(r, remote_label_x, 56, "ARCHIVE", 1, 0,0,0); + fill(r, remote_field_x, 50, remote_field_w, 20, 0,0,0); + outline(r, (SDL_Rect){remote_field_x, 50, remote_field_w, 20}, 220,220,200); + if (state->remote_address_editing) + snprintf(remote_line, sizeof(remote_line), "%.27s", state->remote_address); + else snprintf(remote_line, sizeof(remote_line), "%.27s", + state->remote_archive[0] ? state->remote_archive : "(PASTE DAT URI)"); + text_clipped(r, (SDL_Rect){remote_field_x + 3, 51, remote_field_w - 6, 18}, remote_field_x + 5, 53, + remote_line, 1, 230,230,220); + text(r, remote_label_x, 80, "VERSION", 1, 0,0,0); + fill(r, remote_field_x, 74, remote_field_w, 20, 0,0,0); + outline(r, (SDL_Rect){remote_field_x, 74, remote_field_w, 20}, 220,220,200); + text_clipped(r, (SDL_Rect){remote_field_x + 3, 75, remote_field_w - 6, 18}, + remote_field_x + 5, 77, + state->remote_version[0] ? state->remote_version : "LATEST", 1, 230,230,220); + text_clipped(r, (SDL_Rect){field_x + 166, 78, icon_x - field_x - 200, 18}, + field_x + 170, 80, "F1 HELP F9 LOAD", 1, 0,0,0); + text_clipped(r, (SDL_Rect){icon_x + 18, 78, + remote_label_x - icon_x - 22, 18}, + icon_x + 18, 80, "ESC MENU", 1, 0,0,0); + draw_sound_wave(r, wave_x, 27, wave_w, 52, state); + { + int ix = icon_x; + panel(r, (SDL_Rect){ix - 13, 18, 27, 57}, NULL); + /* Pulses are reserved for archive payload I/O, not DHT control packets. */ + draw_diamond(r, ix, 28, + now < state->download_activity_until && ((now / 180u) & 1u)); + draw_online_hexagon(r, ix, 46, state->online, state->connected_peers > 0); + draw_diamond(r, ix, 64, + now < state->upload_activity_until && ((now / 180u) & 1u)); + } + { + SDL_Rect peers = {margin, top + 26, left_w, body_h - 24}; + SDL_Rect graph = {center_x, top + 2, center_w * 52 / 100, upper_h - 8}; + SDL_Rect files = browser_box(w, h); + SDL_Rect stats = {right_x, top + 2, right_w, body_h - 24}; + SDL_Rect table = {center_x, table_y, center_w, table_h}; + panel(r, peers, "PEERS"); panel(r, graph, "MERKLE"); panel(r, files, "SHARE DIRECTORY"); + panel(r, stats, ""); panel(r, table, ""); + text(r, files.x + files.w - 66, files.y + 8, "ARCHIVES", 1, 220,220,180); + if (state->archive_switcher_visible) { + SDL_Rect list = {files.x + 3, files.y + 20, files.w - 6, + files.h - 23 < (int)state->shared_path_count * 15 ? + files.h - 23 : (int)state->shared_path_count * 15}; + fill(r, list.x, list.y, list.w, list.h, 0,0,0); + outline(r, list, 180,190,150); + for (size_t i = 0; i < state->shared_path_count && + list.y + 5 + (int)i * 15 < list.y + list.h - 9; i++) { + text_clipped(r, (SDL_Rect){list.x + 3, list.y + 2, list.w - 6, list.h - 4}, + list.x + 5, list.y + 5 + (int)i * 15, state->shared_paths[i], 1, + !strcmp(state->shared_paths[i], state->archive_path) ? 255 : 210, + 220, 210); + } + } + { + SDL_Rect import_button = {files.x, table.y - 25, (files.w - 8) / 2, 20}; + SDL_Rect create_button = {import_button.x + import_button.w + 8, table.y - 25, + files.w - import_button.w - 8, 20}; + fill(r, import_button.x, import_button.y, import_button.w, import_button.h, 0,0,0); + outline(r, import_button, 220,220,190); + text_clipped(r, (SDL_Rect){import_button.x + 3, import_button.y + 2, import_button.w - 6, import_button.h - 4}, + import_button.x + 8, import_button.y + 6, "IMPORT DAT", 1, 230,230,210); + fill(r, create_button.x, create_button.y, create_button.w, create_button.h, 0,0,0); + outline(r, create_button, 220,220,190); + text_clipped(r, (SDL_Rect){create_button.x + 3, create_button.y + 2, create_button.w - 6, create_button.h - 4}, + create_button.x + 8, create_button.y + 6, "CREATE NEW DAT", 1, 230,230,210); + } + { + const cesspool_peer_t *known[CESSPOOL_MAX_PEERS]; + size_t found = 0, p; + if (state->pool_initialized) + for (p = 0; p < state->pool.peer_count && found < CESSPOOL_MAX_PEERS; p++) + if (state->pool.peers[p].discovered) known[found++] = &state->pool.peers[p]; + for (p = 0; p < found && peers.y + 27 + (int)p * 18 < peers.y + peers.h - 8; p++) { + char peer_id[24], row[32]; + static const char hex[] = "0123456789abcdef"; + dat_session_peer_t *session_peer = state->session_initialized ? + dat_session_find_peer((dat_session_t *)&state->session,known[p]->public_key) : NULL; + size_t k; + for (k = 0; k < 6; k++) { peer_id[k*2]=hex[known[p]->public_key[k]>>4];peer_id[k*2+1]=hex[known[p]->public_key[k]&15]; } + peer_id[12]='\0'; + snprintf(row,sizeof(row),"%c %s...",session_peer&&(session_peer->state==DAT_SESSION_PEER_READY||session_peer->state==DAT_SESSION_PEER_SECURE)?'*':'o',peer_id); + text(r,peers.x+10,peers.y+27+(int)p*18,row,1,210,220,190); + } + if(!found)text(r,peers.x+10,peers.y+28,"NO PEERS IN ARCHIVE",1,170,170,160); + } + draw_merkle_shape(r, graph, state->entry_count, state->selected_version); + { + DIR *dir = opendir(state->archive_path); + struct dirent *item; + int y = files.y + 25, row = 0, visible = (files.h - 34) / 16; + int rows = browser_row_count(state->archive_path); + char parent[1024]; + SDL_Rect clip = {files.x + 2, files.y + 21, files.w - 11, files.h - 30}; + if (state->browser_scroll > rows - visible) ((gui_state_t *)state)->browser_scroll = rows > visible ? rows - visible : 0; + SDL_RenderSetClipRect(r, &clip); + snprintf(parent, sizeof(parent), "%s", state->archive_path); + if (parent_directory(parent, sizeof(parent))) { + if (row >= state->browser_scroll && row < state->browser_scroll + visible) { + text(r, files.x + 10, y, "[..]", 1, 190,210,160); + y += 16; + } + row++; + } + if (dir) { + while ((item = readdir(dir)) != NULL) { + char target[1200], label[256]; + struct stat info; + if (!strcmp(item->d_name, ".") || !strcmp(item->d_name, "..") || + !strcmp(item->d_name, ".dat")) continue; + if (row++ < state->browser_scroll) continue; + if (y + 12 >= files.y + files.h) break; + if (snprintf(target, sizeof(target), "%s%s%s", state->archive_path, + state->archive_path[strlen(state->archive_path)-1] == '/' ? "" : "/", + item->d_name) >= (int)sizeof(target)) continue; + if (stat(target, &info) == 0 && S_ISDIR(info.st_mode)) + snprintf(label, sizeof(label), "[%.253s]", item->d_name); + else snprintf(label, sizeof(label), "%.255s", item->d_name); + text_clipped(r, clip, files.x + 10, y, label, 1, + state->selected_filename[0] && !strcmp(state->selected_filename, item->d_name) ? 255 : 220, + 220, 220); + y += 16; + } + closedir(dir); + } else text(r, files.x + 10, y, "CANNOT READ DIRECTORY", 1, 230,150,120); + SDL_RenderSetClipRect(r, NULL); + { + int track_y = files.y + 23, track_h = files.h - 29, max_scroll = rows - visible; + if (max_scroll > 0 && track_h > 0) { + int thumb_h = track_h * visible / rows; + int thumb_y; + if (thumb_h < 14) thumb_h = 14; + thumb_y = track_y + (track_h - thumb_h) * state->browser_scroll / max_scroll; + fill(r, files.x + files.w - 7, track_y, 4, track_h, 45,45,45); + fill(r, files.x + files.w - 7, thumb_y, 4, thumb_h, 175,175,150); + } + } + } + { + char rows[9][80], upload[32], download[32], user_address[80]; + format_speed(upload, sizeof(upload), state->upload_kbps); + format_speed(download, sizeof(download), state->download_kbps); + if (state->identity_valid && !cable_user_address_format(state->identity.pubkey, CABLE_ADDRESS_CABLE, + user_address, sizeof(user_address))) + snprintf(rows[0], sizeof(rows[0]), "USER: %.20s...", user_address + 8); + else snprintf(rows[0], sizeof(rows[0]), "USER: (OFFLINE)"); + snprintf(rows[1], sizeof(rows[1]), "CONNECTIONS: %lu", (unsigned long)state->connected_peers); + snprintf(rows[2], sizeof(rows[2]), "DISCOVERED: %lu", (unsigned long)discovered_peer_count(state)); + snprintf(rows[3], sizeof(rows[3]), "SHARED DIRS: %lu", (unsigned long)state->shared_path_count); + snprintf(rows[4], sizeof(rows[4]), "AWKWARD MOMENTS: --"); + snprintf(rows[5], sizeof(rows[5]), "SHARE GUY: --"); + snprintf(rows[6], sizeof(rows[6]), "RANK: --"); + snprintf(rows[7], sizeof(rows[7]), "UPLOAD: %s", upload); + snprintf(rows[8], sizeof(rows[8]), "DOWNLOAD: %s", download); + text_clipped(r, (SDL_Rect){stats.x + 3, stats.y + 3, stats.w - 6, 16}, + stats.x + 8, stats.y + 5, "YOU", 1, 220,220,180); + for (int i = 0; i < 9; i++) + text_clipped(r, (SDL_Rect){stats.x + 3, stats.y + 3, stats.w - 6, stats.h - 6}, + stats.x + 8, stats.y + 23 + i * 19, rows[i], 1, + i >= 4 && i <= 6 ? 128 : 170, + i >= 4 && i <= 6 ? 150 : 150, + i >= 4 && i <= 6 ? 128 : 255); + } + { + static const char *labels[6] = {"TITLE", "DESCRIPTION", "USER", "PROGRESS", "STATUS", "SPEED"}; + int widths[6], total = 0, x = table.x + 8 - state->transfer_scroll; + int defaults[6] = {center_w * 18 / 100, center_w * 32 / 100, + center_w * 12 / 100, center_w * 16 / 100, + center_w * 13 / 100, center_w * 9 / 100}; + SDL_Rect clip = {table.x + 1, table.y + 1, table.w - 2, table.h - 15}; + if (!state->transfer_widths[0]) + for (int i = 0; i < 6; i++) ((gui_state_t *)state)->transfer_widths[i] = defaults[i] < 70 ? 70 : defaults[i]; + for (int i = 0; i < 6; i++) { widths[i] = state->transfer_widths[i]; total += widths[i]; } + if (state->transfer_scroll > total - table.w + 12) + ((gui_state_t *)state)->transfer_scroll = total > table.w - 12 ? total - table.w + 12 : 0; + SDL_RenderSetClipRect(r, &clip); + for (int i = 0; i < 6; i++) { + text(r, x + 4, table.y + 10, labels[i], 1, 128,128,128); + x += widths[i]; + } + { + int total_rows = (int)state->shared_path_count; + int row_y, rows = 0, max_row_scroll; + if (state->session_initialized) for (size_t p = 0; p < state->session.peer_count; p++) { + const dat_session_peer_t *peer = &state->session.peers[p]; + size_t pending = 0; + for (size_t k = 0; k < DAT_SESSION_FEED_COUNT; k++) pending += peer->replication[k].request_count; + if (!((peer->state == DAT_SESSION_PEER_CLOSED || peer->state == DAT_SESSION_PEER_DISCOVERED) && + !state->transfer_paused[p] && !state->transfer_cancelled[p]) && + (pending || peer->payload_bytes_sent || peer->payload_bytes_received)) total_rows++; + } + if (state->remote_import_mode) total_rows++; + max_row_scroll = total_rows * 16 - (table.h - 43); + if (max_row_scroll < 0) max_row_scroll = 0; + if (state->transfer_row_scroll > max_row_scroll) ((gui_state_t *)state)->transfer_row_scroll = max_row_scroll; + row_y = table.y + 31; + row_y -= state->transfer_row_scroll; + for (size_t a = 0; a < state->shared_path_count; a++) { + dat_archive_t shared; + gui_archive_label_t *label; + if (dat_archive_open(&shared, state->shared_paths[a])) continue; + char key_title[64], key_hex[65], key_description[40], status[24]; + const char *title, *description; + label = archive_label((gui_state_t *)state, state->shared_paths[a], shared.metadata_key); + if (label) { + title = label->title; + description = label->description; + } else { + const char *base = strrchr(state->shared_paths[a], '/'); + base = base ? base + 1 : state->shared_paths[a]; + snprintf(key_title, sizeof(key_title), "%.60s", base); + (void)dat_key_to_hex(shared.metadata_key, key_hex); + snprintf(key_description, sizeof(key_description), "DAT://%.16s...", key_hex); + title = key_title; + description = key_description; + } + snprintf(status, sizeof(status), "%s", + state->online && state->session_initialized && + !memcmp(shared.metadata_key, state->session.archive_key, DAT_KEY_BYTES) ? + "LIVE" : state->online ? "ANNOUNCED" : "SHARED"); + { + const char *cells[6] = {title, description, "YOU", "", status, + !strcmp(status, "LIVE") ? "LIVE" : "--"}; + x = table.x + 8 - state->transfer_scroll; + for (int col = 0; col < 6; col++) { + if (col == 3) + draw_progress_bar(r, x + 4, row_y + 2, widths[col] - 8, 11, 100); + else text_clipped(r, (SDL_Rect){x, row_y, widths[col], 15}, + x + 4, row_y, cells[col], 1, 205,225,195); + x += widths[col]; + } + } + dat_archive_close(&shared); + row_y += 16; + rows++; + } + if (state->remote_import_mode && row_y + 12 < table.y + table.h) { + uint64_t remote_length = 0; + unsigned progress = 0; + const char *phase = state->connected_peers ? "RECEIVING METADATA" : "FINDING PEERS"; + if (state->session_initialized) for (size_t p = 0; p < state->session.peer_count; p++) + if (state->session.peers[p].replication[DAT_SESSION_METADATA].remote_length > remote_length) + remote_length = state->session.peers[p].replication[DAT_SESSION_METADATA].remote_length; + if (remote_length && state->metadata_feed.length >= remote_length) progress = 100; + else if (remote_length) progress = (unsigned)(state->metadata_feed.length * 100 / remote_length); + { + const char *cells[6] = {"INCOMING DAT", state->remote_address, "PEERS", "", phase, "--"}; + x = table.x + 8 - state->transfer_scroll; + for (int col = 0; col < 6; col++) { + if (col == 3) draw_progress_bar(r, x + 4, row_y + 2, widths[col] - 8, 11, progress); + else text_clipped(r, clip, x + 4, row_y, cells[col], 1, 210,225,195); + x += widths[col]; + } + } + row_y += 16; + rows++; + } + clip.y = table.y + 28; + clip.h = table.h - 43; + SDL_RenderSetClipRect(r, &clip); + if (state->session_initialized) for (size_t p = 0; + p < state->session.peer_count && row_y + 12 < table.y + table.h; p++) { + const dat_session_peer_t *peer = &state->session.peers[p]; + const gui_transport_peer_t *transport = transport_by_id((gui_state_t *)state, + peer->peer_id); + char identity[17], status[32], speed[32]; + static const char hex[] = "0123456789abcdef"; + size_t k, pending = 0; + if ((peer->state == DAT_SESSION_PEER_CLOSED || peer->state == DAT_SESSION_PEER_DISCOVERED) && + !state->transfer_paused[p] && !state->transfer_cancelled[p]) continue; + for (k = 0; k < 8; k++) { + identity[k * 2] = hex[peer->peer_id[k] >> 4]; + identity[k * 2 + 1] = hex[peer->peer_id[k] & 15]; + } + identity[16] = '\0'; + snprintf(speed, sizeof(speed), "%s", transport && transport->authenticated ? "ACTIVE" : "--"); + for (k = 0; k < DAT_SESSION_FEED_COUNT; k++) + pending += peer->replication[k].request_count; + if (!pending && !peer->payload_bytes_sent && + !peer->payload_bytes_received) continue; + if (state->transfer_cancelled[p]) { + snprintf(status, sizeof(status), "CANCELLED"); + } else if (state->transfer_paused[p]) { + snprintf(status, sizeof(status), "PAUSED"); + } else if (peer->state == DAT_SESSION_PEER_READY) { + snprintf(status, sizeof(status), "%s", pending ? "SYNCING" : "TRANSFERRED"); + } else { + snprintf(status, sizeof(status), "%s", transport && transport->authenticated ? "SESSION" : "AUTHENTICATING"); + } + { + const char *cells[6] = {"DAT ARCHIVE", "DAT URI TRANSFER", identity, + "", status, speed}; + x = table.x + 8 - state->transfer_scroll; + for (int col = 0; col < 6; col++) { + if (col == 3) + draw_progress_bar(r, x + 4, row_y + 2, widths[col] - 8, 11, + session_progress_percent(peer)); + else text(r, x + 4, row_y, cells[col], 1, + pending ? 180 : 210, pending ? 230 : 210, pending ? 240 : 210); + x += widths[col]; + } + } + row_y += 16; + rows++; + } + if (!rows) + text(r, table.x + 8, table.y + 31, + state->archive_open || state->shared_path_count ? + "NO ACTIVE ARCHIVE TRANSFERS" : "LOAD AN ARCHIVE TO SHARE", + 1, 170,170,170); + } + SDL_RenderSetClipRect(r, NULL); + /* draggable divider targets are the column boundaries in the header */ + { + int bar_y = table.y + table.h - 9; + fill(r, table.x + 8, bar_y, table.w - 16, 4, 45,45,45); + if (total > table.w - 16) { + int thumb_w = (table.w - 16) * (table.w - 16) / total; + int max_scroll = total - table.w + 16; + int thumb_x = table.x + 8 + (table.w - 16 - thumb_w) * state->transfer_scroll / max_scroll; + fill(r, thumb_x, bar_y, thumb_w, 4, 170,170,150); + } + } + } + } + text(r, margin + left_w - 110, h - 39, "FILENAME", 1, 0,0,0); + text(r, margin + left_w - 110, h - 20, "DIRECTORY", 1, 0,0,0); + fill(r, margin + left_w, h - 47, w * 64 / 100, 42, 0,0,0); + outline(r, (SDL_Rect){margin + left_w, h - 47, w * 64 / 100, 42}, 230,230,220); + text_clipped(r, (SDL_Rect){margin + left_w + 4, h - 45, w * 64 / 100 - 12, 16}, + margin + left_w + 6, h - 43, state->selected_filename[0] ? state->selected_filename : "(directory)", 1, 230,230,220); + text_clipped(r, (SDL_Rect){margin + left_w + 4, h - 27, w * 64 / 100 - 12, 16}, + margin + left_w + 6, h - 25, state->archive_path, 1, 230,230,220); + if (state->network_status[0]) + text_clipped(r, (SDL_Rect){w - 238, h - 19, 216, 16}, + w - 234, h - 18, state->network_status, 1, 0,0,0); + if (state->help_visible) { + SDL_Rect modal = {36, 28, w - 72, h - 56}; + int visible = (modal.h - 48) / 11, i; + fill(r, modal.x, modal.y, modal.w, modal.h, 0,0,0); + outline(r, modal, 220,220,180); + text(r, modal.x + 14, modal.y + 12, "DAT-THANG MANPAGE UP/DOWN SCROLL ESC CLOSE", 1, 220,220,180); + for (i = 0; i < visible && state->help_scroll + i < state->help_count; i++) + text_clipped(r, (SDL_Rect){modal.x + 10, modal.y + 28, modal.w - 20, modal.h - 38}, + modal.x + 14, modal.y + 32 + i * 11, + state->help_lines[state->help_scroll + i], 1, 210,210,210); + } else if (state->chat_open) { + SDL_Rect modal={center_x+12,top+20,center_w-24,h-top-bottom-55}; + char address[80];int visible=(modal.h-115)/15, shown=0; + (void)cable_address_format(state->chat_peer_id,CABLE_ADDRESS_CABLE,address,sizeof(address)); + fill(r,modal.x,modal.y,modal.w,modal.h,0,0,0);outline(r,modal,220,220,180); + text(r,modal.x+12,modal.y+10,"CABLE-THANG PRIVATE CHAT",1,220,220,180); + text(r,modal.x+12,modal.y+25,address,1,180,205,175); + for(chat_i=(int)state->chat_line_count-1;chat_i>=0&&shownchat_lines[chat_i];char textbuf[96]; + int is_link; + if(memcmp(line->peer_id,state->chat_peer_id,32))continue; + snprintf(textbuf,sizeof(textbuf),"%s: %.82s",line->outgoing?"YOU":"PEER",line->text); + is_link = !strncmp(line->text, "dat://", 6); + text_clipped(r, (SDL_Rect){modal.x + 8, modal.y + 42, modal.w - 16, modal.h - 94}, + modal.x + 12, modal.y + 45 + shown * 15, textbuf, 1, + is_link ? 170 : 210, is_link ? 220 : 220, is_link ? 255 : 200); + shown++; + } + if(!shown)text(r,modal.x+12,modal.y+47,"NO MESSAGES YET",1,150,160,150); + fill(r,modal.x+10,modal.y+modal.h-47,modal.w-20,22,0,0,0); + outline(r,(SDL_Rect){modal.x+10,modal.y+modal.h-47,modal.w-20,22},170,180,160); + text(r,modal.x+16,modal.y+modal.h-41,state->chat_input[0]?state->chat_input:"TYPE MESSAGE, ENTER SENDS",1,220,220,210); + text(r,modal.x+12,modal.y+modal.h-20,state->chat_status,1,160,195,160); + } else if (state->settings_visible) { + SDL_Rect modal = {w / 2 - 220, h / 2 - 124, 440, 248}; + const char *labels[4] = {"DEFAULT ADDRESS FORMAT", "SONAR AUDIO", "CONNECT ON STARTUP", "CESSPOOL NODE"}; + const char *values[4] = {state->address_compact ? "COMPACT 32768" : "LEGACY HEX", + state->audio_enabled ? "ON" : "OFF", + state->auto_connect ? "ON" : "OFF", + state->bootstrap_editing ? state->bootstrap_endpoint : + (state->bootstrap_endpoint[0] ? state->bootstrap_endpoint : "CLICK TO SET")}; + fill(r, modal.x, modal.y, modal.w, modal.h, 0,0,0); + outline(r, modal, 220,220,180); + text(r, modal.x + 14, modal.y + 14, "USER PREFERENCES", 1, 220,220,180); + for (int i = 0; i < 4; i++) { + int y = modal.y + 42 + i * 33; + if (i == state->settings_selection) fill(r, modal.x + 8, y - 3, modal.w - 16, 20, 34,68,38); + text(r, modal.x + 18, y, i == state->settings_selection ? ">" : " ", 1, 220,220,180); + text_clipped(r, (SDL_Rect){modal.x + 34, y, 190, 16}, modal.x + 34, y, labels[i], 1, 210,210,210); + text_clipped(r, (SDL_Rect){modal.x + 230, y, 197, 16}, modal.x + 230, y, values[i], 1, 210,230,190); + } + text_clipped(r, (SDL_Rect){modal.x + 10, modal.y + 216, modal.w - 20, 18}, + modal.x + 14, modal.y + 218, + "LEFT/RIGHT OR ENTER CHANGE NODE: TYPE HOST:PORT ESC BACK", 1, 160,185,160); + } else if (state->menu_visible) { + SDL_Rect modal = {w / 2 - 145, h / 2 - 72, 290, 144}; + const char *items[3] = {"SETTINGS", "RETURN", "EXIT"}; + fill(r, modal.x, modal.y, modal.w, modal.h, 0,0,0); + outline(r, modal, 220,220,180); + text(r, modal.x + 14, modal.y + 14, "DAT-THANG MENU", 1, 220,220,180); + for (int i = 0; i < 3; i++) { + int y = modal.y + 45 + i * 25; + if (i == state->menu_selection) fill(r, modal.x + 8, y - 3, modal.w - 16, 19, 34,68,38); + text(r, modal.x + 18, y, i == state->menu_selection ? ">" : " ", 1, 220,220,180); + text(r, modal.x + 34, y, items[i], 1, 210,210,210); + } + text(r, modal.x + 14, modal.y + 124, "UP/DOWN SELECT ENTER CHOOSE", 1, 160,185,160); + } + if (state->context_menu) { + SDL_Rect menu = {state->context_x, state->context_y, 236, + state->context_menu_rows * 21 + 8}; + if (menu.x + menu.w > w) menu.x = w - menu.w - 4; + if (menu.y + menu.h > h) menu.y = h - menu.h - 4; + fill(r, menu.x, menu.y, menu.w, menu.h, 0,0,0); + outline(r, menu, 220,220,190); + for (int i = 0; i < state->context_menu_rows; i++) + text_clipped(r, (SDL_Rect){menu.x + 6, menu.y + 4 + i * 21, menu.w - 12, 18}, + menu.x + 8, menu.y + 7 + i * 21, state->context_menu_items[i], 1, + 220,220,210); + } + draw_dolphin_porthole(r, w * 95 / 100, 38, state->dolphin_icon); + draw_speaker(r, w, h, state->speaker_icon); + if (state->site_record_modal) { + SDL_Rect modal = {w / 2 - 310, h / 2 - 82, 620, 164}; + SDL_Rect copy = {modal.x + modal.w - 98, modal.y + 132, 86, 20}; + fill(r, modal.x, modal.y, modal.w, modal.h, 0,0,0); + outline(r, modal, 220,220,180); + text(r, modal.x + 14, modal.y + 13, "DAT-DNS SITE NAME SAVED LOCALLY", 1, 220,220,180); + text_clipped(r, (SDL_Rect){modal.x + 12, modal.y + 32, modal.w - 24, 16}, + modal.x + 16, modal.y + 36, state->site_name, 1, 210,220,200); + text(r, modal.x + 14, modal.y + 56, "COPY THIS AS HTTPS /.WELL-KNOWN/DAT CONTENT:", 1, 210,210,210); + fill(r, modal.x + 12, modal.y + 69, modal.w - 24, 38, 0,0,0); + outline(r, (SDL_Rect){modal.x + 12, modal.y + 69, modal.w - 24, 38}, 210,210,195); + { + char first_line[80]; + size_t n = strcspn(state->site_dns_txt, "\r\n"); + if (n >= sizeof(first_line)) n = sizeof(first_line) - 1; + memcpy(first_line, state->site_dns_txt, n); first_line[n] = '\0'; + text_clipped(r, (SDL_Rect){modal.x + 16, modal.y + 70, modal.w - 32, 18}, + modal.x + 20, modal.y + 72, first_line, 1, 225,225,220); + text(r, modal.x + 20, modal.y + 89, "TTL=3600", 1, 225,225,220); + } + text(r, modal.x + 14, modal.y + 114, "PUBLISH THIS FILE ON THE HOSTNAME'S WEB SERVER.", + 1, 160,185,160); + text(r, modal.x + 14, modal.y + 138, "ESC CLOSE", 1, 175,175,165); + fill(r, copy.x, copy.y, copy.w, copy.h, 0,0,0); + outline(r, copy, 210,210,195); + text(r, copy.x + 9, copy.y + 7, "COPY", 1, 220,220,210); + } +} + +static void cable_thang_cycle_channel(gui_state_t *state, int direction) +{ + if (!state->cable_channel_count) return; + if (direction > 0) state->cable_channel_index = (state->cable_channel_index + 1) % state->cable_channel_count; + else state->cable_channel_index = (state->cable_channel_index + state->cable_channel_count - 1) % state->cable_channel_count; + snprintf(state->cable_status, sizeof(state->cable_status), "CHANNEL %.64s", + state->cable_channels[state->cable_channel_index]); +} + +static void cable_text_clipped(SDL_Renderer *renderer, int x, int y, const char *value, + int max_chars, unsigned char red, unsigned char green, + unsigned char blue) +{ + char clipped[256]; + size_t length; + SDL_Rect clip; + if (max_chars < 0) return; + if (max_chars >= (int)sizeof(clipped)) max_chars = (int)sizeof(clipped) - 1; + length = strlen(value); + if (length > (size_t)max_chars) length = (size_t)max_chars; + memcpy(clipped, value, length); + clipped[length] = '\0'; + /* max_chars is derived from the six-pixel DOS cell used by the layout. + * The actual VGA font glyph advance can be wider, so also clip at the + * destination rectangle instead of relying on byte/character truncation. */ + clip = (SDL_Rect){x, y, max_chars * 6, 18}; + text_clipped(renderer, clip, x, y, clipped, 1, red, green, blue); +} + +static void cable_thang_draw(SDL_Renderer *renderer, int width, int height, + const gui_state_t *state) +{ + int margin = 16, top = 58, bottom = 54; + int left_width = width * 20 / 100, right_width = width * 22 / 100; + int center_x = margin + left_width + 12; + int right_x = width - margin - right_width; + int center_width = right_x - center_x - 12; + int body_height = height - top - bottom; + int channel_rows = (body_height - 38) / 18; + int message_rows = (body_height - 38) / 18; + int channel_i, visible = 0, y, max_chars, peer_limit; + size_t i; + SDL_Rect audio_button = {width - 278, 7, 104, 27}; + SDL_Rect video_button = {width - 386, 7, 104, 27}; + fill(renderer, 0, 0, width, height, 8, 13, 12); + fill(renderer, 0, 0, width, 42, 19, 45, 39); + text(renderer, margin, 12, "CABLE-THANG ROOM CHAT", 1, 160, 245, 190); + max_chars = (video_button.x - (margin + 190) - 8) / 6; + if (max_chars < 0) max_chars = 0; + cable_text_clipped(renderer, margin + 190, 12, state->cable_room_address, + max_chars, 190, 220, 205); + fill(renderer, audio_button.x, audio_button.y, audio_button.w, audio_button.h, + state->cable_audio_broadcasting ? 80 : 0, 0, 0); + outline(renderer, audio_button, 125, 190, 150); + cable_text_clipped(renderer, audio_button.x + 3, audio_button.y + 9, + state->cable_audio_broadcasting ? "STOP AUDIO" : "PLAY AUDIO", + (audio_button.w - 6) / 6, + state->cable_audio_broadcasting ? 255 : 170, 235, 185); + fill(renderer, video_button.x, video_button.y, video_button.w, video_button.h, 0, 0, 0); + if (state->cable_video_active) + fill(renderer, video_button.x + 2, video_button.y + 2, + video_button.w - 4, video_button.h - 4, 48, 74, 36); + outline(renderer, video_button, 125, 190, 150); + cable_text_clipped(renderer, video_button.x + 3, video_button.y + 9, + state->cable_video_active ? "STOP VIDEO" : "PLAY VIDEO", + (video_button.w - 6) / 6, + state->cable_video_active ? 255 : 170, 235, 185); + text(renderer, width - 158, 12, state->online ? "ONLINE" : "OFFLINE", 1, + state->online ? 120 : 240, state->online ? 240 : 100, + state->online ? 150 : 100); + { + SDL_Rect channels = {margin, top, left_width, body_height}; + SDL_Rect chat = {center_x, top, center_width, body_height}; + SDL_Rect peers = {right_x, top, right_width, body_height}; + fill(renderer, channels.x, channels.y, channels.w, channels.h, 0, 0, 0); + fill(renderer, chat.x, chat.y, chat.w, chat.h, 0, 0, 0); + fill(renderer, peers.x, peers.y, peers.w, peers.h, 0, 0, 0); + outline(renderer, channels, 72, 132, 115); + outline(renderer, chat, 72, 132, 115); + outline(renderer, peers, 72, 132, 115); + text(renderer, channels.x + 9, channels.y + 9, "CHANNELS", 1, 130, 210, 170); + for (channel_i = 0; channel_i < (int)state->cable_channel_count && channel_i < channel_rows; channel_i++) { + if ((size_t)channel_i == state->cable_channel_index) + fill(renderer, channels.x + 3, channels.y + 28 + channel_i * 18, channels.w - 6, 16, 22, 67, 58); + cable_text_clipped(renderer, channels.x + 10, channels.y + 32 + channel_i * 18, + state->cable_channels[channel_i], (channels.w - 20) / 6, + (size_t)channel_i == state->cable_channel_index ? 195 : 140, + (size_t)channel_i == state->cable_channel_index ? 250 : 190, 170); + } + text(renderer, chat.x + 10, chat.y + 9, "ROOM TRANSCRIPT", 1, 130, 210, 170); + text(renderer, peers.x + 9, peers.y + 9, "ROOM PEERS", 1, 130, 210, 170); + y = peers.y + 33; + peer_limit = peers.y + peers.h * 46 / 100; + for (i = 0; i < state->cable_peer_count && y < peer_limit - 18; i++) { + char name[90]; + const gui_cable_peer_t *peer = &state->cable_peers[i]; + snprintf(name, sizeof(name), "%c %s", peer->connected ? '*' : 'o', + peer->nick[0] ? peer->nick : peer->key); + cable_text_clipped(renderer, peers.x + 9, y, name, (peers.w - 18) / 6, + peer->connected ? 190 : 125, + peer->connected ? 230 : 155, 185); + y += 18; + } + if (y == peers.y + 33) text(renderer, peers.x + 9, y, "NO PEERS YET", 1, 120, 150, 140); + { + int media_y = peers.y + peers.h * 49 / 100; + int media_x = peers.x + 6; + int media_w = peers.w - 12; + int wave_h = peers.h * 20 / 100; + SDL_Rect wave = {media_x, media_y, media_w, wave_h}; + SDL_Rect video = {media_x, media_y + wave_h + 7, media_w, + peers.y + peers.h - (media_y + wave_h + 7) - 6}; + fill(renderer, wave.x, wave.y, wave.w, wave.h, 0, 0, 0); + outline(renderer, wave, 105, 165, 125); + text(renderer, wave.x + 5, wave.y + 4, "LIVE AUDIO / AAC 96K", 1, 130, 210, 170); + cable_text_clipped(renderer, wave.x + 5, wave.y + 20, + state->cable_live_status[0] ? state->cable_live_status : "NO AUDIO STREAM", + (wave.w - 10) / 6, 150, 190, 160); + if (state->cable_live_wave_count && wave.w > 8 && wave.h > 36) { + SDL_Rect clip = {wave.x + 4, wave.y + 34, wave.w - 8, wave.h - 39}; + size_t start = (state->cable_live_wave_head + GUI_CABLE_WAVE_SAMPLES - + state->cable_live_wave_count) % GUI_CABLE_WAVE_SAMPLES; + int previous_y = clip.y + clip.h / 2, x; + SDL_RenderSetClipRect(renderer, &clip); + for (x = 0; x < clip.w; x++) { + size_t sample_index = (size_t)x * state->cable_live_wave_count / + (size_t)clip.w; + int sample = state->cable_live_wave[(start + sample_index) % GUI_CABLE_WAVE_SAMPLES]; + int current_y = clip.y + clip.h / 2 - sample * (clip.h / 2 - 1) / 32768; + SDL_SetRenderDrawColor(renderer, 190, 220, 150, 255); + if (x) SDL_RenderDrawLine(renderer, wave.x + 4 + x - 1, previous_y, + wave.x + 4 + x, current_y); + previous_y = current_y; + } + SDL_RenderSetClipRect(renderer, NULL); + } + fill(renderer, video.x, video.y, video.w, video.h, 0, 0, 0); + outline(renderer, video, 105, 165, 125); + text(renderer, video.x + 5, video.y + 4, "OBS / WEBCAM VIDEO", 1, 130, 210, 170); + if (state->cable_video_texture) { + SDL_Rect target = {video.x + 4, video.y + 22, video.w - 8, video.h - 26}; + SDL_RenderCopy(renderer, state->cable_video_texture, NULL, &target); + } else { + cable_text_clipped(renderer, video.x + 5, video.y + 22, + state->cable_video_status[0] ? state->cable_video_status : "CTRL+SHIFT+V: UDP :5000", + (video.w - 10) / 6, 135, 155, 145); + } + } + for (i = state->cable_line_count; i > 0 && visible < message_rows; i--) { + const cable_room_event_t *event = &state->cable_lines[i - 1].event; + char line[256]; + if (event->type == CABLE_ROOM_TEXT && strcmp(event->channel, state->cable_channels[state->cable_channel_index])) continue; + if (event->type == CABLE_ROOM_TEXT) + snprintf(line, sizeof(line), "%s: %s", event->nick[0] ? event->nick : "PEER", event->text); + else if (event->type == CABLE_ROOM_NICK) + snprintf(line, sizeof(line), "* NICK CHANGED TO %s", event->nick); + else if (event->type == CABLE_ROOM_CHANNEL_JOIN) + snprintf(line, sizeof(line), "* %s JOINED #%s", event->nick, event->channel); + else if (event->type == CABLE_ROOM_PEER_JOIN) + snprintf(line, sizeof(line), "* %s JOINED THE ROOM", event->nick); + else snprintf(line, sizeof(line), "* %s LEFT THE ROOM", event->nick); + visible++; + cable_text_clipped(renderer, chat.x + 10, chat.y + chat.h - 28 - visible * 18, + line, (chat.w - 20) / 6, event->type == CABLE_ROOM_TEXT ? 205 : 145, + event->type == CABLE_ROOM_TEXT ? 220 : 180, 200); + } + if (!visible) text(renderer, chat.x + 10, chat.y + 38, "NO MESSAGES IN THIS CHANNEL", 1, 130, 150, 140); + } + fill(renderer, margin, height - bottom + 2, width - 2 * margin, 22, 0, 0, 0); + outline(renderer, (SDL_Rect){margin, height - bottom + 2, width - 2 * margin, 22}, 72, 132, 115); + text(renderer, margin + 8, height - bottom + 9, ">", 1, 150, 230, 185); + cable_text_clipped(renderer, margin + 20, height - bottom + 9, + state->cable_input[0] ? state->cable_input : "TYPE MESSAGE OR /HELP", + (width - 2 * margin - 28) / 6, 210, 220, 210); + cable_text_clipped(renderer, margin, height - 19, state->cable_status, + (width - 2 * margin) / 6, 145, 190, 165); +} + +static int cable_room_address_prompt(char *address, size_t capacity, int smoke) +{ + SDL_Window *window = NULL; + SDL_Renderer *renderer = NULL; + char input[512] = ""; + char status[160] = "PASTE A ROOM ADDRESS, THEN PRESS ENTER"; + int running = 1, accepted = 0; + if (SDL_Init(SDL_INIT_VIDEO) != 0) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "cable-thang", SDL_GetError(), NULL); + return -1; + } + window = SDL_CreateWindow("cable-thang - open a room", SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, 820, 260, + SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + if (!window) goto fail; + SDL_SetWindowMinimumSize(window, 640, 220); + renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if (!renderer) renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE); + if (!renderer || gui_font_init(renderer) != 0) goto fail; + SDL_StartTextInput(); + while (running) { + SDL_Event event; + int width, height; + while (SDL_PollEvent(&event)) { + if (event.type == SDL_QUIT) running = 0; + else if (event.type == SDL_TEXTINPUT) { + size_t used = strlen(input), added = strlen(event.text.text); + if (used + added < sizeof(input)) memcpy(input + used, event.text.text, added + 1); + } else if (event.type == SDL_KEYDOWN) { + SDL_Keycode key = event.key.keysym.sym; + if (key == SDLK_ESCAPE) running = 0; + else if ((event.key.keysym.mod & KMOD_CTRL) && key == SDLK_a) input[0] = '\0'; + else if ((event.key.keysym.mod & KMOD_CTRL) && key == SDLK_v) { + char *paste = SDL_GetClipboardText(); + if (paste) { + size_t used = strlen(input), added = strlen(paste); + while (added && (paste[added - 1] == '\r' || paste[added - 1] == '\n')) added--; + if (used + added < sizeof(input)) { memcpy(input + used, paste, added); input[used + added] = '\0'; } + SDL_free(paste); + } + } else if (key == SDLK_BACKSPACE) { + size_t n = strlen(input); + if (n) { n--; while (n && ((unsigned char)input[n] & 0xc0) == 0x80) n--; input[n] = '\0'; } + } else if (key == SDLK_RETURN || key == SDLK_KP_ENTER) { + cable_room_address_t parsed; + if (!cable_room_address_parse(input, &parsed) && strlen(input) < capacity) { + strcpy(address, input); accepted = 1; running = 0; + } else snprintf(status, sizeof(status), "INVALID ADDRESS - EXPECTED CABLE:// OR CABAL:// ROOM KEY"); + } + } else if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT) { + int window_width, window_height, click_x, click_y; + SDL_GetWindowSize(window, &window_width, &window_height); + SDL_GetRendererOutputSize(renderer, &width, &height); + click_x = window_width > 0 ? event.button.x * width / window_width : event.button.x; + click_y = window_height > 0 ? event.button.y * height / window_height : event.button.y; + if (click_y >= height - 48 && click_x >= width - 300 && + click_x < width - 150) { + const char *home = gui_home_directory(); + uint8_t room_key[32] = {0}; + cable_room_address_t room; + if (home && cable_room_create(home, room_key) == 0) { + memset(&room, 0, sizeof(room)); + memcpy(room.key, room_key, sizeof(room.key)); + room.scheme = CABLE_ADDRESS_CABLE; + if (cable_room_address_format(&room, CABLE_ADDRESS_CABLE, + address, capacity) == 0) { + accepted = 1; + running = 0; + } else { + snprintf(status, sizeof(status), "ROOM CREATED, ADDRESS FORMATTING FAILED: %.80s", strerror(errno)); + } + } else { + if (!home) errno = ENOENT; + snprintf(status, sizeof(status), "ROOM CREATION FAILED: %.100s", strerror(errno)); + } + sodium_memzero(room_key, sizeof(room_key)); + } else if (click_y >= height - 48 && click_x >= width - 140) { + SDL_Event enter; + memset(&enter, 0, sizeof(enter)); enter.type = SDL_KEYDOWN; + enter.key.keysym.sym = SDLK_RETURN; SDL_PushEvent(&enter); + } + } + } + SDL_GetRendererOutputSize(renderer, &width, &height); + fill(renderer, 0, 0, width, height, 83, 158, 70); + text(renderer, 22, 18, "CABLE-THANG", 1, 0, 0, 0); + text(renderer, 22, 48, "ENTER A ROOM ADDRESS TO OPEN THE CHAT", 1, 0, 0, 0); + fill(renderer, 22, 76, width - 44, 38, 0, 0, 0); + outline(renderer, (SDL_Rect){22, 76, width - 44, 38}, 236, 236, 220); + text_clipped(renderer, (SDL_Rect){28, 78, width - 56, 34}, 32, 88, + input[0] ? input : "CABLE:// OR CABAL:// ADDRESS", 1, 230, 230, 220); + text_clipped(renderer, (SDL_Rect){22, 128, width - 44, 24}, 22, 132, status, 1, 0, 0, 0); + text(renderer, 22, height - 28, "CTRL+V PASTE ENTER OPEN ESC CLOSE", 1, 0, 0, 0); + fill(renderer, width - 300, height - 42, 140, 28, 0, 0, 0); + outline(renderer, (SDL_Rect){width - 300, height - 42, 140, 28}, 236, 236, 220); + text(renderer, width - 290, height - 32, "CREATE ROOM", 1, 230, 230, 220); + fill(renderer, width - 140, height - 42, 118, 28, 0, 0, 0); + outline(renderer, (SDL_Rect){width - 140, height - 42, 118, 28}, 236, 236, 220); + text(renderer, width - 124, height - 32, "OPEN ROOM", 1, 230, 230, 220); + if (smoke) { + SDL_Surface *surface = SDL_CreateRGBSurfaceWithFormat(0, width, height, 32, SDL_PIXELFORMAT_ARGB8888); + if (!surface || SDL_RenderReadPixels(renderer, NULL, SDL_PIXELFORMAT_ARGB8888, + surface->pixels, surface->pitch) != 0 || + SDL_SaveBMP(surface, DAT_BUILD_DIR "/cable-thang-address-smoke.bmp") != 0) { + if (surface) SDL_FreeSurface(surface); + goto fail; + } + SDL_FreeSurface(surface); running = 0; + } + SDL_RenderPresent(renderer); + SDL_Delay(16); + } + SDL_StopTextInput(); gui_font_close(); + SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); + return accepted; +fail: + if (renderer && gui_font_ready) gui_font_close(); + if (renderer) SDL_DestroyRenderer(renderer); + if (window) SDL_DestroyWindow(window); + SDL_Quit(); + return -1; +} + +static int cable_thang_main(int argc, char **argv) +{ + static gui_state_t state; + char prompted_address[512]; + SDL_Window *window; + SDL_Renderer *renderer; + const char *room_address = getenv("CABLE_ROOM"); + int offline = 0, smoke = 0, running = 1, i; + memset(&state, 0, sizeof(state)); + state.cable_thang_mode = 1; + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "--offline")) { offline = 1; continue; } + if (!strcmp(argv[i], "--smoke")) { smoke = 1; continue; } + if (!strcmp(argv[i], "--help")) { + puts("cable-thang cable://KEY [--offline] [--smoke]\n" + "Join a Cable room in the SDL2 chat client. Use the startup window to open an address or create a room.\n" + "Commands: /join NAME /nick NAME /channels /help\n" + "Ctrl+N / Ctrl+P switch joined channels; Enter sends a chat message."); + return 0; + } + if (argv[i][0] != '-') { room_address = argv[i]; continue; } + fprintf(stderr, "cable-thang: unknown option %s\n", argv[i]); return 2; + } + if (!room_address) { + int prompt_result = cable_room_address_prompt(prompted_address, + sizeof(prompted_address), smoke); + if (prompt_result <= 0) return prompt_result < 0 ? 1 : 0; + room_address = prompted_address; + } + if (cable_thang_room_init(&state, room_address)) { + perror("cable-thang: room setup"); + return 1; + } + if (cable_thang_scheme_init(&state)) { perror("cable-thang: Scheme setup"); return 1; } + if (SDL_Init(SDL_INIT_VIDEO) != 0) { fprintf(stderr, "cable-thang: %s\n", SDL_GetError()); dat_scheme_runtime_close(&state.metadata_scheme); return 1; } + if (!offline && cable_thang_network_start(&state)) { + int network_error = errno; + state.online = 0; + snprintf(state.network_status, sizeof(state.network_status), "OFFLINE - NETWORK SETUP FAILED"); + snprintf(state.cable_status, sizeof(state.cable_status), "NETWORK SETUP: %.70s", strerror(network_error)); + } + if (offline) snprintf(state.cable_status, sizeof(state.cable_status), "OFFLINE - NO ROOM ANNOUNCEMENT"); + window = SDL_CreateWindow("cable-thang", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + 1000, 640, SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + if (!window) { fprintf(stderr, "cable-thang: %s\n", SDL_GetError()); network_stop(&state); SDL_Quit(); return 1; } + SDL_SetWindowMinimumSize(window, 720, 460); + renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if (!renderer) renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE); + if (!renderer) { fprintf(stderr, "cable-thang: %s\n", SDL_GetError()); SDL_DestroyWindow(window); network_stop(&state); SDL_Quit(); return 1; } + if (gui_font_init(renderer) != 0) { + fprintf(stderr, "cable-thang: cannot load More Perfect DOS VGA font: %s\n", TTF_GetError()); + SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); network_stop(&state); SDL_Quit(); return 1; + } + if (gui_unicode_font_init() != 0) { + fprintf(stderr, "cable-thang: cannot load bundled Unicode address font: %s\n", TTF_GetError()); + gui_font_close(); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); network_stop(&state); SDL_Quit(); return 1; + } + SDL_StartTextInput(); + while (running) { + SDL_Event event; + int width, height; + while (SDL_PollEvent(&event)) { + if (event.type == SDL_QUIT) running = 0; + else if (event.type == SDL_TEXTINPUT) { + size_t used = strlen(state.cable_input), added = strlen(event.text.text); + if (used + added <= CABLE_CHAT_TEXT_BYTES) + memcpy(state.cable_input + used, event.text.text, added + 1); + } else if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT && + event.button.y >= 7 && event.button.y < 34) { + int window_width, window_height; + SDL_GetWindowSize(window, &window_width, &window_height); + (void)window_height; + if (event.button.x >= window_width - 278 && event.button.x < window_width - 174) { + if (state.cable_audio_broadcasting) cable_audio_stop(&state, 1); + else (void)cable_audio_start(&state); + } else if (event.button.x >= window_width - 386 && event.button.x < window_width - 282) { + cable_video_toggle(&state); + } + } else if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT && + event.button.x < 230 && event.button.y >= 85) { + int row = (event.button.y - 86) / 18; + if (row >= 0 && row < (int)state.cable_channel_count) + state.cable_channel_index = (size_t)row; + } else if (event.type == SDL_KEYDOWN) { + SDL_Keycode key = event.key.keysym.sym; + SDL_Keymod mod = (SDL_Keymod)event.key.keysym.mod; + if ((mod & KMOD_CTRL) && key == SDLK_n) cable_thang_cycle_channel(&state, 1); + else if ((mod & KMOD_CTRL) && key == SDLK_p) cable_thang_cycle_channel(&state, -1); + else if ((mod & KMOD_CTRL) && (mod & KMOD_SHIFT) && key == SDLK_v) { + cable_video_toggle(&state); + } + else if ((mod & KMOD_CTRL) && (mod & KMOD_SHIFT) && key == SDLK_a) { + if (state.cable_audio_broadcasting) cable_audio_stop(&state, 1); + else (void)cable_audio_start(&state); + } + else if ((mod & KMOD_CTRL) && key == SDLK_c) SDL_SetClipboardText(state.cable_room_address); + else if (key == SDLK_ESCAPE) running = 0; + else if (key == SDLK_BACKSPACE) { size_t length = strlen(state.cable_input); if (length) state.cable_input[length - 1] = '\0'; } + else if (key == SDLK_RETURN || key == SDLK_KP_ENTER) (void)cable_room_submit(&state); + } + } + if (state.cable_network_active) network_pump(&state, SDL_GetTicks()); + cable_audio_pump(&state); + cable_video_pump(&state, renderer); + SDL_GetRendererOutputSize(renderer, &width, &height); + cable_thang_draw(renderer, width, height, &state); + if (smoke) { + SDL_Surface *surface = SDL_CreateRGBSurfaceWithFormat(0, width, height, 32, SDL_PIXELFORMAT_ARGB8888); + if (!surface || SDL_RenderReadPixels(renderer, NULL, SDL_PIXELFORMAT_ARGB8888, + surface ? surface->pixels : NULL, + surface ? surface->pitch : 0) != 0 || SDL_SaveBMP(surface, DAT_BUILD_DIR "/cable-thang-smoke.bmp") != 0) { + if (surface) SDL_FreeSurface(surface); + gui_font_close(); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_StopTextInput(); network_stop(&state); SDL_Quit(); return 1; + } + SDL_FreeSurface(surface); + puts("cable-thang SDL2 room chat smoke: PASS"); + running = 0; + } + SDL_RenderPresent(renderer); + SDL_Delay(16); + } + SDL_StopTextInput(); + network_stop(&state); + cable_video_source_close(state.cable_video_source); + if (state.cable_video_texture) SDL_DestroyTexture(state.cable_video_texture); + gui_font_close(); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + cable_audio_stop(&state, 1); + cable_aac_decoder_close(&state.cable_aac_decoder); + if (state.cable_audio_playback) SDL_CloseAudioDevice(state.cable_audio_playback); + SDL_Quit(); + dat_scheme_runtime_close(&state.metadata_scheme); + return 0; +} + +int main(int argc, char **argv) +{ + SDL_Window *window; + SDL_Renderer *renderer; + /* The session/cache tables are large; keep them out of the small process stack. */ + static gui_state_t state; + int running = 1, start_online = 1, archive_argument = 0; + int smoke = 0, i; + Uint32 last_refresh = 0; + memset(&state, 0, sizeof(state)); + load_preferences(&state); + (void)cesspool_bootstrap_config_get(state.bootstrap_endpoint, sizeof(state.bootstrap_endpoint)); + start_online = state.auto_connect; + { + const char *name = strrchr(argv[0], '/'); + const char *win_name = strrchr(argv[0], '\\'); + if (win_name && (!name || win_name > name)) name = win_name; + name = name ? name + 1 : argv[0]; + if (!strcmp(name, "cable-thang") || !strcmp(name, "cable-thang.exe")) + return cable_thang_main(argc, argv); + } + if (argc == 2 && (!strcmp(argv[1], "--version") || !strcmp(argv[1], "-v"))) { + printf("dat-thang %s (%s)\n", DAT_THANG_VERSION, DAT_THANG_RELEASE_STATUS); + return 0; + } + strcpy(state.archive_path, "."); + snprintf(state.network_status, sizeof(state.network_status), "NO ARCHIVE"); + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "--smoke")) { smoke = 1; continue; } + if (!strcmp(argv[i], "--offline")) { start_online = 0; continue; } + if (!strcmp(argv[i], "--site") && i + 1 < argc) { + snprintf(state.site_name, sizeof(state.site_name), "%s", argv[++i]); + state.site_name_explicit = 1; + continue; + } + if ((!strcmp(argv[i], "--archive") || !strcmp(argv[i], "--share")) && i + 1 < argc) { + snprintf(state.archive_path, sizeof(state.archive_path), "%s", argv[++i]); + strcpy(state.selected_filename, "(directory)"); + archive_argument = 1; + continue; + } + if (!strcmp(argv[i], "--import") && i + 1 < argc) { + snprintf(state.remote_address, sizeof(state.remote_address), "%s", argv[++i]); + (void)parse_remote_address(&state); + continue; + } + if (argv[i][0] != '-') { + snprintf(state.archive_path, sizeof(state.archive_path), "%s", argv[i]); + strcpy(state.selected_filename, "(directory)"); + archive_argument = 1; + } + } + if (!state.selected_filename[0]) strcpy(state.selected_filename, "(directory)"); + /* S7 reads archive dat.scm as data for the archive list labels. */ + if (dat_scheme_runtime_init(&state.metadata_scheme) != 0) + fprintf(stderr, "dat-thang: S7 metadata reader unavailable; using directory names\n"); + load_shared_paths(&state); + if (archive_argument) { + refresh_archive(&state); + if (state.archive_open) remember_shared_path(&state, state.archive_path); + } else if (state.shared_path_count) { + (void)refresh_archive_at(&state, state.shared_paths[0]); + } + if (!state.archive_open && state.remote_key_valid && start_online) + network_start_remote_import(&state); + else if (start_online) network_start(&state); + else if (state.archive_open) snprintf(state.network_status, sizeof(state.network_status), "OFFLINE"); + if (SDL_Init(SDL_INIT_VIDEO) != 0) { fprintf(stderr, "dat-thang: %s\n", SDL_GetError()); return 1; } + if (SDL_InitSubSystem(SDL_INIT_AUDIO) == 0) fm_audio_start(&state); + window = SDL_CreateWindow("dat-thang v0.0.002 (prototype, pre-alpha)", + SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + 1024, 650, SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + if (!window) { fprintf(stderr, "dat-thang: %s\n", SDL_GetError()); SDL_Quit(); return 1; } + SDL_SetWindowMinimumSize(window, 760, 480); + renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if (!renderer) renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE); + if (!renderer) { fprintf(stderr, "dat-thang: %s\n", SDL_GetError()); SDL_DestroyWindow(window); SDL_Quit(); return 1; } + if (gui_font_init(renderer) != 0) { + fprintf(stderr, "dat-thang: cannot load More Perfect DOS VGA font: %s\n", TTF_GetError()); + SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); fm_audio_stop(&state); + network_stop(&state); SDL_Quit(); dat_scheme_runtime_close(&state.metadata_scheme); return 1; + } + if (gui_unicode_font_init() != 0) { + fprintf(stderr, "dat-thang: cannot load bundled Unicode address font: %s\n", TTF_GetError()); + gui_font_close(); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); fm_audio_stop(&state); + network_stop(&state); SDL_Quit(); dat_scheme_runtime_close(&state.metadata_scheme); return 1; + } + state.dolphin_icon = icon_texture_from_rle(renderer, DAT_THANG_DOLPHIN_W, DAT_THANG_DOLPHIN_H, + datthang_dolphin_palette, + sizeof(datthang_dolphin_palette) / sizeof(datthang_dolphin_palette[0]), + datthang_dolphin_rle, DAT_THANG_DOLPHIN_RUNS); + state.speaker_icon = speaker_texture_from_mockup(renderer); + while (running) { + SDL_Event event; + int w, h, margin = 22, gap = 20, left_w, right_w, center_x, right_x, center_w; + Uint32 now; + SDL_GetRendererOutputSize(renderer, &w, &h); + left_w = w * 18 / 100; + right_w = w * 17 / 100; + center_x = margin + left_w + gap; + right_x = w - margin - right_w; + center_w = right_x - center_x - gap; + while (SDL_PollEvent(&event)) { + if (event.type == SDL_QUIT) running = 0; + if (state.site_record_modal && event.type != SDL_QUIT) { + if (event.type == SDL_KEYDOWN && + (event.key.keysym.sym == SDLK_ESCAPE || event.key.keysym.sym == SDLK_RETURN || + event.key.keysym.sym == SDLK_KP_ENTER)) { + state.site_record_modal = 0; + } else if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT) { + int mx = w / 2 - 310, my = h / 2 - 82; + if (event.button.x >= mx + 522 && event.button.x < mx + 608 && + event.button.y >= my + 132 && event.button.y < my + 152) { + (void)SDL_SetClipboardText(state.site_dns_txt); + snprintf(state.network_status, sizeof(state.network_status), "DAT-DNS RECORD COPIED - PUBLISH /.WELL-KNOWN/DAT"); + } else state.site_record_modal = 0; + } + continue; + } + if (state.chat_open && event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT) { + { + int modal_x = center_x + 12, modal_y = 124, modal_w = center_w - 24; + int row = (event.button.y - modal_y - 45) / 15, shown = 0; + int visible = (h - 104 - 52 - 55 - 115) / 15; + if (event.button.x >= modal_x && event.button.x < modal_x + modal_w && + event.button.y >= modal_y && event.button.y < h - 55) { + if (row < 0 || row >= visible || event.button.y >= modal_y + 45 + visible * 15) + continue; + for (int i = (int)state.chat_line_count - 1; i >= 0 && shown <= row; i--) { + const gui_chat_line_t *line = &state.chat_lines[i]; + if (memcmp(line->peer_id, state.chat_peer_id, 32)) continue; + if (shown++ != row) continue; + if (!strncmp(line->text, "dat://", 6)) { + (void)SDL_SetClipboardText(line->text); + snprintf(state.chat_status, sizeof(state.chat_status), "DAT LINK COPIED - PASTE INTO ARCHIVE BAR"); + } + break; + } + continue; + } + } + } + if (state.settings_visible && event.type == SDL_MOUSEBUTTONDOWN && + event.button.button == SDL_BUTTON_LEFT) { + SDL_Rect modal = {w / 2 - 220, h / 2 - 124, 440, 248}; + int row = (event.button.y - modal.y - 39) / 33; + if (event.button.x >= modal.x + 8 && event.button.x < modal.x + modal.w - 8 && + row >= 0 && row < 4 && event.button.y >= modal.y + 39 && event.button.y < modal.y + 174) { + state.settings_selection = row; + if (row == 3) begin_bootstrap_edit(&state); + else change_preference(&state, 1); + } else if (event.button.x < modal.x || event.button.x >= modal.x + modal.w || + event.button.y < modal.y || event.button.y >= modal.y + modal.h) { + state.settings_visible = 0; + } + continue; + } + if (state.menu_visible && event.type == SDL_MOUSEBUTTONDOWN && + event.button.button == SDL_BUTTON_LEFT) { + SDL_Rect modal = {w / 2 - 145, h / 2 - 72, 290, 144}; + int row = (event.button.y - modal.y - 42) / 25; + if (event.button.x >= modal.x + 8 && event.button.x < modal.x + modal.w - 8 && + row >= 0 && row < 3 && event.button.y >= modal.y + 42 && event.button.y < modal.y + 111) { + state.menu_selection = row; + activate_main_menu(&state, &running); + } else if (event.button.x < modal.x || event.button.x >= modal.x + modal.w || + event.button.y < modal.y || event.button.y >= modal.y + modal.h) { + state.menu_visible = 0; + } + continue; + } + if (event.type == SDL_MOUSEMOTION && state.resize_column > 0) { + int col = state.resize_column - 1; + int delta = event.motion.x - state.transfer_drag_x; + state.transfer_widths[col] += delta; + if (state.transfer_widths[col] < 70) state.transfer_widths[col] = 70; + state.transfer_drag_x = event.motion.x; + } + if (event.type == SDL_MOUSEMOTION && state.transfer_scroll_drag) { + int total = 0, viewport = center_w - 16, defaults[6] = {center_w * 18 / 100, center_w * 32 / 100, + center_w * 12 / 100, center_w * 16 / 100, center_w * 13 / 100, center_w * 9 / 100}; + if (!state.transfer_widths[0]) for (int c = 0; c < 6; c++) state.transfer_widths[c] = defaults[c] < 70 ? 70 : defaults[c]; + for (int c = 0; c < 6; c++) total += state.transfer_widths[c]; + if (total > viewport && viewport > 0) { + int max_scroll = total - viewport, thumb = viewport * viewport / total; + int travel = viewport - thumb; + state.transfer_scroll += (event.motion.x - state.transfer_drag_x) * max_scroll / (travel > 0 ? travel : 1); + if (state.transfer_scroll < 0) state.transfer_scroll = 0; + if (state.transfer_scroll > max_scroll) state.transfer_scroll = max_scroll; + } + state.transfer_drag_x = event.motion.x; + } + if (event.type == SDL_MOUSEMOTION && state.browser_drag) { + SDL_Rect files = browser_box(w, h); + int rows = browser_row_count(state.archive_path), visible = (files.h - 34) / 16; + int max_scroll = rows - visible, track_h = files.h - 29; + int thumb_h = rows > 0 ? track_h * visible / rows : track_h; + int travel; + if (thumb_h < 14) thumb_h = 14; + travel = track_h - thumb_h; + if (max_scroll > 0 && travel > 0) { + state.browser_scroll = state.browser_drag_scroll + + (event.motion.y - state.browser_drag_y) * max_scroll / travel; + if (state.browser_scroll < 0) state.browser_scroll = 0; + if (state.browser_scroll > max_scroll) state.browser_scroll = max_scroll; + } + state.browser_drag_y = event.motion.y; + } + if (event.type == SDL_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_LEFT) { + state.resize_column = 0; + state.transfer_scroll_drag = 0; + state.browser_drag = 0; + } + if (event.type == SDL_TEXTINPUT && state.bootstrap_editing) { + size_t used = strlen(state.bootstrap_endpoint), added = strlen(event.text.text); + if (used + added < sizeof(state.bootstrap_endpoint)) + memcpy(state.bootstrap_endpoint + used, event.text.text, added + 1); + } else if (event.type == SDL_TEXTINPUT && state.site_name_editing) { + size_t used = strlen(state.site_name), added = strlen(event.text.text); + if (used + added < sizeof(state.site_name)) + memcpy(state.site_name + used, event.text.text, added + 1); + } else if (event.type == SDL_TEXTINPUT && state.chat_open) { + size_t used=strlen(state.chat_input),added=strlen(event.text.text); + if(used+added state.help_count - 1) state.help_scroll = state.help_count ? state.help_count - 1 : 0; + } + if (event.type == SDL_MOUSEWHEEL && !state.help_visible && event.wheel.mouseY >= 0) { + int top = 104, bottom = 52, body_h = h - top - bottom - 44; + int table_y = top + body_h * 53 / 100 + 34; + SDL_Rect files = browser_box(w, h); + if (event.wheel.mouseX >= files.x && event.wheel.mouseX < files.x + files.w && + event.wheel.mouseY >= files.y && event.wheel.mouseY < files.y + files.h) { + state.browser_scroll -= event.wheel.y; + if (state.browser_scroll < 0) state.browser_scroll = 0; + } else if (event.wheel.mouseY >= table_y && event.wheel.mouseX >= center_x && + event.wheel.mouseX < center_x + center_w) { + if (event.wheel.x || (SDL_GetModState() & KMOD_SHIFT)) + state.transfer_scroll -= (event.wheel.x ? event.wheel.x : event.wheel.y) * 24; + else state.transfer_row_scroll -= event.wheel.y * 16; + if (state.transfer_scroll < 0) state.transfer_scroll = 0; + if (state.transfer_row_scroll < 0) state.transfer_row_scroll = 0; + } + } + if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_RIGHT) { + state.context_menu = 1; + state.context_x = event.button.x; + state.context_y = event.button.y; + state.context_peer_index = -1; + { + int kind = 0, field_x = 84; + int site_w = w * 25 / 100, archive_w = w * 29 / 100; + int remote_x = w * 72 / 100, remote_w = w * 18 / 100; + int table_y = 104 + (h - 104 - 52 - 44) * 53 / 100 + 34; + SDL_Rect peers = {22, 130, w * 18 / 100, h - 224}; + SDL_Rect files = browser_box(w, h); + if (event.button.y >= 26 && event.button.y < 46 && + event.button.x >= field_x && event.button.x < field_x + site_w) kind = 3; + else if (event.button.y >= 50 && event.button.y < 70 && + event.button.x >= field_x && event.button.x < field_x + archive_w) kind = 4; + else if (event.button.y >= 26 && event.button.y < 46 && + event.button.x >= remote_x && event.button.x < remote_x + remote_w) kind = 8; + else if (event.button.y >= 50 && event.button.y < 70 && + event.button.x >= remote_x && event.button.x < remote_x + remote_w) kind = 5; + else if (event.button.x >= peers.x && event.button.x < peers.x + peers.w && + event.button.y >= peers.y + 23 && event.button.y < peers.y + peers.h - 4 && state.pool_initialized) { + int target = (event.button.y - peers.y - 27) / 18; + for (size_t p = 0; p < state.pool.peer_count; p++) if (state.pool.peers[p].discovered) + if (target-- == 0) { state.context_peer_index = (int)p; kind = 6; break; } + } + if (state.archive_switcher_visible && event.button.x >= files.x + 3 && + event.button.x < files.x + files.w - 3 && event.button.y >= files.y + 20) { + int row = (event.button.y - files.y - 25) / 15; + if (row >= 0 && (size_t)row < state.shared_path_count) { + state.context_peer_index = row; + kind = 7; + } + } + if (kind == 0 && event.button.x >= files.x && event.button.x < files.x + files.w && + event.button.y >= files.y + 20 && event.button.y < files.y + files.h) + kind = 1; + /* Hit-test the center table in its actual display order: saved + * archives, incoming import, then active peer transfers. */ + if (event.button.x >= center_x && event.button.x < center_x + center_w) { + int table_h = h - table_y - 52 - 12; + int row_y = table_y + 31 - state.transfer_row_scroll; + int row_hit = event.button.y >= table_y + 28 && + event.button.y < table_y + table_h - 15; + if (row_hit) { + for (size_t a = 0; a < state.shared_path_count; a++) { + dat_archive_t shared; + if (dat_archive_open(&shared, state.shared_paths[a])) continue; + if (event.button.y >= row_y && event.button.y < row_y + 16) { + kind = 7; + state.context_peer_index = (int)a; + dat_archive_close(&shared); + break; + } + dat_archive_close(&shared); + row_y += 16; + } + if (kind == 0 && state.remote_import_mode && row_y + 12 < table_y + table_h) { + if (event.button.y >= row_y && event.button.y < row_y + 16) kind = 9; + row_y += 16; + } + if (kind == 0 && state.session_initialized) for (size_t p = 0; + p < state.session.peer_count; p++) { + const dat_session_peer_t *peer = &state.session.peers[p]; + size_t pending = peer->replication[0].request_count + peer->replication[1].request_count; + if ((peer->state == DAT_SESSION_PEER_CLOSED || peer->state == DAT_SESSION_PEER_DISCOVERED) && + !state.transfer_paused[p] && !state.transfer_cancelled[p]) continue; + if (!pending && !peer->payload_bytes_sent && !peer->payload_bytes_received) continue; + if (event.button.y >= row_y && event.button.y < row_y + 16) { + kind = 2; + state.context_peer_index = (int)p; + break; + } + row_y += 16; + } + } + } + context_menu_prepare(&state, kind); + } + if (!state.context_menu_rows) state.context_menu = 0; + if (state.context_x + 236 > w) state.context_x = w - 240; + if (state.context_y + state.context_menu_rows * 21 + 8 > h) + state.context_y = h - state.context_menu_rows * 21 - 12; + } + if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT && state.context_menu) { + int mx = state.context_x, my = state.context_y; + int menu_height = state.context_menu_rows * 21 + 8; + if (event.button.x >= mx && event.button.x <= mx + 236 && + event.button.y >= my && event.button.y <= my + menu_height) { + int row = (event.button.y - my - 4) / 21; + int action = row >= 0 && row < state.context_menu_rows ? + state.context_menu_actions[row] : 0; + if (action == GUI_CTX_LOAD_ARCHIVE) (void)open_existing_archive(&state); + else if (action == GUI_CTX_CREATE_ARCHIVE) (void)prepare_archive(&state); + else if (action == GUI_CTX_COPY_ARCHIVE) { + if (state.context_menu_kind == 7 && state.context_peer_index >= 0 && + (size_t)state.context_peer_index < state.shared_path_count) + (void)copy_archive_path_link(&state, state.shared_paths[state.context_peer_index]); + else if (state.context_menu_kind == 1) + (void)copy_archive_path_link(&state, state.archive_path); + else (void)copy_selected_archive_link(&state); + } + else if (action == GUI_CTX_TOGGLE_FORMAT) state.address_compact = !state.address_compact; + else if (action == GUI_CTX_EDIT_SITE && state.archive_open) { + snprintf(state.site_name_before_edit, sizeof(state.site_name_before_edit), "%s", state.site_name); + state.site_name_explicit_before_edit = state.site_name_explicit; + state.site_name[0] = '\0'; + state.site_name_editing = 1; + SDL_StartTextInput(); + } else if (action == GUI_CTX_COPY_SITE_RECORD && state.archive_open && state.site_name[0]) { + char hex[65]; + if (!dat_key_to_hex(state.archive_key_bytes, hex)) { + snprintf(state.site_dns_txt, sizeof(state.site_dns_txt), "dat://%s\nTTL=3600", hex); + (void)SDL_SetClipboardText(state.site_dns_txt); + snprintf(state.network_status, sizeof(state.network_status), "DAT-DNS RECORD COPIED"); + } + } else if (action == GUI_CTX_CLEAR_SITE && state.site_name[0]) { + if (!dat_dns_name_remove(state.site_name)) { + state.site_name[0] = '\0'; + state.site_name_explicit = 0; + snprintf(state.network_status, sizeof(state.network_status), "LOCAL DAT-DNS ALIAS REMOVED"); + } + } else if (action == GUI_CTX_EDIT_REMOTE) { + snprintf(state.remote_address_before_edit, sizeof(state.remote_address_before_edit), "%s", state.remote_address); + snprintf(state.remote_address, sizeof(state.remote_address), "dat://"); + state.remote_address_editing = 1; + SDL_StartTextInput(); + } else if (action == GUI_CTX_PASTE_REMOTE) { + if (!paste_remote_address(&state)) (void)parse_remote_address(&state); + } else if (action == GUI_CTX_COPY_REMOTE_SITE && state.remote_site[0]) { + (void)SDL_SetClipboardText(state.remote_site); + } else if (action == GUI_CTX_CLEAR_REMOTE) { + state.remote_address[0] = state.remote_site[0] = state.remote_archive[0] = '\0'; + state.remote_version[0] = '\0'; + state.remote_key_valid = 0; + } else if (action == GUI_CTX_IMPORT_REMOTE && state.remote_key_valid) { + if (state.pool_initialized) network_stop(&state); + network_start_remote_import(&state); + } else if (action == GUI_CTX_CANCEL_IMPORT && state.remote_import_mode) { + network_stop(&state); + snprintf(state.network_status, sizeof(state.network_status), "REMOTE IMPORT CANCELLED"); + } else if (action == GUI_CTX_COPY_IMPORT_LINK && state.remote_address[0]) { + (void)SDL_SetClipboardText(state.remote_address); + } else if (action == GUI_CTX_CHAT_PEER && state.context_peer_index >= 0 && + (size_t)state.context_peer_index < state.pool.peer_count) { + memcpy(state.chat_peer_id, state.pool.peers[state.context_peer_index].public_key, 32); + state.chat_open = 1; + SDL_StartTextInput(); + } else if (action == GUI_CTX_COPY_PEER && state.context_peer_index >= 0 && + (size_t)state.context_peer_index < state.pool.peer_count) { + char key[65]; + if (!dat_key_to_hex(state.pool.peers[state.context_peer_index].public_key, key)) + (void)SDL_SetClipboardText(key); + } else if (action == GUI_CTX_PAUSE_TRANSFER && state.context_peer_index >= 0) { + int index = state.context_peer_index; + if (index < (int)DAT_SESSION_MAX_PEERS) { + if (state.transfer_paused[index]) transfer_resume(&state, (size_t)index); + else transfer_disconnect(&state, (size_t)index, 0); + } + } else if (action == GUI_CTX_CANCEL_TRANSFER && state.context_peer_index >= 0) + transfer_disconnect(&state, (size_t)state.context_peer_index, 1); + else if (action == GUI_CTX_REVEAL_DESTINATION) { + const char *destination = state.remote_import_mode ? state.archive_path : + state.archive_loaded_path[0] ? state.archive_loaded_path : state.archive_path; + if (reveal_directory_in_file_browser(destination) != 0) + snprintf(state.network_status, sizeof(state.network_status), "COULD NOT REVEAL DESTINATION"); + } else if (action == GUI_CTX_SELECT_SHARED && state.context_peer_index >= 0 && + (size_t)state.context_peer_index < state.shared_path_count) { + if (select_shared_archive(&state, (size_t)state.context_peer_index)) + snprintf(state.network_status, sizeof(state.network_status), "ARCHIVE IS NO LONGER AVAILABLE"); + } + } + state.context_menu = 0; + event.button.y = -1; + } + if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT && + !state.chat_open && event.button.x >= 10 && event.button.x < 84 && event.button.y >= 5 && event.button.y < 25) { + if (event.button.x < 47) network_stop(&state); + else if (!state.network_requested) network_start(&state); + } + if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT && + !state.chat_open && !state.help_visible && !state.menu_visible) { + int field_x = 84, archive_w = w * 29 / 100; + int format_x = field_x + archive_w + 5, copy_x = format_x + 60; + if (event.button.y >= 26 && event.button.y < 46 && + event.button.x >= field_x && event.button.x < field_x + w * 25 / 100) { + if (state.archive_open) { + snprintf(state.site_name_before_edit, sizeof(state.site_name_before_edit), "%s", state.site_name); + state.site_name_explicit_before_edit = state.site_name_explicit; + state.site_name[0] = '\0'; + state.site_name_editing = 1; + state.site_record_modal = 0; + SDL_StartTextInput(); + snprintf(state.network_status, sizeof(state.network_status), "TYPE SITE HOSTNAME - ENTER TO SAVE"); + } else snprintf(state.network_status, sizeof(state.network_status), "LOAD AN ARCHIVE BEFORE SETTING ITS SITE NAME"); + event.button.y = -1; + } else if (event.button.y >= 50 && event.button.y < 70 && + event.button.x >= format_x && event.button.x < format_x + 56) { + state.address_compact = !state.address_compact; + event.button.y = -1; + } else if (event.button.y >= 50 && event.button.y < 70 && + event.button.x >= copy_x && event.button.x < copy_x + 48 && state.archive_open) { + char address[DAT_URL_BYTES + 128]; + dat_link_t link; + memset(&link, 0, sizeof(link)); + memcpy(link.key, state.archive_key_bytes, sizeof(link.key)); + link.has_version = 1; + link.version = state.selected_version; + if (state.address_compact) (void)dat_link_format_compact(&link, address, sizeof(address)); + else (void)dat_link_format(&link, address, sizeof(address)); + (void)SDL_SetClipboardText(address); + snprintf(state.network_status, sizeof(state.network_status), "DAT ADDRESS COPIED"); + event.button.y = -1; + } else if (click_archive_version_bar(&state, event.button.x, event.button.y, field_x)) { + event.button.y = -1; + } + } + if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT && !state.help_visible) { + SDL_Rect files = browser_box(w, h); + int track_y = files.y + 23, track_h = files.h - 29; + if (event.button.x >= files.x + files.w - 8 && event.button.x < files.x + files.w && + event.button.y >= track_y && event.button.y < track_y + track_h) { + int rows = browser_row_count(state.archive_path), visible = (files.h - 34) / 16; + int max_scroll = rows - visible, thumb_h = rows > 0 ? track_h * visible / rows : track_h; + int travel, thumb_pos; + if (thumb_h < 14) thumb_h = 14; + travel = track_h - thumb_h; + thumb_pos = event.button.y - track_y - thumb_h / 2; + if (thumb_pos < 0) thumb_pos = 0; + if (thumb_pos > travel) thumb_pos = travel; + if (max_scroll > 0 && travel > 0) state.browser_scroll = thumb_pos * max_scroll / travel; + state.browser_drag = 1; + state.browser_drag_y = event.button.y; + state.browser_drag_scroll = state.browser_scroll; + event.button.y = -1; + } + } + if(event.type==SDL_MOUSEBUTTONDOWN&&event.button.button==SDL_BUTTON_LEFT&&event.button.clicks>=2&& + !state.chat_open&&!state.help_visible&&!state.menu_visible){ + int left= w*18/100, top=104, body=h-top-52-44; + int panel_y=top+26, first_y=panel_y+27, row=(event.button.y-first_y)/18; + cesspool_peer_t *known[CESSPOOL_MAX_PEERS];size_t found=0; + if(event.button.x>=22&&event.button.x<22+left&&event.button.y>=first_y&&row>=0&& + state.pool_initialized) + for(size_t p=0;p=0&&(size_t)rowpublic_key,32);state.chat_open=1; + state.chat_input[0]='\0';snprintf(state.chat_status,sizeof(state.chat_status),"SEALED DIRECT MESSAGE OVER CESSPOOL"); + SDL_StartTextInput(); + } + } + if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT && + !state.chat_open && !state.help_visible && !state.menu_visible) { + SDL_Rect speaker = speaker_bounds(w, h); + int speaker_x = speaker.x + speaker.w / 2; + int speaker_y = speaker.y + speaker.h / 2; + int dx = event.button.x - speaker_x; + int dy = event.button.y - speaker_y; + int radius = speaker.w / 2; + if (speaker.w > 0 && dx * dx + dy * dy <= radius * radius) audio_cue_broadcast(&state, 2); + dx = event.button.x - w * 95 / 100; + dy = event.button.y - 38; + if (dx * dx + dy * dy <= 34 * 34) audio_cue_broadcast(&state, 1); + } + if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT && + !state.chat_open && !state.help_visible && !state.menu_visible) { + int field_x = w * 72 / 100, field_w = w * 18 / 100; + if (event.button.x >= field_x && event.button.x < field_x + field_w && + event.button.y >= 50 && event.button.y < 70) { + state.remote_address_editing = 1; + snprintf(state.remote_address_before_edit, sizeof(state.remote_address_before_edit), "%s", state.remote_address); + strcpy(state.remote_address, "dat://"); + SDL_StartTextInput(); + } + } + if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT && + !state.chat_open && !state.help_visible && !state.menu_visible) { + SDL_Rect files = browser_box(w, h); + int top = 104, bottom = 52, body_h = h - top - bottom - 44; + int table_y = top + body_h * 53 / 100 + 34; + SDL_Rect import_button = {files.x, table_y - 25, (files.w - 8) / 2, 20}; + SDL_Rect create_button = {import_button.x + import_button.w + 8, table_y - 25, + files.w - import_button.w - 8, 20}; + if (event.button.x >= files.x + files.w - 72 && + event.button.x < files.x + files.w && event.button.y >= files.y && + event.button.y < files.y + 20) { + state.archive_switcher_visible = !state.archive_switcher_visible; + } + if (state.archive_switcher_visible && event.button.x >= files.x + 3 && + event.button.x < files.x + files.w - 3 && event.button.y >= files.y + 20) { + int row = (event.button.y - files.y - 25) / 15; + if (row >= 0 && (size_t)row < state.shared_path_count) { + if (select_shared_archive(&state, (size_t)row)) { + snprintf(state.network_status, sizeof(state.network_status), "ARCHIVE IS NO LONGER AVAILABLE"); + } else snprintf(state.network_status, sizeof(state.network_status), state.online ? "CLIENT ONLINE - ARCHIVE SELECTED" : "ARCHIVE SELECTED - CLIENT OFFLINE"); + event.button.y = -1; + } + } + if (event.button.x >= import_button.x && event.button.x < import_button.x + import_button.w && + event.button.y >= import_button.y && event.button.y < import_button.y + import_button.h) { + if (state.remote_key_valid && state.remote_address[0]) { + if (state.pool_initialized) network_stop(&state); + network_start_remote_import(&state); + } else if (state.archive_open) { + (void)open_existing_archive(&state); + } else if (open_existing_archive(&state) == 0) + snprintf(state.network_status, sizeof(state.network_status), "ARCHIVE LOADED - CLICK ON TO SHARE"); + } else if (event.button.x >= create_button.x && event.button.x < create_button.x + create_button.w && + event.button.y >= create_button.y && event.button.y < create_button.y + create_button.h) { + if (prepare_archive(&state) == 0) + snprintf(state.network_status, sizeof(state.network_status), "ARCHIVE CREATED - CLICK ON TO SHARE"); + } + { + int sum = 0, widths[6], defaults[6] = {center_w * 18 / 100, center_w * 32 / 100, + center_w * 12 / 100, center_w * 16 / 100, center_w * 13 / 100, center_w * 9 / 100}; + SDL_Rect table = {center_x, table_y, center_w, h - table_y - bottom - 12}; + if (!state.transfer_widths[0]) for (int j = 0; j < 6; j++) state.transfer_widths[j] = defaults[j] < 70 ? 70 : defaults[j]; + for (int j = 0; j < 6; j++) widths[j] = state.transfer_widths[j]; + for (int j = 0; j < 6; j++) { + sum += widths[j]; + if (j < 5) { + int boundary = table.x + 8 + sum - state.transfer_scroll; + if (event.button.y >= table.y + 4 && event.button.y < table.y + 23 && + abs(event.button.x - boundary) <= 5) { + state.resize_column = j + 1; + state.transfer_drag_x = event.button.x; + } + } + } + } + } + if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT && !state.help_visible) { + int top = 104, bottom = 52, body_h = h - top - bottom - 44; + int table_y = top + body_h * 53 / 100 + 34; + SDL_Rect table = {center_x, table_y, center_w, h - table_y - bottom - 12}; + int bar_y = table.y + table.h - 9; + if (event.button.x >= table.x + 8 && event.button.x < table.x + table.w - 8 && + event.button.y >= bar_y - 3 && event.button.y < bar_y + 8) { + int total = 0, defaults[6] = {center_w * 18 / 100, center_w * 32 / 100, + center_w * 12 / 100, center_w * 16 / 100, center_w * 13 / 100, center_w * 9 / 100}; + if (!state.transfer_widths[0]) for (int c = 0; c < 6; c++) state.transfer_widths[c] = defaults[c] < 70 ? 70 : defaults[c]; + for (int c = 0; c < 6; c++) total += state.transfer_widths[c]; + if (total > table.w - 16) { + int viewport = table.w - 16, max_scroll = total - viewport; + state.transfer_scroll = (event.button.x - table.x - 8) * max_scroll / viewport; + if (state.transfer_scroll > max_scroll) state.transfer_scroll = max_scroll; + state.transfer_scroll_drag = 1; + state.transfer_drag_x = event.button.x; + } + event.button.y = -1; + } + } + if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT && + !state.help_visible && !state.menu_visible) { + SDL_Rect files = browser_box(w, h); + if (!state.archive_switcher_visible && event.button.x >= files.x && event.button.x < files.x + files.w && + event.button.y >= files.y + 24 && event.button.y < files.y + files.h) { + char previous[sizeof(state.archive_path)]; + strcpy(previous, state.archive_path); + if (browser_item(&state, state.browser_scroll + (event.button.y - files.y - 24) / 16)) { + if (strcmp(previous, state.archive_path)) { + snprintf(state.network_status, sizeof(state.network_status), + state.archive_open ? "BROWSING - SELECTED ARCHIVE STAYS LOADED" : + "DIRECTORY SELECTED - LOAD OR CREATE DAT"); + } + } + } + } + if (event.type == SDL_KEYDOWN) { + SDL_Keycode key = event.key.keysym.sym; + if (state.bootstrap_editing && key == SDLK_ESCAPE) { + snprintf(state.bootstrap_endpoint, sizeof(state.bootstrap_endpoint), "%s", + state.bootstrap_endpoint_before_edit); + state.bootstrap_editing = 0; + SDL_StopTextInput(); + } else if (state.bootstrap_editing && key == SDLK_v && (event.key.keysym.mod & KMOD_CTRL)) { + char *clipboard = SDL_GetClipboardText(); + if (clipboard) { + size_t used = strlen(state.bootstrap_endpoint), added = strcspn(clipboard, "\r\n\t "); + if (added && used + added < sizeof(state.bootstrap_endpoint)) { + memcpy(state.bootstrap_endpoint + used, clipboard, added); + state.bootstrap_endpoint[used + added] = '\0'; + } + SDL_free(clipboard); + } + } else if (state.bootstrap_editing && key == SDLK_BACKSPACE) { + size_t len = strlen(state.bootstrap_endpoint); + if (len) state.bootstrap_endpoint[len - 1] = '\0'; + } else if (state.bootstrap_editing && (key == SDLK_RETURN || key == SDLK_KP_ENTER)) { + if (save_bootstrap_edit(&state) != 0) + snprintf(state.network_status, sizeof(state.network_status), "INVALID NODE - USE HOST:PORT"); + } else if (state.settings_visible && key == SDLK_ESCAPE) { + state.settings_visible = 0; + } else if (state.settings_visible && (key == SDLK_UP || key == SDLK_DOWN)) { + state.settings_selection = (state.settings_selection + (key == SDLK_DOWN ? 1 : 3)) % 4; + } else if (state.settings_visible && (key == SDLK_RETURN || key == SDLK_KP_ENTER || + key == SDLK_LEFT || key == SDLK_RIGHT || key == SDLK_SPACE)) { + if (state.settings_selection == 3) begin_bootstrap_edit(&state); + else change_preference(&state, key == SDLK_LEFT ? -1 : 1); + } else if (state.menu_visible && key == SDLK_ESCAPE) { + state.menu_visible = 0; + } else if (state.menu_visible && (key == SDLK_UP || key == SDLK_DOWN)) { + state.menu_selection = (state.menu_selection + (key == SDLK_DOWN ? 1 : 2)) % 3; + } else if (state.menu_visible && (key == SDLK_RETURN || key == SDLK_KP_ENTER)) { + activate_main_menu(&state, &running); + } else if (state.site_name_editing && key == SDLK_ESCAPE) { + snprintf(state.site_name, sizeof(state.site_name), "%s", state.site_name_before_edit); + state.site_name_explicit = state.site_name_explicit_before_edit; + state.site_name_editing = 0; + SDL_StopTextInput(); + } else if (state.site_name_editing && key == SDLK_BACKSPACE) { + size_t len = strlen(state.site_name); + if (len) state.site_name[len - 1] = '\0'; + } else if (state.site_name_editing && (key == SDLK_RETURN || key == SDLK_KP_ENTER)) { + if (save_site_name(&state) == 0) { + state.site_name_editing = 0; + SDL_StopTextInput(); + } else snprintf(state.network_status, sizeof(state.network_status), "INVALID HOSTNAME OR DAT-DNS SAVE FAILED"); + } else if (state.chat_open && key == SDLK_ESCAPE) { + state.chat_open=0;state.chat_input[0]='\0';SDL_StopTextInput(); + } else if (state.chat_open && key == SDLK_v && (event.key.keysym.mod & KMOD_CTRL)) { + if (paste_chat_text(&state) != 0) + snprintf(state.chat_status, sizeof(state.chat_status), "CLIPBOARD EMPTY OR MESSAGE TOO LONG"); + } else if (state.chat_open && key == SDLK_BACKSPACE) { + size_t len=strlen(state.chat_input);if(len)state.chat_input[len-1]='\0'; + } else if (state.chat_open && key == SDLK_RETURN) { + (void)cable_chat_send(&state); + } else if (state.remote_address_editing && key == SDLK_v && + (event.key.keysym.mod & KMOD_CTRL)) { + if (paste_remote_address(&state)) + snprintf(state.network_status, sizeof(state.network_status), "CLIPBOARD DOES NOT CONTAIN A DAT LINK"); + } else if (state.remote_address_editing && key == SDLK_BACKSPACE) { + size_t len = strlen(state.remote_address); + if (len) state.remote_address[len - 1] = '\0'; + } else if (state.remote_address_editing && (key == SDLK_RETURN || key == SDLK_KP_ENTER)) { + if (begin_remote_import(&state) == 0) { + state.remote_address_editing = 0; + SDL_StopTextInput(); + } + } else if (key == SDLK_ESCAPE && state.remote_address_editing) { + snprintf(state.remote_address, sizeof(state.remote_address), "%s", state.remote_address_before_edit); + state.remote_address_editing = 0; + SDL_StopTextInput(); + } else if (key == SDLK_ESCAPE) { + if (state.context_menu) state.context_menu = 0; + else if (state.help_visible) state.help_visible = 0; + else { state.menu_visible = 1; state.menu_selection = 0; } + } else if (key == SDLK_F1) { + if (!state.help_count) load_manpage(&state); + state.help_visible = !state.help_visible; + state.menu_visible = 0; + state.settings_visible = 0; + } else if (key == SDLK_F9) { + if (!state.archive_open && state.remote_key_valid) { + if (state.pool_initialized) network_stop(&state); + network_start_remote_import(&state); + } + else (void)open_existing_archive(&state); + } else if (state.help_visible && key == SDLK_DOWN) { + if (state.help_scroll + 1 < state.help_count) state.help_scroll++; + } else if (state.help_visible && key == SDLK_UP) { + if (state.help_scroll > 0) state.help_scroll--; + } + } + } + now = SDL_GetTicks(); + if (now - last_refresh >= 1000 && state.archive_open && !state.remote_import_mode) { + char previous_key[65]; + int was_online = state.online; + strcpy(previous_key, state.archive_key); + if (state.archive_open && state.archive_loaded_path[0]) + refresh_archive_at(&state, state.archive_loaded_path); + if (!state.archive_open) { + snprintf(state.network_status, sizeof(state.network_status), "NO ARCHIVE"); + } else if (was_online && strcmp(previous_key, state.archive_key)) { + /* Global online state survives local archive refreshes. */ + } + last_refresh = now; + } + network_pump(&state, now); + update_transfer_rates(&state, now); + SDL_GetRendererOutputSize(renderer, &w, &h); + draw(renderer, w, h, now, &state); + if (smoke) { + SDL_Surface *surface = SDL_CreateRGBSurfaceWithFormat(0, w, h, 32, + SDL_PIXELFORMAT_ARGB8888); + if (!surface || SDL_RenderReadPixels(renderer, NULL, + SDL_PIXELFORMAT_ARGB8888, + surface->pixels, + surface->pitch) != 0 || + SDL_SaveBMP(surface, DAT_BUILD_DIR "/dat-thang-smoke.bmp") != 0) { + fprintf(stderr, "dat-thang screenshot: %s\n", SDL_GetError()); + if (surface) SDL_FreeSurface(surface); + gui_font_close(); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); + return 1; + } + SDL_FreeSurface(surface); + } + SDL_RenderPresent(renderer); + if (smoke) running = 0; + } + cable_video_source_close(state.cable_video_source); + if (state.cable_video_texture) SDL_DestroyTexture(state.cable_video_texture); + network_stop(&state); + fm_audio_stop(&state); + if (state.dolphin_icon) SDL_DestroyTexture(state.dolphin_icon); + if (state.speaker_icon) SDL_DestroyTexture(state.speaker_icon); + gui_font_close(); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + dat_scheme_runtime_close(&state.metadata_scheme); + return 0; +} + diff --git a/src/sleep.c b/src/sleep.c index 3ed5418..320c679 100644 --- a/src/sleep.c +++ b/src/sleep.c @@ -1,46 +1,24 @@ #include "sleep.h" +#include #include -#include #include #include -#include +#ifdef _WIN32 +#include +#define DAT_MKDIR(path) _mkdir(path) +#else +#define DAT_MKDIR(path) mkdir(path, 0700) +#endif -static int write_header(const char *path, uint32_t magicka, uint16_t entry_size, const char *algo) { - FILE *f = fopen(path, "wb"); - if (!f) return -1; - - sleep_header_t h; - memset(&h, 0, sizeof(h)); - h.magicka = ((magicka >> 24) & 0xff) | ((magicka >> 8) & 0xff00) | ((magicka << 8) & 0xff0000) | ((magicka << 24) & 0xff000000); - h.version = 0; - h.entry_size = (entry_size >> 8) | (entry_size << 8); // BE16 - h.algo_len = algo ? (uint8_t)strlen(algo) : 0; - if (algo) strncopy(h.algo, algo, sizeof(h.algo)); - if (fwrite(&h, 1, sizeof(h), f) != sizeof(h)) { - fclose(f); - return -1; - } - fclose(f); - return 0; +static int make_directory(const char *path) { + if (DAT_MKDIR(path) == 0 || errno == EEXIST) return 0; + return -1; } int sleep_init(const char *root) { - char path[512]; - - // makedir .dat(t) testing - sprintf(path, sizeof(path), "%s/.datt", root); - if (mkdir(path, 0700) && errno != EEXIST) return -1; - - // content.* signatures, tree, bitfield, key - - // metadata.* signatures, tree, bitfield, key - return 0; -} - -int main(void) { - if (sleep_init(".")) { - perror("sleep_init failed"); - return 1; - } - return 0; + char path[1024]; int n; + if (!root || !*root) { errno = EINVAL; return -1; } + n = snprintf(path, sizeof(path), "%s/.dat", root); + if (n < 0 || (size_t)n >= sizeof(path)) { errno = ENAMETOOLONG; return -1; } + return make_directory(path); } diff --git a/src/sleep_bitfield.c b/src/sleep_bitfield.c new file mode 100644 index 0000000..09ea633 --- /dev/null +++ b/src/sleep_bitfield.c @@ -0,0 +1,34 @@ +#include "sleep_bitfield.h" +#include +#include + +static int bit_get(const uint8_t *bits, size_t bytes, size_t bit) { + if (bit >= bytes * 8) { errno = ERANGE; return -1; } + return (bits[bit >> 3] >> (7 - (bit & 7))) & 1; +} +static int bit_set(uint8_t *bits, size_t bytes, size_t bit, int value) { + uint8_t mask; if (bit >= bytes * 8) { errno = ERANGE; return -1; } + mask = (uint8_t)(1u << (7 - (bit & 7))); if (value) bits[bit >> 3] |= mask; else bits[bit >> 3] &= (uint8_t)~mask; return 0; +} +static unsigned byte_state(uint8_t v) { return v == 0 ? 0u : (v == 0xff ? 3u : 2u); } +void sleep_bitfield_clear(sleep_bitfield_t *f) { if (f) memset(f, 0, sizeof(*f)); } +int sleep_bitfield_get_data(const sleep_bitfield_t *f, size_t bit) { if (!f) { errno = EINVAL; return -1; } return bit_get(f->data, sizeof(f->data), bit); } +int sleep_bitfield_get_tree(const sleep_bitfield_t *f, size_t bit) { if (!f) { errno = EINVAL; return -1; } return bit_get(f->tree, sizeof(f->tree), bit); } +void sleep_bitfield_reindex(sleep_bitfield_t *f) { + size_t pair; if (!f) return; memset(f->index, 0, sizeof(f->index)); + for (pair = 0; pair < 512; pair++) { unsigned tuple = byte_state(f->data[pair*2]) | (byte_state(f->data[pair*2+1]) << 2); size_t byte = pair >> 1; + f->index[byte] |= (uint8_t)(tuple << ((pair & 1) ? 4 : 0)); } +} +int sleep_bitfield_set_data(sleep_bitfield_t *f, size_t bit, int present) { if (!f) { errno = EINVAL; return -1; } if (bit_set(f->data, sizeof(f->data), bit, present)) return -1; sleep_bitfield_reindex(f); return 0; } +int sleep_bitfield_set_tree(sleep_bitfield_t *f, size_t bit, int present) { if (!f) { errno = EINVAL; return -1; } return bit_set(f->tree, sizeof(f->tree), bit, present); } +size_t sleep_bitfield_next_data(const sleep_bitfield_t *f, size_t start, int present) { + size_t bit; if (!f) return SIZE_MAX; for (bit = start; bit < sizeof(f->data)*8; bit++) if (bit_get(f->data, sizeof(f->data), bit) == !!present) return bit; return SIZE_MAX; +} +int sleep_bitfield_encode(const sleep_bitfield_t *f, uint8_t *out, size_t cap) { + if (!f || !out) { errno = EINVAL; return -1; } if (cap < sizeof(*f)) { errno = ENOSPC; return -1; } + memcpy(out, f->data, 1024); memcpy(out+1024, f->tree, 2048); memcpy(out+3072, f->index, 256); memcpy(out+3328, f->reserved, 256); return 0; +} +int sleep_bitfield_decode(sleep_bitfield_t *f, const uint8_t *in, size_t len) { + if (!f || !in) { errno = EINVAL; return -1; } if (len < sizeof(*f)) { errno = EPROTO; return -1; } + memcpy(f->data, in, 1024); memcpy(f->tree, in+1024, 2048); memcpy(f->index, in+3072, 256); memcpy(f->reserved, in+3328, 256); return 0; +} diff --git a/src/sleep_crypto.c b/src/sleep_crypto.c index df028e1..8d78c96 100644 --- a/src/sleep_crypto.c +++ b/src/sleep_crypto.c @@ -1,29 +1,94 @@ #include "sleep_crypto.h" +#include +#include #include #include #include -int sleep_generate_keypair(sleep_keypair_t *kp) { +static void put64be(uint8_t *p, uint64_t v) { + unsigned int i; + for (i = 0; i < 8; i++) p[i] = (uint8_t)(v >> (56 - i * 8)); +} + +static int encode_roots(uint8_t **out, size_t *out_len, const uint8_t *roots, + size_t nroots, const uint64_t *indices, const uint64_t *lengths) { + size_t i, len; + uint8_t *p; + if (!out || !out_len || (nroots && (!roots || !indices || !lengths))) { errno = EINVAL; return -1; } + if (nroots > (SIZE_MAX - 1) / SLEEP_ROOT_RECORD_BYTES) { errno = EOVERFLOW; return -1; } + len = 1 + nroots * SLEEP_ROOT_RECORD_BYTES; + p = malloc(len); + if (!p) return -1; + p[0] = 2; /* legacy SLEEP root domain */ + for (i = 0; i < nroots; i++) { + memcpy(p + 1 + i * 48, roots + i * 32, 32); + put64be(p + 33 + i * 48, indices[i]); + put64be(p + 41 + i * 48, lengths[i]); + } + *out = p; *out_len = len; return 0; +} + +int sleep_crypto_init(void) { return sodium_init() < 0 ? -1 : 0; } + +int sleep_stream_xsalsa20_xor(uint8_t *out, const uint8_t *in, size_t length, + const uint8_t nonce[24], const uint8_t key[32], + uint64_t stream_offset) +{ + uint8_t *aligned; + size_t skip = (size_t)(stream_offset & 63u), total; + uint64_t counter = stream_offset >> 6; + if ((!out && length) || (!in && length) || !nonce || !key || + length > SIZE_MAX - skip) { errno = EINVAL; return -1; } + total = skip + length; + if (!total) return 0; + if (counter > UINT64_MAX - (total / 64u + !!(total % 64u))) { errno = EOVERFLOW; return -1; } + aligned = calloc(total, 1); + if (!aligned) return -1; + if (length) memcpy(aligned + skip, in, length); + if (crypto_stream_xsalsa20_xor_ic(aligned, aligned, total, nonce, counter, key) != 0) { + free(aligned); errno = EIO; return -1; + } + if (length) memmove(out, aligned + skip, length); + sodium_memzero(aligned, total); + free(aligned); + return 0; +} + +int sleep_compute_discovery_key(const uint8_t pubkey[32], uint8_t out[32]) +{ + static const uint8_t domain[] = "hypercore"; + if (!pubkey || !out) { errno = EINVAL; return -1; } + /* Dat discovery keys are BLAKE2b(key=feed public key, message="hypercore"). */ + return crypto_generichash(out, 32, domain, sizeof(domain) - 1, pubkey, 32); +} + +int sleep_gen_keypair(sleep_keypair_t *kp) { + if (!kp) { errno = EINVAL; return -1; } if (crypto_sign_keypair(kp->pubkey, kp->seckey) !=0) return -1; return 0; } -int sleep_save_pubkey(const char *path, const uint8_t pubkey[SLEEP_PUBLIC_KEY_BYTES]) { +int sleep_save_pubkey(const char *path, const uint8_t pubkey[SLEEP_PUB_KEY_BYTES]) { FILE *f = fopen(path, "wb"); - size_t written = fwrite(pub, 1, SLEEP_PUBLIC_KEY_BYTES, f); + size_t written; + if (!f) return -1; + written = fwrite(pubkey, 1, SLEEP_PUB_KEY_BYTES, f); fclose(f); - return (written == SLEEP_PUBLIC_KEY_BYTES) ? 0 : -1; + return (written == SLEEP_PUB_KEY_BYTES) ? 0 : -1; } -int sleep_load_pubkey(const char *path, uint8_t pubkey[SLEEP_PUBLIC_KEY_BYTES]) { +int sleep_load_pubkey(const char *path, uint8_t pubkey[SLEEP_PUB_KEY_BYTES]) { FILE *f = fopen(path, "rb"); if (!f) return -1; - size_t read = fread(pub, 1, SLEEP_PUBLIC_KEY_BYTES, f); + size_t read = fread(pubkey, 1, SLEEP_PUB_KEY_BYTES, f); fclose(f); - return (read == SLEEP_PUBLIC_KEY_BYTES) ? 0 : -1; + return (read == SLEEP_PUB_KEY_BYTES) ? 0 : -1; } +int sleep_save_keypair(const char *path,const sleep_keypair_t*kp){FILE*f;size_t n;if(!path||!kp){errno=EINVAL;return-1;}f=fopen(path,"wb");if(!f)return-1;n=fwrite(kp,1,sizeof(*kp),f);if(fclose(f))return-1;return n==sizeof(*kp)?0:-1;} +int sleep_load_keypair(const char *path,sleep_keypair_t*kp){FILE*f;size_t n;if(!path||!kp){errno=EINVAL;return-1;}f=fopen(path,"rb");if(!f)return-1;n=fread(kp,1,sizeof(*kp),f);fclose(f);return n==sizeof(*kp)?0:-1;} + int sleep_blake2b(uint8_t out[SLEEP_BLAKE2B_BYTES], const uint8_t *in, size_t inlen) { return crypto_generichash(out, SLEEP_BLAKE2B_BYTES, in, inlen, NULL, 0); } @@ -32,26 +97,20 @@ int sleep_sign(uint8_t sig[SLEEP_SIGNATURE_BYTES], const uint8_t *msg, size_t ms return crypto_sign_detached(sig, NULL, msg, msglen, kp->seckey); } -int sleep_verify(const uint8_t sig[SLEEP_SIGNATURE_BYTES], const uint8_t *msg, size_t msglen, const uint8_t pubkey[SLEEP_PUBLIC_KEY_BYTES]) { +int sleep_verify(const uint8_t sig[SLEEP_SIGNATURE_BYTES], const uint8_t *msg, size_t msglen, const uint8_t pubkey[SLEEP_PUB_KEY_BYTES]) { return crypto_sign_verify_detached(sig, msg, msglen, pubkey); } int sleep_sign_roots(uint8_t sig[SLEEP_SIGNATURE_BYTES], const uint8_t *roots, size_t nroots, const uint64_t *indices, const uint64_t *lengths, const sleep_keypair_t *kp) { - uint8_t bug[1024]; // FIXME dynamic - size_t ctxlen = strlen(SLEEP_CONTEXT); - memcpy(bug, SLEEP_CONTEXT, ctxlen); - size_t off = ctxlen; // off defined in scope? - off += encode_roots(bug + off, roots, nroots, indices, lengths); - return sleep_sign(sig, bug, off, kp); + uint8_t *message, digest[32]; size_t len; int rc; + if (encode_roots(&message, &len, roots, nroots, indices, lengths)) return -1; + rc = sleep_blake2b(digest, message, len); free(message); + return rc ? rc : sleep_sign(sig, digest, sizeof(digest), kp); } -int sleep_verify_roots(const uint8_t sig[SLEEP_SIGNATURE_BYTES], const uint8_t *roots, size_t nroots, const uint64_t *indices, const uint64_t *lengths, const uint8_t pub[SLEEP_PUBLIC_KEY_BYTES]) { - uint8_t bug[1024]; // FIXME dynamic - size_t ctxlen = strlen(SLEEP_CONTEXT); - memcpy(bug, SLEEP_CONTEXT, ctxlen); - size_t off = ctxlen; // off defined in scope? - off += encode_roots(bug + off, roots, nroots, indices, lengths -); - return sleep_verify(sig, bug, off, kp); +int sleep_verify_roots(const uint8_t sig[SLEEP_SIGNATURE_BYTES], const uint8_t *roots, size_t nroots, const uint64_t *indices, const uint64_t *lengths, const uint8_t pub[SLEEP_PUB_KEY_BYTES]) { + uint8_t *message, digest[32]; size_t len; int rc; + if (encode_roots(&message, &len, roots, nroots, indices, lengths)) return -1; + rc = sleep_blake2b(digest, message, len); free(message); + return rc ? rc : sleep_verify(sig, digest, sizeof(digest), pub); } - diff --git a/src/sleep_feed.c b/src/sleep_feed.c new file mode 100644 index 0000000..ffd5e33 --- /dev/null +++ b/src/sleep_feed.c @@ -0,0 +1,131 @@ +#define _POSIX_C_SOURCE 200809L +#include "sleep_feed.h" +#include "sleep.h" +#include "sleep_crypto.h" +#include "sleep_writer.h" +#include "sleep_bitfield.h" +#include "sleep_proof.h" +#include +#include +#include +#include +#include + +static uint64_t get64be(const uint8_t *p) { uint64_t v=0; unsigned i; for(i=0;i<8;i++) v=(v<<8)|p[i]; return v; } +static uint32_t get32be(const uint8_t *p) { return ((uint32_t)p[0]<<24)|((uint32_t)p[1]<<16)|((uint32_t)p[2]<<8)|p[3]; } +static void put64be(uint8_t *p,uint64_t v){unsigned i;for(i=0;i<8;i++)p[i]=(uint8_t)(v>>(56-i*8));} +static void put32be(uint8_t *p,uint32_t v){p[0]=(uint8_t)(v>>24);p[1]=(uint8_t)(v>>16);p[2]=(uint8_t)(v>>8);p[3]=(uint8_t)v;} +static int path_for(char *out, size_t cap, const sleep_feed_t *f, const char *suffix) { int n=snprintf(out,cap,"%s/%s.%s",f->directory,f->name,suffix); if(n<0||(size_t)n>=cap){errno=ENAMETOOLONG;return -1;}return 0; } +static int read_file(const char *path, uint8_t **out, size_t *len) { + FILE *fp=fopen(path,"rb"); long n; uint8_t *p; if(!fp) return -1; + if(fseek(fp,0,SEEK_END)||(n=ftell(fp))<0||fseek(fp,0,SEEK_SET)){fclose(fp);return -1;} + p=malloc(n?(size_t)n:1); if(!p){fclose(fp);return -1;} if(n&&fread(p,1,(size_t)n,fp)!=(size_t)n){free(p);fclose(fp);errno=EIO;return -1;} + fclose(fp);*out=p;*len=(size_t)n;return 0; +} +static int reserve_feed(sleep_feed_t *f,size_t needed,int with_proofs){ + size_t capacity=f->capacity?f->capacity:8,old=f->capacity; + uint8_t **blocks,**proofs; + size_t *lengths,*proof_lengths; + if(needed<=f->capacity&&(!with_proofs||f->proofs))return 0; + while(capacitySIZE_MAX/2){errno=EOVERFLOW;return-1;}capacity*=2;} + if(capacity>SIZE_MAX/sizeof(*f->blocks)||capacity>SIZE_MAX/sizeof(*f->lengths)){errno=EOVERFLOW;return-1;} + blocks=realloc(f->blocks,capacity*sizeof(*blocks));if(!blocks)return-1;f->blocks=blocks; + lengths=realloc(f->lengths,capacity*sizeof(*lengths));if(!lengths)return-1;f->lengths=lengths; + memset(f->blocks+old,0,(capacity-old)*sizeof(*f->blocks)); + memset(f->lengths+old,0,(capacity-old)*sizeof(*f->lengths)); + if(with_proofs||f->proofs){ + proofs=realloc(f->proofs,capacity*sizeof(*proofs));if(!proofs)return-1;f->proofs=proofs; + proof_lengths=realloc(f->proof_lengths,capacity*sizeof(*proof_lengths));if(!proof_lengths)return-1;f->proof_lengths=proof_lengths; + memset(f->proofs+old,0,(capacity-old)*sizeof(*f->proofs)); + memset(f->proof_lengths+old,0,(capacity-old)*sizeof(*f->proof_lengths)); + } + f->capacity=capacity;return 0; +} +static int append_remote_record(sleep_feed_t *f,size_t index,const uint8_t *data,size_t len,const uint8_t *proof,size_t proof_len){ + static const uint8_t magic[8]={'S','F','E','E','D','R','1',0}; + char path[1200];uint8_t header[40],record[16];FILE *fp;long end; + if(len>UINT32_MAX||proof_len>UINT32_MAX||path_for(path,sizeof(path),f,"remote"))return-1; + fp=fopen(path,"ab+");if(!fp)return-1; + if(fseek(fp,0,SEEK_END)||(end=ftell(fp))<0){fclose(fp);return-1;} + if(end==0){memcpy(header,magic,8);memcpy(header+8,f->keypair.pubkey,32);if(fwrite(header,1,sizeof(header),fp)!=sizeof(header)){fclose(fp);errno=EIO;return-1;}} + put64be(record,(uint64_t)index);put32be(record+8,(uint32_t)len);put32be(record+12,(uint32_t)proof_len); + if(fwrite(record,1,sizeof(record),fp)!=sizeof(record)||(len&&fwrite(data,1,len,fp)!=len)||(proof_len&&fwrite(proof,1,proof_len,fp)!=proof_len)||fflush(fp)||fsync(fileno(fp))){fclose(fp);errno=EIO;return-1;} + return fclose(fp); +} +int sleep_feed_open(sleep_feed_t *f,const char *dir,const char *name,const sleep_keypair_t *kp) { + char path[1200]; uint8_t *tree=NULL,*data=NULL; size_t tree_len=0,data_len=0,count,i,off=0; + if(!f||!dir||!name){errno=EINVAL;return -1;} memset(f,0,sizeof(*f)); + if(strlen(dir)>=sizeof(f->directory)||strlen(name)>=sizeof(f->name)){errno=ENAMETOOLONG;return -1;} strcpy(f->directory,dir);strcpy(f->name,name); + if(kp){f->keypair=*kp;f->writable=1;} + if(path_for(path,sizeof(path),f,"tree")||read_file(path,&tree,&tree_len)) { + if(errno==ENOENT&&kp)return 0; + if(errno==ENOENT&&!kp){ + char key_path[1200]; uint8_t public_key[32]; int n; + n=snprintf(key_path,sizeof(key_path),"%s/%s.key",dir,name); + if(n<0||(size_t)n>=sizeof(key_path)){errno=ENAMETOOLONG;return-1;} + if(sleep_load_pubkey(key_path,public_key))return-1; + return sleep_feed_open_remote(f,dir,name,public_key); + } + return -1; + } + if(tree_len<32||(tree_len-32)%40){free(tree);errno=EPROTO;return -1;} count=((tree_len-32)/40+1)/2; + if(path_for(path,sizeof(path),f,"data")||read_file(path,&data,&data_len)){free(tree);return -1;} + f->blocks=calloc(count,sizeof(*f->blocks));f->lengths=calloc(count,sizeof(*f->lengths));f->capacity=count;if(count&&(!f->blocks||!f->lengths))goto fail; + for(i=0;iSIZE_MAX||n>data_len-off){errno=EPROTO;goto fail;}f->lengths[i]=(size_t)n;f->blocks[i]=malloc(n?(size_t)n:1);if(!f->blocks[i])goto fail;if(n)memcpy(f->blocks[i],data+off,(size_t)n);off+=(size_t)n;f->length++;} + free(tree);free(data);return 0; +fail: free(tree);free(data);sleep_feed_close(f);return -1; +} +int sleep_feed_open_remote(sleep_feed_t *f,const char *dir,const char *name,const uint8_t public_key[32]) { + static const uint8_t magic[8]={'S','F','E','E','D','R','1',0}; + char path[1200];uint8_t *bytes=NULL;size_t length=0,off=40; + if(!f||!dir||!name||!public_key){errno=EINVAL;return-1;} + if(strlen(dir)>=sizeof(f->directory)||strlen(name)>=sizeof(f->name)){errno=ENAMETOOLONG;return-1;} + memset(f,0,sizeof(*f));strcpy(f->directory,dir);strcpy(f->name,name); + memcpy(f->keypair.pubkey,public_key,32);f->remote_only=1;f->writable=0;f->persist_remote=1; + if(path_for(path,sizeof(path),f,"remote")||read_file(path,&bytes,&length)){ + if(errno==ENOENT)return 0; + sleep_feed_close(f);return-1; + } + if(length<40||memcmp(bytes,magic,8)||memcmp(bytes+8,public_key,32))goto corrupt; + f->persist_remote=0; + while(off=SLEEP_BITFIELD_DATA_BYTES*8u||!proof_len||data_len>length-off||proof_len>length-off-data_len)goto corrupt; + if(sleep_proof_decode(&proof,bytes+off+data_len,proof_len)||proof.block!=index|| + sleep_proof_verify(&proof,bytes+off,data_len,public_key))goto corrupt; + if(sleep_feed_append_remote(f,(size_t)index,bytes+off,data_len, + bytes+off+data_len,proof_len))goto corrupt; + off+=(size_t)data_len+proof_len; + } + f->persist_remote=1;free(bytes);return 0; +corrupt: + free(bytes);sleep_feed_close(f);errno=EBADMSG;return-1; +} +int sleep_feed_load_remote(sleep_feed_t *f,const uint8_t public_key[32]) { + sleep_feed_t cached; + int persist,rc=0; + size_t i; + if(!f||!public_key||f->writable){errno=EINVAL;return-1;} + if(sleep_feed_open_remote(&cached,f->directory,f->name,public_key))return-1; + persist=f->persist_remote; + f->persist_remote=0; + memcpy(f->keypair.pubkey,public_key,32); + for(i=0;ipersist_remote=persist; + sleep_feed_close(&cached); + return rc; +} +size_t sleep_feed_length(const sleep_feed_t *f){return f?f->length:0;} +int sleep_feed_read(const sleep_feed_t *f,size_t index,uint8_t **out,size_t *len){uint8_t *p;if(!f||!out||!len||index>=f->length){errno=EINVAL;return -1;}if(!f->blocks[index]){errno=ENODATA;return-1;}p=malloc(f->lengths[index]?f->lengths[index]:1);if(!p)return -1;if(f->lengths[index])memcpy(p,f->blocks[index],f->lengths[index]);*out=p;*len=f->lengths[index];return 0;} +int sleep_feed_append(sleep_feed_t *f,const uint8_t *data,size_t len){uint8_t *copy;if(!f||!f->writable||(!data&&len)){errno=EINVAL;return-1;}if(reserve_feed(f,f->length+1,0))return-1;copy=malloc(len?len:1);if(!copy)return-1;if(len)memcpy(copy,data,len);f->blocks[f->length]=copy;f->lengths[f->length]=len;f->length++;f->dirty=1;return 0;} +int sleep_feed_append_remote(sleep_feed_t *f,size_t index,const uint8_t *data,size_t len,const uint8_t *proof,size_t proof_len){uint8_t *copy,*proof_copy;if(!f||f->writable||index>=SLEEP_BITFIELD_DATA_BYTES*8u||(!data&&len)||!proof||!proof_len){errno=EINVAL;return-1;}if(indexlength&&f->blocks[index]){errno=EEXIST;return-1;}if(reserve_feed(f,index+1,1))return-1;copy=malloc(len?len:1);proof_copy=malloc(proof_len);if(!copy||!proof_copy){free(copy);free(proof_copy);return-1;}if(len)memcpy(copy,data,len);memcpy(proof_copy,proof,proof_len);if(f->persist_remote&&append_remote_record(f,index,data,len,proof,proof_len)){free(copy);free(proof_copy);return-1;}f->blocks[index]=copy;f->lengths[index]=len;f->proofs[index]=proof_copy;f->proof_lengths[index]=proof_len;if(f->length<=index)f->length=index+1;f->remote_only=1;return 0;} +int sleep_feed_truncate(sleep_feed_t*f,size_t length){size_t i;if(!f||!f->writable||length>f->length){errno=EINVAL;return-1;}for(i=length;ilength;i++)free(f->blocks[i]);f->length=length;f->dirty=1;return 0;} +int sleep_feed_has(const sleep_feed_t*f,size_t index){return f&&indexlength&&f->blocks[index]!=NULL;} +int sleep_feed_byte_offset(const sleep_feed_t*f,size_t index,uint64_t*out){size_t i;uint64_t n=0;if(!f||!out||index>f->length){errno=EINVAL;return-1;}for(i=0;iblocks[i]){errno=ENODATA;return-1;}if(UINT64_MAX-nlengths[i]){errno=EOVERFLOW;return-1;}n+=f->lengths[i];}*out=n;return 0;} +int sleep_feed_flush(sleep_feed_t *f){if(!f||!f->writable){errno=EINVAL;return-1;}if(!f->dirty)return 0;if(sleep_write_feed(f->directory,f->name,(const uint8_t *const*)f->blocks,f->lengths,f->length,&f->keypair))return-1;f->dirty=0;return 0;} +void sleep_feed_close(sleep_feed_t *f){size_t i;if(!f)return;for(i=0;ilength;i++){free(f->blocks?f->blocks[i]:NULL);free(f->proofs?f->proofs[i]:NULL);}free(f->blocks);free(f->lengths);free(f->proofs);free(f->proof_lengths);memset(f,0,sizeof(*f));} diff --git a/src/sleep_proof.c b/src/sleep_proof.c new file mode 100644 index 0000000..0ea69c8 --- /dev/null +++ b/src/sleep_proof.c @@ -0,0 +1,302 @@ +#define _POSIX_C_SOURCE 200809L +#include "sleep_proof.h" + +#include "sleep.h" +#include "sleep_crypto.h" +#include +#include +#include +#include + +#define PROOF_FIXED_BYTES 51u +#define PROOF_NODE_BYTES 48u + +static void put64(uint8_t *p, uint64_t value) +{ + unsigned i; + for (i = 0; i < 8; i++) p[i] = (uint8_t)(value >> (56 - i * 8)); +} + +static uint64_t get64(const uint8_t *p) +{ + uint64_t value = 0; + unsigned i; + for (i = 0; i < 8; i++) value = (value << 8) | p[i]; + return value; +} + +static int read_file(const char *path, uint8_t **data, size_t *length) +{ + FILE *file; + long size; + uint8_t *buffer; + file = fopen(path, "rb"); + if (!file) return -1; + if (fseek(file, 0, SEEK_END) || (size = ftell(file)) < 0 || + fseek(file, 0, SEEK_SET)) { fclose(file); return -1; } + buffer = malloc(size ? (size_t)size : 1); + if (!buffer) { fclose(file); return -1; } + if (size && fread(buffer, 1, (size_t)size, file) != (size_t)size) { + free(buffer); fclose(file); errno = EIO; return -1; + } + fclose(file); + *data = buffer; + *length = (size_t)size; + return 0; +} + +static int add_node(sleep_proof_node_t *nodes, size_t *count, + uint64_t index, const uint8_t *entry) +{ + sleep_proof_node_t *node; + if (*count >= SLEEP_PROOF_MAX_NODES) { errno = EOVERFLOW; return -1; } + node = &nodes[(*count)++]; + memcpy(node->hash, entry, 32); + node->index = index; + node->length = get64(entry + 32); + return 0; +} + +void sleep_proof_init(sleep_proof_t *proof, uint64_t block) +{ + if (proof) { memset(proof, 0, sizeof(*proof)); proof->block = block; } +} + +int sleep_proof_add_node(sleep_proof_t *proof, uint64_t index, + uint64_t length, const uint8_t hash[32]) +{ + sleep_proof_node_t *node; + if (!proof || !hash || proof->node_count >= SLEEP_PROOF_MAX_NODES) { + errno = EINVAL; return -1; + } + node = &proof->nodes[proof->node_count++]; + node->index = index; node->length = length; memcpy(node->hash, hash, 32); + return 0; +} + +int sleep_proof_add_root(sleep_proof_t *proof, uint64_t index, + uint64_t length, const uint8_t hash[32]) +{ + sleep_proof_node_t *root; + if (!proof || !hash || proof->root_count >= SLEEP_PROOF_MAX_NODES) { + errno = EINVAL; return -1; + } + root = &proof->roots[proof->root_count++]; + root->index = index; root->length = length; memcpy(root->hash, hash, 32); + return 0; +} + +int sleep_proof_create(const sleep_feed_t *feed, uint64_t block, + sleep_proof_t *proof) +{ + char tree_path[1200], sig_path[1200]; + uint8_t *tree = NULL, *signatures = NULL; + size_t tree_len = 0, sig_len = 0, node_count, root_count, i; + uint64_t root_indexes[SLEEP_PROOF_MAX_NODES], index; + const uint8_t *leaf; + int is_root; + if (!feed || !proof || !feed->length || block >= feed->length || feed->dirty) { + errno = feed && feed->dirty ? EBUSY : EINVAL; + return -1; + } + /* A read-only replica can forward the publisher's original signed proof. */ + if (feed->proofs && feed->proof_lengths && feed->proofs[block]) + return sleep_proof_decode(proof, feed->proofs[block], feed->proof_lengths[block]); + if (snprintf(tree_path, sizeof(tree_path), "%s/%s.tree", feed->directory, + feed->name) >= (int)sizeof(tree_path) || + snprintf(sig_path, sizeof(sig_path), "%s/%s.signatures", feed->directory, + feed->name) >= (int)sizeof(sig_path)) { errno = ENAMETOOLONG; return -1; } + if (read_file(tree_path, &tree, &tree_len) || + read_file(sig_path, &signatures, &sig_len)) goto fail; + if (tree_len < SLEEP_HEADER_SIZE || (tree_len - SLEEP_HEADER_SIZE) % SLEEP_TREE_ENTRY_SIZE || + sig_len < SLEEP_HEADER_SIZE || (sig_len - SLEEP_HEADER_SIZE) / SLEEP_SIG_ENTRY_SIZE < feed->length) { + errno = EPROTO; goto fail; + } + node_count = (tree_len - SLEEP_HEADER_SIZE) / SLEEP_TREE_ENTRY_SIZE; + root_count = sleep_tree_full_roots((uint64_t)feed->length * 2, + root_indexes, SLEEP_PROOF_MAX_NODES); + if (!root_count || root_count > SLEEP_PROOF_MAX_NODES || block > UINT64_MAX / 2) { + errno = EOVERFLOW; goto fail; + } + sleep_proof_init(proof, block); + index = block * 2; + if (index >= node_count) { errno = EPROTO; goto fail; } + leaf = tree + SLEEP_HEADER_SIZE + (size_t)index * SLEEP_TREE_ENTRY_SIZE; + proof->block_length = get64(leaf + 32); + memcpy(proof->block_hash, leaf, 32); + for (;;) { + is_root = 0; + for (i = 0; i < root_count; i++) if (root_indexes[i] == index) is_root = 1; + if (is_root) break; + { + uint64_t sibling; + const uint8_t *entry; + if (sleep_tree_sibling(index, &sibling) || sibling >= node_count) { + errno = EPROTO; goto fail; + } + entry = tree + SLEEP_HEADER_SIZE + (size_t)sibling * SLEEP_TREE_ENTRY_SIZE; + if (add_node(proof->nodes, &proof->node_count, sibling, entry) || + sleep_tree_parent(index) == UINT64_MAX) goto fail; + index = sleep_tree_parent(index); + } + } + for (i = 0; i < root_count; i++) { + uint64_t root = root_indexes[i]; + const uint8_t *entry; + if (root >= node_count) { errno = EPROTO; goto fail; } + entry = tree + SLEEP_HEADER_SIZE + (size_t)root * SLEEP_TREE_ENTRY_SIZE; + if (add_node(proof->roots, &proof->root_count, root, entry)) goto fail; + } + memcpy(proof->signature, + signatures + SLEEP_HEADER_SIZE + (feed->length - 1) * SLEEP_SIG_ENTRY_SIZE, + sizeof(proof->signature)); + proof->has_signature = 1; + free(tree); free(signatures); + return 0; +fail: + free(tree); free(signatures); memset(proof, 0, sizeof(*proof)); + return -1; +} + +int sleep_proof_from_nodes(uint64_t block, const uint8_t *value, + size_t value_len, const sleep_proof_node_t *nodes, + size_t node_count, const uint8_t signature[64], + uint64_t feed_length, sleep_proof_t *proof) +{ + uint64_t root_indexes[SLEEP_PROOF_MAX_NODES], index; + size_t root_count, i, j; + int is_root; + if (!proof || (!value && value_len) || !signature || + (node_count && !nodes) || !feed_length || block >= feed_length || + feed_length > UINT64_MAX / 2 || block > UINT64_MAX / 2) { + errno = EINVAL; + return -1; + } + root_count = sleep_tree_full_roots(feed_length * 2, root_indexes, + SLEEP_PROOF_MAX_NODES); + if (!root_count || root_count > SLEEP_PROOF_MAX_NODES) { errno = EOVERFLOW; return -1; } + sleep_proof_init(proof, block); + proof->block_length = value_len; + if (sleep_tree_hash_block(value, value_len, proof->block_hash)) return -1; + index = block * 2; + for (;;) { + is_root = 0; + for (i = 0; i < root_count; i++) if (root_indexes[i] == index) is_root = 1; + if (is_root) break; + { + uint64_t sibling; + if (sleep_tree_sibling(index, &sibling)) goto malformed; + for (j = 0; j < node_count; j++) if (nodes[j].index == sibling) break; + if (j == node_count || sleep_proof_add_node(proof, sibling, + nodes[j].length, nodes[j].hash)) goto malformed; + index = sleep_tree_parent(index); + if (index == UINT64_MAX) goto malformed; + } + } + for (i = 0; i < root_count; i++) { + for (j = 0; j < node_count; j++) if (nodes[j].index == root_indexes[i]) break; + if (j == node_count || sleep_proof_add_root(proof, nodes[j].index, + nodes[j].length, nodes[j].hash)) goto malformed; + } + memcpy(proof->signature, signature, 64); + proof->has_signature = 1; + return 0; +malformed: + memset(proof, 0, sizeof(*proof)); + errno = EBADMSG; + return -1; +} + +static void encode_node(uint8_t *out, const sleep_proof_node_t *node) +{ + memcpy(out, node->hash, 32); put64(out + 32, node->index); put64(out + 40, node->length); +} + +int sleep_proof_encode(const sleep_proof_t *proof, uint8_t *out, + size_t capacity, size_t *written) +{ + size_t need, off = 0, i; + if (!proof || !out || proof->node_count > SLEEP_PROOF_MAX_NODES || + proof->root_count > SLEEP_PROOF_MAX_NODES) { errno = EINVAL; return -1; } + need = PROOF_FIXED_BYTES + (proof->node_count + proof->root_count) * PROOF_NODE_BYTES + + (proof->has_signature ? 64 : 0); + if (capacity < need) { errno = ENOSPC; return -1; } + put64(out + off, proof->block); off += 8; + put64(out + off, proof->block_length); off += 8; + memcpy(out + off, proof->block_hash, 32); off += 32; + out[off++] = (uint8_t)proof->node_count; + out[off++] = (uint8_t)!!proof->has_signature; + out[off++] = (uint8_t)proof->root_count; + for (i = 0; i < proof->node_count; i++, off += PROOF_NODE_BYTES) encode_node(out + off, &proof->nodes[i]); + for (i = 0; i < proof->root_count; i++, off += PROOF_NODE_BYTES) encode_node(out + off, &proof->roots[i]); + if (proof->has_signature) { memcpy(out + off, proof->signature, 64); off += 64; } + if (written) *written = off; + return 0; +} + +static void decode_node(sleep_proof_node_t *node, const uint8_t *in) +{ + memcpy(node->hash, in, 32); node->index = get64(in + 32); node->length = get64(in + 40); +} + +int sleep_proof_decode(sleep_proof_t *proof, const uint8_t *input, size_t input_len) +{ + size_t off = 0, i, need; + if (!proof || !input || input_len < PROOF_FIXED_BYTES) { errno = EPROTO; return -1; } + sleep_proof_init(proof, get64(input)); off = 8; + proof->block_length = get64(input + off); off += 8; + memcpy(proof->block_hash, input + off, 32); off += 32; + proof->node_count = input[off++]; + proof->has_signature = !!input[off++]; + proof->root_count = input[off++]; + if (proof->node_count > SLEEP_PROOF_MAX_NODES || proof->root_count > SLEEP_PROOF_MAX_NODES) { + errno = EPROTO; return -1; + } + need = PROOF_FIXED_BYTES + (proof->node_count + proof->root_count) * PROOF_NODE_BYTES + + (proof->has_signature ? 64 : 0); + if (input_len != need) { errno = EPROTO; return -1; } + for (i = 0; i < proof->node_count; i++, off += PROOF_NODE_BYTES) decode_node(&proof->nodes[i], input + off); + for (i = 0; i < proof->root_count; i++, off += PROOF_NODE_BYTES) decode_node(&proof->roots[i], input + off); + if (proof->has_signature) memcpy(proof->signature, input + off, 64); + return 0; +} + +int sleep_proof_verify(const sleep_proof_t *proof, const uint8_t *block, + size_t block_len, const uint8_t public_key[32]) +{ + uint8_t hash[32], parent[32], root_hashes[SLEEP_PROOF_MAX_NODES * 32]; + uint64_t indices[SLEEP_PROOF_MAX_NODES], lengths[SLEEP_PROOF_MAX_NODES]; + uint64_t index, length; + size_t i, r; + int root_found = 0; + if (!proof || (!block && block_len) || !public_key || !proof->has_signature || + !proof->root_count || proof->root_count > SLEEP_PROOF_MAX_NODES) { errno = EINVAL; return -1; } + if (proof->block > UINT64_MAX / 2 || proof->block_length != block_len || + sleep_tree_hash_block(block, block_len, hash) || memcmp(hash, proof->block_hash, 32)) { + errno = EBADMSG; return -1; + } + index = proof->block * 2; + length = block_len; + for (i = 0; i < proof->node_count; i++) { + const sleep_proof_node_t *node = &proof->nodes[i]; + uint64_t sibling; + if (sleep_tree_sibling(index, &sibling) || node->index != sibling || + UINT64_MAX - length < node->length) { errno = EBADMSG; return -1; } + if (sibling < index) { + if (sleep_tree_hash_parent(node->hash, hash, node->length + length, parent)) return -1; + } else if (sleep_tree_hash_parent(hash, node->hash, length + node->length, parent)) return -1; + memcpy(hash, parent, 32); length += node->length; index = sleep_tree_parent(index); + } + for (r = 0; r < proof->root_count; r++) { + const sleep_proof_node_t *root = &proof->roots[r]; + indices[r] = root->index; lengths[r] = root->length; + memcpy(root_hashes + r * 32, root->hash, 32); + if (root->index == index && root->length == length && !memcmp(root->hash, hash, 32)) root_found = 1; + if (r && proof->roots[r - 1].index >= root->index) { errno = EBADMSG; return -1; } + } + if (!root_found || sleep_verify_roots(proof->signature, root_hashes, + proof->root_count, indices, lengths, public_key)) { + errno = EBADMSG; return -1; + } + return 0; +} diff --git a/src/sleep_proto.c b/src/sleep_proto.c index 5ea1201..688f786 100644 --- a/src/sleep_proto.c +++ b/src/sleep_proto.c @@ -1,47 +1,58 @@ -// tiny protobuf -// at init call sleep_write_metadata_header("./.dat/metadata.data", content_pubkey, 32); -#include "sleep.h" +#include "sleep_proto.h" +#include #include #include -#include +static size_t bounded_len(const char *s,size_t cap){size_t n=0;while(n= 0x80) { - buf[n++] = (uint8_t)((val & 0x7F) | 0x80); - val >>= 7; - } - buf[n++] = (uint8_t)val; - return n; +size_t sleep_varint_size(uint64_t v) { size_t n = 1; while (v >= 128) { v >>= 7; n++; } return n; } +int sleep_varint_encode(uint64_t v, uint8_t *out, size_t cap, size_t *written) { + size_t need = sleep_varint_size(v), i = 0; if (!out || cap < need) { errno = out ? ENOSPC : EINVAL; return -1; } + while (v >= 128) { out[i++] = (uint8_t)((v & 127) | 128); v >>= 7; } out[i++] = (uint8_t)v; + if (written) *written = i; + return 0; } - -// write Header {type="hyperdrive", content=pubkey} -int sleep_write_metadata_header(const char *path, const uint8_t *content_pubkey, size_t pk_len) { - if (!content_pubkey || pk_len != 32) { - errno = EINVAL; - return -1; - } - - FILE *f = fopen(path, "wb"); +int sleep_varint_decode(const uint8_t *in, size_t len, uint64_t *value, size_t *used) { + uint64_t v = 0; size_t i; if (!in || !value) { errno = EINVAL; return -1; } + for (i = 0; i < len && i < 10; i++) { uint8_t b = in[i]; if (i == 9 && b > 1) { errno = EOVERFLOW; return -1; } + v |= (uint64_t)(b & 127) << (i * 7); if (!(b & 128)) { *value = v; if (used) *used = i + 1; return 0; } } + errno = len < 10 ? EAGAIN : EOVERFLOW; return -1; +} +static int put_field(uint8_t tag, const uint8_t *data, size_t len, uint8_t *out, size_t cap, size_t *off) { + size_t n; if (*off >= cap) { errno = ENOSPC; return -1; } out[(*off)++] = tag; + if (sleep_varint_encode(len, out + *off, cap - *off, &n)) return -1; + *off += n; + if (len > cap - *off) { errno = ENOSPC; return -1; } memcpy(out + *off, data, len); *off += len; return 0; +} +int sleep_metadata_header_encode(const sleep_metadata_header_t *h, uint8_t *out, size_t cap, size_t *written) { + size_t off = 0, type_len; if (!h || !out || h->content_len > 32) { errno = EINVAL; return -1; } type_len = strlen(h->type); + if (put_field(0x0a, (const uint8_t *)h->type, type_len, out, cap, &off) || put_field(0x12, h->content, h->content_len, out, cap, &off)) return -1; + if (written) *written = off; + return 0; +} +int sleep_metadata_header_decode(sleep_metadata_header_t *h, const uint8_t *in, size_t len) { + size_t off = 0; if (!h || (!in && len)) { errno = EINVAL; return -1; } memset(h, 0, sizeof(*h)); + while (off < len) { uint8_t tag = in[off++]; uint64_t n64; size_t n, copy; + if (sleep_varint_decode(in + off, len - off, &n64, &n)) return -1; + off += n; + if (n64 > len - off) { errno = EPROTO; return -1; } + if (tag == 0x0a) { copy = n64 < sizeof(h->type)-1 ? (size_t)n64 : sizeof(h->type)-1; memcpy(h->type, in+off, copy); } + else if (tag == 0x12) { if (n64 > 32) { errno = EOVERFLOW; return -1; } memcpy(h->content, in+off, (size_t)n64); h->content_len = (size_t)n64; } + off += (size_t)n64; } + return 0; +} +int sleep_write_metadata_header(const char *path, const uint8_t key[32]) { + sleep_metadata_header_t h; uint8_t buf[128]; size_t len; FILE *f; if (!path || !key) { errno = EINVAL; return -1; } + memset(&h, 0, sizeof(h)); strcpy(h.type, "hyperdrive"); memcpy(h.content, key, 32); h.content_len = 32; + if (sleep_metadata_header_encode(&h, buf, sizeof(buf), &len)) return -1; + f = fopen(path, "wb"); if (!f) return -1; - - uint8_t buf[128]; - size_t n = 0; -// 1st field - buf[n++] = 0x0A; - const char *type = "hyperdrive"; - size_t type_len = strlen(type); - n += write_varint(type_len, buf + n); - memcpy(buf + n, type, type_len); - n += type_len; -// 2nd field - buf[n++] = 0x12; - n += write_varing(pk_len, buf + n); - memcpy(buf + n, content_pubkey, pk_len); - n += pk_len; - - size_t written = fwrite(buf, 1, n, f); - fclose(f); - - return written == n ? 0 : -1; + if (fwrite(buf, 1, len, f) != len) { fclose(f); errno = EIO; return -1; } return fclose(f); } + +static int put_uint_field(uint8_t tag,uint64_t value,uint8_t *out,size_t cap,size_t *off){size_t n;if(*off>=cap){errno=ENOSPC;return-1;}out[(*off)++]=tag;if(sleep_varint_encode(value,out+*off,cap-*off,&n))return-1;*off+=n;return 0;} +int sleep_metadata_entry_encode(const sleep_metadata_entry_t *e,uint8_t *out,size_t cap,size_t *written){size_t off=0,path_len,link_len;if(!e||!out){errno=EINVAL;return-1;}path_len=bounded_len(e->path,sizeof(e->path));link_len=bounded_len(e->linkname,sizeof(e->linkname)); + if(put_field(0x0a,(const uint8_t*)e->path,path_len,out,cap,&off)||put_uint_field(0x10,e->type,out,cap,&off)||put_uint_field(0x18,e->mode,out,cap,&off)||put_uint_field(0x20,e->size,out,cap,&off)||put_uint_field(0x28,e->mtime,out,cap,&off)||put_uint_field(0x30,e->block_offset,out,cap,&off)||put_uint_field(0x38,e->block_count,out,cap,&off))return-1; + if(link_len&&put_field(0x42,(const uint8_t*)e->linkname,link_len,out,cap,&off))return-1; + if(written)*written=off; + return 0;} +int sleep_metadata_entry_decode(sleep_metadata_entry_t *e,const uint8_t *in,size_t len){size_t off=0;if(!e||(!in&&len)){errno=EINVAL;return-1;}memset(e,0,sizeof(*e));while(offlen-off){errno=EPROTO;return-1;}if(tag==0x0a){copy=valuepath)-1?(size_t)value:sizeof(e->path)-1;memcpy(e->path,in+off,copy);}else if(tag==0x42){copy=valuelinkname)-1?(size_t)value:sizeof(e->linkname)-1;memcpy(e->linkname,in+off,copy);}off+=(size_t)value;}else if((tag&7)==0){if(sleep_varint_decode(in+off,len-off,&value,&n))return-1;off+=n;switch(tag){case 0x10:e->type=(uint32_t)value;break;case 0x18:e->mode=(uint32_t)value;break;case 0x20:e->size=value;break;case 0x28:e->mtime=value;break;case 0x30:e->block_offset=value;break;case 0x38:e->block_count=value;break;default:break;}}else{errno=EPROTO;return-1;}}return 0;} diff --git a/src/sleep_replication.c b/src/sleep_replication.c new file mode 100644 index 0000000..2d467fa --- /dev/null +++ b/src/sleep_replication.c @@ -0,0 +1,160 @@ +#include "sleep_replication.h" +#include +#include +#include + +int sleep_replication_init(sleep_replication_t *r, sleep_feed_t *feed, + uint64_t channel) +{ + if (!r || !feed) { errno = EINVAL; return -1; } + memset(r, 0, sizeof(*r)); + r->feed = feed; + r->channel = channel; + r->live = 1; + r->uploading = 1; + r->downloading = 1; + return 0; +} + +int sleep_replication_have(sleep_replication_t *r, uint64_t start, + uint64_t length) +{ + uint64_t i, end; + if (!r || start > UINT64_MAX - length) { errno = EINVAL; return -1; } + end = start + length; + for (i = start; i < end && i < SLEEP_BITFIELD_DATA_BYTES * 8u; i++) + if (sleep_bitfield_set_data(&r->remote_bitfield, (size_t)i, 1)) return -1; + if (end > r->remote_length) r->remote_length = end; + return 0; +} + +int sleep_replication_unhave(sleep_replication_t *r, uint64_t start, + uint64_t length) +{ + uint64_t i, end; + size_t last = SIZE_MAX; + if (!r || start > UINT64_MAX - length) { errno = EINVAL; return -1; } + end = start + length; + for (i = start; i < end && i < SLEEP_BITFIELD_DATA_BYTES * 8u; i++) + if (sleep_bitfield_set_data(&r->remote_bitfield, (size_t)i, 0)) return -1; + for (i = 0; i < SLEEP_BITFIELD_DATA_BYTES * 8u; i++) + if (sleep_bitfield_get_data(&r->remote_bitfield, (size_t)i) == 1) last = (size_t)i; + r->remote_length = last == SIZE_MAX ? 0 : (uint64_t)last + 1; + return 0; +} + +int sleep_replication_request(sleep_replication_t *r, + const sleep_replication_request_t *q) +{ + size_t i, tail; + if (!r || !q) { errno = EINVAL; return -1; } + for (i = 0; i < r->request_count; i++) { + size_t slot = (r->request_head + i) % SLEEP_REPLICATION_QUEUE; + if (r->requests[slot].index == q->index) return 0; + } + if (r->request_count >= SLEEP_REPLICATION_QUEUE) { errno = ENOSPC; return -1; } + tail = (r->request_head + r->request_count) % SLEEP_REPLICATION_QUEUE; + r->requests[tail] = *q; + r->request_count++; + return 0; +} + +int sleep_replication_want(sleep_replication_t *r, uint64_t start, + uint64_t length) +{ + uint64_t i; + size_t additions = 0; + if (!r || start > UINT64_MAX - length) { errno = EINVAL; return -1; } + if (length > SLEEP_REPLICATION_QUEUE) { errno = ENOSPC; return -1; } + for (i = start; i < start + length; i++) { + if (!sleep_replication_is_requested(r, i)) additions++; + } + if (additions > SLEEP_REPLICATION_QUEUE - r->request_count) { + errno = ENOSPC; + return -1; + } + for (i = start; i < start + length; i++) { + sleep_replication_request_t q = {i, 0, 1, 1}; + if (sleep_replication_request(r, &q)) return -1; + } + return 0; +} + +int sleep_replication_next(sleep_replication_t *r, + sleep_replication_request_t *q) +{ + if (!r || !q) { errno = EINVAL; return -1; } + if (!r->request_count) return 0; + *q = r->requests[r->request_head]; + r->request_head = (r->request_head + 1) % SLEEP_REPLICATION_QUEUE; + r->request_count--; + return 1; +} + +int sleep_replication_complete(sleep_replication_t *r, uint64_t index) +{ + sleep_replication_request_t kept[SLEEP_REPLICATION_QUEUE]; + size_t i, count = 0; + if (!r) { errno = EINVAL; return -1; } + for (i = 0; i < r->request_count; i++) { + size_t slot = (r->request_head + i) % SLEEP_REPLICATION_QUEUE; + if (r->requests[slot].index != index) kept[count++] = r->requests[slot]; + } + memcpy(r->requests, kept, count * sizeof(*kept)); + r->request_head = 0; + r->request_count = count; + return 0; +} + +int sleep_replication_is_requested(const sleep_replication_t *r, uint64_t index) +{ + size_t i; + if (!r) { errno = EINVAL; return -1; } + for (i = 0; i < r->request_count; i++) { + size_t slot = (r->request_head + i) % SLEEP_REPLICATION_QUEUE; + if (r->requests[slot].index == index) return 1; + } + return 0; +} + +int sleep_replication_unwant(sleep_replication_t *r, uint64_t start, + uint64_t length) +{ + uint64_t i; + if (!r || start > UINT64_MAX - length || length > SLEEP_REPLICATION_QUEUE) { errno = EINVAL; return -1; } + for (i = start; i < start + length; i++) + if (sleep_replication_complete(r, i)) return -1; + return 0; +} + +void sleep_replication_clear_requests(sleep_replication_t *r) +{ + if (!r) return; + r->request_head = 0; + r->request_count = 0; +} + +int sleep_replication_receive(sleep_replication_t *r, const uint8_t *block, + size_t len, const sleep_proof_t *proof, + const uint8_t public_key[32]) +{ + uint8_t encoded[8192]; + uint8_t *old = NULL; + size_t encoded_len, old_len; + if (!r || !r->feed || (!block && len) || !proof || !public_key) { + errno = EINVAL; + return -1; + } + if (proof->block >= SLEEP_BITFIELD_DATA_BYTES * 8u || + sleep_proof_verify(proof, block, len, public_key)) return -1; + if (sleep_proof_encode(proof, encoded, sizeof(encoded), &encoded_len) || + (sleep_feed_has(r->feed, (size_t)proof->block) && + sleep_feed_read(r->feed, (size_t)proof->block, &old, &old_len))) return -1; + if (old) { + int same = old_len == len && !memcmp(old, block, len); + free(old); + if (!same) { errno = EBADMSG; return -1; } + } else if (sleep_feed_append_remote(r->feed, (size_t)proof->block, block, len, + encoded, encoded_len)) return -1; + return sleep_replication_have(r, proof->block, 1); +} diff --git a/src/sleep_tree.c b/src/sleep_tree.c index 1a2ded9..244e02e 100644 --- a/src/sleep_tree.c +++ b/src/sleep_tree.c @@ -1,194 +1,287 @@ /* -Merkle tree -derived from flat-tree implementation DEP-0002, datrs -*/ + * SLEEP Merkle tree. + * + * The on-disk tree is flat-tree/in-order notation: + * + * 3 + * 1 5 + * 0 2 4 6 + * + * even indexes = leaves + * odd indexes = parent nodes + * + * SLEEP adds domain-separated LEAF/PARENT/ROOT hashing and stores + * aggregate byte lengths in every tree entry. + */ #include "sleep.h" +#include +#include +#include +#include + #ifdef SLEEP_SALT #include #endif -#include -#include -#include -#include -#include - -// count trailing zeros -static inline unsigned int _ctzll_fallback(uint64_t x) { - if (x == 0) return 64; - unsigned int n = 0; - while ((x & 1ULL) == 0ULL) { - n++; - x >>= 1; - } - return 1; -} -static inline unsigned int tree_ctzll(uint64_t x) { -#if defined(__GNUC__) || defined(__clang__) || define(__MUSL__) - if (x == 0) return 64; +static unsigned int sleep_tree_ctz64(uint64_t x) +{ +#if defined(__GNUC__) || defined(__clang__) + if (x == 0) + return 64; return (unsigned int)__builtin_ctzll(x); #else - return _ctzll_fallback(x); + unsigned int n = 0; + + if (x == 0) + return 64; + + while ((x & 1ULL) == 0) { + x >>= 1; + n++; + } + + return n; #endif } -static inline uint64_t sleep_tree_index(uint64_t depth, uint64_t offset) { +uint64_t sleep_tree_index(uint64_t depth, uint64_t offset) +{ if (depth >= 63) { + errno = EOVERFLOW; return UINT64_MAX; } - return (offset << (depth + 1)) | ((depth == 0) ? 0ULL : ((1ULL << depth) - 1ULL)); + + return ((1ULL + (2ULL * offset)) << depth) - 1ULL; } -static inline uint64_t sleep_tree_depth(uint64_t i) { - uint64_t inverted = ~i; - return (uint64_t)tree_ctzll(inverted); +uint64_t sleep_tree_depth(uint64_t index) +{ + return sleep_tree_ctz64(index + 1ULL); } -static inline int tree_is_even(uint64_t i) { - return (i & 1ULL) == 0ULL; } -static inline int tree_is_odd(uint64_t i) { - return (i & 1ULL) != 0ULL; } +uint64_t sleep_tree_offset(uint64_t index, uint64_t depth) +{ + if ((index & 1ULL) == 0) + return index / 2ULL; -static inline uint64_t sleep_tree_offset(uint64_t i) { - uint64_t d = sleep_tree_depth(i); - if (tree_is_even(i)) { - return i / 2; - } else { - return i >> (d + 1); - } + if (depth == UINT64_MAX) + depth = sleep_tree_depth(index); + + return (((index + 1ULL) >> depth) - 1ULL) / 2ULL; } -static inline uint64_t sleep_tree_parent(uint64_t i) { - uint64_t d = sleep_tree_depth(i); - return sleep_tree_index(d, sleep_tree_offset(i) ^ 1ULL); +uint64_t sleep_tree_parent(uint64_t index) +{ + uint64_t depth = sleep_tree_depth(index); + uint64_t offset = sleep_tree_offset(index, depth); + + return sleep_tree_index(depth + 1ULL, offset >> 1); } -static inline int sleep_tree_left_child(uint64_t i, uint64_t *out) { // set - if (tree_is_even(i)) return 0; - uint64_t d = sleep_tree_depth(i); - if (d == 0) { - *out = i; - return 1; - } - *out = sleep_tree_index(d - 1, sleep_tree_offset(i) << 1); - return 1; -} +int sleep_tree_sibling(uint64_t index, uint64_t *out) +{ + uint64_t depth; + uint64_t offset; -static inline int sleep_tree_right_child(uint64_t i, uint64_t *out) { // horus - if (tree_is_even(i)) return 0; - uint64_t d = sleep_tree_depth(i); - if (d == 0) { - *out = i; - return 1; - } - *out = sleep_tree_index(d - 1, (slep_tree_offset(i) << 1) + 1ULL); - return 1; -} - -static inline uint64_t sleep_tree_left_span(uint64_t i) { - uint64_t d = sleep_tree_depth(i); - if (d == 0) return i; - return sleep_tree_offset(i) * (2ULL << d); -} - -static inline uint64_t sleep_tree_right_span(uint64_t i) { - uint64_t d = sleep_tree_depth(i); - if (d == 0) return i; - return (sleep_tree_offset(i) + 1ULL) * (2ULL << d) - 2ULL; -} - -static inline void sleep_tree_spans(uint64_t i, uint64_t *left, uint64_t *right) { - *left = sleep_tree_left_span(i); - *right = sleep_tree_right_span(i); -} - -// TODO static inline int sleep_tree_full_roots - -// भव न सन हृदयं -int sleep_tree_hash_block(const uint8_t *data, size_t len, uint8_t out_hash[32]) { -#ifndef SLEEP_SALT - errno = ENOSYS; - return -1; -#else - if (crypto_generichash(out_hash, 32, data, len, NULL, 0) != 0) - return -1; - return 0; -#endif -} - -int sleep_tree_hash_parent(const uint8_t left[32], const uint8_t right[32], uint8_t out_hash[32]) { -#ifndef SLEEP_SALT - errno = ENOSYS; - return -1; -#else - uint8_t buf[64]; - memcpy(buf, left, 32); - memcpy(buf + 32, right, 32); - if (crypto_generichash(out_hash, 32, buf, sizeof(buf), NULL, 0) != 0) - return -1; - return 0; -#endif -} - -int sleep_tree_build(const uint8_t **blocks, size_t *block_lens, size_t nblocks, sleep_tree_entry_t *out_nodes, size_t *out_count) { - if (!blocks || !block_lens || !out_nodes || !out_count) { + if (out == NULL) { errno = EINVAL; return -1; } - if (nblocks == 0) { - *out_count = 0; - return 0; - } - // 2 arrays: nodes_vec (2*nblocks) , roots (64) - size_t max_node = (nblocks == 0) ? 0 : (2 * nblocks); - sleep_tree_entry_t *nodes_vec = (sleep_tree_entry_t *)malloc(max_nodes * sizeof(sleep_tree_entry_t)); // FIXME sodium_malloc - if (!nodes_vec) { errno = ENOMEM; return -1; } - size_t nodes_len = 0; - sleep_tree_entry_t *roots = (sleep_tree_entry_t *)malloc((64 + 2) * sizeof(sleep_tree_entry_t)); // FIXME salt - if (!roots) { free(nodes_vec); errno = ENOMEM; return -1; } - size_t roots_len = 0; + depth = sleep_tree_depth(index); + offset = sleep_tree_offset(index, depth); - for (size_t i = 0; i < nblocks; i++) { - sleep_tree_entry_t leaf; - memset(&leaf, 0, sizeof(leaf)); - if (sleep_tree_hash_block(blocks[i], block_lens[i], leaf.hash) != 0) { - free(nodes_vec); - free(roots); - return -1; - } - leaf.index = sleep_tree_index(0, (uint64_t)i); // 2 * i ... - leaf.size = (uint64_t)block_lens[i]; - if (nodes_len >= max_nodes) { - size_t newcap = max_nodes * 2; - sleep_tree_entry_t *nvec = (sleep_tree_entry_t *)realloc(nodes_vec, newcap * sizeof(slep_tree_entry_t)); - if (!nvec) { free(nodes_vec); free(roots); errno = ENOMEM; return -1; } - nodes_vec = nvec; - max_nodes = newcap; - } - nodes_vec[nodes_len++] = leaf; - - if (roots_len >= 64 + 2) { - size_t newcap = (roots_len + 16); - sleep_tree_entry_t *r = (sleep_tree_entry_t *)realloc(roots, newcap * sizeof(sleep_tree_entry_t)); - if (!r) { free(nodes_vec); free(roots); errno = ENOMEM; return -1; } - roots = r; - } - roots[roots_len++] = leaf; - - while (roots_len >= 2) { - sleep_tree_entry_t *a = &roots[roots_len - 2]; - sleep_tree_entry_t *b = &roots[roots_len - 1]; - - uint64_t ra = /* FIXME tree depth (a->index) */ - uint64_t rb = /*(b->index)*/ - if (ra != rb) break; - } - } - memcpy(out_nodes, nodes_vec, nodes_len * sizeof(sleep_tree_entry_t)); - *out_count = nodes_len; - free(nodes_vec); - free(roots); + *out = sleep_tree_index(depth, offset & 1ULL ? offset - 1ULL + : offset + 1ULL); return 0; } + +int sleep_tree_left_child(uint64_t index, uint64_t *out) +{ + uint64_t depth; + uint64_t offset; + + if (out == NULL) { + errno = EINVAL; + return -1; + } + + if ((index & 1ULL) == 0) { + errno = EINVAL; + return -1; + } + + depth = sleep_tree_depth(index); + if (depth == 0) { + errno = EINVAL; + return -1; + } + + offset = sleep_tree_offset(index, depth); + *out = sleep_tree_index(depth - 1ULL, offset * 2ULL); + return 0; +} + +int sleep_tree_right_child(uint64_t index, uint64_t *out) +{ + uint64_t depth; + uint64_t offset; + + if (out == NULL) { + errno = EINVAL; + return -1; + } + + if ((index & 1ULL) == 0) { + errno = EINVAL; + return -1; + } + + depth = sleep_tree_depth(index); + if (depth == 0) { + errno = EINVAL; + return -1; + } + + offset = sleep_tree_offset(index, depth); + *out = sleep_tree_index(depth - 1ULL, offset * 2ULL + 1ULL); + return 0; +} + +uint64_t sleep_tree_left_span(uint64_t index) +{ + uint64_t depth; + + if ((index & 1ULL) == 0) + return index; + + depth = sleep_tree_depth(index); + return sleep_tree_offset(index, depth) * (1ULL << (depth + 1ULL)); +} + +uint64_t sleep_tree_right_span(uint64_t index) +{ + uint64_t depth; + + if ((index & 1ULL) == 0) + return index; + + depth = sleep_tree_depth(index); + return (sleep_tree_offset(index, depth) + 1ULL) * + (1ULL << (depth + 1ULL)) - 2ULL; +} + +void sleep_tree_spans(uint64_t index, uint64_t *left, uint64_t *right) +{ + if (left != NULL) + *left = sleep_tree_left_span(index); + if (right != NULL) + *right = sleep_tree_right_span(index); +} + +size_t sleep_tree_count(uint64_t index) +{ + uint64_t depth; + + if ((index & 1ULL) == 0) + return 1; + + depth = sleep_tree_depth(index); + if (depth >= sizeof(size_t) * 8 - 1) + return SIZE_MAX; + + return ((size_t)1 << (depth + 1ULL)) - 1; +} + +size_t sleep_tree_count_leaves(uint64_t index) +{ + return (sleep_tree_count(index) + 1) / 2; +} + +size_t sleep_tree_full_roots(uint64_t index, uint64_t *out, size_t capacity) +{ + uint64_t blocks; + uint64_t offset = 0; + uint64_t factor = 1; + size_t count = 0; + + if (index & 1ULL) { + errno = EINVAL; + return 0; + } + + /* mafintosh/flat-tree fullRoots(index) works on index / 2. */ + blocks = index / 2ULL; + + while (blocks != 0) { + while (factor <= UINT64_MAX / 2ULL && factor * 2ULL <= blocks) + factor *= 2ULL; + + if (out != NULL && count < capacity) + out[count] = offset + factor - 1ULL; + + count++; + offset += 2ULL * factor; + blocks -= factor; + factor = 1; + } + + return count; +} + +#ifdef SLEEP_SALT +int sleep_tree_hash_block(const uint8_t *data, size_t len, + uint8_t out_hash[32]) +{ + uint8_t header[9]; + crypto_generichash_state state; + + if (out_hash == NULL || (data == NULL && len != 0)) { + errno = EINVAL; + return -1; + } + + header[0] = SLEEP_HASH_LEAF; + for (int i = 0; i < 8; i++) + header[1 + i] = (uint8_t)((uint64_t)len >> (56 - i * 8)); + + if (crypto_generichash_init(&state, NULL, 0, 32) != 0) + return -1; + if (crypto_generichash_update(&state, header, sizeof(header)) != 0) + return -1; + if (len != 0 && crypto_generichash_update(&state, data, len) != 0) + return -1; + + return crypto_generichash_final(&state, out_hash, 32); +} + +int sleep_tree_hash_parent(const uint8_t left[32], + const uint8_t right[32], + uint64_t length, + uint8_t out_hash[32]) +{ + uint8_t header[9]; + crypto_generichash_state state; + + if (left == NULL || right == NULL || out_hash == NULL) { + errno = EINVAL; + return -1; + } + + header[0] = SLEEP_HASH_PARENT; + for (int i = 0; i < 8; i++) + header[1 + i] = (uint8_t)(length >> (56 - i * 8)); + + if (crypto_generichash_init(&state, NULL, 0, 32) != 0) + return -1; + if (crypto_generichash_update(&state, header, sizeof(header)) != 0) + return -1; + if (crypto_generichash_update(&state, left, 32) != 0) + return -1; + if (crypto_generichash_update(&state, right, 32) != 0) + return -1; + + return crypto_generichash_final(&state, out_hash, 32); +} +#endif diff --git a/src/sleep_utp.c b/src/sleep_utp.c index e69de29..8717e6f 100644 --- a/src/sleep_utp.c +++ b/src/sleep_utp.c @@ -0,0 +1,114 @@ +// hyperswarm dht +#include "sleep_utp.h" +#include +#include +#include +#include "dat_socket.h" +static void put16(uint8_t*p,uint16_t v){p[0]=(uint8_t)(v>>8);p[1]=(uint8_t)v;}static void put32(uint8_t*p,uint32_t v){p[0]=(uint8_t)(v>>24);p[1]=(uint8_t)(v>>16);p[2]=(uint8_t)(v>>8);p[3]=(uint8_t)v;}static uint16_t get16(const uint8_t*p){return((uint16_t)p[0]<<8)|p[1];}static uint32_t get32(const uint8_t*p){return((uint32_t)p[0]<<24)|((uint32_t)p[1]<<16)|((uint32_t)p[2]<<8)|p[3];} +int sleep_utp_init(sleep_utp_t*u,dat_socket_t fd){if(!u||fd==DAT_INVALID_SOCKET){errno=EINVAL;return-1;}memset(u,0,sizeof(*u));u->socket=fd;u->state=SLEEP_UTP_CLOSED;u->receive_window=1024*1024;u->send_window=SLEEP_UTP_WINDOW*SLEEP_UTP_MTU;u->congestion_window=2*SLEEP_UTP_MTU;u->slow_start_threshold=32*SLEEP_UTP_MTU;u->round_trip_time=500;u->round_trip_variance=250;u->send_id=(uint16_t)rand();u->receive_id=u->send_id+1;return 0;} +int sleep_utp_packet_encode(const sleep_utp_packet_t*p,uint8_t*out,size_t cap,size_t*written){size_t off=20;if(!p||!out||(!p->payload&&p->payload_len)){errno=EINVAL;return-1;}if(p->type>SLEEP_UTP_SYN||p->version!=1){errno=EPROTO;return-1;}if(p->type==SLEEP_UTP_DATA&&p->payload_len>SLEEP_UTP_MTU){errno=EMSGSIZE;return-1;}if(cap<20+(p->has_selective_ack?6:0)+p->payload_len){errno=ENOSPC;return-1;}out[0]=(uint8_t)((p->type<<4)|(p->version&15));out[1]=p->has_selective_ack?1:0;put16(out+2,p->connection_id);put32(out+4,p->timestamp);put32(out+8,p->timestamp_diff);put32(out+12,p->window_size);put16(out+16,p->sequence);put16(out+18,p->acknowledgement);if(p->has_selective_ack){out[off++]=0;out[off++]=4;put32(out+off,p->selective_ack);off+=4;}if(p->payload_len)memcpy(out+off,p->payload,p->payload_len);if(written)*written=off+p->payload_len;return 0;} +int sleep_utp_packet_decode(sleep_utp_packet_t *p, const uint8_t *in, size_t len) +{ + size_t off = 20; + uint8_t ext; + if (!p || !in || len < 20) { errno = EPROTO; return -1; } + memset(p, 0, sizeof(*p)); + p->type = in[0] >> 4; + p->version = in[0] & 15; + if (p->type > SLEEP_UTP_SYN || p->version != 1) { errno = EPROTO; return -1; } + ext = in[1]; + p->connection_id = get16(in + 2); + p->timestamp = get32(in + 4); + p->timestamp_diff = get32(in + 8); + p->window_size = get32(in + 12); + p->sequence = get16(in + 16); + p->acknowledgement = get16(in + 18); + while (ext) { + uint8_t next, n; + if (len - off < 2) { errno = EPROTO; return -1; } + next = in[off++]; + n = in[off++]; + if (n > len - off) { errno = EPROTO; return -1; } + if (ext == 1) { + if (n != 4 || p->has_selective_ack) { errno = EPROTO; return -1; } + p->has_selective_ack = 1; + p->selective_ack = get32(in + off); + } + off += n; + ext = next; + } + p->payload = in + off; + p->payload_len = len - off; + if ((p->type == SLEEP_UTP_DATA && p->payload_len > SLEEP_UTP_MTU) || + (p->type != SLEEP_UTP_DATA && p->payload_len != 0)) { + errno = p->type == SLEEP_UTP_DATA ? EMSGSIZE : EPROTO; + return -1; + } + return 0; +} +static int emit_sequence(sleep_utp_t*u,uint8_t type,const uint8_t*data,size_t len,uint16_t sequence,int advance,int track){sleep_utp_packet_t p;uint8_t packet[26+SLEEP_UTP_MTU];size_t n,i;sleep_utp_slot_t*tracked=NULL;if(track&&type==SLEEP_UTP_DATA){tracked=&u->unacked[sequence%SLEEP_UTP_WINDOW];if(tracked->used){errno=EAGAIN;return-1;}}memset(&p,0,sizeof(p));p.type=type;p.version=1;p.connection_id=u->send_id;p.window_size=u->receive_window;p.sequence=sequence;p.acknowledgement=u->acknowledgement;if(type==SLEEP_UTP_STATE){for(i=0;i<32;i++){uint16_t seq=(uint16_t)(u->acknowledgement+2+i);sleep_utp_slot_t*s=&u->reorder[seq%SLEEP_UTP_WINDOW];if(s->used&&s->sequence==seq)p.selective_ack|=(uint32_t)1<socket,packet,n,0,(struct sockaddr*)&u->remote,u->remote_len)!=(ssize_t)n)return-1;if(advance)u->sequence++;if(tracked){tracked->sequence=sequence;tracked->length=len;if(len)memcpy(tracked->data,data,len);tracked->used=1;tracked->sent_at=0;tracked->retries=0;u->bytes_in_flight+=len;}return 0;} +static int emit(sleep_utp_t*u,uint8_t type,const uint8_t*data,size_t len){int advance=type==SLEEP_UTP_DATA||type==SLEEP_UTP_SYN||type==SLEEP_UTP_FIN;return emit_sequence(u,type,data,len,u->sequence,advance,type==SLEEP_UTP_DATA);} +int sleep_utp_connect(sleep_utp_t*u,const struct sockaddr*r,socklen_t n){if(!u||!r||n>sizeof(u->remote)){errno=EINVAL;return-1;}memcpy(&u->remote,r,n);u->remote_len=n;u->state=SLEEP_UTP_SYN_SENT;u->retransmit_at=0;u->syn_retries=0;return emit(u,SLEEP_UTP_SYN,NULL,0);} +int sleep_utp_accept(sleep_utp_t*u,const sleep_utp_packet_t*syn,const struct sockaddr*r,socklen_t n){if(!u||!syn||!r||n>sizeof(u->remote)){errno=EINVAL;return-1;}if(syn->type!=SLEEP_UTP_SYN||syn->version!=1){errno=EPROTO;return-1;}memcpy(&u->remote,r,n);u->remote_len=n;u->receive_id=syn->connection_id;u->send_id=(uint16_t)(syn->connection_id+1);u->sequence=(uint16_t)(syn->sequence+1);u->acknowledgement=syn->sequence;u->state=SLEEP_UTP_CONNECTED;return emit(u,SLEEP_UTP_STATE,NULL,0);} +int sleep_utp_input(sleep_utp_t *u, const uint8_t *buf, size_t len) +{ + sleep_utp_packet_t packet; + size_t i; + if (!u) { errno = EINVAL; return -1; } + if (sleep_utp_packet_decode(&packet, buf, len)) return -1; + if (packet.version != 1 || packet.type > SLEEP_UTP_SYN || + packet.connection_id != u->receive_id) { errno = EPROTO; return -1; } + u->send_window = packet.window_size; + for (i = 0; i < SLEEP_UTP_WINDOW; i++) { + sleep_utp_slot_t *slot = &u->unacked[i]; + uint16_t behind; + int acknowledged; + if (!slot->used) continue; + behind = (uint16_t)(packet.acknowledgement - slot->sequence); + acknowledged = behind < 0x8000; + if (!acknowledged && packet.has_selective_ack) { + uint16_t ahead = (uint16_t)(slot->sequence - (uint16_t)(packet.acknowledgement + 2)); + if (ahead < 32 && (packet.selective_ack & ((uint32_t)1 << ahead))) acknowledged = 1; + } + if (!acknowledged) continue; + u->bytes_in_flight = slot->length > u->bytes_in_flight ? 0 : u->bytes_in_flight - slot->length; + slot->used = 0; + if (u->congestion_window < u->slow_start_threshold) u->congestion_window += SLEEP_UTP_MTU; + else u->congestion_window += SLEEP_UTP_MTU * SLEEP_UTP_MTU / u->congestion_window; + } + if (packet.type == SLEEP_UTP_STATE && u->state == SLEEP_UTP_SYN_SENT) { + u->state = SLEEP_UTP_CONNECTED; + return 0; + } + if (packet.type == SLEEP_UTP_DATA) { + uint16_t distance = (uint16_t)(packet.sequence - (uint16_t)(u->acknowledgement + 1)); + sleep_utp_slot_t *slot; + if (distance >= 0x8000 || distance >= SLEEP_UTP_WINDOW) + return emit(u, SLEEP_UTP_STATE, NULL, 0); + slot = &u->reorder[packet.sequence % SLEEP_UTP_WINDOW]; + if (!slot->used) { + slot->sequence = packet.sequence; + slot->length = packet.payload_len; + if (packet.payload_len) memcpy(slot->data, packet.payload, packet.payload_len); + slot->used = 1; + } + for (;;) { + uint16_t next = (uint16_t)(u->acknowledgement + 1); + slot = &u->reorder[next % SLEEP_UTP_WINDOW]; + if (!slot->used || slot->sequence != next) break; + u->acknowledgement = next; + if (u->on_data) u->on_data(u, slot->data, slot->length); + slot->used = 0; + } + return emit(u, SLEEP_UTP_STATE, NULL, 0); + } + if (packet.type == SLEEP_UTP_FIN) { + u->state = SLEEP_UTP_CLOSED; + return emit(u, SLEEP_UTP_STATE, NULL, 0); + } + if (packet.type == SLEEP_UTP_RESET_PACKET) u->state = SLEEP_UTP_RESET; + return 0; +} +int sleep_utp_send(sleep_utp_t*u,const uint8_t*data,size_t len){size_t off=0,part,slots=0,i,needed;if(!u||u->state!=SLEEP_UTP_CONNECTED||(!data&&len)){errno=ENOTCONN;return-1;}needed=(len+SLEEP_UTP_MTU-1)/SLEEP_UTP_MTU;for(i=0;iunacked[i].used)slots++;if(needed>slots||u->bytes_in_flight>u->congestion_window||len>u->congestion_window-u->bytes_in_flight||len>u->send_window){errno=EAGAIN;return-1;}while(offSLEEP_UTP_MTU?SLEEP_UTP_MTU:len-off;if(emit(u,SLEEP_UTP_DATA,data+off,part))return-1;off+=part;}return 0;} +int sleep_utp_tick(sleep_utp_t*u,uint64_t now){size_t i;if(!u){errno=EINVAL;return-1;}if(u->state==SLEEP_UTP_SYN_SENT){if(!u->retransmit_at){u->retransmit_at=now+250;}else if(now>=u->retransmit_at){if(u->syn_retries>=8){u->state=SLEEP_UTP_RESET;errno=ETIMEDOUT;return-1;}if(emit_sequence(u,SLEEP_UTP_SYN,NULL,0,(uint16_t)(u->sequence-1),0,0))return-1;u->syn_retries++;u->retransmit_at=now+250;}}for(i=0;iunacked[i];if(!s->used){continue;}if(!s->sent_at){s->sent_at=now;continue;}if(now-s->sent_at>=u->round_trip_time+4*u->round_trip_variance){u->slow_start_threshold=u->congestion_window/2;u->congestion_window=SLEEP_UTP_MTU;s->sent_at=now;s->retries++;if(s->retries>8){u->state=SLEEP_UTP_RESET;errno=ETIMEDOUT;return-1;}if(emit_sequence(u,SLEEP_UTP_DATA,s->data,s->length,s->sequence,0,0))return-1;}}return 0;} +int sleep_utp_close(sleep_utp_t*u){if(!u){errno=EINVAL;return-1;}if(u->state==SLEEP_UTP_CONNECTED){u->state=SLEEP_UTP_FIN_SENT;return emit(u,SLEEP_UTP_FIN,NULL,0);}u->state=SLEEP_UTP_CLOSED;return 0;} diff --git a/src/sleep_verify.c b/src/sleep_verify.c new file mode 100644 index 0000000..2aae0d4 --- /dev/null +++ b/src/sleep_verify.c @@ -0,0 +1,589 @@ +#include "sleep.h" +#include "sleep_verify.h" + +#include +#include +#include +#include +#include + +#ifdef SLEEP_SALT +#include +#endif + + +static uint16_t +be16(const uint8_t *p) +{ + return ((uint16_t)p[0] << 8) | p[1]; +} + + +static uint32_t +be32(const uint8_t *p) +{ + return ((uint32_t)p[0] << 24) | + ((uint32_t)p[1] << 16) | + ((uint32_t)p[2] << 8) | + p[3]; +} + + +static uint64_t +be64(const uint8_t *p) +{ + return ((uint64_t)p[0] << 56) | + ((uint64_t)p[1] << 48) | + ((uint64_t)p[2] << 40) | + ((uint64_t)p[3] << 32) | + ((uint64_t)p[4] << 24) | + ((uint64_t)p[5] << 16) | + ((uint64_t)p[6] << 8) | + p[7]; +} + + +static int +load_file(const char *path, + uint8_t **buf, + size_t *len) +{ + FILE *f; + long size; + uint8_t *p; + + f = fopen(path, "rb"); + + if (!f) { + fprintf(stderr, + "sleep-verify: %s: %s\n", + path, + strerror(errno)); + return -1; + } + + if (fseek(f, 0, SEEK_END) != 0 || + (size = ftell(f)) < 0 || + fseek(f, 0, SEEK_SET) != 0) { + fclose(f); + return -1; + } + + p = malloc(size ? (size_t)size : 1); + + if (!p) { + fclose(f); + errno = ENOMEM; + return -1; + } + + if (size && + fread(p, 1, (size_t)size, f) != (size_t)size) { + free(p); + fclose(f); + errno = EIO; + return -1; + } + + fclose(f); + + *buf = p; + *len = (size_t)size; + + return 0; +} + + +static int +check_header(const uint8_t *buf, + size_t len, + uint32_t magic, + uint16_t entry_size, + const char *algo) +{ + size_t algo_len; + + if (len < SLEEP_HEADER_SIZE) + return -1; + + if (be32(buf) != magic || + buf[4] != 0 || + be16(buf + 5) != entry_size) + return -1; + + algo_len = buf[7]; + + if (algo_len != strlen(algo)) + return -1; + + return memcmp(buf + 8, algo, algo_len) == 0 ? 0 : -1; +} + + +static int +verify_tree_structure(const sleep_tree_entry_t *tree, + size_t node_count) +{ + size_t i; + + for (i = 1; i < node_count; i += 2) { + uint64_t left; + uint64_t right; + uint64_t left_len; + uint64_t right_len; + uint64_t length; + uint8_t hash[32]; + + if (sleep_tree_left_child(i, &left) != 0 || + sleep_tree_right_child(i, &right) != 0) + return -1; + + /* + * This parent belongs to a subtree that does not + * completely exist yet. + */ + if (right >= node_count) { + static const uint8_t zero[40]; + + if (memcmp(&tree[i], + zero, + sizeof(tree[i])) != 0) { + fprintf(stderr, + " tree: nonzero incomplete node %zu\n", + i); + return -1; + } + + continue; + } + + left_len = + be64((const uint8_t *)&tree[left].length_be); + + right_len = + be64((const uint8_t *)&tree[right].length_be); + + if (UINT64_MAX - left_len < right_len) + return -1; + + length = left_len + right_len; + + if (sleep_tree_hash_parent(tree[left].hash, + tree[right].hash, + length, + hash) != 0) + return -1; + + if (memcmp(hash, tree[i].hash, 32) != 0 || + be64((const uint8_t *)&tree[i].length_be) != length) { + fprintf(stderr, + " tree: parent mismatch at node %zu\n", + i); + return -1; + } + } + + return 0; +} + + +static int +verify_data_leaves(const uint8_t *data, + size_t data_len, + const sleep_tree_entry_t *tree, + size_t node_count) +{ + size_t leaf_count = (node_count + 1) / 2; + size_t offset = 0; + size_t i; + + for (i = 0; i < leaf_count; i++) { + size_t node = i * 2; + uint64_t len = + be64((const uint8_t *)&tree[node].length_be); + uint8_t hash[32]; + + if (len > data_len - offset) { + fprintf(stderr, + " data: leaf %zu exceeds metadata.data\n", + i); + return -1; + } + + if (sleep_tree_hash_block(data + offset, + (size_t)len, + hash) != 0) + return -1; + + if (memcmp(hash, tree[node].hash, 32) != 0) { + fprintf(stderr, + " data: leaf hash mismatch at %zu\n", + i); + return -1; + } + + offset += (size_t)len; + } + + if (offset != data_len) { + fprintf(stderr, + " data: tree consumes %zu bytes, file is %zu\n", + offset, + data_len); + return -1; + } + + return 0; +} + + +#ifdef SLEEP_SALT + +static int +verify_signatures(const uint8_t *signatures, + size_t signatures_len, + const uint8_t pubkey[32], + const sleep_tree_entry_t *tree, + size_t node_count, + size_t feed_length) +{ + size_t sequence; + uint64_t roots[64]; + uint8_t message[1 + 64 * 48]; + uint8_t digest[32]; + + if (signatures_len < SLEEP_HEADER_SIZE || + (signatures_len - SLEEP_HEADER_SIZE) % + SLEEP_SIG_ENTRY_SIZE != 0) + return -1; + + if ((signatures_len - SLEEP_HEADER_SIZE) / + SLEEP_SIG_ENTRY_SIZE != feed_length) + return -1; + + for (sequence = 1; + sequence <= feed_length; + sequence++) { + const uint8_t *signature = signatures + SLEEP_HEADER_SIZE + + (sequence - 1) * SLEEP_SIG_ENTRY_SIZE; + size_t root_count; + size_t off = 0; + size_t r; + size_t signature_byte; + int signature_present = 0; + + for (signature_byte = 0; + signature_byte < SLEEP_SIG_ENTRY_SIZE; + signature_byte++) + signature_present |= signature[signature_byte] != 0; + + /* Sparse content feeds retain empty signature slots. */ + if (!signature_present) + continue; + + root_count = + sleep_tree_full_roots((uint64_t)sequence * 2, + roots, + 64); + + message[off++] = SLEEP_HASH_ROOT; + + for (r = 0; r < root_count; r++) { + uint64_t index = roots[r]; + uint64_t length; + + if (index >= node_count) + return -1; + + memcpy(message + off, + tree[index].hash, + 32); + off += 32; + + message[off++] = (uint8_t)(index >> 56); + message[off++] = (uint8_t)(index >> 48); + message[off++] = (uint8_t)(index >> 40); + message[off++] = (uint8_t)(index >> 32); + message[off++] = (uint8_t)(index >> 24); + message[off++] = (uint8_t)(index >> 16); + message[off++] = (uint8_t)(index >> 8); + message[off++] = (uint8_t)index; + + length = + be64((const uint8_t *) + &tree[index].length_be); + + message[off++] = (uint8_t)(length >> 56); + message[off++] = (uint8_t)(length >> 48); + message[off++] = (uint8_t)(length >> 40); + message[off++] = (uint8_t)(length >> 32); + message[off++] = (uint8_t)(length >> 24); + message[off++] = (uint8_t)(length >> 16); + message[off++] = (uint8_t)(length >> 8); + message[off++] = (uint8_t)length; + } + + if (crypto_generichash(digest, + sizeof(digest), + message, + off, + NULL, + 0) != 0) + return -1; + + if (crypto_sign_verify_detached( + signature, + digest, + sizeof(digest), + pubkey) != 0) { + fprintf(stderr, + " signatures: failed at sequence %zu\n", + sequence); + return -1; + } + } + + return 0; +} + +#endif + + +static int +verify_feed(const char *dir, + const char *name, + int verify_data, + size_t *out_length) +{ + char path[4096]; + + uint8_t *tree_file = NULL; + uint8_t *sig_file = NULL; + uint8_t *key_file = NULL; + uint8_t *data_file = NULL; + uint8_t *bitfield = NULL; + + size_t tree_len = 0; + size_t sig_len = 0; + size_t key_len = 0; + size_t data_len = 0; + size_t bitfield_len = 0; + + size_t node_count; + size_t feed_length; + + int rc = -1; + + snprintf(path, + sizeof(path), + "%s/%s.tree", + dir, + name); + + if (load_file(path, &tree_file, &tree_len) != 0) + goto done; + + if (check_header(tree_file, + tree_len, + SLEEP_MAGIC_TREE, + SLEEP_TREE_ENTRY_SIZE, + SLEEP_ALGO_BLAKE2B) != 0) { + fprintf(stderr, " tree: invalid header\n"); + goto done; + } + + if ((tree_len - SLEEP_HEADER_SIZE) % + sizeof(sleep_tree_entry_t) != 0) + goto done; + + node_count = + (tree_len - SLEEP_HEADER_SIZE) / + sizeof(sleep_tree_entry_t); + + if (!node_count || !(node_count & 1)) + goto done; + + feed_length = (node_count + 1) / 2; + + if (verify_tree_structure( + (const sleep_tree_entry_t *)(tree_file + + SLEEP_HEADER_SIZE), + node_count) != 0) + goto done; + + snprintf(path, + sizeof(path), + "%s/%s.key", + dir, + name); + + if (load_file(path, &key_file, &key_len) != 0 || + key_len != 32) + goto done; + + snprintf(path, + sizeof(path), + "%s/%s.signatures", + dir, + name); + + if (load_file(path, &sig_file, &sig_len) != 0) + goto done; + + if (check_header(sig_file, + sig_len, + SLEEP_MAGIC_SIGNATURE, + SLEEP_SIG_ENTRY_SIZE, + SLEEP_ALGO_ED25519) != 0) + goto done; + +#ifdef SLEEP_SALT + + if (verify_signatures( + sig_file, + sig_len, + key_file, + (const sleep_tree_entry_t *)(tree_file + + SLEEP_HEADER_SIZE), + node_count, + feed_length) != 0) + goto done; + +#else + + fprintf(stderr, + " signatures: build with SLEEP_SALT\n"); + goto done; + +#endif + + snprintf(path, + sizeof(path), + "%s/%s.bitfield", + dir, + name); + + if (load_file(path, + &bitfield, + &bitfield_len) != 0) + goto done; + + if (bitfield_len < SLEEP_HEADER_SIZE || + be32(bitfield) != SLEEP_MAGIC_BITFIELD || + bitfield[4] != 0) + goto done; + + /* + * The SLEEP V2 document says 3328 here. + * + * The supplied historical Dat fixture says 3584. + * We deliberately accept both until the extra 256 bytes + * in the historical format have been identified. + */ + if (be16(bitfield + 5) != SLEEP_BITFIELD_ENTRY_SIZE && + be16(bitfield + 5) != 3584) + goto done; + + if (verify_data) { + snprintf(path, + sizeof(path), + "%s/%s.data", + dir, + name); + + if (load_file(path, + &data_file, + &data_len) != 0) + goto done; + + if (verify_data_leaves( + data_file, + data_len, + (const sleep_tree_entry_t *)(tree_file + + SLEEP_HEADER_SIZE), + node_count) != 0) + goto done; + } + + if (out_length) + *out_length = feed_length; + + rc = 0; + +done: + free(tree_file); + free(sig_file); + free(key_file); + free(data_file); + free(bitfield); + + return rc; +} + + +int +sleep_verify_dat(const char *dir) +{ + size_t metadata_length; + size_t content_length; + + printf("sleep-verify: %s\n", dir); + + printf("metadata: "); + + if (verify_feed(dir, + "metadata", + 1, + &metadata_length) != 0) { + printf("FAIL\n"); + return -1; + } + + printf("OK (%zu entries)\n", metadata_length); + + printf("content: "); + + if (verify_feed(dir, + "content", + 0, + &content_length) != 0) { + printf("FAIL\n"); + return -1; + } + + printf("OK (%zu entries)\n", content_length); + + printf("sleep-verify: PASS\n"); + + return 0; +} + + +#ifndef SLEEP_VERIFY_NO_MAIN +int +main(int argc, char **argv) +{ + if (argc != 2) { + fprintf(stderr, + "usage: %s .dat\n", + argv[0]); + return 2; + } + +#ifdef SLEEP_SALT + + if (sodium_init() < 0) + return 1; + +#else + + fprintf(stderr, + "sleep-verify: built without SLEEP_SALT\n"); + + return 1; + +#endif + + return sleep_verify_dat(argv[1]) == 0 ? 0 : 1; +} +#endif diff --git a/src/sleep_writer.c b/src/sleep_writer.c new file mode 100644 index 0000000..e45ce09 --- /dev/null +++ b/src/sleep_writer.c @@ -0,0 +1,530 @@ +#include "sleep_writer.h" +#include "sleep.h" + +#include +#include +#include +#include +#include + +#ifdef SLEEP_SALT +#include +#endif + +#define SLEEP_MAGIC_BITFIELD 0x05025700u +#define SLEEP_MAGIC_SIGNATURE 0x05025701u +#define SLEEP_MAGIC_TREE 0x05025702u + +#define SLEEP_BITFIELD_SIZE 3584u +#define SLEEP_DATA_BITS 8192u +#define SLEEP_TREE_BITS 16384u +#define SLEEP_INDEX_BYTES 256u + +#define SLEEP_HASH_BYTES 32u +#define SLEEP_STACK_MAX 64u + +typedef struct writer_node { + uint8_t hash[SLEEP_HASH_BYTES]; + uint64_t length; + uint64_t index; + uint64_t depth; +} writer_node_t; + +typedef struct writer_peak { + uint64_t index; + uint64_t depth; +} writer_peak_t; + +static void put_u16be(uint8_t *p, uint16_t v) +{ + p[0] = (uint8_t)(v >> 8); + p[1] = (uint8_t)v; +} + +static void put_u32be(uint8_t *p, uint32_t v) +{ + p[0] = (uint8_t)(v >> 24); + p[1] = (uint8_t)(v >> 16); + p[2] = (uint8_t)(v >> 8); + p[3] = (uint8_t)v; +} + +static void put_u64be(uint8_t *p, uint64_t v) +{ + p[0] = (uint8_t)(v >> 56); + p[1] = (uint8_t)(v >> 48); + p[2] = (uint8_t)(v >> 40); + p[3] = (uint8_t)(v >> 32); + p[4] = (uint8_t)(v >> 24); + p[5] = (uint8_t)(v >> 16); + p[6] = (uint8_t)(v >> 8); + p[7] = (uint8_t)v; +} + +static int make_path(char *out, size_t out_size, + const char *dir, const char *name, + const char *suffix) +{ + int n = snprintf(out, out_size, "%s/%s%s", dir, name, suffix); + if (n < 0 || (size_t)n >= out_size) { + errno = ENAMETOOLONG; + return -1; + } + return 0; +} + +static int write_header(FILE *f, uint32_t magic, + uint16_t entry_size, const char *algo) +{ + uint8_t header[SLEEP_HEADER_SIZE]; + size_t len = algo ? strlen(algo) : 0; + + if (len > 24) { + errno = EINVAL; + return -1; + } + + memset(header, 0, sizeof(header)); + put_u32be(header, magic); + put_u16be(header + 5, entry_size); + header[7] = (uint8_t)len; + if (len) + memcpy(header + 8, algo, len); + + if (fwrite(header, 1, sizeof(header), f) != sizeof(header)) { + errno = EIO; + return -1; + } + return 0; +} + +static int hash_leaf(const uint8_t *data, size_t len, + uint8_t out[32]) +{ +#ifdef SLEEP_SALT + uint8_t head[9]; + crypto_generichash_state st; + + head[0] = 0; + put_u64be(head + 1, (uint64_t)len); + + if (crypto_generichash_init(&st, NULL, 0, 32) != 0) + return -1; + if (crypto_generichash_update(&st, head, sizeof(head)) != 0) + return -1; + if (len && crypto_generichash_update(&st, data, len) != 0) + return -1; + return crypto_generichash_final(&st, out, 32); +#else + (void)data; (void)len; (void)out; + errno = ENOSYS; + return -1; +#endif +} + +static int hash_parent(const writer_node_t *left, + const writer_node_t *right, + uint8_t out[32]) +{ +#ifdef SLEEP_SALT + uint8_t head[9]; + crypto_generichash_state st; + + head[0] = 1; + put_u64be(head + 1, left->length + right->length); + + if (crypto_generichash_init(&st, NULL, 0, 32) != 0) + return -1; + if (crypto_generichash_update(&st, head, sizeof(head)) != 0) + return -1; + if (crypto_generichash_update(&st, left->hash, 32) != 0) + return -1; + if (crypto_generichash_update(&st, right->hash, 32) != 0) + return -1; + return crypto_generichash_final(&st, out, 32); +#else + (void)left; (void)right; (void)out; + errno = ENOSYS; + return -1; +#endif +} + +static int tree_build(const uint8_t *const *blocks, + const size_t *lengths, + size_t nblocks, + writer_node_t **out_nodes, + size_t *out_count) +{ + writer_node_t *nodes; + writer_peak_t stack[SLEEP_STACK_MAX]; + size_t stack_len = 0; + size_t node_count; + size_t i; + + if (nblocks > SIZE_MAX / 2 + 1) { + errno = EOVERFLOW; + return -1; + } + + node_count = nblocks ? nblocks * 2 - 1 : 0; + nodes = node_count ? calloc(node_count, sizeof(*nodes)) : NULL; + if (node_count && !nodes) { + errno = ENOMEM; + return -1; + } + + for (i = 0; i < nblocks; i++) { + writer_node_t *leaf; + uint64_t leaf_index = (uint64_t)(i * 2); + + leaf = &nodes[leaf_index]; + leaf->index = leaf_index; + leaf->depth = 0; + leaf->length = lengths[i]; + + if (hash_leaf(blocks[i], lengths[i], leaf->hash) != 0) + goto fail; + + stack[stack_len].index = leaf_index; + stack[stack_len].depth = 0; + stack_len++; + + while (stack_len >= 2 && + stack[stack_len - 1].depth == stack[stack_len - 2].depth) { + writer_peak_t right = stack[stack_len - 1]; + writer_peak_t left = stack[stack_len - 2]; + writer_node_t *ln = &nodes[left.index]; + writer_node_t *rn = &nodes[right.index]; + uint64_t parent_index; + writer_node_t *parent; + + stack_len -= 2; + parent_index = sleep_tree_parent(left.index); + if (parent_index >= node_count) { + errno = EINVAL; + goto fail; + } + + parent = &nodes[parent_index]; + parent->index = parent_index; + parent->depth = left.depth + 1; + parent->length = ln->length + rn->length; + + if (hash_parent(ln, rn, parent->hash) != 0) + goto fail; + + stack[stack_len].index = parent_index; + stack[stack_len].depth = parent->depth; + stack_len++; + } + } + + *out_nodes = nodes; + *out_count = node_count; + return 0; + +fail: + free(nodes); + return -1; +} + +static int write_tree(const char *path, + const writer_node_t *nodes, size_t node_count) +{ + FILE *f; + size_t i; + + f = fopen(path, "wb"); + if (!f) + return -1; + + if (write_header(f, SLEEP_MAGIC_TREE, + SLEEP_TREE_ENTRY_SIZE, SLEEP_ALGO_BLAKE2B) != 0) + goto fail; + + for (i = 0; i < node_count; i++) { + uint8_t entry[40]; + memcpy(entry, nodes[i].hash, 32); + put_u64be(entry + 32, nodes[i].length); + if (fwrite(entry, 1, sizeof(entry), f) != sizeof(entry)) { + errno = EIO; + goto fail; + } + } + + return fclose(f) == 0 ? 0 : -1; +fail: + fclose(f); + return -1; +} + +static int sign_state(const writer_node_t *nodes, + size_t block_count, + const sleep_keypair_t *keypair, + uint8_t signature[64]) +{ +#ifdef SLEEP_SALT + uint64_t root_indexes[64]; + size_t root_count; + size_t i; + size_t msg_len; + uint8_t *msg; + uint8_t digest[32]; + crypto_generichash_state st; + + /* The Dat/SLEEP root set is fullRoots(2 * length). */ + root_count = sleep_tree_full_roots((uint64_t)(2 * block_count), + root_indexes, 64); + + if (root_count > (SIZE_MAX - 1) / 48) { + errno = EOVERFLOW; + return -1; + } + msg_len = 1 + root_count * 48; + msg = malloc(msg_len); + if (!msg) { + errno = ENOMEM; + return -1; + } + + msg[0] = 2; + for (i = 0; i < root_count; i++) { + const writer_node_t *root = &nodes[root_indexes[i]]; + size_t off = 1 + i * 48; + + memcpy(msg + off, root->hash, 32); + put_u64be(msg + off + 32, root->index); + put_u64be(msg + off + 40, root->length); + } + + if (crypto_generichash_init(&st, NULL, 0, 32) != 0) + goto fail; + if (crypto_generichash_update(&st, msg, msg_len) != 0) + goto fail; + if (crypto_generichash_final(&st, digest, 32) != 0) + goto fail; + + free(msg); + + if (crypto_sign_detached(signature, NULL, + digest, sizeof(digest), + keypair->seckey) != 0) + return -1; + + return 0; + +fail: + free(msg); + return -1; +#else + (void)nodes; (void)block_count; (void)keypair; (void)signature; + errno = ENOSYS; + return -1; +#endif +} + +static int write_signatures(const char *path, + const writer_node_t *nodes, + size_t nblocks, + const sleep_keypair_t *keypair) +{ + FILE *f; + size_t length; + + f = fopen(path, "wb"); + if (!f) + return -1; + + if (write_header(f, SLEEP_MAGIC_SIGNATURE, + SLEEP_SIG_ENTRY_SIZE, SLEEP_ALGO_ED25519) != 0) + goto fail; + + for (length = 1; length <= nblocks; length++) { + uint8_t sig[64]; + + if (sign_state(nodes, length, keypair, sig) != 0) + goto fail; + if (fwrite(sig, 1, sizeof(sig), f) != sizeof(sig)) { + errno = EIO; + goto fail; + } + } + + return fclose(f) == 0 ? 0 : -1; +fail: + fclose(f); + return -1; +} + +static void set_bit(uint8_t *bits, size_t bit) +{ + bits[bit >> 3] |= (uint8_t)(1u << (7 - (bit & 7))); +} + +static unsigned int byte_state(uint8_t v) +{ + if (v == 0x00) + return 0; + if (v == 0xff) + return 3; + return 2; +} + +static void build_index(uint8_t index[SLEEP_INDEX_BYTES], + const uint8_t data[1024]) +{ + size_t pair; + + memset(index, 0, SLEEP_INDEX_BYTES); + + /* Four index bits describe every two bytes of the data bitfield. */ + for (pair = 0; pair < 512; pair++) { + unsigned int a = byte_state(data[pair * 2]); + unsigned int b = byte_state(data[pair * 2 + 1]); + unsigned int tuple = a | (b << 2); + size_t byte = pair >> 1; + index[byte] |= (uint8_t)(tuple << ((pair & 1) ? 4 : 0)); + } +} + +static int write_bitfield(const char *path, + size_t nblocks, + size_t node_count) +{ + uint8_t entry[SLEEP_BITFIELD_SIZE]; + uint8_t *data = entry; + uint8_t *tree = entry + 1024; + uint8_t *index = entry + 3072; + FILE *f; + size_t i; + + memset(entry, 0, sizeof(entry)); + + if (nblocks > SLEEP_DATA_BITS || node_count > SLEEP_TREE_BITS) { + errno = EOVERFLOW; + return -1; + } + + for (i = 0; i < nblocks; i++) + set_bit(data, i); + + for (i = 0; i < node_count; i++) + set_bit(tree, i); + + build_index(index, data); + + f = fopen(path, "wb"); + if (!f) + return -1; + + if (write_header(f, SLEEP_MAGIC_BITFIELD, + SLEEP_BITFIELD_SIZE, NULL) != 0) + goto fail; + if (fwrite(entry, 1, sizeof(entry), f) != sizeof(entry)) { + errno = EIO; + goto fail; + } + + return fclose(f) == 0 ? 0 : -1; +fail: + fclose(f); + return -1; +} + +static int write_data(const char *path, + const uint8_t *const *blocks, + const size_t *lengths, + size_t nblocks) +{ + FILE *f; + size_t i; + + f = fopen(path, "wb"); + if (!f) + return -1; + + for (i = 0; i < nblocks; i++) { + if (lengths[i] && + fwrite(blocks[i], 1, lengths[i], f) != lengths[i]) { + errno = EIO; + goto fail; + } + } + + return fclose(f) == 0 ? 0 : -1; +fail: + fclose(f); + return -1; +} + +static int write_key(const char *path, const sleep_keypair_t *keypair) +{ + FILE *f = fopen(path, "wb"); + if (!f) + return -1; + if (fwrite(keypair->pubkey, 1, 32, f) != 32) { + errno = EIO; + fclose(f); + return -1; + } + return fclose(f) == 0 ? 0 : -1; +} + +int sleep_write_feed(const char *feed_dir, + const char *feed_name, + const uint8_t *const *blocks, + const size_t *lengths, + size_t nblocks, + const sleep_keypair_t *keypair) +{ + char path[1024]; + writer_node_t *nodes = NULL; + size_t node_count = 0, i; + int rc = -1; + + if (!feed_dir || !feed_name || !keypair || + !feed_name[0] || !strcmp(feed_name, ".") || !strcmp(feed_name, "..") || + strchr(feed_name, '/') || strchr(feed_name, '\\') || + (nblocks && (!blocks || !lengths))) { + errno = EINVAL; + return -1; + } + for (i = 0; i < nblocks; i++) { + if (lengths[i] && !blocks[i]) { errno = EINVAL; return -1; } + } + + if (make_path(path, sizeof(path), feed_dir, feed_name, ".tree") != 0) + return -1; + + if (tree_build(blocks, lengths, nblocks, &nodes, &node_count) != 0) + return -1; + + if (write_tree(path, nodes, node_count) != 0) + goto done; + + if (make_path(path, sizeof(path), feed_dir, feed_name, ".signatures") != 0) + goto done; + if (write_signatures(path, nodes, nblocks, keypair) != 0) + goto done; + + if (make_path(path, sizeof(path), feed_dir, feed_name, ".key") != 0) + goto done; + if (write_key(path, keypair) != 0) + goto done; + + if (make_path(path, sizeof(path), feed_dir, feed_name, ".bitfield") != 0) + goto done; + if (write_bitfield(path, nblocks, node_count) != 0) + goto done; + + if (make_path(path, sizeof(path), feed_dir, feed_name, ".data") != 0) + goto done; + if (write_data(path, blocks, lengths, nblocks) != 0) + goto done; + + rc = 0; + +done: + free(nodes); + return rc; +} diff --git a/tests/fixtures/README.md b/tests/fixtures/README.md new file mode 100644 index 0000000..5b3fe01 --- /dev/null +++ b/tests/fixtures/README.md @@ -0,0 +1,13 @@ +# Test fixtures + +Keep reusable test input here, outside the disposable `build/` tree. + +- `dat-history/` preserves archive snapshots used during version-bar work. +- `dillo-cache/archives/` preserves the historical Dillo cache used for + Dat/Dillo compatibility checks. Its legacy `dat.json` is test input; new + archives from this project use `dat.scm`. +- `dillo-dpip/cold-input` is a canned DPIP request sequence. +- `windows/hello.c` is the tiny MSYS2 compiler smoke source. + +Per-machine keys, bootstrap identity, and test run output belong under the +ignored `local-data/` directory and are not source-distribution contents. diff --git a/tests/fixtures/dat-history/site-previous/.dat/content.bitfield b/tests/fixtures/dat-history/site-previous/.dat/content.bitfield new file mode 100644 index 0000000000000000000000000000000000000000..bd639a5f0e39320c46823a9e475ebacc965ab58e GIT binary patch literal 3616 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 IqXb?41Bc}hkpKVy literal 0 HcmV?d00001 diff --git a/tests/fixtures/dat-history/site-previous/.dat/content.key b/tests/fixtures/dat-history/site-previous/.dat/content.key new file mode 100644 index 0000000..30ea44f --- /dev/null +++ b/tests/fixtures/dat-history/site-previous/.dat/content.key @@ -0,0 +1 @@ +’Œ±‡ á«ÌÕ. (ÓkyÈlQ=k06m@%OWK8}wR6T|(zEr`b>0jlu&mA1gfz?86z3MI1Bt|qPk(CDwS7`#C z<1)o$yOyT`gyqA1C0bC3sG0E9N4=yKaV2~3=F(?J-ts6|1lwvPDNRQ5KskrA?;ynF zpUnXI9*q1GVOIv!+C|*f*1 zolO;kY->)URS>5&_&-Dp72$pL&yo&gDaSsk9OuleT6&0Ke`SA|f$hr6AcdSx|F;DT#vO?q$~!oBM-Hw>kz3IXCDY1`B<1( z0!+4J0~hoB`&P&PJaf7a6ptY1X~wKRiR;DC$Tev0d;T%xW$17bS0EbkOUVbZloX0` zW`q@L49NN;#+0GP?>?;^eu{BDC!C KxjVl(9!CXYG`Ge8 literal 0 HcmV?d00001 diff --git a/tests/fixtures/dat-history/site-previous/.dat/content.tree b/tests/fixtures/dat-history/site-previous/.dat/content.tree new file mode 100644 index 0000000000000000000000000000000000000000..977c70e55519c86e92a361b71e7e8589241e3107 GIT binary patch literal 472 zcmZQ&3TI+q&|r7+arAaIN@73<8k0<#mCm;WE-v91J z7de>vQxdCVCFU+#Rbc1$rdHx>_yOj}oi&BlZm(vp|5PGXy&S48`je$M&u1I&B~Rxx zx~S$oZ~naRXTx_pEvZz&`QJR8wp@UzV@?(_xl+&j_WPZ)91py+cg190+R6WIHCO-g zqQu^rF;I1!tmS`MK5p7t%FuV}=gg3rnNx2rTK3<9 z^3pQBGqICS%@JDstjybMvtu}abVxaOGyi8~oSmfdK1XT9WX zcQkXqnbvkSyGK$I>|Gxq>X_d^-AND}I3g7py?1r~q>V=QE@{m14XdAg*?n%GM*6Os zUr+k2QHL0i7`f>?dxgO4eU0yDw~7QiYAs+)nk~0mI6g&kxuC(fLg6Ziy3~tKd)VEN lwmC1j{_{`dr8%JrD=u{Hoj3bRh)1(H&q^kn7>GKS3IH|esaF61 literal 0 HcmV?d00001 diff --git a/tests/fixtures/dat-history/site-previous/.dat/metadata.bitfield b/tests/fixtures/dat-history/site-previous/.dat/metadata.bitfield new file mode 100644 index 0000000000000000000000000000000000000000..bd639a5f0e39320c46823a9e475ebacc965ab58e GIT binary patch literal 3616 ycmeItu?YY$2t?6Cu+4f|KZ6L`3oc;(EB{sJcBP|Hj0=G6@@;5+NF{(id5OB`S2x3TTILN@MLL@l-90SgUb+|9xN$D(RN}diyCZGOxlLJC|fG@ z^a9uu*QQ7r9$EwEkF-JAL(0yq9Gp$N2<#4Yb_=YBOCk(j5vZphEO7% zRyRLdV0OPG&^=q@%(_h+_d`y#SX|AJzF^csU0%4_`Thhi*608nyHkHhnCaFoGP3&= zKo(#@2TCi`nNDtJ5>o K%MDzKzZMCA!^9o{ literal 0 HcmV?d00001 diff --git a/tests/fixtures/dat-history/site-previous/.dat/metadata.tree b/tests/fixtures/dat-history/site-previous/.dat/metadata.tree new file mode 100644 index 0000000000000000000000000000000000000000..11332b6d56ff12c4bc81b98660a99933e239352e GIT binary patch literal 472 zcmZQ&3TI+q&|r7+arAaIN@73g-bI5o2VSZc8y~fDp3=1S^iTU$2L3Bd&F_T^RX239MlXe`^OjegHFfI2L;TZ} zSHw>^x;Omu@n7dFcsK6qbCB4c{6nM_s!rBZq(j~~q2iv_rLuVkg9;7=OYXelaxOpq zH&0ZH^+Jo|P<7p%{vY%wcW5i|pGtnZd&AoO1`qc#2NnEb(p)IL`1RKxJD}dbg1_6UA9$&;cBU0pvyOjx~i$OgVo*6i$T>ncwe8WUU9bhs7fgP;F{@iub+ZRlowL{M7pJZ+ejcSD fcK?XN%&SwxEE>hm&DxyW_AfE`g@}C~RGk6AIEM#0DfOe4?BqW}p200+1MZvX%Q literal 0 HcmV?d00001 diff --git a/tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/content.data b/tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/content.data new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/content.key b/tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/content.key new file mode 100644 index 0000000..74bf268 --- /dev/null +++ b/tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/content.key @@ -0,0 +1 @@ +4.–^Ê ¨b »PÌÏrü{…´=®‰èf[årÔ-C \ No newline at end of file diff --git a/tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/content.signatures b/tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/content.signatures new file mode 100644 index 0000000000000000000000000000000000000000..b1f55a7483ef966d82ab28a60c06249c38d11c8f GIT binary patch literal 96 zcmZQ&3TI?saA0>$F)}qZv}8aBX}yUK$!i*gCAQq0`FNAXD;}Se1~2=gi+|nI-}+Qa o$nyCafA=-{2aaC-ZK%KQ@awgAwRieNC4Eq1d|vxj;gmQR0PQOy6951J literal 0 HcmV?d00001 diff --git a/tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/content.tree b/tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/content.tree new file mode 100644 index 0000000000000000000000000000000000000000..9327358472c9ec2a27918e1ef3bb23b1d301e996 GIT binary patch literal 72 zcmZQ&3TI+q&|r7+arAaIN@73<8k0<#mCm;WE-v91J I7de=E062^jPXGV_ literal 0 HcmV?d00001 diff --git a/tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/metadata.bitfield b/tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/metadata.bitfield new file mode 100644 index 0000000000000000000000000000000000000000..23de5dedce0127d10306e83674656a5fdfcb222d GIT binary patch literal 3616 wcmZQ&3TI&8W55X-aLSB=Q7{UII~dLXqhJ(_f>AIEM#0DfOe4?BqW}p200+1MZvX%Q literal 0 HcmV?d00001 diff --git a/tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/metadata.data b/tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/metadata.data new file mode 100644 index 0000000..95e222f --- /dev/null +++ b/tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/metadata.data @@ -0,0 +1,3 @@ + + +hyperdrive 4.–^Ê ¨b »PÌÏrü{…´=®‰èf[årÔ-C \ No newline at end of file diff --git a/tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/metadata.key b/tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/metadata.key new file mode 100644 index 0000000..be73b90 --- /dev/null +++ b/tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/metadata.key @@ -0,0 +1 @@ +ðP½ñ›ôj#ZLªøŠ²´Õõl—NF3ô1N \ No newline at end of file diff --git a/tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/metadata.signatures b/tests/fixtures/dat-history/test-version-bar-277-0/first/.dat/metadata.signatures new file mode 100644 index 0000000000000000000000000000000000000000..04ff73eee316ac10aa13ce66cddfc44b90bb2255 GIT binary patch literal 96 zcmZQ&3TI?saA0>$F)}qZv}8aB7u%PMndG(K^jrFCmA6C4VSN?rH}faw|b5XPXP>y}RTyIsa9ePL7QJQ_k{>2iHjlH>xfy J_J^w10|4}$5WoNc literal 0 HcmV?d00001 diff --git a/tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/content.bitfield b/tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/content.bitfield new file mode 100644 index 0000000000000000000000000000000000000000..23de5dedce0127d10306e83674656a5fdfcb222d GIT binary patch literal 3616 wcmZQ&3TI&8W55X-aLSB=Q7{UII~dLXqhJ(_f>AIEM#0DfOe4?BqW}p200+1MZvX%Q literal 0 HcmV?d00001 diff --git a/tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/content.data b/tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/content.data new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/content.key b/tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/content.key new file mode 100644 index 0000000000000000000000000000000000000000..d2838f7ed6d89de4e6312689512b4c59f7f34cf8 GIT binary patch literal 32 qcmV+*0N?+tRDr$!unAq0_|BZ4cjk%X*Xxl0a7wEPmhk9U0JelYbP{m@ literal 0 HcmV?d00001 diff --git a/tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/content.signatures b/tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/content.signatures new file mode 100644 index 0000000000000000000000000000000000000000..2849a7dd734790a58cadf206261b3850f66f6472 GIT binary patch literal 96 zcmZQ&3TI?saA0>$F)}qZv}8aBzYCT=kKQh~pjg>Z?PUIreC8c0iws!pWg4%pU0>P$ n-FBaqp@4#g*29PFa&6j64!Ycrs5+p2NRdbM+_}Ap8v;21e3u@( literal 0 HcmV?d00001 diff --git a/tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/content.tree b/tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/content.tree new file mode 100644 index 0000000000000000000000000000000000000000..9327358472c9ec2a27918e1ef3bb23b1d301e996 GIT binary patch literal 72 zcmZQ&3TI+q&|r7+arAaIN@73<8k0<#mCm;WE-v91J I7de=E062^jPXGV_ literal 0 HcmV?d00001 diff --git a/tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/metadata.bitfield b/tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/metadata.bitfield new file mode 100644 index 0000000000000000000000000000000000000000..23de5dedce0127d10306e83674656a5fdfcb222d GIT binary patch literal 3616 wcmZQ&3TI&8W55X-aLSB=Q7{UII~dLXqhJ(_f>AIEM#0DfOe4?BqW}p200+1MZvX%Q literal 0 HcmV?d00001 diff --git a/tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/metadata.data b/tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/metadata.data new file mode 100644 index 0000000000000000000000000000000000000000..441dbfbdb556919340818d376a65af89fe2fe298 GIT binary patch literal 46 zcmV+}0MY*n3TSz7WpZS4X?A52Agok@z5lQYU6c6EoS%2*iR0JnkpOT?s|c3x=vV-@ Egzn%L`2YX_ literal 0 HcmV?d00001 diff --git a/tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/metadata.key b/tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/metadata.key new file mode 100644 index 0000000..ceae144 --- /dev/null +++ b/tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/metadata.key @@ -0,0 +1 @@ +3Ìp=õ{“³©xs–ó«Hb4>a9u]HåŠܱ  \ No newline at end of file diff --git a/tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/metadata.signatures b/tests/fixtures/dat-history/test-version-bar-277-0/second/.dat/metadata.signatures new file mode 100644 index 0000000000000000000000000000000000000000..441a01f53d21f3734996f72efbb77149a60573db GIT binary patch literal 96 zcmZQ&3TI?saA0>$F)}qZv}8aBPZv9z JHgBkUJpc%k5ibA$ literal 0 HcmV?d00001 diff --git a/tests/fixtures/dat-history/test-version-bar-282-0/.dat/dat-dns b/tests/fixtures/dat-history/test-version-bar-282-0/.dat/dat-dns new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/content.bitfield b/tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/content.bitfield new file mode 100644 index 0000000000000000000000000000000000000000..23de5dedce0127d10306e83674656a5fdfcb222d GIT binary patch literal 3616 wcmZQ&3TI&8W55X-aLSB=Q7{UII~dLXqhJ(_f>AIEM#0DfOe4?BqW}p200+1MZvX%Q literal 0 HcmV?d00001 diff --git a/tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/content.data b/tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/content.data new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/content.key b/tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/content.key new file mode 100644 index 0000000..214ef31 --- /dev/null +++ b/tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/content.key @@ -0,0 +1,2 @@ +þ¥w€� ŒÏ…vZÍ?kDá/j + »¤=än÷Å�%½ \ No newline at end of file diff --git a/tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/content.signatures b/tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/content.signatures new file mode 100644 index 0000000000000000000000000000000000000000..1ea1fc872a557c3d84d1517dbda0f62f14884ac4 GIT binary patch literal 96 zcmZQ&3TI?saA0>$F)}qZv}8aB{esUZVa8fA!6?( ox4W-i%zu#6`YB{(o>;}BU52V3x+>3HEBXE*w=_AHE3JwF05|F+(f|Me literal 0 HcmV?d00001 diff --git a/tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/content.tree b/tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/content.tree new file mode 100644 index 0000000000000000000000000000000000000000..9327358472c9ec2a27918e1ef3bb23b1d301e996 GIT binary patch literal 72 zcmZQ&3TI+q&|r7+arAaIN@73<8k0<#mCm;WE-v91J I7de=E062^jPXGV_ literal 0 HcmV?d00001 diff --git a/tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/metadata.bitfield b/tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/metadata.bitfield new file mode 100644 index 0000000000000000000000000000000000000000..23de5dedce0127d10306e83674656a5fdfcb222d GIT binary patch literal 3616 wcmZQ&3TI&8W55X-aLSB=Q7{UII~dLXqhJ(_f>AIEM#0DfOe4?BqW}p200+1MZvX%Q literal 0 HcmV?d00001 diff --git a/tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/metadata.data b/tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/metadata.data new file mode 100644 index 0000000..e0bf0b4 --- /dev/null +++ b/tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/metadata.data @@ -0,0 +1,4 @@ + + +hyperdrive þ¥w€� ŒÏ…vZÍ?kDá/j + »¤=än÷Å�%½ \ No newline at end of file diff --git a/tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/metadata.key b/tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/metadata.key new file mode 100644 index 0000000..338bd22 --- /dev/null +++ b/tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/metadata.key @@ -0,0 +1 @@ +ÉG‰ø3°*Uf¢'ã⯡™ñ±æ”ì~~ ÓU \ No newline at end of file diff --git a/tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/metadata.signatures b/tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/metadata.signatures new file mode 100644 index 0000000000000000000000000000000000000000..b341344e64aad5f54668de584d8e3ed402792792 GIT binary patch literal 96 zcmZQ&3TI?saA0>$F)}qZv}8aBYSv5NOgWI@@a}3$v;wm-XZ!Zoseq9`OxUrP=wAmHU$mM@4_Vn@q0J}XRA^-pY literal 0 HcmV?d00001 diff --git a/tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/metadata.tree b/tests/fixtures/dat-history/test-version-bar-282-0/first/.dat/metadata.tree new file mode 100644 index 0000000000000000000000000000000000000000..aca69c7831c93eba35d1276ac8683dd1ffab225f GIT binary patch literal 72 zcmZQ&3TI+q&|r7+arAaIN@73j3~xs})iJ literal 0 HcmV?d00001 diff --git a/tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/content.bitfield b/tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/content.bitfield new file mode 100644 index 0000000000000000000000000000000000000000..23de5dedce0127d10306e83674656a5fdfcb222d GIT binary patch literal 3616 wcmZQ&3TI&8W55X-aLSB=Q7{UII~dLXqhJ(_f>AIEM#0DfOe4?BqW}p200+1MZvX%Q literal 0 HcmV?d00001 diff --git a/tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/content.data b/tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/content.data new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/content.key b/tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/content.key new file mode 100644 index 0000000..2711680 --- /dev/null +++ b/tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/content.key @@ -0,0 +1 @@ +Fpº£B„7¼[a5¬‚ñ´Þ°šG�“õtÿrå(Má \ No newline at end of file diff --git a/tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/content.signatures b/tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/content.signatures new file mode 100644 index 0000000000000000000000000000000000000000..90c47a689f207f42c7e4671fbeda0de127d610dd GIT binary patch literal 96 zcmZQ&3TI?saA0>$F)}qZv}8aBFHB_J53X%L^VT45PMn}iBhgAt{Q7n^y@uX1x}~tiCNoae0=nwN{RVx)xK|Dc{z*#t~ek{ literal 0 HcmV?d00001 diff --git a/tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/content.tree b/tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/content.tree new file mode 100644 index 0000000000000000000000000000000000000000..9327358472c9ec2a27918e1ef3bb23b1d301e996 GIT binary patch literal 72 zcmZQ&3TI+q&|r7+arAaIN@73<8k0<#mCm;WE-v91J I7de=E062^jPXGV_ literal 0 HcmV?d00001 diff --git a/tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/metadata.bitfield b/tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/metadata.bitfield new file mode 100644 index 0000000000000000000000000000000000000000..23de5dedce0127d10306e83674656a5fdfcb222d GIT binary patch literal 3616 wcmZQ&3TI&8W55X-aLSB=Q7{UII~dLXqhJ(_f>AIEM#0DfOe4?BqW}p200+1MZvX%Q literal 0 HcmV?d00001 diff --git a/tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/metadata.data b/tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/metadata.data new file mode 100644 index 0000000..0afa3ba --- /dev/null +++ b/tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/metadata.data @@ -0,0 +1,3 @@ + + +hyperdrive Fpº£B„7¼[a5¬‚ñ´Þ°šG�“õtÿrå(Má \ No newline at end of file diff --git a/tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/metadata.key b/tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/metadata.key new file mode 100644 index 0000000..5a5802d --- /dev/null +++ b/tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/metadata.key @@ -0,0 +1 @@ +7pèI¤¤óäùVCÙ½ö¶ÂjVb©ëòMŽO„ \ No newline at end of file diff --git a/tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/metadata.signatures b/tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/metadata.signatures new file mode 100644 index 0000000000000000000000000000000000000000..0ff6c649708c8bc84c469ecfafe069080da6e52e GIT binary patch literal 96 zcmZQ&3TI?saA0>$F)}qZv}8aBrj;j}v}Z5vH=Ek3^U9@j`N@r4DiK?AcHK#KpSL+v oS#pJEj)ht9UEAf4-P!!NIxhR6=5Msw#p1vJ;;+9a7(ZqQ0JZWVPXGV_ literal 0 HcmV?d00001 diff --git a/tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/metadata.tree b/tests/fixtures/dat-history/test-version-bar-282-0/second/.dat/metadata.tree new file mode 100644 index 0000000000000000000000000000000000000000..14c73c80706535885469cffed2f8c0aae8822469 GIT binary patch literal 72 zcmZQ&3TI+q&|r7+arAaIN@73<6HojL*#Glz<*Vq;Hfj!beg!vMzejs5(tgEy^%&E& J6-S}!^#EH!6nFpt literal 0 HcmV?d00001 diff --git a/tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/.dat/content.bitfield b/tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/.dat/content.bitfield new file mode 100644 index 0000000000000000000000000000000000000000..bd639a5f0e39320c46823a9e475ebacc965ab58e GIT binary patch literal 3616 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 IqXb?41Bc}hkpKVy literal 0 HcmV?d00001 diff --git a/tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/.dat/content.key b/tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/.dat/content.key new file mode 100644 index 0000000..30ea44f --- /dev/null +++ b/tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/.dat/content.key @@ -0,0 +1 @@ +’Œ±‡ á«ÌÕ. (ÓkyÈlQ=k06m@%OWK8}wR6T|(zEr`b>0jlu&mA1gfz?86z3MI1Bt|qPk(CDwS7`#C z<1)o$yOyT`gyqA1C0bC3sG0E9N4=yKaV2~3=F(?J-ts6|1lwvPDNRQ5KskrA?;ynF zpUnXI9*q1GVOIv!+C|*f*1 zolO;kY->)URS>5&_&-Dp72$pL&yo&gDaSsk9OuleT6&0Ke`SA|f$hr6AcdSx|F;DT#vO?q$~!oBM-Hw>kz3IXCDY1`B<1( z0!+4J0~hoB`&P&PJaf7a6ptY1X~wKRiR;DC$Tev0d;T%xW$17bS0EbkOUVbZloX0` zW`q@L49NN;#+0GP?>?;^eu{BDC!C KxjVl(9!CXYG`Ge8 literal 0 HcmV?d00001 diff --git a/tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/.dat/content.tree b/tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/.dat/content.tree new file mode 100644 index 0000000000000000000000000000000000000000..977c70e55519c86e92a361b71e7e8589241e3107 GIT binary patch literal 472 zcmZQ&3TI+q&|r7+arAaIN@73<8k0<#mCm;WE-v91J z7de>vQxdCVCFU+#Rbc1$rdHx>_yOj}oi&BlZm(vp|5PGXy&S48`je$M&u1I&B~Rxx zx~S$oZ~naRXTx_pEvZz&`QJR8wp@UzV@?(_xl+&j_WPZ)91py+cg190+R6WIHCO-g zqQu^rF;I1!tmS`MK5p7t%FuV}=gg3rnNx2rTK3<9 z^3pQBGqICS%@JDstjybMvtu}abVxaOGyi8~oSmfdK1XT9WX zcQkXqnbvkSyGK$I>|Gxq>X_d^-AND}I3g7py?1r~q>V=QE@{m14XdAg*?n%GM*6Os zUr+k2QHL0i7`f>?dxgO4eU0yDw~7QiYAs+)nk~0mI6g&kxuC(fLg6Ziy3~tKd)VEN lwmC1j{_{`dr8%JrD=u{Hoj3bRh)1(H&q^kn7>GKS3IH|esaF61 literal 0 HcmV?d00001 diff --git a/tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/.dat/metadata.bitfield b/tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/.dat/metadata.bitfield new file mode 100644 index 0000000000000000000000000000000000000000..bd639a5f0e39320c46823a9e475ebacc965ab58e GIT binary patch literal 3616 ycmeItu?YY$2t?6Cu+4f|KZ6L`3oc;(EB{sJcBP|Hj0=G6@@;5+NF{(id5OB`S2x3TTILN@MLL@l-90SgUb+|9xN$D(RN}diyCZGOxlLJC|fG@ z^a9uu*QQ7r9$EwEkF-JAL(0yq9Gp$N2<#4Yb_=YBOCk(j5vZphEO7% zRyRLdV0OPG&^=q@%(_h+_d`y#SX|AJzF^csU0%4_`Thhi*608nyHkHhnCaFoGP3&= zKo(#@2TCi`nNDtJ5>o K%MDzKzZMCA!^9o{ literal 0 HcmV?d00001 diff --git a/tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/.dat/metadata.tree b/tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/.dat/metadata.tree new file mode 100644 index 0000000000000000000000000000000000000000..11332b6d56ff12c4bc81b98660a99933e239352e GIT binary patch literal 472 zcmZQ&3TI+q&|r7+arAaIN@73g-bI5o2VSZc8y~fDp3=1S^iTU$2L3Bd&F_T^RX239MlXe`^OjegHFfI2L;TZ} zSHw>^x;Omu@n7dFcsK6qbCB4c{6nM_s!rBZq(j~~q2iv_rLuVkg9;7=OYXelaxOpq zH&0ZH^+Jo|P<7p%{vY%wcW5i|pGtnZd&AoO1`qc#2NnEb(p)IL`1RKxJD}dbg1_6UA9$&;cBU0pvyOjx~i$OgVo*6i$T>ncwe8WUU9bhs7fgP;F{@iub+ZRlowL{M7pJZ+ejcSD fcK?XN%&SwxEE>hm&DxyW_AfE`g@}C~RGk6yhwa*Ht?OKCovUmi?^NVSh-irb03cD2msSUW+YkV_Z9;Gd z0D#o{{WSn!@>oIoji%?+R_YmyQmgj!E{qo@>48r#ORp(SE;uxLYf@7mBJC{YBt@zA zj*|YJCgr=2UTWrtPjB`to_~G$a?O zXp2Gn^>)u;*K!0*L1dEufBk5i>7LYMu8Z8J>c_>(l^4-~I+ah?xqmlD4#djKz{7dc zL(R#t`YHqlRwGK=H+H8rCp4(-^g^uvdHf>X;na}Xt8}wR>q$)L*XiUb+hc4kR=Rj<805) zlF!Li?>nm;?F!}^cu?W*Du6(l-Zp2E;(=eANbjP#>i-`@=>@ zr*lq=y-?4y_>?Ri^}}!c)#mrG>cG!6HDUJNg{r1xHJeUR(Q3wT&n)%UU3b^_s{g3m z=?X2xJLZVaH;{aVa>?y4D7`Z=wy~MbF>p(EYo=NQqqN+&#BiafGWTb6lTPHo9uz$FwRr*G(Xc zlk)gMHd)(p@16s+UK#nQtuPwK5&NTT@e(=OyA)Ye5rs zE{jZ#VP8Q0XyCK*Rw{EPx2L}e@xb^U1D;fl^CWHLk!Q;JB?lNLMYGW~5>Rz7L1#$h z#Nr?CXuB9)y^n9#7xdo2otDQ&ul3x3poePvYoy6%}n7Zohg*$ zlFEX22^CCAB^G#-x&8yWW_8)ND}Za(pGQiPywV&@9_+Gm3G%GsiS!)aUHoTeWLRL@ zqSm!u{1%O`5q-MA)TM#=gLgfbM3*oN9@NeS56Im3U1Ax1aNX5s5uEW`OA$F6U{gU8}fdZ)_E`uv?*Ahm7@3 z57@-kF3f#C&8`>UR5M7Y;`bLH49!sY` z9I2Z8eh#HyPe%Ldi6xhGy*XBsHp_R|Vd`Zb#W&VPn=gkIx-M6hHFk(AE%0y=N(41~ zT0~bEn!G>{^!(_LHMD5W_MW4@@v<{B=$NwWawRYVL?2_v3igYp9*CUr(ipd5y$VP7 ztPoS}KEVPbuUEdd{Bf%jWLW+A4jUx`%1bCQ{%4s&Xa6PXGx6&1a%M-a%_r#&-~Qq5 z!fsT`)84l$Vs{-=+F4$JXQFpym3`%m+I?O9#u{>P%oq9vZkQ_e8NY``Z<7)uZ?(bK z**|R*5BDAY;D#~2OdK(laOW+R>!ES;;Vb%^!!A(pR!`y%>wtXrR0;jhiC8Dt@{@Oe zsd`Q_W7XiEsCUJlkyEGavDl0qd6dudQ2i>hfhgu3lE{>TVek)KFtW)Z@~=&I)O_t8R)2LY_{)CZSq_p|GaSC$u83<<3*iiTxLIDiCiZ00 zrsw8DvptzpkJjU@ipoQON3?&wV#c4R^u}|Grzn(k^&8vkA+@;9*SMaiQzBq$8=dqu z&pYCH^z>n_Fk(~@8O)3mEnRD=XkKPf9Q`!SaYCwbN+5BRQ<8vb=XYI7(>qfONGXSw zsX%|MlNBzYE)CXXcI`9saOmFUniSE#$^A|jmi$s zLg;G(rfdWDABbpfp(r|{K(uGd*C56fSrUeH$5(@~;Y6-kL#iGZ|6o!$kaqvP{4+e9 zw-`bNq41AO;XnOSM^&D+ia};_>QwOHW9#-%nQ!H`T!?1X_x=rAhmLtO1~tx*hcXDI zoh5bu_^NwBWReci3HU@Ss{v^p+cA3s@F2$dJ9lrrDgHzKl`cVCL-Y=|R=KdER7QJn zYNWzR9y~$BLrD6UgNa+r6*ZYxg6&&-zAB>x7zdeyqQ9?2^jv>E*P&`>xnK~4d?-ni z-V0+TZ}n|WIq>P)z>&d%kIThhbrqRr8z|$FSj`0Es}8M>$R3AaiobFO21z(y46+Y5bvDEP_un zGNxbM(iL@=yZ%OAsqK3|6s)6cxs9R1;?QKSIhnUAj2QYFB(Z8q_{Z`nbeGv6J>j%& zjJGL~lD-H_U~Pz`I#`8@z^m){b*Q;w*ETIp@VCG0A{H(6b zf`t*Bglin78?6x}&05NQ)zCknq1||8>@8We*3++RYx)%(@f&+0?lxEcrCK>#EfuL1 zw_T_j#X5)ul&0XYL zQNpz5bgRBqWm~M1paTR*Gyj@dkB8Q=%^zS6igwR)ZCUCNCNX$Hx6w&L)O>EP=&3kT zuU>d{Vn&>Y?Wl&PMB(SjHT{akx((fL37b9Kv^XonXXZuXkD|3u#Mt(R*TQAeuKPz9 z-*E-efVvBmO^_KdB8}a-pAbIu)mi?W{!Otlp zT){w>x!8MJIvs4odm?I8IJX}6ODR@>+rtq$)o^r7Spyvzm!!NCpP66PMfsx1lDn~I z6%~(rP97C-(IAe1Y4Y`ukXmc;)>K;qu`&ev9vdo*w0?44FxS-4T8Qr}PC;@&UfQbN z+SL#Ixq#tdIXUJFHB%d`o6~#DKUsO&ok{6e;3(eVJ%>lYpU_zy@0@d=-qR|R&QQYx znSDFyFtWu<(I!iz+mF8{Mdg^TSSK0>YT(bkvP_C@-f=5v(H4)$y>0AuXM5`la$NLe zj9gPk>pfAQ$OQ2Lr_7g1#UCW-E4AD1hk`AXYiY#7%*iF|<9tB)5864h-i2r}hK}EQ ztT0{3VvF>u$}lv6S@wxwEo|`m>Gai`!|o00)7tww0LZu9Buk^?H%xQ@?Sje?Fl9RglumML}G?l;^*7&oFJn!IX z^)b|h(bJ9QZjRYq2n;Vrww0O8j zfcXUsQrqpCvo#d#1s65IyhP{(MOVp;>xk*@hwhtZrDJ{tKIpm&h`$jOPcwb!b2g(s zq$gfcPK0e3IhSNt z1V8jrbX^nkrSu-tMXL9&&3?QG!0qSq-twBvpMo*Sg0G@rbn?ifV$gz$B*A+{HG>3c z(C6fH9j+*aQ#I~dfb99vV~j`EagX1xQjr{;1KCk&a5({?w_EDy_nzwTCHr6PipXH+ z7`zW3NMrjoC7MDMWuc~nLn624>{6nv$tl|EHPAIP(gP4Hbc{n!=O-%Qd&req2q*u^ zK&X}&PtIpSX5AoMCJ8$uPLBjp2Zy@ndSZp>c}XI`N$RM_KjuxvNZl&_Q4XbFI5keUfeO1Vl@Y`BD9yHTGmqK13xQ0TFX;!SqvxypFKhcZB7!Y_ z^k3WpbhXnHa{T=r9X9{e962X7S+Qoyd`UcPj}#gz%}%W8-{6Ou{EQ-I0bbWVq)g}{ zDz`=#`RWuz+P$eD*jI;-u5~x$;PgSkhn$2bs{sVS{4upNb2uNjE#1Oc{CMvdpU2J0J#ghq`Ec-q|}qGuL1GAP+7dxfacI^jOD0 zG^eC`Lf>cio)2Yev1%4%i0Q9EpXFHF@JN7WKT*nrt{1(tCh~4v*V+n>Nev#nnKJTx z{Qko)*5A!ODTO3hD0l`?F{<>)+y$CvL^GvrKh}sN^!8ut_>|loWNz!`oTyfG0Ii!% zSCbCBGfn8mBL{DW__yfm z`$>`eAv?FK;m0wb@LkBbBz0qpJqF@ZI4H(-EaE|>7=6E6y9<)Iz`Pzq)RYKiu0i-Y z(H#YUKfFwtE_B(dQ|YwsNT=xRs+`gCTTjIJ+i6R_K z9Gg-0iJ!;Bo{$fFrmXN1lCGNK0Av#wBu{3nn#qrWf-NQu_lqtYYC?BIuImYXE3NFd zjRtF9&u26bfe*%6bCl_}4x4{Ueg7Tg9hO5GePw*f!{nr5S_?EYonexc&y1b`Ret#@Y zn0WMMp2}sQ2=BgD)uUNf(jwZ-t1c2%Iw4-42hV8k0f05H2dFV(QiL(YL$++Wb7~IK z$9&cGHI(Ofgrrge5ggk)F5;x-PO=y?>zv>XR~zJ{C=kGz?1(1BEn+k*cE4NbNK_`{5oH|-YLDn(L<`{VTZ=57( z>ge6Z3ILdAI#N-Jw5ac#smd+36EC|z$lT8YGpaGCw0yWG_9;;|iH-mOW(x<0M0i+N zWph-KbRp(3Re7t@2Hi8ZX)nUG8d(?QPEBtEA5>4D+U$0HmEJ_Wm0;G*hI*AceXHp& z;It&2T<#?RfJUj^F3t$rY7+j?vh1TSS?MsAek9jqteOQaJipE6o{=~J5CE5yc&iv> zoom|bvaZVf=_Vav%H!vb_(f$bzKlk%0D#~@78RHE=JSd@1L?3fSEK&NSrD1f1PF=g zV}1a@{U)U5s)vtmy56laA}{>Dnh-#UbbAPst! zR1Hls*R1%y{cGT^`5gfG$%Mnts%}YL=um?b%_(8EnK3iO+mvYjV3kMaAYR|%p#2sA zSd%YhcF;5V?b3r!Z)L7jOOK1$KC*l7VFH8m5COnzL`wYZ!-Ou;-k%~P8JV9iJaEuD zX#*)T_j9D6q^_z61$fZzJn?M+_`}VYU|ksYP`lg#v;{&%jWFvP_WrU*tfX6bi~RXB z%z=s}Te1fL;F}Z=d24d&t1rsT z@G@86_1b(g4nm@Xljh?wjlaGPmnpbYGhw+E#&cjd%;%!0$zeI zXCA}4uybL80{}lwQBIyO7N+OqV`__O+92JE9$TbY`hE|D`XrF@Ch(whIWG}10BE-V zOY>X&T<;o51m!U@QOM@=m*{lTe+=2bqSY-htvU;qDt=ffR=+0V}nWI-`V2C0MM+)ci#b@8S;h<87zT2`w|xboQk$@ zgCGQBbKvr5E&u@7h$qOA--)RK0H{zwI(V$M^WuVD2gopq0>JA7a3LA;d$BRp=D8aH z0P;PrgmFRN^@1`~kk+>Vpsf@23dSQ{a@!~{;hO{}004sHI;?d$nGS@Ml1cn!;m3S=Oj zgq+6^0`L&Vsx%W103weoGP_dW1wzsH@oDe@!1|I{%g})&$e0PjJ^@;@mjVFmZIv(8 zyL1H90SQyAhSJ0UaF_MzQ42Exbu1nL+(TCoom=Y?1bi~i(WwG}$9o{Kcs(Olhqs>a zJ6uwL?60JI69ZkqC*vCFii)9x%9lTI8`#t7 z{q)^noSYMv7yv#bCXsRChOsdMfDt;N(=@7)_Iz0_rr{tAaBlpJ<$OG z1=!>lTdsgl#(18a(T4yq@1$_&M)rqU9RTQ0Fmb}ZO6<9D8*s7)MQ^{xC2qNG2BYrB z!TOzmI$-6Zib?M?01)bO|8d6}^}+i#0DP{(5h5j^4!~2Xf*Nm<0RauBSOW>D_i*un zW&u(xgxJvWtfOs_EFt6cwsIc417F|$3PJj#tv&VsV}KZ!BwVT)u*vuzHb%2AXB>f8*X(a*0FBo&#`3`B~jGM8N}~8nQoE+ zLuY)76D_+721%DI7rU^EbK%3a>#KOd6Xn?Ojjz&h_65)BhPA=s&TwNUNVI87fmo9f z9}#CaWU(3VgCNPRp{e1Xb=VGy(f29r>ddOHn1Ai+!XQ)cv!ZN5Y{Voy$h+o5AA`Oz zL%ksY-6?)f2r?_AK;}Ivx;B;}etZT6D@^AOT-hf)8Ba238gDP=Pk>8{gUj~6`wZk8 z{R0$Rz^LcGl??IYkV2YR{p+6==iG8d{JpBn?bSVsW)uwM5j6gFNA>^e@nwN%H`&Yk zQGW=Gkkr`otF6>y6ms5ht1IS(UGDOnna@@4Z~mrbrBkgvxxk#=*mm&@!tOnF=lgbm ztEh-+xPIgGm2URj^@SA1fze@{+2SJGJgeAepfvp-gQ^3dXdCJ>yy%_V?)Uj&kohiw z)#mmm+BPwb?>fzDPV~?J70V3){w$8Mt^2BFBVDqdjDP(#thH~etfTzv2^S$0x+`>Y z$om9BK#i+h@+=77$!I3CNJiLq`RkMMua5UFAMqUbYm9%rwNun|@b5V&9zvt_zR2t5 z_8t<{T9o)@sD8a$cZ_9<>dvEwO&u2nMZ8h9C)4MbH`lsdFY{odEtDF!Zj56qw(0WP z_bl}SJ#Mt!*!ZZE38)eRca?Xr7za(-jVyFe;ffLz(*=k_$yCq6d!0a$flV#?lIwJz_PD)NB09w~7i~)nAOKg+0g z#^z#f4P1#@VtEdCGq@P3kuvIzkvL3#f>%FIZ{u@TFHIb*XkO)Qy4zXft|x&QTO27_ z;!hkfc<6p%1oc~Q=Y0afZILXQZmRq>r-1O>8TFuFf9ID~G>XZk7!+W0l-^F@?62|A zHh#FbHDD%NLdN;Jp=nIOh(-6ox4Rbq@k zZw)wz|8p%wY9W1e;by7Hp*wEa?c;cRRwJ>kR~Jdlab~h5*mCxfRPV(j5r2vn#UWIo z38TFlo7sf(vVh0+Tilf!-;#0SqlDiP?3zaq20*t%6Vf-XAM9=I{;NH9csiz*7Pq&h z&|1Plihq7`?k0sl7_*}7TOSu$M|f^Z;fCekmK9Gw_M5*WiogDkP$XsH>%Eg8W_(kk71}WleX;t^H{xUWyy6=(K75#6A}0i?F1Hy<+Kni5}*FHC7Lra z@)lvJaeG|}{-((Ma>@AItZ z+X~3&x=woa#3!=|W+~mEXrWx?m^W58NLy2IiXadhy0tiOhINkC~?#-=hEr@YJ)-8-Un z=QnC72O2Qk0V^-N5B@%%)3g)xktw02>kAg%7xWWQArUERa(eEBlh)vdO@I|4(|uoqm)<(&v?sL8=(`5GA#{9NBpcCJ5-2joJuHIVOr z{YJ=tG5b>Ia=-Hvoxfo+F%9=r`s5YP=j}8#UB#!qFK3E|_b`g`Wj@9NsHe^gZ-c2o zu3WLht&#j=9k&JNC%10`pvZJFk$)>?co%lD&_%QVxAcCQ9a`Aur?nC{z^0NaTjJ0> z+&T-dA%=H#aG~r&Tuk8;5wDyN;<2&-p-V0?WKF9*ur_uli7;m~q~ z7&_+I_C^{UoE_~BPwneDDv>_CiVQ#7b4@zfCgpQ%QTG`w{8>LGE>{UXU!Ko+;<9m+ z6rY-M2)i-~8>^9^Tth_}LVb6acu6Gy?uy-nvB(H-QZZF=>lLfut^&GAU@uN(wx z`7VFs&sE-ep$9i0yq6kR)he3XlGmOaE71!X`;aJS;NdVji^2Iw&B>)DadnU~vMmxl zLRIk6!%^Hvg_=`m^k|aRC&#?wo6+)Odl+H0@MX}|@44#>TOVtOA4(Mo3n&J|Pi37vw<`_h?jI~!Dm znj`%`)k+#KEF&8V6Z!|TwKAbQNabYVC)_}4Y;|Tr&uV!Q8S?s)b;+hFHk?n1IX!u< zpGe^D-o47jZ*Z`k)3V1*ja8Hz%&v<>JA39XPFf%~B?1!EJN!ji*XfJ&S{F3rat2Bg zQyR11P#`-BCpD>#*#h6zd2|($q8&~z_{cfga1mFC)8pQ?4M+Zs>6)!%bo?W6@99b5 zim;~ML&orgz0}zlPCtf#m-9kjZh&NG6>Mrt5KNG`#%MGezS91cn&mDQF^6Y6(M4(( zK(?j~>{oTn*$Td&32b~>-DuBt!L^n1FU zgu$y}|G56{6wZLrt2D4Tnt2kDZN;4)Hz7DM*;I^T?L9N;ECud)x?f|n5v&LgMuRR5 zYg)ctEXD8QD$!7$Yay?=@g?GUBInF^opL>>1E4DxpN8~(hGTa%Jp(Wv?xYvwZU}~}PjfWBCWP_j_0tT)@y1)@)ZIi? z#_x6B&)Gz4WU1-)WM&CbFs#f9_CDBq6n+y9*}VxGeUih@ar+n|IJe zarp~|zXB7(j=5f5Vee?-Haf!JNHo)Tuh^&^_7?xwY)5JwtA)m+`ZDyD$Z<0cs~ zi5K*p-$nf9z%#1MSy@FW4)bQ$=Qr-spHaToVk6=`&$srfzu^9-j_L@>w;){txJCea z>kDtB9dYI61L~i)RK58ootV=`;Zw8kcSL<|>QC=z78x1xn0F_x__iJl+EDn5raCfi zk2qxR6_G&1Ot*B&BQm5WO|{XJF*ah>EXd54^r+fg!u+MyNW|?|^EEwmIBDdZxM3-^ zs(4D}&`yVbR~!_qIH5}tTJDFm5Q{Bhs0%dC@zDEWFvsI}*GQUvTXr|tq*IP3^(Wt! zr_J;K8M0psVl!-&Df#B_y2iIwyN}+%mkXAVfWG&cB;Uer|uyqI0p^0d|*zGiX z$vdL>R?S!n6p}T0PbKI=bKIhM!V!@2M{v4}7FvW~wS)G<1^Gb*w-=h*pX~2y7l{Pt z+a8ZG%Ks`^`hoaNkAKsv_wpLji!s@^BMIO&1|6T2XoTiS1Rn8>hbMMXLL0eoL6ZRq zJ5<{pX$moqppR5_8)@AyT((b}Nj#5l8Z3C$$hJr`$#i>`3wW8*sgbTr#DKwQ@WI4^ z7nA^uQoN&*F0@J1($iJV!uQ3b^AUf5jObH{k#c%p1ZpK9$I<vP5K^L{95Evkuo!V$Ao=hRi?G32vd{AS52Wq3CkjDIe^8~ zQ6sx-*J(KKh~nGcz*u>X))OpLj!&I=RvyV2f3ndS@3Gk!O?vV+arfReGf0)XYwWOY zWjcKg2L!2oy85^De&pqj?$=|(SH@8+XAnZAVtbzUC>aeZMhkMbu{MwZWB1~cko&%~ z@Ic*_NJxAEF`586TUYT~g@&7WmM+ozdr>EzE$S@!bk58fx z40Ch5^+Hi`II(zPl_B5G(`Hm~F!ppHoUiw4H+c6-e(W13ygYFLU$Nwc(ZE-fL;jSD zf12$7L6}7oM{$@2y3~@g-Fz#J-DP=k|Hz)+ zp7wD}@u*i+lD5Ds?MXgTN1GG$lCmpQ3a_eU?n@5aSa*l3h`Ku`hJP@&Zc%!14k2L9 zXylhO_f7;CGy{T7c7of(G7a7>_kUQMC-KAs^+JUF! zXL&rw+8QZji5uqmrsN>BXZYz;?`XuO^ss07xM`)nfqZB6V(*Fd2)A1#XO;O%sieHl4f;cHKY96kNF5QNinENH}Gh zgK)KmA@ZR|0z?1_E>shodB4xJxSp?gC|dcMzai8G3cPp;dm z_=f~;?ak^7~)&BT6t6L)(tOlybsy7i`|4a{EJ{@yXZ znX=5mV_7WvsqU4J9Y5Us+_2wC1aZRt?qUFIjFed|{v7by2+Vu8Z_+cq~<*eh%-+D`xLR zEVtLyK7n)>O>!!!?IIf9k`f*NuB92CBLwB@9DMB{x_(o&fpe%y{#6W4PfHa z@f*zS7Ha%X0DcnV910~703$fhVfVzAw;P2D3$~xxMl0aA-evMEKxipOtR zoB~#cwd?daA-Yp}BA`S9uxa;{mtFA^DnR?hI*cV>Jnn|yKN+)DUU6vYy^8p&2aP^|%*~*#(pV3V8ITIvbvN{&>6PloZdT3f#H?diC*oUcD-yJIX0a{ip zck13~CK0*!&nzQVe3DmnwXxy<(%tXRB>{Z=;nmn@z7-VXay3=Waj2I4jJS}?*3XNp zPR4_ABUp}MiN7;=nj8sN0Gpd?QZx3%#3kyU{K@UqU-`ySGZdPU5!Nu11p8jM;r4Cn z;A&gi;r~HTIYo#k$7OT`hxOB88yV-?12O-yi};2S_z%n_{>s**D?8N>w^SG|B4Oz> zb^G%6OnP_d{j=kO_I;(it(UZ)xfv?)D>k*!9@lDY2vzFt2@LyqKRuVagjK;nNn%}O zUp^I#=YLmwg6v|KUnePfe@BbxWS$&ZHIS=c;+ZqkyV2IIQAELx+lv!!_kh4~Vo%oA zq7`n``g|YMmV75{v)X8z_Z7}eO>IoPNvG@R+BBOnPMX2Zo@Ax_PL{1|g5ml{J`N$j zVlrga{@71di^&a9A0HkA96xeSM#3?zLxv~m(o3#Xi2~tPy7_H3l@_G-RaV9uqXiz6 zGc$h3yPo%sKUm=sx7duLBtG9`e7jDvhiX04;C@Un79nw+Bw^V*MqjtB%g#eY6@YhF zWc4dK=b~bZ;tE}mH)*4@uM&sVJ0?h9lK3WD{-j3w!D?Clqr|1#nbJJS805-5y5c$EG!&aNm!daXwtXGyw0e zr{zD@y7r4u0vGK)BT;Hch94sKFw-16U!`a~CDN4{1HCodd>37n3e8W#7B9QCgz-Sx zkxU~pm)ry@Q)i}_t^W%x#O%HIUU49tc)v_Jpr!Vj{I1-j)vCJZs9zo9@bWOazVj2CznkaOiI%nE-XXh&}7XhEVb zH`6%(KS#6G{S<^ws;C7HekTMwyvb+t)~Zn`q0RYJP0*rQywR3Y)fY_9qm}LmyXlK5 zk(wUPj#JFarM{;IB^VM~CVZTq^b_1d>m%=)XS?b2g2oeDPkT}tZhyvQZGiJy|JRGJ zJn&vt;BfS3<4KL|mE#UrF)tj==}nA5zkJ2)&LdcxTx#aO?4!0QDiogNQ)h^jC*^a zhHSJBgtWM{4(H6_fy|pej$hfo;2AYmBiNla>0H`q3Y9P~D%&EL#Imbr*q>p%dH?%J zR?s+;2GDST zuS`xYD8gjT&Ed(+LxnIhVP`u*v9Nl+F&hM9fEIOX+1;IitS zY)uY#L1^aJi+4>AT3i$uk)BMVj*s}ZzTR}6Mt#h7_0E5*3CNLSC)o-obb)K5YudiK zy0g>#>$({nVNI;rQF^4V5z1Qih5c{f63cBy5wiR-oWb<2HzU)QO2ZoL`iX_$y_vz2 z_GIUMXTpJkxdjzV@~U{uCI0YJa3^Vo+_89!T59#t6Hq{cU^s3T`o zObqTz!^Hc`>IS*0B5SI>4_n$K1DRo&jUZ)V^3F8JO<*qZim@Av*gir2lqWVyUMfp|%B;u3@yylHe*b^BA&^K^l|BS~m0FOkfBhJ&KU0zJ`x}1=*=GW5 zYGXFNf(CL{-%1t}!TkwaLcEL%H-1jPi!Z2lk^;wXSH7?tt$kaP>}^$9Rw*^WW*DC& zVX35W=AB=NV#B{j9}`IOcPOX_owD6TGDF=QA^0wj!J+=e|3F>(Xc$O8>FS8?t5+xN z!bOXSelgBD&n>FH!?vC7Ws%)($Ew{>?{yW&%9d}}gGl*VTX_dGa=SbEYA)U4x}~*H zcvb3i6vsoo$4rSxxDfhw^!Oc7{B7(KvG=}#4{C&TbM1KiCX4UM7jazPU{ zOQUypclzm+&32IydhRSMk4933o>h}Q z(q(!*bVnh(*i#;&v038tB{YRlzay#enWcI(Zm)w?S!24i<&S}^JRe6sl=}z^TGUQL z!z{{JF~@xUb6HlW$}7>W375Xdqg5N z)cKkx2aGp<*F+wTDpVWi97ObY(2bt@X~bOti@`A*Pr>x;@q5JJo>}S5>)*Bt58NNQ zqW^<6C%iuMpn?F-?a5e;1`(3FxySzs{T5L1D2fITk^gLTflXz`jBAviGL9Wm+xMCy z$^vypTbHVEdSxhh=ydkfDtX}=#jV!l`kUH-?J5mi$fewSrfNx=Pm)rd=lgW| z6qCQtD+8}{;LcBrdheLTy<-)nuTW(!gf5x1o7n&Hf13hO4k~Eduf4WGFfRQiS2;dm zHTpJY?5FBVSLxDwkvlxZ@STSE%5|@h`uu*__%V8D(m*e<>M&ioMYqhwpeITW#_iV| zEYkREGuWA<&|++1E4zLL9^xb6#0}$&B)%GS)$#Zw|FkLQL|hdba~p@ zR{6V;3~E_*u2#o>9i`o*G(3jNdJU^_B65n$KIoZ^m7P(cJEG^V|Fkb*Qw``M3hLs8 zY~yu7pP@=-H$$;OYPfc{T>9H`$1&||LmZ<=7Ew}zT+h|TEZI{7a;hbUxsDRA>RIYG zHGj%P-q)5w?7rzUaL^MX^pdBx}a`1NYy&Pathl0SS?lK?GwFlRqs))<$Il z5{iV8$7KuC$ERa&MdT8*^`57ASFxIO(Bq^%yqPSJrg-utL%baXEiP6hvG*@Sra({E zxtXjn{)OPqeU^WxaOIM%arYDY<>2kxE#61#+K<*t!i|MGBWHX>Jq&3@?Bu{5Q}wm; zBQ~GuQWk~Jo-gJ67e0z2>z;umEiQ5za=0K~qk*rRHHVqiPEU8znPMcNhQU>Z{JsU9 zc&rz`^md`W|5cwJ_Tt&Lw{C19Ue#p<>81sABnGuL_OeCT?bKQ~ z>dgtREymZV`Vsx7w*TI?A$~lNy3~b&>Ukho1QVFs4M-^B<9?!F+VMnHBbIC&gqHdOxdjD^R^7YPgE z)U#s{D%4L`&fEVpacbh7l0>c>5q$OV{hVv00Sn}qn1CV0*O*k9TWihIQ&Z9&a_I#< z3aW@C9{Pki?h_TfdTIV6t}d84X?N8<77p9 z`P%jG71slmnKhU7;@(DbPJ_?at%_ctt^=GXgCt7f$QOr>*pQ#QQo;iN-Yix=iqKIZ5vf{ZxnuX=7X;Ba ze~XkSJ^#=N=-_iRgv>diWxjJ<9jmQ;sn!Ps1*7*OLVa@ANAHH$LIOF z%A)O9;;Bl>3*s_W)b&*HiRk|ZY&4zt8EunCY)U$^F z^%rTeNGjCKrMt=OdZ;Cc&7H;;9WSspE|W&X+2=Fd7_S7Y)hM$W^|F3iwX$(B5H)*(RBWkuA8r8i_!$4IM z2gQb!1)&oAvi=VKYVI2(w1Nciz7Ib%8Bnpuu15au!wCt&!CHv$m5pEL0i#I6r%7*x zXC6b2c_Y2_kJDVO3KvV2*tQc%m=BTebClbk6Ico}H=6PX=;TWNru;Pg=wG^xdY6G( za-`?`FKpSFr84>It?Rd5X{W%sVpE%TV_NEFYh8H9(BfVjVb|U0rc?ZT^!uNUCskLI zjyL|m6r1AiC#vLKOgoBJXHC|O;}v5pA0*}WzZuxPo)xh@#^{#Vjd+-jMcVnuj3#{A zL2$}dL1q2;6u2+f?GT|7SG9laR(h^Pue1E;cDAoYrL+qz|CdEcwa6|{r8}C9>@s-; zu~YXl_6a>U&t?u*(-gGr724vH9q|3~5DmJn@Pm%})Xzq57^N{G2J8ATiXXTM{0(38i+H0!<##=DH=x}r_5CvZReKi%Gj}ciZP|NmMQJOo z)Gd~E`AYqB8-6Wh+ojgK>B7^yYz?Ii9>S!an}x8(jE~SX>h(I%#<0U@CTkTazA+1W z(a>v{$uuNCI2(-JuN*dSo!pL0_3?`0(mrZ5#(>;O$!}e?fH;v36+O z!cUuB?MugRn~!A148|+p*$Xu(tupE{4UC7|`@0tZxweCn8zLW=Dg12#JJ?5tO*uAC z=;_|el;OGXAj%SMBnp^ig} zrhG(5&j?g67-bn95jp-|$p31*AndBQp9IJKE;Oy;Vq@F(^7Zpl|6r~+-YbWvIU0o= zk>iLj7=B@=g)as_8xPDsDvP~PO+#Jo2&5-mim``Xb4kN>_P0?PgYc$^adqUWRPPrq z#_rm;eSJ1aU;Ls;?M0F1dfsmAsVHCF6SOhS ztbAOF4VA;@tLT;u-?tIzJ`G&v={`^f{9RAPq}RBFqwj3WT=sUtuYrU=#{{!+2n|xk zg;HmsQ9aJusCP?iLuS2)#~!9fv)#v5nV;4ZrlOW3#L_3OYbMtYs|UJfa|^9ct=3_d zhQ2`|GZuQ6=jvKt?20q}9VaiVj&?Jz$7UzbYecIqwdzR6qYZZTK_5vFe_{moRkj!Y z9o^rQ=ysaz>oWNbR%@rZECxz$7iPu=(@C|eo=eaOih3c4iyinLoN+XoQq<0^sY88G zvM`qVeG+wL^|d`VP@_vCO>w(|=d3GT(8l@5Y<6G}b?lxM{Ia&LVX?02jd@rhD7|xT zaGQr{i6W~>>bNJ~u=GM@KL+zHO&cb0lGCLBSd{mwVHC9*0kg&Yq>XA7?;j-r{eOL& zc|25K+{X`+K_ZzXSw^yr<%duRk!(ejF=IE@$P(GtWF6IrOqQaAEMpyO_I)Qi*|!w3 zZ&{z$)$@A(eCDrt&78UCe!t%{=bn4+`}6TO-*Wp@62j*1zc=)McLtYiyqfjCJ+D;X zd*a&XDQr8*y@g5;zI48~3??C=cE7e-t2>3&dA?%Q9&VgR?m1z!Ew%QrpFPpepWfIZ zG7mv_vN=^+;jyx*?Qgfgir!>Cwv!TGCG+h(ixrIMGvy(ph|1%1Fh_ z&@KQ(UrjnvJD&$S(oKA%>JPWPyWUzQ7++btT6o&U8Dylvi_T_Ops;BAzjFvJx`-Tv zsx{@!uBGsuZUoTQ20q^=(YPje(QuA)+YLWU*Vx^WII}Y)N@ah#_I{D2kO{^V1wz=$ z?Stb_S00qdc4Z15;Q?8y^LevPx=RlOpj|lFrIb$?z6G)x`d=~f+?>k$>lxK-5j@a=s9$t>79sCVJBfw@sc%^P0F6_?61>LYhSk=Hp&a(Vc7@ptG3n#R3hxq!H5AZ@CC9Oj++w89;?Ae{OoRHZmkhG zksWI4gs`1p)k;nF^?oRk{VTcgl69jeSi9Y4)kQn@@BUbsK~S$B^Nbw?*1ft_-BRYV zFu#}a$oUZe$azL*>DKOoC&oLoKehgic&_K{_mk7E+jU1@guxX8JTc=;dgz@|C;K!3 z9u`kgW_p>fr&ZD%VW&`A%!Dqoi$Wof9~yt-hl;GXLJuVQrL?37*(XHFuij zo<157V8;%f2;SH*e!uCV2mp6th#>_q3tEV7E}8InE~J* zmYy?&uyKVS!fU})Ky`#dsFDQn?7JF+VQ`?JZy*&jpi9B z&aZR0hztJ^W#{yHMcykzWZ=w_ef@ptZ`pbgiOL3m{AdsXwZ(CefdIC15XqnMINz2M zfT2<8yAU&INCp%bCvHOT2Lk6OJ3^$94Kx7g8z;=W_CUM2DYTcEX#jx5TOc3^pWxxC z!~npD3qq@%H}B>#2rf**rUM~Rf8;{z-8@yNGsx#MNGQ5^DY{A>7~T|t1^`SjW{-|f z%0S`#DCFoGeF4A}0WKufb}2B}w8%JTPv;+)PEcxG`gqxq0sse-9_Qv28t7{UA~Z#S z)gpI=yf7qX1RM##+KMg8i%RVp8aDVk;_Tl*NIFz}{(@i>s`gaFv*)D@b*96ECZfrJ z!l%2)i?xb*Q9|fANT3r?1FzxrHY2O0NjDXZWC4IPzVJ>j48=eh@uC9&(5Y_uAxHjR z9y#~v&p}EW4@+%w3mX973iCAwcZl|xH?RFxfNji#1E64MNBTjE4>tX)lNTuf0QlNw z2a)@UC@3*cmcD;GKnBiVtW6GJ1AtSaU?9qf>r6XUU`&@80RZq~O!7wm&1-05xFCuM zAjJv{Z|eClPq9QLgG3I1lkzHYX_Xs+W@i-C7X|>7dMjsX7cvOa!%!()gm;q1kkZ1( z7X_attz$x4CcT${oxL_f7fzEh!yE%+7dPc*a@9l@5 z9QRYu;e!_!>&sutV*vnq&+s#(>TE;Zbnm}3*9~Gs&$QD~i|BHwx-Nffk0`b|vKWlb`lzzQg?3OoNghg7(xtDbVvb&e*?(uMp#UHyWiT^=F)QgDR+lZ> zpv}7-pWvrN9E%rj)JmrpShq!Sn}nkrkAIN?5OvSGk(cl+^mkfj;>Nume+Hz8MQe`h zMt4D|{WVQK{SiXY$~2^Cn!3C{U+`JG++L^7+ew#z{@aZ}8F#2!Hn3%S$1ah0FctPN z0KOYP9}RX*`%@!WR+Nr^Dz6h^=y@Bp@~~VBRYzZ;6F#_k$>cl$0fU2>@r(vr-HKw^iwmo~8sJWA4$G-fJ&Mtmho5j;tuIulv zQ&NC{08xEycJY{m-kvA;!k{`4Nh=>7D8ZPpM@yVUh;P3=1E)@4RrYQHj zq_?@$UN9kOWrmgxfG5QKUU`$pSj!S4RiD+xxC2J4a&Hp9f&UHsc+WP*1ntvG1~|8$ z>tT!PDzMwRW5zmuHrJe$$6{0ltFV`Z#s|&Lyi6ejoRniR{BJorTv1*ZriLo*7?3j) zkCTRZ*aj{JXcrRrdlqZoa{v%k!-m6uVdR&{)vtUc$CA%A(Tb}NhP=iEgNC#&O_#Gk z0;%)sM8+%$t9&ohfxd#J$K%Q?!wu0a21hZT5*oM7f_T$K8b2Cd2H?|c3xR@8>;OVu zqfP56b^zxgU#>Z5=>bs}M2V${_`YbkZglpsJltL*2C z4z>s{wXU~14Y)H>sRvRmFYQ8OsnkUDk|kmzXn_Lf@8mr$0hW+;Nj3rRmcsa?3rNK) zB%+Jc}{m5end+W0mXet zvK=rw`tXFk#YaQU@vmpMa|14N(8}H@PWIt{!xi@FiB5G|&f4T)&FABQ3gKDBgHRm1 znAgsS$1LWwCk84b$8y3W31C>alJl<**7k(kJLg+GD5$L$4! zinw3jb(CPrfn6KB9Z~kDuCkHh`Yy0%hNTcUWriu)t7GZn5T;8gtTQAS890{s~k2;a@w{1 ztuI!ySI9JI?(XGs;S6wM8bAEonSK}7>53B3trDSn!(m!MhcIqS=@d^jSIce4!o<*- zyRXb9!@zfkv;6t`W)tUqN)NB|=j2)q;23hPAfF=-&w7soHU7^w@TGj2#Th4FW>pY? z!k4t_$e-9<%97DwkLGX_yhntsYYcLXBPx+5k|nNaOEnL`tN{WpJ;Sp*UW`#yp7~Rg zpzhsLzErJ1gst28($$-SOFpEnZIRXFw@Jgg;;4zkZBvlf}cv(SilEucSMt zB6;Mlqz}K;`<)v@*1qY&Tt;P)cogv zzxRn|hL6cOr(98SiwjcP=Ex74*YPbw2|u||L1%8yhe}51Zht6~v-5nFF9R54zwu4H z`H{6xof|!m5lh~>)_`N^m7R3>t1KNOhrK$>aIAT|)pa467WnZ#s#AK0hU+&yv%u?B zBMwB8_Nm+%6woqvb+eim#uOxo70k7i@Wf;CopBq4TGc+5l;jT&Mi*($@HU=}sm zFCVEkB6p%OGk-$}O|nT5Qm=MOY{ZBAB4K20cZaK zQx}6!OZ7spXV1<8d~$+dHKT;w<3;JLuNss0h%iLgT=e4V9HC$$Tts#;Tx2V?ahMIP zUBWMwy&&Wt7pgSU797|d8Lz;C7@EaP_v{a-h1Y}mc&7DO$|-vOo_|{HC%N1BtG}IxYGH5$cTwy3BY}^| zPIGszh!`8?vUCcCc-G)W-R*@hO?Cu_smfUWy{W0t+fl9n&C7%nc0HsW5Lx<>}GFSm^89vx1I?JO*zdo#e9!thj-%p41x1=#nywfuwiZy@vrT-?rT~Ud${8@0Iizo? zMEtuy;{k>j6ay1-JSkpXT)qYurM_L;Ny(WvmfBz&@5#COAO^GXY`r85jtKhoRL1Zc zn}&DX5((8jD(zHH24l!AGOc+1%}q9HlLo5{LkzXg6^sZios~y66(a&Bmx%j(`2rH| zX4PNTL>q7niuzH14E<~{r)gH ziFUcbU%=guPhboX@zQ^wjn!olmot2g+^5Os3X0s}wssGP%$6$R|Cuf6oSH3-$;#xp zPy7e<^`Rp)wXegGHIsL8O>e7=H>m z#-_5ZleVU4EPHQ6Ozwu|JFC{-@g;N-S5V|*i-q;u&MFgMnudz&ZxMnHoWv=d;1|ER zIm$fposTULVL&Rv)Ko4xm|SOR6!I8dV@4h~bac{j`c^O$nW&3qE9`c7ljU;-F-Xl- z7Kr@eod`;X7Hb8K56*KAG%z8bf7G4P%`(_LP@S(8YJnqyN`)#nOvHw);e4%LZxh%R z1e!*J7Gg(*e@SPJ7%(GHK@2lLA9IWglw>bir+4GjJjxie1swu`nwd|(-$=t&&8F9H zUZ`ds{F`=qKeU>fiqDf+6AR1u$qSRL-=VW#vy|t#n;a0A<*M98^aK=P>o)gd9@t_G zLaE2DlKr^v-pDoXhFEvcHun?Zmo;q6gd>9X5apWvYR~^Xthbw9%#b z6Jha=^$J@i@5Y+@B9Ak#lum$^Y#jev-Jiv@yV-2qqgtzXa$pE<)e-;2etBd5gBHW2 z)Wq-gtIr-$!X~p~9oZdS9{JH@zPSrQqwkL9z~^jd{}oengU}Xrjk=dCTy$iX>W{fX zbSGc6#rl-}p@IfROUz+0ubzYOuO~U(`!?oh&TV?VINNPk- z~fagB@~-{oh8H%rC%X8 zvVR&t$`J-#tuKZv=s@a|gs9!;t?1ZiQ};Yr`zCj**Kqjt(%gDE*&inDob}tyStH%0kDSzn>jcvKhxNR~)SySEL zFdt_;P@Ck_?-QgRZ-2nv+u))dP7GsVU2Gb>En@IySuqEO2r9kyMe2U>B;S2LOsXg~ zp2R&C$10Z+CU_ksAI};*5jAO0;GUyMgj$Lu;FsSS2iLz+a=MlkV?#IplU^;wF6}N4 zGGDBNZu#MaQrhz21t^%A<3s&~Yach*MIBkYbdv`zvVhPf;>0WUl<#?L=$PvwdVYLf zziv&U6=8_Pwjqg4$Z{)$)#yG~l6Dg1`TtbI2N+$xd|&bGZut>^dKn1vkjW(`ak9jO z3=orJgC=pk^0fN+)L6JgPV9t!zlzoBr$6L~AO?=qxte?VG)dw+>^Q+9Zdvr?P4Np< zsvWm+)CBDbM}bwu#p2UzENtEJ=Ze0-NA-?vjF&RkRVA*evn~`S;p*IzhSm}(8*CZ3 zboA<^@C=HooML7NQqC@ApXIW|G!!rPcfQ*fv{|m3FbN;vYDZgdo31moju_OP4lvW7 zac!bM_lbwGL7|V5;5K6Lz7AJZNz6_(OZg>E6yyo&V3Z;I_{LqXw0o%$g4DYzlH`r! zxZQW?n8sJGs4pcxZ=fP0{JRUYH|Hi(k20#B}{@37j`A!eDGE$0X`UM^G`qcSvmg;-6>37d188sIei}}so_}ALiFT3@vFkCBs zRQk|D?IvxR8uFXkvc|Wrw*My5xHQfZDuYk+`nzkmjeiHHG9#TB8^pT^XR_d(FGArL zrL$t!H(QAws7(A*B`B#3QuFIGQp@njhHZ>`p|RDnruwg(Sm+*_lw5t7=SvMuVL_!5 z0|zB-N|xQjNG@t+BrnhBD%FQNr7G)HWnGIu3qt3&3rhA4Nj$V8{?9_ zr%sFQ_j^tnLI|akGCQKG8tX4W6>7-xEHkYbGbDH{zcGH&gA{%>{zPju_`bP3a{MdN zU|u2PW+6)%RGh=eL`soiBri4PC)tTXGBaDsU1U|n6+Ie9p;hGM(|yX*5*`u?d=usp zabI7NR=92vg0L;o&nh}cwarBe&ciVqTR%$vb-Nm8yd6EJ)6;C2B+;ClUBB&dU)w63 zl-U+VMMKqo{OId(t;tnH5Ch|5S;RADpE#}>8_yE!Zh{k* zV`@JW{nt}z64`-hF>y7K((fTDa1qwV*Xp*~{OO3MuG ziWx3wkuGE`iJLn1up*+q&%Kg(VjZ>cqFzC=-e=^{qx+!JaZ;a7?jsCE}a29J5-o%*bq6rIH=fyPdgwwWUkW8lU(ZHU~O{Tu#BXefjPzz znF?w(Vv-#G6#cmRa-eo=qQ>giRiSD=G#NPQZY6!K3)wf_|KUX3YG$u;phfSdQUyyY zSxxpJYH0u(gMTAGWV@$%Uck3(m~ALzlm?Ncc1XBPl+>i7L4lgZAJ4ff?|vIda`j)( zH0zeD%lRLFuae- zc8DEkuR5w)&phD<-=7t;b7)rE4_8Q5xNjd23retgnP0!=Rcm|e4bXsJPa~=gm;Zbm zi8JK-sKul-SWQDBB?<)jm-qbZ(|hET4@KQpV;rxPQkT{Z#kQmlH;l zfu;J|zEH((pJe$gl&x;MeoUtP%YpHAAA)gPMUmLmE7g8_Ybj&_u9oX1Njc@#&L&>O z`p5i+CwI@GR+$vFGP`ZPRQRN^ca$qymviz$$B#Tb`<@DMR2Mr1Uz2xKixV%)cExl6dyfvIDCCFoJ m>ef>tu}nmR|Nr&NshVOx+@$!x^~e!wsiv%XH%HMl@P7al{lM1% literal 0 HcmV?d00001 diff --git a/tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/dat.json b/tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/dat.json new file mode 100644 index 0000000..d615ce2 --- /dev/null +++ b/tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/dat.json @@ -0,0 +1,4 @@ +{ + "title": "Dillo setup test", + "description": "Dillo Dat browsing and version check" +} diff --git a/tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/index.html b/tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/index.html new file mode 100644 index 0000000..87e0722 --- /dev/null +++ b/tests/fixtures/dillo-cache/archives/15/81eb720d2a548eee2f2ad85d38917b5574e090d240fcd0c3477db33220fea2/index.html @@ -0,0 +1,25 @@ + + + + + + Site is online + + + +
    + +

    The site is online

    +

    This is the current version of the Dat Dillo test archive.

    +

    The earlier “site is being set up� page remains available at its pinned archive version.

    +
    + + diff --git a/tests/fixtures/dillo-dpip/cold-input b/tests/fixtures/dillo-dpip/cold-input new file mode 100644 index 0000000..82c22e3 --- /dev/null +++ b/tests/fixtures/dillo-dpip/cold-input @@ -0,0 +1,3 @@ + + + diff --git a/tests/fixtures/windows/hello.c b/tests/fixtures/windows/hello.c new file mode 100644 index 0000000..d55b9da --- /dev/null +++ b/tests/fixtures/windows/hello.c @@ -0,0 +1 @@ +int main(void){return 0;} \ No newline at end of file diff --git a/tests/helpers/dillo-title-probe.c b/tests/helpers/dillo-title-probe.c new file mode 100644 index 0000000..175c82c --- /dev/null +++ b/tests/helpers/dillo-title-probe.c @@ -0,0 +1,24 @@ +#include +#include +#include +#include +static void walk(Display *d, Window w, int depth) { + Window root, parent, *children = NULL; + unsigned int n = 0, i; + char *name = NULL; + if (XFetchName(d, w, &name) && name) { + if (strstr(name, "Dillo") || strstr(name, "Site is online")) printf("%.*s%s\n", depth * 2, " ", name); + XFree(name); + } + if (XQueryTree(d, w, &root, &parent, &children, &n)) { + for (i = 0; i < n; i++) walk(d, children[i], depth + 1); + if (children) XFree(children); + } +} +int main(void) { + Display *d = XOpenDisplay(NULL); + if (!d) { fputs("XOpenDisplay failed\n", stderr); return 1; } + walk(d, DefaultRootWindow(d), 0); + XCloseDisplay(d); + return 0; +} diff --git a/tests/integration/dillo/README.md b/tests/integration/dillo/README.md new file mode 100644 index 0000000..0aaca74 --- /dev/null +++ b/tests/integration/dillo/README.md @@ -0,0 +1,16 @@ +# Dillo integration checks + +These scripts were moved out of `build/` so cleaning or recreating compiler +outputs cannot remove the test procedure. + +- `cold-site-dpip.sh` invokes the local Linux DPI with a canned DPIP exchange + and writes its response and diagnostics to ignored `local-data/`. +- `real-site-dpip.sh` invokes an installed Dillo DPI against the seeded test + archive. Set `DILLO_DPID_KEY_FILE` when the dpid key is outside + `$HOME/.dillo/dpid_comm_keys`; set `DAT_DPI_BIN` if the DPI executable is at + a different path. Its captured response goes to ignored `local-data/`. +- `../../test_dpi.sh` is the automated socket-framing check run by `make test`. + +These checks need a running Cesspool bootstrap and a peer seeding the requested +archive to exercise a network fetch. Cached responses can be inspected under +`local-data/test-runs/dillo/` after running them. diff --git a/tests/integration/dillo/cold-site-dpip.sh b/tests/integration/dillo/cold-site-dpip.sh new file mode 100644 index 0000000..cfae57a --- /dev/null +++ b/tests/integration/dillo/cold-site-dpip.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -eu +repo=$(CDPATH= cd -- "$(dirname -- "$0")/../../.." && pwd) +cold="$repo/local-data/test-runs/dpi-cold-parity-20261007" +output="$repo/local-data/test-runs/dillo" +mkdir -p "$cold/.dillo" "$cold/.config/dat-gpt" +mkdir -p "$output" +printf '12345 abcdef1234567890\n' > "$cold/.dillo/dpid_comm_keys" +printf '127.0.0.1:3282\n' > "$cold/.config/dat-gpt/bootstrap" +{ + printf "\n" + printf "%s\n" "" + printf "%s\n" "" +} | HOME="$cold" "$repo/build/linux/dat.dpi" > "$output/dpi-cold-site.response" 2> "$output/dpi-cold-site.error" diff --git a/tests/integration/dillo/real-site-dpip.sh b/tests/integration/dillo/real-site-dpip.sh new file mode 100644 index 0000000..edb2c4d --- /dev/null +++ b/tests/integration/dillo/real-site-dpip.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -eu +repo=$(CDPATH= cd -- "$(dirname -- "$0")/../../.." && pwd) +key_file=${DILLO_DPID_KEY_FILE:-"$HOME/.dillo/dpid_comm_keys"} +dpi_bin=${DAT_DPI_BIN:-/usr/local/lib/dillo/dpi/dat/dat.dpi.exe} +output="$repo/local-data/test-runs/dillo/real-site-dpi.response" +mkdir -p "$(dirname -- "$output")" +read -r _ secret _ < "$key_file" +{ + printf "\n" "$secret" + printf "%s\n" "" + printf "%s\n" "" + printf "%s\n" "" +} | "$dpi_bin" > "$output" +grep -E "start_send_page|Site is online|The site is online|Content-Type|Content-Length" "$output" diff --git a/tests/interop/cabal/cabal_cable_side_by_side.js b/tests/interop/cabal/cabal_cable_side_by_side.js new file mode 100644 index 0000000..2fafadd --- /dev/null +++ b/tests/interop/cabal/cabal_cable_side_by_side.js @@ -0,0 +1,150 @@ +'use strict' + +// Cabal's JS implementation is the reference side. Cable is exercised via +// its CLI against the exact room key and admin URI generated by that side. +// This deliberately does not call a Cable feed API: Cabal feed wire support +// has not been connected to Cable yet. +const assert = require('assert') +const fs = require('fs') +const os = require('os') +const path = require('path') +const { spawnSync } = require('child_process') +const Cabal = require(process.env.CABAL_JS_MODULE || 'cabal-core') + +function callback (fn, ...args) { + return new Promise((resolve, reject) => fn(...args, (err, value) => err ? reject(err) : resolve(value))) +} + +function close (cabal) { + return new Promise((resolve, reject) => cabal.close(err => err ? reject(err) : resolve())) +} + +function cable (binary, home, ...args) { + let command = binary + let commandArgs = args + let env = { ...process.env, HOME: home } + if (process.env.CABLE_RUNNER === 'wsl') { + // Node and cabal-core may run on Windows while Cable was built in WSL. + // Convert both the executable and per-test HOME to paths visible in WSL. + const wslPath = value => { + const match = /^([A-Za-z]):[\\/](.*)$/.exec(value) + if (!match) return value.replace(/\\/g, '/') + return `/mnt/${match[1].toLowerCase()}/${match[2].replace(/\\/g, '/')}` + } + command = process.env.WSL_BIN || 'wsl.exe' + commandArgs = ['--exec', 'env', `HOME=${wslPath(home)}`, wslPath(binary), ...args] + env = { ...process.env } + } + const result = spawnSync(command, commandArgs, { encoding: 'utf8', env }) + if (result.error) throw result.error + if (result.status !== 0) throw new Error(`cable ${args.join(' ')} failed: ${result.stderr}`) + return result.stdout.trim() +} + +function waitForMessage (cabal, expected, timeoutMs) { + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + cabal.messages.events.removeListener('message', onMessage) + reject(new Error('Cabal JS replica did not receive chat/text record')) + }, timeoutMs) + function onMessage (message) { + if (!message || !message.value || !message.value.content || + message.value.content.text !== expected) return + clearTimeout(timeout) + cabal.messages.events.removeListener('message', onMessage) + resolve(message) + } + cabal.messages.events.on('message', onMessage) + }) +} + +async function main () { + const root = fs.mkdtempSync(path.join(process.env.CABAL_TEST_DIR || os.tmpdir(), 'cabal-cable-')) + const home = path.join(root, 'cable-home') + const binary = path.resolve(process.env.CABLE_BIN || 'build/linux/cable') + fs.mkdirSync(home) + let source, replica, otherRoom, sourceStream, replicaStream + try { + const sourceStorage = path.join(root, 'js-source') + source = Cabal(sourceStorage) + await callback(source.ready.bind(source)) + const roomKey = String(source.key).replace(/^cabal:\/\//, '').toLowerCase() + assert.match(roomKey, /^[0-9a-f]{64}$/, 'Cabal JS must create a 32-byte room key') + + replica = Cabal(path.join(root, 'js-replica'), roomKey) + await callback(replica.ready.bind(replica)) + const jsUserKey = await callback(source.getLocalKey.bind(source)) + assert.match(jsUserKey, /^[0-9a-f]{64}$/, 'Cabal JS local writer key must be 32 bytes') + + const expectedText = 'hello from Cabal JS to its replicated room' + const received = waitForMessage(replica, expectedText, 15000) + sourceStream = source.replicate(true) + replicaStream = replica.replicate(false) + sourceStream.on('error', () => {}) + replicaStream.on('error', () => {}) + sourceStream.pipe(replicaStream).pipe(sourceStream) + + await callback(source.publishNick.bind(source), 'cable-side-by-side-user') + await callback(source.publish.bind(source), { + type: 'chat/text', + content: { channel: 'cable-interop', text: expectedText } + }) + const replicated = await received + assert.strictEqual(replicated.value.type, 'chat/text') + assert.strictEqual(replicated.value.content.channel, 'cable-interop') + + const nickRecord = await callback(source.getMessage.bind(source), { key: jsUserKey, seq: 0 }) + // cabal-core releases have returned either the Hypercore node wrapper or + // its decoded value from getMessage; accept both documented representations. + const nickValue = nickRecord.value || nickRecord + assert.strictEqual(nickValue.type, 'about') + assert.strictEqual(nickValue.content.name, 'cable-side-by-side-user') + const replicatedNick = await callback(replica.getMessage.bind(replica), { key: jsUserKey, seq: 0 }) + const replicatedNickValue = replicatedNick.value || replicatedNick + assert.strictEqual(replicatedNickValue.type, 'about') + assert.strictEqual(replicatedNickValue.content.name, 'cable-side-by-side-user') + + sourceStream.destroy() + replicaStream.destroy() + sourceStream = null + replicaStream = null + await close(source) + source = Cabal(sourceStorage, roomKey) + await callback(source.ready.bind(source)) + assert.strictEqual(await callback(source.getLocalKey.bind(source)), jsUserKey, + 'Cabal JS local user/feed address must persist for the same room') + + const adminKey = 'a'.repeat(64) + const roomAddress = `cabal://${roomKey}?admin=${adminKey}` + assert.strictEqual(cable(binary, home, 'address', roomAddress), + `cable://${roomKey}?admin=${adminKey}`) + + const cableUser = cable(binary, home, 'whoami', roomAddress) + assert.match(cableUser, /^cable:\/\/[0-9a-f]{64}$/) + assert.notStrictEqual(cableUser.slice(8), roomKey, 'room key is not a user address') + assert.notStrictEqual(cableUser.slice(8), adminKey, 'room admin key is not a user address') + assert.notStrictEqual(cableUser.slice(8), jsUserKey, + 'Cabal JS writer and Cable CLI represent separate local users') + assert.strictEqual(cable(binary, home, 'whoami', `cable://${roomKey}`), cableUser, + 'Cable user address must persist for the same room, regardless of URI scheme/admin option') + + otherRoom = Cabal(path.join(root, 'js-other-room')) + await callback(otherRoom.ready.bind(otherRoom)) + const otherRoomKey = String(otherRoom.key).replace(/^cabal:\/\//, '').toLowerCase() + const otherJsUserKey = await callback(otherRoom.getLocalKey.bind(otherRoom)) + assert.notStrictEqual(otherJsUserKey, jsUserKey, + 'Cabal JS local writer keys must be independent across rooms') + const otherCableUser = cable(binary, home, 'whoami', `cabal://${otherRoomKey}`) + assert.notStrictEqual(otherCableUser, cableUser, + 'Cable user address must be room-scoped like Cabal local writer feeds') + + process.stdout.write(`Cabal JS nick/feed replication + Cable room/user identity: PASS room=cabal://${roomKey} js-user=${jsUserKey} cable-user=${cableUser}\n`) + } finally { + for (const stream of [sourceStream, replicaStream]) if (stream) stream.destroy() + for (const cabal of [source, replica, otherRoom]) if (cabal) await close(cabal) + if (process.env.CABAL_TEST_KEEP === '1') process.stdout.write(`kept Cabal test state at ${root}\n`) + else fs.rmSync(root, { recursive: true, force: true }) + } +} + +main().catch(err => { console.error(err); process.exitCode = 1 }) diff --git a/tests/interop/cabal/cabal_cli_features.js b/tests/interop/cabal/cabal_cli_features.js new file mode 100644 index 0000000..585ed19 --- /dev/null +++ b/tests/interop/cabal/cabal_cli_features.js @@ -0,0 +1,162 @@ +'use strict' + +// Exercise the APIs that cabal-cli 15.x uses, with two independent local +// Cabal clients connected by the same live Hypercore replication streams. +const assert = require('assert') +const fs = require('fs') +const os = require('os') +const path = require('path') +const EventEmitter = require('events') +const Module = require('module') +const Client = require(process.env.CABAL_CLIENT_MODULE || 'cabal-client') + +function once (emitter, event, predicate = () => true, timeoutMs = 12000) { + return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + emitter.removeListener(event, listener) + reject(new Error(`timed out waiting for Cabal ${event}`)) + }, timeoutMs) + function listener (value) { + if (!predicate(value)) return + clearTimeout(timer) + emitter.removeListener(event, listener) + resolve(value) + } + emitter.on(event, listener) + }) +} + +function close (client, details) { + return new Promise((resolve, reject) => client.removeCabal(details.key, err => err ? reject(err) : resolve())) +} + +function waitForUserName (details, name, timeoutMs = 12000) { + return new Promise((resolve, reject) => { + const deadline = Date.now() + timeoutMs + const poll = () => { + const user = Object.values(details.getUsers()).find(candidate => candidate.name === name) + if (user) return resolve(user) + if (Date.now() >= deadline) return reject(new Error(`${name} nickname did not replicate`)) + setTimeout(poll, 25) + } + poll() + }) +} + +function createCliScreen (client) { + const clientPath = require.resolve(process.env.CABAL_CLIENT_MODULE || 'cabal-client') + const cliDirectory = path.resolve(path.dirname(clientPath), '..', '..') + const screenPath = path.join(cliDirectory, 'neat-screen.js') + const originalLoad = Module._load + function fakeNeatLog () { + const input = new EventEmitter() + input.cursor = 0 + input.rawLine = () => '' + input.set = () => {} + return { + input, + render: () => {}, + clear: () => {}, + use: plugin => { + const state = {} + plugin(state, new EventEmitter()) + } + } + } + Module._load = function (request, parent, isMain) { + if (request === 'neat-log') return fakeNeatLog + return originalLoad.call(this, request, parent, isMain) + } + try { + const NeatScreen = require(screenPath) + return new NeatScreen({ + client, + frontendConfig: { messageTimeformat: '%H:%M:%S', messageIndent: 'nick' } + }) + } finally { + Module._load = originalLoad + } +} + +async function main () { + const root = fs.mkdtempSync(path.join(process.env.CABAL_TEST_DIR || os.tmpdir(), 'cabal-cli-features-')) + const sourceClient = new Client({ config: { dbdir: path.join(root, 'alice'), preferredPort: 0 } }) + const peerClient = new Client({ config: { dbdir: path.join(root, 'bob'), preferredPort: 0 } }) + let alice, bob, aliceStream, bobStream, screen + try { + alice = await sourceClient.createCabal() + const roomKey = alice.key + assert.match(roomKey, /^[0-9a-f]{64}$/i, 'create room returns a Cabal room key') + + bob = await peerClient.addCabal(`cabal://${roomKey}`) + aliceStream = alice.core.replicate(true) + bobStream = bob.core.replicate(false) + aliceStream.on('error', () => {}) + bobStream.on('error', () => {}) + aliceStream.pipe(bobStream).pipe(aliceStream) + + const aliceNick = once(alice, 'publish-nick', update => update.name === 'alice') + alice.processLine('/nick alice') + await aliceNick + const aliceChannel = once(alice, 'channel-join', update => update.channel === 'lobby') + alice.processLine('/join lobby') + await aliceChannel + const secondChannel = once(alice, 'channel-join', update => update.channel === 'garden') + alice.processLine('/join garden') + await secondChannel + assert(alice.getJoinedChannels().includes('lobby')) + assert(alice.getJoinedChannels().includes('garden')) + + screen = createCliScreen(sourceClient) + const cliChannels = alice.getChannels({ includePM: true, onlyJoined: true }) + alice.focusChannel(cliChannels[0]) + screen.neat.input.emit('ctrl-n') + assert.strictEqual(alice.getCurrentChannel(), cliChannels[1], 'the cabal-cli Ctrl+N handler advances channels') + screen.neat.input.emit('ctrl-n') + assert.strictEqual(alice.getCurrentChannel(), cliChannels[2], 'the cabal-cli Ctrl+N handler continues the cycle') + alice.focusChannel('lobby') + + const bobJoined = once(alice, 'channel-join', update => update.channel === 'lobby' && !update.isLocal) + const bobChannel = once(bob, 'channel-join', update => update.channel === 'lobby') + bob.processLine('/join lobby') + await bobChannel + await bobJoined + const bobNick = once(bob, 'publish-nick', update => update.name === 'bob') + bob.processLine('/nick bob') + await bobNick + await waitForUserName(alice, 'bob') + + const fromAlice = 'hello from Alice in the lobby' + const gotAlice = once(bob, 'new-message', update => + update.channel === 'lobby' && update.message.value.content.text === fromAlice) + alice.processLine(fromAlice) + await gotAlice + + // Verify the actual room stream carries the published nickname record to Bob. + await waitForUserName(bob, 'alice') + + const fromBob = 'hello back from Bob' + const gotBob = once(alice, 'new-message', update => + update.channel === 'lobby' && update.message.value.content.text === fromBob) + bob.processLine(fromBob) + await gotBob + + aliceStream.destroy() + bobStream.destroy() + aliceStream = bobStream = null + process.stdout.write(`cabal-cli 15.x room, /join, /nick, Ctrl+N channel cycle, and two-peer chat: PASS room=cabal://${roomKey}\n`) + } finally { + if (aliceStream) aliceStream.destroy() + if (bobStream) bobStream.destroy() + if (screen) { + if (screen.updateTimer) clearTimeout(screen.updateTimer) + if (alice && screen._updateHandler && screen._updateHandler[alice.key]) + alice.removeListener('update', screen._updateHandler[alice.key]) + } + if (alice) await close(sourceClient, alice) + if (bob) await close(peerClient, bob) + fs.rmSync(root, { recursive: true, force: true }) + } +} + +main().catch(err => { console.error(err); process.exitCode = 1 }) diff --git a/tests/interop/cabal/cabal_core_hyperswarm_live.js b/tests/interop/cabal/cabal_core_hyperswarm_live.js new file mode 100644 index 0000000..c091c4c --- /dev/null +++ b/tests/interop/cabal/cabal_core_hyperswarm_live.js @@ -0,0 +1,118 @@ +'use strict' + +// Direct Cabal Core live discovery/chat check. The cross-process SDL/C to JS +// integration is covered separately by cable_js_c_live.js. +const crypto = require('crypto') +const fs = require('fs') +const os = require('os') +const path = require('path') +const dgram = require('dgram') +const { startHyperswarm, loadCabalCore } = require('./cable_hyperswarm') + +function freeUdpPort () { + return new Promise((resolve, reject) => { + const socket = dgram.createSocket('udp4') + socket.once('error', reject) + socket.bind(0, '127.0.0.1', () => { + const port = socket.address().port + socket.close(err => err ? reject(err) : resolve(port)) + }) + }) +} + +const { Cabal, requireCabal } = loadCabalCore() +const DHT = requireCabal('hyperdht') +const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cabal-core-hyperswarm-')) +const roomKey = crypto.randomBytes(32).toString('hex') +const clients = [] +let bootstrapper = null + +function ready (cabal) { return new Promise((resolve, reject) => cabal.ready(err => err ? reject(err) : resolve())) } +function localKey (cabal) { return new Promise((resolve, reject) => cabal.getLocalKey((err, key) => err ? reject(err) : resolve(key))) } +function swarm (cabal) { return new Promise((resolve, reject) => cabal.swarm({ preferredPort: 0 }, err => err ? reject(err) : resolve())) } +function publish (cabal, value) { return new Promise((resolve, reject) => cabal.publish(value, err => err ? reject(err) : resolve())) } +function nick (cabal, value) { return new Promise((resolve, reject) => cabal.publishNick(value, err => err ? reject(err) : resolve())) } + +function waitFor (predicate, description, timeout = 90000) { + const started = Date.now() + return new Promise((resolve, reject) => { + const timer = setInterval(() => { + if (Date.now() - started > timeout) { clearInterval(timer); reject(new Error('timeout: ' + description)); return } + try { const result = predicate(); if (result) { clearInterval(timer); resolve(result) } } catch (err) { clearInterval(timer); reject(err) } + }, 100) + }) +} + +async function main () { + const port = await freeUdpPort() + bootstrapper = DHT.bootstrapper(port, '127.0.0.1', { host: '127.0.0.1' }) + await bootstrapper.ready() + let bootstrapRequests = 0 + bootstrapper.on('request', request => { + bootstrapRequests++ + if (process.env.CABLE_TRACE_DHT) console.log('local DHT request command=' + request.command) + }) + console.log('local Hyperswarm DHT bootstrap node listening at ' + bootstrapper.address().host + ':' + bootstrapper.address().port) + process.env.CABLE_BOOTSTRAP = `127.0.0.1:${port}` + process.env.CABLE_DHT_HOST = '127.0.0.1' + const alice = Cabal(path.join(tempRoot, 'alice'), roomKey) + const bob = Cabal(path.join(tempRoot, 'bob'), roomKey) + clients.push(alice, bob) + await Promise.all([ready(alice), ready(bob)]) + const [aliceKey, bobKey] = await Promise.all([localKey(alice), localKey(bob)]) + const [alicePort, bobPort] = await Promise.all([freeUdpPort(), freeUdpPort()]) + if (aliceKey === bobKey) throw new Error('Cabal Core reused a room feed identity across clients') + const events = { alice: [], bob: [] } + alice.on('peer-added', key => events.alice.push({ kind: 'peer', key })) + bob.on('peer-added', key => events.bob.push({ kind: 'peer', key })) + alice.kcore.api.messages.events.on('message', message => events.alice.push({ kind: 'message', message })) + bob.kcore.api.messages.events.on('message', message => events.bob.push({ kind: 'message', message })) + await startHyperswarm(alice, { host: '127.0.0.1', port: alicePort }) + await startHyperswarm(bob, { host: '127.0.0.1', port: bobPort }) + console.log('both Cabal Core clients joined the shared discovery topic') + await Promise.all([alice._swarm.dht.fullyBootstrapped(), bob._swarm.dht.fullyBootstrapped()]) + console.log('both Cabal Core DHTs bootstrapped') + await Promise.all([alice._swarm.flush(), bob._swarm.flush()]) + await Promise.all([alice._swarm.__cabalDiscovery.flushed(), bob._swarm.__cabalDiscovery.flushed()]) + console.log(`Hyperswarm connections queued: ${alice._swarm.peers.size} / ${bob._swarm.peers.size}`) + await Promise.all([nick(alice, 'Alice'), nick(bob, 'Bob')]) + await Promise.all([ + publish(alice, { type: 'channel/join', content: { channel: 'default' } }), + publish(bob, { type: 'channel/join', content: { channel: 'default' } }) + ]) + setTimeout(() => console.log('DHT state', JSON.stringify({ + bootstrap: bootstrapper.toArray(), + bootstrapRequests, + bootstrapStats: bootstrapper.stats.requests, + configuredBootstraps: [alice._swarm.dht.bootstrapNodes, bob._swarm.dht.bootstrapNodes], + alice: alice._swarm.dht.toArray(), bob: bob._swarm.dht.toArray(), + queries: [alice._swarm.dht.stats.queries, bob._swarm.dht.stats.queries], + requests: [alice._swarm.dht.stats.requests, bob._swarm.dht.stats.requests], + commands: [alice._swarm.dht.stats.commands, bob._swarm.dht.stats.commands], + announcedPeers: [Array.from(alice._swarm.__cabalDiscovery.discovery._discovered), + Array.from(bob._swarm.__cabalDiscovery.discovery._discovered)], + aliceConnections: alice._swarm.peers.size, bobConnections: bob._swarm.peers.size, + aliceConnecting: alice._swarm.connecting, bobConnecting: bob._swarm.connecting + })), 10000).unref() + await Promise.all([ + waitFor(() => events.alice.find(event => event.kind === 'peer' && event.key === bobKey), 'Alice discovers Bob over Hyperswarm', 20000), + waitFor(() => events.bob.find(event => event.kind === 'peer' && event.key === aliceKey), 'Bob discovers Alice over Hyperswarm', 20000) + ]) + await publish(alice, { type: 'chat/text', content: { channel: 'default', text: 'JS-to-Cable bridge path A' } }) + await waitFor(() => events.bob.find(event => event.kind === 'message' && event.message.value.content.text === 'JS-to-Cable bridge path A'), 'Alice-to-Bob Cabal feed replication') + await publish(bob, { type: 'chat/text', content: { channel: 'default', text: 'JS-to-Cable bridge path B' } }) + await waitFor(() => events.alice.find(event => event.kind === 'message' && event.message.value.content.text === 'JS-to-Cable bridge path B'), 'Bob-to-Alice Cabal feed replication') + console.log('Cabal Core Hyperswarm discovery and bidirectional Hypercore chat: PASS') +} + +main().catch(err => { console.error(err.stack || err); process.exitCode = 1 }).finally(async () => { + for (const cabal of clients) if (cabal._swarm && cabal._swarm.__markShutdown) cabal._swarm.__markShutdown() + for (const cabal of clients) { + if (cabal._swarm && cabal._swarm.__shutdown) { try { await cabal._swarm.__shutdown() } catch (_) {} } + try { await cabal.close() } catch (_) {} + } + if (bootstrapper) { try { await bootstrapper.destroy() } catch (_) {} } + const resolved = path.resolve(tempRoot) + if (resolved.startsWith(path.resolve(os.tmpdir()) + path.sep) && path.basename(resolved).startsWith('cabal-core-hyperswarm-')) + fs.rmSync(resolved, { recursive: true, force: true }) +}) diff --git a/tests/interop/cabal/cable_bootstrap.js b/tests/interop/cabal/cable_bootstrap.js new file mode 100644 index 0000000..02b98cf --- /dev/null +++ b/tests/interop/cabal/cable_bootstrap.js @@ -0,0 +1,33 @@ +'use strict' + +// Run a public Cabal-compatible HyperDHT bootstrap node in the foreground. +// Put this under a service manager and advertise the reachable IPv4 host/UDP port. +const { loadCabalCore } = require('./cable_hyperswarm') + +async function main () { + const host = process.argv[2] + const port = Number(process.argv[3]) + if (!host || !Number.isInteger(port) || port < 1 || port > 65535) { + throw new Error('usage: cable bootstrap serve ') + } + const { requireCabal } = loadCabalCore() + const DHT = requireCabal('hyperdht') + const node = DHT.bootstrapper(port, host) + await node.ready() + const address = node.address() + process.stdout.write('Cabal Hyperswarm bootstrap serving ' + host + ':' + address.port + + ' (bound ' + address.host + ':' + address.port + ')\n') + const close = async () => { + try { await node.destroy() } finally { process.exit(0) } + } + process.once('SIGINT', close) + process.once('SIGTERM', close) +} + +if (require.main === module) main().catch(err => { + process.stderr.write((err && err.stack) || String(err)) + process.stderr.write('\n') + process.exitCode = 1 +}) + +module.exports = { main } diff --git a/tests/interop/cabal/cable_cabal_hyperswarm_live.js b/tests/interop/cabal/cable_cabal_hyperswarm_live.js new file mode 100644 index 0000000..36b61e7 --- /dev/null +++ b/tests/interop/cabal/cable_cabal_hyperswarm_live.js @@ -0,0 +1,78 @@ +'use strict' + +// Live Cabal Core/Hyperswarm smoke test for the same S-expression IPC used by +// cable-thang. Run with `node tests/interop/cabal/cable_cabal_hyperswarm_live.js`. +const { spawn } = require('child_process') +const crypto = require('crypto') +const fs = require('fs') +const os = require('os') +const path = require('path') +const readline = require('readline') + +const root = path.resolve(__dirname, '../..') +const bridgePath = path.join(__dirname, 'cable_hyperswarm.js') +const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'dat-gpt-cabal-live-')) +const room = crypto.randomBytes(32).toString('hex') +const peers = [] + +function startPeer (name) { + const child = spawn(process.execPath, [bridgePath], { cwd: root, stdio: ['pipe', 'pipe', 'inherit'] }) + const peer = { name, child, lines: [], error: null, rl: readline.createInterface({ input: child.stdout }) } + peer.rl.on('line', line => { peer.lines.push(line); process.stdout.write(name + ' ' + line + '\n') }) + child.on('error', err => { peer.error = err }) + child.on('exit', (code, signal) => { if (code && code !== 0) peer.error = new Error(`${name} exited ${code} (${signal || 'no signal'})`) }) + peers.push(peer) + return peer +} + +function send (peer, expression) { + if (!peer.child.stdin.write(expression + '\n')) throw new Error(`${peer.name} bridge stdin closed`) +} + +async function waitFor (peer, predicate, description, timeout = 90000) { + const start = Date.now() + while (Date.now() - start < timeout) { + if (peer.error) throw peer.error + const found = peer.lines.find(predicate) + if (found) return found + await new Promise(resolve => setTimeout(resolve, 100)) + } + throw new Error(`timeout waiting for ${description}; ${peer.name} last output: ${peer.lines.slice(-5).join(' | ')}`) +} + +function startExpression (peer, nick) { + const storage = path.join(tempRoot, peer.name) + return `(start ${JSON.stringify(room)} ${JSON.stringify(storage)} ${JSON.stringify(nick)} "default")` +} + +async function main () { + const alice = startPeer('alice') + const bob = startPeer('bob') + send(alice, startExpression(alice, 'Alice')) + send(bob, startExpression(bob, 'Bob')) + await Promise.all([waitFor(alice, line => line.startsWith('(event ready '), 'Alice Cabal feed'), + waitFor(bob, line => line.startsWith('(event ready '), 'Bob Cabal feed')]) + await Promise.all([waitFor(alice, line => line.startsWith('(event peer-up '), 'Alice Hyperswarm peer discovery'), + waitFor(bob, line => line.startsWith('(event peer-up '), 'Bob Hyperswarm peer discovery')]) + + send(alice, `(send "default" "Cable bridge says hello")`) + await waitFor(bob, line => line.includes('Cable bridge says hello'), 'Alice-to-Bob Cabal message') + send(bob, `(send "default" "Cabal bridge replies")`) + await waitFor(alice, line => line.includes('Cabal bridge replies'), 'Bob-to-Alice Cabal message') + console.log('Cabal Core Hyperswarm discovery and bidirectional chat: PASS') +} + +main().catch(err => { console.error(err.stack || err); process.exitCode = 1 }).finally(async () => { + for (const peer of peers) { + try { send(peer, '(close)') } catch (_) {} + peer.child.stdin.end() + } + await Promise.all(peers.map(peer => new Promise(resolve => { + if (peer.child.exitCode !== null) return resolve() + const timer = setTimeout(() => { peer.child.kill('SIGTERM'); resolve() }, 5000) + peer.child.once('exit', () => { clearTimeout(timer); resolve() }) + }))) + const resolvedRoot = path.resolve(tempRoot) + if (resolvedRoot.startsWith(path.resolve(os.tmpdir()) + path.sep) && path.basename(resolvedRoot).startsWith('dat-gpt-cabal-live-')) + fs.rmSync(resolvedRoot, { recursive: true, force: true }) +}) diff --git a/tests/interop/cabal/cable_hyperswarm.c b/tests/interop/cabal/cable_hyperswarm.c new file mode 100644 index 0000000..9de2aa2 --- /dev/null +++ b/tests/interop/cabal/cable_hyperswarm.c @@ -0,0 +1,226 @@ +#ifdef _WIN32 +#include "cable_hyperswarm_win.inc" +#else +#define _POSIX_C_SOURCE 200809L +#include "cable_hyperswarm.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cable_media.h" + +static const char *find_bridge_script(char *path, size_t capacity) +{ + const char *configured = getenv("CABLE_HYPERSWARM_BRIDGE"); + static const char *candidates[] = { + "src/cable_hyperswarm.js", "./cable_hyperswarm.js", + "/usr/local/share/datgpt/cable_hyperswarm.js", + "/usr/share/datgpt/cable_hyperswarm.js" + }; + size_t i; + if (configured && *configured) { + if (strlen(configured) >= capacity) { errno = ENAMETOOLONG; return NULL; } + strcpy(path, configured); + return access(path, R_OK) == 0 ? path : NULL; + } + for (i = 0; i < sizeof(candidates) / sizeof(candidates[0]); i++) + if (access(candidates[i], R_OK) == 0) { + if (strlen(candidates[i]) >= capacity) { errno = ENAMETOOLONG; return NULL; } + strcpy(path, candidates[i]); + return path; + } + errno = ENOENT; + return NULL; +} + +static int send_form(cable_hyperswarm_t *bridge, const char *verb, + const char *a, const char *b, const char *c, const char *d) +{ + s7_scheme *sc; + s7_pointer port, form; + const char *serialized; + size_t length, offset = 0; + const char *values[4] = {a, b, c, d}; + s7_pointer args[5]; + int i; + if (!bridge || !bridge->scheme.scheme || bridge->input_fd < 0 || !verb) { errno = EINVAL; return -1; } + sc = bridge->scheme.scheme; + args[0] = s7_make_symbol(sc, verb); + length = 1; + for (i = 0; i < 4 && values[i]; i++) args[length++] = s7_make_string(sc, values[i]); + form = s7_nil(sc); + while (length) form = s7_cons(sc, args[--length], form); + port = s7_open_output_string(sc); + if (!port) { errno = ENOMEM; return -1; } + (void)s7_write(sc, form, port); + serialized = s7_get_output_string(sc, port); + if (!serialized) { errno = EIO; return -1; } + length = strlen(serialized); + while (offset < length) { + ssize_t written = write(bridge->input_fd, serialized + offset, length - offset); + if (written < 0 && errno == EINTR) continue; + if (written <= 0) return -1; + offset += (size_t)written; + } + return write(bridge->input_fd, "\n", 1) == 1 ? 0 : -1; +} + +static int parse_event_line(cable_hyperswarm_t *bridge, const char *line, + size_t length, cable_hyperswarm_event_fn callback, + void *context) +{ + s7_scheme *sc = bridge->scheme.scheme; + s7_pointer parser, event; + if (length > 8192) { errno = EMSGSIZE; return -1; } + { + char *terminated = malloc(length + 1); + if (!terminated) return -1; + memcpy(terminated, line, length); terminated[length] = '\0'; + parser = s7_name_to_value(sc, "cable:parse-bridge-event"); + event = s7_call(sc, parser, s7_list(sc, 1, s7_make_string(sc, terminated))); + free(terminated); + } + if (!event || s7_is_eq(event, s7_f(sc))) { errno = EPROTO; return -1; } + callback(context, event); + return 0; +} + +int cable_hyperswarm_start(cable_hyperswarm_t *bridge, const char *room_key, + const char *storage, const char *nick, + const char *channel) +{ + int child_in[2] = {-1, -1}, child_out[2] = {-1, -1}; + char script[2048]; + const char *node = getenv("CABLE_NODE"); + pid_t pid; + if (!bridge || !room_key || !storage || !nick || !channel) { errno = EINVAL; return -1; } + memset(bridge, 0, sizeof(*bridge)); bridge->input_fd = -1; bridge->output_fd = -1; + if (!find_bridge_script(script, sizeof(script))) return -1; + if (!node || !*node) node = "node"; + if (dat_scheme_runtime_init(&bridge->scheme)) { errno = EINVAL; return -1; } + { + char scheme_file[2048]; + const char *dir = getenv("DAT_SCHEME_DIR"); + int n; + if (!dir || !*dir) dir = access("scheme/cable-room.scm", R_OK) == 0 ? "scheme" : "/usr/local/share/datgpt/scheme"; + n = snprintf(scheme_file, sizeof(scheme_file), "%s/cable-room.scm", dir); + if (n < 0 || (size_t)n >= sizeof(scheme_file) || !s7_load(bridge->scheme.scheme, scheme_file)) { + dat_scheme_runtime_close(&bridge->scheme); errno = ENOENT; return -1; + } + } + if (pipe(child_in) || pipe(child_out)) goto fail; + pid = fork(); + if (pid < 0) goto fail; + if (pid == 0) { + (void)signal(SIGPIPE, SIG_DFL); + (void)dup2(child_in[0], STDIN_FILENO); + (void)dup2(child_out[1], STDOUT_FILENO); + close(child_in[0]); close(child_in[1]); close(child_out[0]); close(child_out[1]); + execlp(node, node, script, (char *)NULL); + _exit(127); + } + /* A sidecar shutdown must become EPIPE here, not terminate SDL. */ + (void)signal(SIGPIPE, SIG_IGN); + close(child_in[0]); child_in[0] = -1; + close(child_out[1]); child_out[1] = -1; + bridge->pid = pid; bridge->input_fd = child_in[1]; bridge->output_fd = child_out[0]; + if (fcntl(bridge->output_fd, F_SETFL, fcntl(bridge->output_fd, F_GETFL) | O_NONBLOCK) < 0 || + send_form(bridge, "start", room_key, storage, nick, channel)) goto fail; + return 0; +fail: + { + int saved = errno; + if (child_in[0] >= 0) close(child_in[0]); + if (child_in[1] >= 0) close(child_in[1]); + if (child_out[0] >= 0) close(child_out[0]); + if (child_out[1] >= 0) close(child_out[1]); + if (bridge->pid > 0) { kill(bridge->pid, SIGTERM); (void)waitpid(bridge->pid, NULL, 0); } + bridge->pid = 0; bridge->input_fd = -1; bridge->output_fd = -1; + dat_scheme_runtime_close(&bridge->scheme); errno = saved; return -1; + } +} + +int cable_hyperswarm_send(cable_hyperswarm_t *bridge, const char *channel, const char *text) +{ return send_form(bridge, "send", channel, text, NULL, NULL); } +int cable_hyperswarm_nick(cable_hyperswarm_t *bridge, const char *nick) +{ return send_form(bridge, "nick", nick, NULL, NULL, NULL); } +int cable_hyperswarm_join(cable_hyperswarm_t *bridge, const char *channel) +{ return send_form(bridge, "join", channel, NULL, NULL, NULL); } + +int cable_hyperswarm_live(cable_hyperswarm_t *bridge, + const uint8_t *packet, size_t packet_length) +{ + size_t capacity; + char *encoded; + int result; + if (!packet || !packet_length || packet_length > CABLE_MEDIA_MAX_PACKET_BYTES) { + errno = EMSGSIZE; return -1; + } + capacity = sodium_base64_encoded_len(packet_length, sodium_base64_VARIANT_ORIGINAL); + encoded = (char *)malloc(capacity); + if (!encoded) return -1; + if (!sodium_bin2base64(encoded, capacity, packet, packet_length, + sodium_base64_VARIANT_ORIGINAL)) { + free(encoded); errno = EINVAL; return -1; + } + result = send_form(bridge, "live", encoded, NULL, NULL, NULL); + free(encoded); + return result; +} + +int cable_hyperswarm_poll(cable_hyperswarm_t *bridge, + cable_hyperswarm_event_fn callback, void *context) +{ + char incoming[4096]; + ssize_t got; + if (!bridge || bridge->output_fd < 0 || !callback) { errno = EINVAL; return -1; } + while ((got = read(bridge->output_fd, incoming, sizeof(incoming))) > 0) { + size_t i; + for (i = 0; i < (size_t)got; i++) { + if (incoming[i] == '\n') { + if (parse_event_line(bridge, bridge->pending, bridge->pending_length, callback, context)) return -1; + bridge->pending_length = 0; + } else { + if (bridge->pending_length + 1 >= sizeof(bridge->pending)) { errno = EMSGSIZE; return -1; } + bridge->pending[bridge->pending_length++] = incoming[i]; + } + } + } + if (got < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) return -1; + if (bridge->pid > 0) { + int status; + pid_t ended = waitpid(bridge->pid, &status, WNOHANG); + if (ended == bridge->pid) { bridge->pid = 0; errno = EPIPE; return -1; } + } + return 0; +} + +void cable_hyperswarm_stop(cable_hyperswarm_t *bridge) +{ + if (!bridge) return; + if (bridge->input_fd >= 0) { (void)send_form(bridge, "close", NULL, NULL, NULL, NULL); close(bridge->input_fd); } + if (bridge->output_fd >= 0) close(bridge->output_fd); + if (bridge->pid > 0) { + int status; + unsigned tries; + for (tries = 0; tries < 20; tries++) { + pid_t ended = waitpid(bridge->pid, &status, WNOHANG); + if (ended == bridge->pid || (ended < 0 && errno == ECHILD)) break; + { + struct timespec delay = {0, 10000000L}; + (void)nanosleep(&delay, NULL); + } + } + if (tries == 20) { kill(bridge->pid, SIGTERM); (void)waitpid(bridge->pid, &status, 0); } + } + dat_scheme_runtime_close(&bridge->scheme); + memset(bridge, 0, sizeof(*bridge)); bridge->input_fd = -1; bridge->output_fd = -1; +} +#endif diff --git a/tests/interop/cabal/cable_hyperswarm.h b/tests/interop/cabal/cable_hyperswarm.h new file mode 100644 index 0000000..85f8e0a --- /dev/null +++ b/tests/interop/cabal/cable_hyperswarm.h @@ -0,0 +1,42 @@ +#ifndef CABLE_HYPERSWARM_H +#define CABLE_HYPERSWARM_H + +#include "dat_s7.h" +#include "s7.h" +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +typedef struct cable_hyperswarm { +#ifdef _WIN32 + HANDLE process_handle; +#else + pid_t pid; +#endif + int input_fd; + int output_fd; + char pending[16384]; + size_t pending_length; + dat_scheme_runtime_t scheme; +} cable_hyperswarm_t; + +typedef void (*cable_hyperswarm_event_fn)(void *context, s7_pointer event); + +int cable_hyperswarm_start(cable_hyperswarm_t *bridge, const char *room_key, + const char *storage, const char *nick, + const char *channel); +int cable_hyperswarm_send(cable_hyperswarm_t *bridge, const char *channel, + const char *text); +int cable_hyperswarm_nick(cable_hyperswarm_t *bridge, const char *nick); +int cable_hyperswarm_join(cable_hyperswarm_t *bridge, const char *channel); +/* Carry one bounded, binary-safe live media packet over Cable's room peers. */ +int cable_hyperswarm_live(cable_hyperswarm_t *bridge, + const uint8_t *packet, size_t packet_length); +int cable_hyperswarm_poll(cable_hyperswarm_t *bridge, + cable_hyperswarm_event_fn callback, void *context); +void cable_hyperswarm_stop(cable_hyperswarm_t *bridge); + +#endif diff --git a/tests/interop/cabal/cable_hyperswarm.js b/tests/interop/cabal/cable_hyperswarm.js new file mode 100644 index 0000000..077aa98 --- /dev/null +++ b/tests/interop/cabal/cable_hyperswarm.js @@ -0,0 +1,362 @@ +'use strict' + +// Cable's SDL-facing bridge speaks S-expressions on stdin/stdout. Cabal Core +// owns the network boundary and continues to exchange its canonical JSON +// Hypercore blocks over Hyperswarm. +const fs = require('fs') +const { createRequire } = require('module') +const os = require('os') +const path = require('path') +const readline = require('readline') + +function loadCabalCore () { + const configured = process.env.CABLE_CABAL_CORE || process.env.CABAL_JS_MODULE + if (configured) { + const entry = require.resolve(configured) + const requireCabal = createRequire(entry) + return { Cabal: requireCabal(entry), requireCabal } + } + try { + const entry = require.resolve('cabal-core') + const requireCabal = createRequire(entry) + return { Cabal: requireCabal(entry), requireCabal } + } catch (_) {} + const home = os.homedir() + const candidates = [ + path.join(home, 'AppData', 'Roaming', 'npm', 'node_modules', 'cabal-cli', 'node_modules', 'cabal-core'), + path.join(home, '.npm-global', 'lib', 'node_modules', 'cabal-cli', 'node_modules', 'cabal-core'), + '/usr/local/lib/node_modules/cabal-cli/node_modules/cabal-core', + '/usr/lib/node_modules/cabal-cli/node_modules/cabal-core' + ] + for (const candidate of candidates) { + if (fs.existsSync(path.join(candidate, 'index.js'))) { + const entry = path.join(candidate, 'index.js') + const requireCabal = createRequire(entry) + return { Cabal: requireCabal(entry), requireCabal } + } + } + throw new Error('Cabal Core not found; install cabal-cli or set CABLE_CABAL_CORE') +} + +function parseBootstrap (requireCabal) { + const setting = process.env.CABLE_BOOTSTRAP || '' + const configPath = path.join(os.homedir(), '.config', 'dat-gpt', 'cable-bootstrap') + let saved = '' + try { saved = fs.readFileSync(configPath, 'utf8') } catch (err) { + if (err.code !== 'ENOENT') throw err + } + const endpoints = (setting + '\n' + saved).split(/[;,\s]+/).filter(Boolean) + if (!endpoints.length) return requireCabal('./bootstrap_nodes') + return Array.from(new Set(endpoints)) +} + +// Mirrors Cabal Core's swarm.js replication and peer-id extension, while +// allowing the same DHT bootstrap list to be selected by the local client. +async function startHyperswarm (cabal, networkOptions = {}) { + const loaded = loadCabalCore() + const DHT = loaded.requireCabal('hyperdht') + const Swarm = loaded.requireCabal('hyperswarm') + const crypto = loaded.requireCabal('hypercore-crypto') + const pump = loaded.requireCabal('pump') + const localKey = await new Promise((resolve, reject) => + cabal.getLocalKey((err, key) => err ? reject(err) : resolve(key))) + const dhtOptions = { bootstrap: parseBootstrap(loaded.requireCabal) } + const host = networkOptions.host || process.env.CABLE_DHT_HOST + const port = networkOptions.port || process.env.CABLE_DHT_PORT + if (host) dhtOptions.host = host + if (port) { + const numericPort = Number(port) + if (!Number.isInteger(numericPort) || numericPort < 1 || numericPort > 65535) + throw new Error('CABLE_DHT_PORT must be an integer from 1 to 65535') + dhtOptions.port = numericPort + } + const advertiseHost = networkOptions.advertiseHost || process.env.CABLE_DHT_ADVERTISE_HOST + if (advertiseHost) dhtOptions.firewalled = false + const dht = new DHT(dhtOptions) + if (advertiseHost && Number.isInteger(dhtOptions.port) && dht._nat && typeof dht._nat.add === 'function') + dht._nat.add(advertiseHost, dhtOptions.port) + const swarm = new Swarm({ dht }) + mediaExtensions.clear() + mediaSeen.clear() + swarm.on('error', err => { + if (process.env.CABLE_DEBUG) process.stderr.write('Cable Hyperswarm error: ' + err.message + '\n') + }) + const discoveryKey = crypto.discoveryKey(Buffer.from(cabal.key, 'hex')) + if (process.env.CABLE_DEBUG) process.stderr.write('Cable swarm pid=' + process.pid + ' room=' + cabal.key + + ' discovery=' + discoveryKey.toString('hex') + '\n') + const discovery = swarm.join(discoveryKey, { server: true, client: true }) + let shuttingDown = false + swarm.__cabalDiscovery = discovery + swarm.__markShutdown = () => { shuttingDown = true } + swarm.__shutdown = async function () { + swarm.__markShutdown() + await discovery.destroy() + await swarm.destroy() + } + swarm.on('connection', (socket, info) => { + if (process.env.CABLE_DEBUG) process.stderr.write('Cable Hyperswarm connection opened pid=' + process.pid + + ' client=' + Boolean(info.client) + ' peer=' + (info.publicKey ? info.publicKey.toString('hex') : 'unknown') + '\n') + socket.on('error', err => { + if (process.env.CABLE_DEBUG) process.stderr.write('Cable Hyperswarm socket error: ' + err.message + '\n') + }) + let remoteKey + const replication = cabal.replicate(info.client) + if (process.env.CABLE_DEBUG) replication.on('error', err => process.stderr.write('Cable Hypercore replication error: ' + err.message + '\n')) + pump(socket, replication, socket, err => { + if (err && !shuttingDown && err.message !== 'Readable stream closed' && err.code !== 'ECONNRESET') + process.stderr.write('Cable replication: ' + err.message + '\n') + if (remoteKey) cabal._removeConnection(remoteKey) + }) + const extension = replication.registerExtension('peer-id', { + encoding: 'json', + onmessage (message) { + if (remoteKey || !message || !message.id) return + const key = Buffer.from(message.id, 'hex') + if (key.length !== 32) return + remoteKey = message.id + cabal._addConnection(remoteKey) + if (process.env.CABLE_DEBUG) process.stderr.write('Cable Cabal peer id ' + remoteKey + '\n') + } + }) + const liveExtension = replication.registerExtension('cable-live', { + onmessage (message) { + const packet = Buffer.isBuffer(message) ? message : Buffer.from(message) + if (packet.length > 4096) return + relayMediaPacket(packet, liveExtension) + if (packet.length >= 42 && packet.toString('ascii', 0, 4) === 'CBLM') + emit('live', remoteKey || '', packet.toString('base64')) + } + }) + mediaExtensions.add(liveExtension) + socket.once('close', () => mediaExtensions.delete(liveExtension)) + extension.send({ id: localKey }) + if (process.env.CABLE_DEBUG) process.stderr.write('Cable local peer id sent: ' + localKey + '\n') + }) + cabal._swarm = swarm + if (networkOptions.waitForBootstrap || process.env.CABLE_WAIT_DHT === '1') { + await dht.fullyBootstrapped() + await swarm.flush() + } + if (!(await discovery.flushed())) { + await swarm.__shutdown() + throw new Error('Hyperswarm topic discovery did not complete') + } + return swarm +} + +function quote (value) { + const text = String(value == null ? '' : value) + let quoted = '"' + for (const character of text) { + switch (character) { + case '"': quoted += '\\"'; break + case '\\': quoted += '\\\\'; break + case '\b': quoted += '\\b'; break + case '\f': quoted += '\\f'; break + case '\n': quoted += '\\n'; break + case '\r': quoted += '\\r'; break + case '\t': quoted += '\\t'; break + default: quoted += character + } + } + return quoted + '"' +} + +function emit (kind, ...fields) { + process.stdout.write('(event ' + kind + (fields.length ? ' ' + fields.map(value => + typeof value === 'number' ? String(value) : quote(value)).join(' ') : '') + ')\n') +} + +function parse (source) { + let at = 0 + function white () { while (/\s/.test(source[at] || '')) at++ } + function value () { + white() + if (source[at] === '(') { + at++ + const list = [] + while (true) { + white() + if (source[at] === ')') { at++; return list } + if (at >= source.length) throw new Error('unterminated S-expression') + list.push(value()) + } + } + if (source[at] === '"') { + at++ + let text = '' + while (at < source.length) { + const c = source[at++] + if (c === '"') return text + if (c !== '\\') { text += c; continue } + if (at >= source.length) throw new Error('incomplete S-expression escape') + const escape = source[at++] + switch (escape) { + case '"': text += '"'; break + case '\\': text += '\\'; break + case 'b': text += '\b'; break + case 'f': text += '\f'; break + case 'n': text += '\n'; break + case 'r': text += '\r'; break + case 't': text += '\t'; break + default: throw new Error('unsupported S-expression escape: \\' + escape) + } + } + throw new Error('unterminated S-expression string') + } + const begin = at + while (at < source.length && !/[\s()]/.test(source[at])) at++ + if (begin === at) throw new Error('expected S-expression atom') + const atom = source.slice(begin, at) + if (/^-?[0-9]+$/.test(atom)) return Number(atom) + return atom + } + const result = value() + white() + if (at !== source.length) throw new Error('trailing S-expression data') + return result +} + +let cabal = null +let localKey = null +let starting = false +const names = new Map() +const pending = [] +const mediaExtensions = new Set() +const mediaSeen = new Map() + +function markMediaPacket (packet) { + if (!Buffer.isBuffer(packet) || packet.length < 42 || packet.length > 4096 || + packet.toString('ascii', 0, 4) !== 'CBLM' || packet[4] !== 1 || + packet[5] < 1 || packet[5] > 6 || packet.readUInt16BE(40) !== packet.length - 42) return false + const id = packet.subarray(8, 32).toString('hex') + if (mediaSeen.has(id)) return false + mediaSeen.set(id, true) + if (mediaSeen.size > 2048) mediaSeen.delete(mediaSeen.keys().next().value) + return true +} + +function relayMediaPacket (packet, source) { + if (!markMediaPacket(packet)) return + for (const extension of mediaExtensions) { + if (extension === source) continue + try { extension.send(packet) } catch (_) {} + } +} + +function channelName (msg) { + return msg && msg.value && msg.value.content && msg.value.content.channel +} + +async function start (roomKey, storage, nick, channel) { + if (starting || cabal) throw new Error('Cabal room already started') + starting = true + try { + if (process.env.CABLE_DEBUG) process.stderr.write('Cable Core storage: ' + storage + '\n') + const { Cabal } = loadCabalCore() + fs.mkdirSync(storage, { recursive: true }) + cabal = Cabal(storage, roomKey) + await new Promise((resolve, reject) => cabal.ready(err => err ? reject(err) : resolve())) + localKey = await new Promise((resolve, reject) => cabal.getLocalKey((err, key) => err ? reject(err) : resolve(key))) + + cabal.on('peer-added', key => emit('peer-up', key)) + cabal.on('peer-dropped', key => emit('peer-down', key)) + cabal.kcore.api.users.events.on('update', (key, msg) => { + if (key === localKey) return + const name = msg && msg.value && msg.value.content && msg.value.content.name + if (typeof name === 'string') names.set(key, name) + emit('nick', key, names.get(key) || key.slice(0, 8)) + }) + cabal.kcore.api.messages.events.on('message', msg => { + const val = msg && msg.value + const content = val && val.content + const key = msg && msg.key + if (!val || !content || key === localKey || !key) return + if (val.type === 'chat/text' && typeof content.channel === 'string' && typeof content.text === 'string') { + emit('message', key, names.get(key) || key.slice(0, 8), content.channel, + content.text, val.timestamp || 0) + } else if (val.type === 'channel/join' && typeof content.channel === 'string') { + emit('joined', key, names.get(key) || key.slice(0, 8), content.channel) + } + }) + + await startHyperswarm(cabal) + emit('ready', localKey) + await new Promise((resolve, reject) => cabal.publishNick(nick, err => err ? reject(err) : resolve())) + await publish({ type: 'channel/join', content: { channel } }) + emit('joined-local', channel) + for (const command of pending.splice(0)) await handle(command) + } finally { + starting = false + } +} + +function publish (message) { + return new Promise((resolve, reject) => cabal.publish(message, err => err ? reject(err) : resolve())) +} + +async function handle (command) { + if (!Array.isArray(command) || !command.length) throw new Error('expected a command list') + const [verb, ...args] = command + if (verb === 'start') return start(...args) + if (verb === 'close') { + if (cabal && cabal._swarm && cabal._swarm.__shutdown) await cabal._swarm.__shutdown() + if (cabal) await cabal.close() + cabal = null + localKey = null + emit('closed') + return + } + if (!cabal) { + if (starting) { pending.push(command); return } + throw new Error('Cabal room is not started') + } + if (verb === 'send') { + const [channel, text] = args + if (typeof channel !== 'string' || typeof text !== 'string') throw new Error('send expects channel and text strings') + return publish({ type: 'chat/text', content: { channel, text } }) + } + if (verb === 'nick') { + const [nick] = args + if (typeof nick !== 'string') throw new Error('nick expects a string') + return new Promise((resolve, reject) => cabal.publishNick(nick, err => err ? reject(err) : resolve())) + } + if (verb === 'join') { + const [channel] = args + if (typeof channel !== 'string') throw new Error('join expects a channel string') + return publish({ type: 'channel/join', content: { channel } }) + } + if (verb === 'live') { + const [encoded] = args + if (typeof encoded !== 'string' || encoded.length > 5500) + throw new Error('live expects one bounded base64 media packet') + const packet = Buffer.from(encoded, 'base64') + if (!markMediaPacket(packet)) throw new Error('invalid or repeated Cable live packet') + for (const extension of mediaExtensions) { + try { extension.send(packet) } catch (_) {} + } + return + } + throw new Error('unknown Cable bridge command: ' + verb) +} + +module.exports = { startHyperswarm, loadCabalCore, quote, parse } + +if (require.main === module) { + process.stdout.on('error', err => { + if (err && err.code === 'EPIPE') process.exit(0) + throw err + }) + const input = readline.createInterface({ input: process.stdin, crlfDelay: Infinity }) + input.on('line', line => { + if (!line.trim()) return + let result + try { result = handle(parse(line)) } catch (err) { emit('error', err.message); return } + Promise.resolve(result).catch(err => emit('error', err && err.message ? err.message : String(err))) + }) + input.on('close', async () => { + if (cabal && cabal._swarm && cabal._swarm.__shutdown) { try { await cabal._swarm.__shutdown() } catch (_) {} } + if (cabal) { try { await cabal.close() } catch (_) {} } + process.exit(0) + }) +} diff --git a/tests/interop/cabal/cable_hyperswarm_win.inc b/tests/interop/cabal/cable_hyperswarm_win.inc new file mode 100644 index 0000000..8813089 --- /dev/null +++ b/tests/interop/cabal/cable_hyperswarm_win.inc @@ -0,0 +1,226 @@ +#define _POSIX_C_SOURCE 200809L +#include "cable_hyperswarm.h" +#include +#include +#include +#include +#include +#include +#include +#include "cable_media.h" + +static const char *find_bridge_script(char *path, size_t capacity) +{ + const char *configured = getenv("CABLE_HYPERSWARM_BRIDGE"); + static const char *candidates[] = {"src/cable_hyperswarm.js", ".\\cable_hyperswarm.js", "cable_hyperswarm.js"}; + char module[MAX_PATH * 4], sibling[MAX_PATH * 4]; + size_t i; + if (configured && *configured) { + if (strlen(configured) >= capacity) { errno = ENAMETOOLONG; return NULL; } + strcpy(path, configured); + return _access(path, 4) == 0 ? path : NULL; + } + for (i = 0; i < sizeof(candidates) / sizeof(candidates[0]); i++) + if (_access(candidates[i], 4) == 0) { + if (strlen(candidates[i]) >= capacity) { errno = ENAMETOOLONG; return NULL; } + strcpy(path, candidates[i]); + return path; + } + { + DWORD length = GetModuleFileNameA(NULL, module, (DWORD)sizeof(module)); + char *separator; + int n; + if (length && length < sizeof(module) && (separator = strrchr(module, '\\')) != NULL) { + *separator = '\0'; + n = snprintf(sibling, sizeof(sibling), "%s\\cable_hyperswarm.js", module); + if (n >= 0 && (size_t)n < sizeof(sibling) && _access(sibling, 4) == 0 && + strlen(sibling) < capacity) { strcpy(path, sibling); return path; } + } + } + errno = ENOENT; + return NULL; +} + +static int send_form(cable_hyperswarm_t *bridge, const char *verb, + const char *a, const char *b, const char *c, const char *d) +{ + s7_scheme *sc; + s7_pointer port, form, args[5]; + const char *serialized; + const char *values[4] = {a,b,c,d}; + size_t length, offset = 0; + int count = 1, i; + if (!bridge || !bridge->scheme.scheme || bridge->input_fd < 0 || !verb) { errno = EINVAL; return -1; } + sc = bridge->scheme.scheme; + args[0] = s7_make_symbol(sc, verb); + for (i = 0; i < 4 && values[i]; i++) args[count++] = s7_make_string(sc, values[i]); + form = s7_nil(sc); + while (count) form = s7_cons(sc, args[--count], form); + port = s7_open_output_string(sc); + if (!port) { errno = ENOMEM; return -1; } + (void)s7_write(sc, form, port); + serialized = s7_get_output_string(sc, port); + if (!serialized) { errno = EIO; return -1; } + length = strlen(serialized); + while (offset < length) { + unsigned int chunk = length - offset > 0x7fffffffu ? 0x7fffffffu : (unsigned int)(length - offset); + int written = _write(bridge->input_fd, serialized + offset, chunk); + if (written <= 0) { errno = EPIPE; return -1; } + offset += (size_t)written; + } + return _write(bridge->input_fd, "\n", 1) == 1 ? 0 : -1; +} + +static int parse_event_line(cable_hyperswarm_t *bridge, const char *line, + size_t length, cable_hyperswarm_event_fn callback, + void *context) +{ + s7_scheme *sc = bridge->scheme.scheme; + s7_pointer parser, event; + char *terminated; + if (length > 8192) { errno = EMSGSIZE; return -1; } + terminated = (char *)malloc(length + 1); + if (!terminated) return -1; + memcpy(terminated, line, length); terminated[length] = '\0'; + parser = s7_name_to_value(sc, "cable:parse-bridge-event"); + event = s7_call(sc, parser, s7_list(sc, 1, s7_make_string(sc, terminated))); + free(terminated); + if (!event || s7_is_eq(event, s7_f(sc))) { errno = EPROTO; return -1; } + callback(context, event); + return 0; +} + +int cable_hyperswarm_start(cable_hyperswarm_t *bridge, const char *room_key, + const char *storage, const char *nick, const char *channel) +{ + SECURITY_ATTRIBUTES security; + HANDLE child_in = NULL, parent_in = NULL, parent_out = NULL, child_out = NULL; + STARTUPINFOA startup; + PROCESS_INFORMATION process; + char script[2048], command[4096]; + const char *node = getenv("CABLE_NODE"); + const char *dir = getenv("DAT_SCHEME_DIR"); + char scheme_file[2048]; + char module[MAX_PATH * 4]; + int n; + if (!bridge || !room_key || !storage || !nick || !channel) { errno = EINVAL; return -1; } + memset(bridge, 0, sizeof(*bridge)); bridge->input_fd = -1; bridge->output_fd = -1; + if (!find_bridge_script(script, sizeof(script))) return -1; + if (!node || !*node) node = "node"; + if (dat_scheme_runtime_init(&bridge->scheme)) { errno = EINVAL; return -1; } + if (dir && *dir) n = snprintf(scheme_file, sizeof(scheme_file), "%s\\cable-room.scm", dir); + else if (_access("scheme/cable-room.scm", 4) == 0) n = snprintf(scheme_file, sizeof(scheme_file), "scheme\\cable-room.scm"); + else { + DWORD length = GetModuleFileNameA(NULL, module, (DWORD)sizeof(module)); + char *separator = length && length < sizeof(module) ? strrchr(module, '\\') : NULL; + if (separator) { *separator = '\0'; n = snprintf(scheme_file, sizeof(scheme_file), "%s\\scheme\\cable-room.scm", module); } + else n = snprintf(scheme_file, sizeof(scheme_file), "share\\datgpt\\scheme\\cable-room.scm"); + } + if (n < 0 || (size_t)n >= sizeof(scheme_file) || !s7_load(bridge->scheme.scheme, scheme_file)) { + dat_scheme_runtime_close(&bridge->scheme); errno = ENOENT; return -1; + } + security.nLength = sizeof(security); security.lpSecurityDescriptor = NULL; security.bInheritHandle = TRUE; + if (!CreatePipe(&child_in, &parent_in, &security, 0) || !CreatePipe(&parent_out, &child_out, &security, 0)) goto fail; + SetHandleInformation(parent_in, HANDLE_FLAG_INHERIT, 0); + SetHandleInformation(parent_out, HANDLE_FLAG_INHERIT, 0); + memset(&startup, 0, sizeof(startup)); startup.cb = sizeof(startup); + startup.dwFlags = STARTF_USESTDHANDLES; + startup.hStdInput = child_in; startup.hStdOutput = child_out; startup.hStdError = GetStdHandle(STD_ERROR_HANDLE); + memset(&process, 0, sizeof(process)); + n = snprintf(command, sizeof(command), "\"%s\" \"%s\"", node, script); + if (n < 0 || (size_t)n >= sizeof(command) || + !CreateProcessA(NULL, command, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &startup, &process)) { + errno = ENOENT; goto fail; + } + CloseHandle(child_in); child_in = NULL; + CloseHandle(child_out); child_out = NULL; + CloseHandle(process.hThread); + bridge->process_handle = process.hProcess; + bridge->input_fd = _open_osfhandle((intptr_t)parent_in, _O_BINARY | _O_WRONLY); parent_in = NULL; + bridge->output_fd = _open_osfhandle((intptr_t)parent_out, _O_BINARY | _O_RDONLY); parent_out = NULL; + if (bridge->input_fd < 0 || bridge->output_fd < 0) { errno = EIO; goto fail; } + if (send_form(bridge, "start", room_key, storage, nick, channel)) goto fail; + return 0; +fail: + if (child_in) CloseHandle(child_in); + if (parent_in) CloseHandle(parent_in); + if (parent_out) CloseHandle(parent_out); + if (child_out) CloseHandle(child_out); + if (bridge->input_fd >= 0) _close(bridge->input_fd); + if (bridge->output_fd >= 0) _close(bridge->output_fd); + if (bridge->process_handle) { TerminateProcess(bridge->process_handle, 1); WaitForSingleObject(bridge->process_handle, INFINITE); CloseHandle(bridge->process_handle); } + bridge->input_fd = bridge->output_fd = -1; bridge->process_handle = NULL; + dat_scheme_runtime_close(&bridge->scheme); + return -1; +} + +int cable_hyperswarm_send(cable_hyperswarm_t *bridge, const char *channel, const char *text) +{ return send_form(bridge, "send", channel, text, NULL, NULL); } +int cable_hyperswarm_nick(cable_hyperswarm_t *bridge, const char *nick) +{ return send_form(bridge, "nick", nick, NULL, NULL, NULL); } +int cable_hyperswarm_join(cable_hyperswarm_t *bridge, const char *channel) +{ return send_form(bridge, "join", channel, NULL, NULL, NULL); } + +int cable_hyperswarm_live(cable_hyperswarm_t *bridge, + const uint8_t *packet, size_t packet_length) +{ + size_t capacity; + char *encoded; + int result; + if (!packet || !packet_length || packet_length > CABLE_MEDIA_MAX_PACKET_BYTES) { + errno = EMSGSIZE; return -1; + } + capacity = sodium_base64_encoded_len(packet_length, sodium_base64_VARIANT_ORIGINAL); + encoded = (char *)malloc(capacity); + if (!encoded) return -1; + if (!sodium_bin2base64(encoded, capacity, packet, packet_length, + sodium_base64_VARIANT_ORIGINAL)) { + free(encoded); errno = EINVAL; return -1; + } + result = send_form(bridge, "live", encoded, NULL, NULL, NULL); + free(encoded); + return result; +} + +int cable_hyperswarm_poll(cable_hyperswarm_t *bridge, + cable_hyperswarm_event_fn callback, void *context) +{ + char incoming[4096]; + HANDLE pipe; + if (!bridge || bridge->output_fd < 0 || !callback) { errno = EINVAL; return -1; } + pipe = (HANDLE)_get_osfhandle(bridge->output_fd); + for (;;) { + DWORD available = 0, received = 0; + size_t i; + if (!PeekNamedPipe(pipe, NULL, 0, NULL, &available, NULL)) { errno = EPIPE; return -1; } + if (!available) break; + if (!ReadFile(pipe, incoming, available < sizeof(incoming) ? available : (DWORD)sizeof(incoming), &received, NULL)) { errno = EPIPE; return -1; } + for (i = 0; i < (size_t)received; i++) { + if (incoming[i] == '\n') { + if (parse_event_line(bridge, bridge->pending, bridge->pending_length, callback, context)) return -1; + bridge->pending_length = 0; + } else { + if (bridge->pending_length + 1 >= sizeof(bridge->pending)) { errno = EMSGSIZE; return -1; } + bridge->pending[bridge->pending_length++] = incoming[i]; + } + } + } + if (bridge->process_handle && WaitForSingleObject(bridge->process_handle, 0) == WAIT_OBJECT_0) { errno = EPIPE; return -1; } + return 0; +} + +void cable_hyperswarm_stop(cable_hyperswarm_t *bridge) +{ + if (!bridge) return; + if (bridge->input_fd >= 0) { (void)send_form(bridge, "close", NULL, NULL, NULL, NULL); _close(bridge->input_fd); } + if (bridge->output_fd >= 0) _close(bridge->output_fd); + if (bridge->process_handle) { + if (WaitForSingleObject(bridge->process_handle, 200) == WAIT_TIMEOUT) { + TerminateProcess(bridge->process_handle, 1); + WaitForSingleObject(bridge->process_handle, INFINITE); + } + CloseHandle(bridge->process_handle); + } + dat_scheme_runtime_close(&bridge->scheme); + memset(bridge, 0, sizeof(*bridge)); bridge->input_fd = -1; bridge->output_fd = -1; +} diff --git a/tests/interop/cabal/cable_js_c_live.js b/tests/interop/cabal/cable_js_c_live.js new file mode 100644 index 0000000..015449e --- /dev/null +++ b/tests/interop/cabal/cable_js_c_live.js @@ -0,0 +1,184 @@ +'use strict' + +// One SDL/C Cable client and one Cabal Core JavaScript client share a local +// Hyperswarm bootstrap node, discover one another, and exchange chat records. +const assert = require('assert') +const { spawn } = require('child_process') +const crypto = require('crypto') +const dgram = require('dgram') +const fs = require('fs') +const os = require('os') +const path = require('path') +const readline = require('readline') +const { startHyperswarm, loadCabalCore } = require('./cable_hyperswarm') + +const binary = path.resolve(process.argv[2] || 'build/linux/test_cable_thang') +const { Cabal, requireCabal } = loadCabalCore() +const DHT = requireCabal('hyperdht') +const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cable-js-c-live-')) +const roomKey = crypto.randomBytes(32).toString('hex') +const address = 'cabal://' + roomKey +const cHome = path.join(root, 'c-client') +const jsHome = path.join(root, 'js-client') +const cLines = [] +const cWaiters = [] +let cClient = null +let cabal = null +let bootstrapper = null +let relay = null + +function freeUdpPort () { + return new Promise((resolve, reject) => { + const socket = dgram.createSocket('udp4') + socket.once('error', reject) + socket.bind(0, '127.0.0.1', () => { + const port = socket.address().port + socket.close(err => err ? reject(err) : resolve(port)) + }) + }) +} + +function ready (instance) { + return new Promise((resolve, reject) => instance.ready(err => err ? reject(err) : resolve())) +} + +function waitFor (predicate, description, timeoutMs = 90000) { + const started = Date.now() + return new Promise((resolve, reject) => { + const timer = setInterval(() => { + if (Date.now() - started > timeoutMs) { + clearInterval(timer) + reject(new Error('timeout waiting for ' + description)) + return + } + try { + const value = predicate() + if (value) { clearInterval(timer); resolve(value) } + } catch (err) { clearInterval(timer); reject(err) } + }, 50) + }) +} + +function cLine (line) { + cLines.push(line) + process.stdout.write('C> ' + line + '\n') + for (let i = cWaiters.length - 1; i >= 0; i--) { + if (cWaiters[i].predicate(line)) { + const waiter = cWaiters.splice(i, 1)[0] + clearTimeout(waiter.timer) + waiter.resolve(line) + } + } +} + +function waitC (predicate, description, timeoutMs = 90000) { + const existing = cLines.find(predicate) + if (existing) return Promise.resolve(existing) + return new Promise((resolve, reject) => { + const waiter = { predicate, resolve, reject, timer: null } + waiter.timer = setTimeout(() => { + const index = cWaiters.indexOf(waiter) + if (index >= 0) cWaiters.splice(index, 1) + reject(new Error('timeout waiting for C client ' + description)) + }, timeoutMs) + cWaiters.push(waiter) + }) +} + +async function main () { + fs.mkdirSync(cHome, { recursive: true }) + fs.mkdirSync(jsHome, { recursive: true }) + const port = await freeUdpPort() + const relayPort = await freeUdpPort() + const jsPort = await freeUdpPort() + const cPort = await freeUdpPort() + bootstrapper = DHT.bootstrapper(port, '127.0.0.1', { host: '127.0.0.1' }) + await bootstrapper.ready() + relay = new DHT({ bootstrap: ['127.0.0.1:' + port], host: '127.0.0.1', port: relayPort, + ephemeral: false, firewalled: false }) + await relay.ready() + const endpoint = '127.0.0.1:' + relayPort + process.env.CABLE_BOOTSTRAP = endpoint + process.env.CABLE_DHT_HOST = '127.0.0.1' + process.env.CABLE_DHT_ADVERTISE_HOST = '127.0.0.1' + process.env.CABLE_WAIT_DHT = '1' + console.log('Local Hyperswarm bootstrap listening at ' + endpoint) + + cabal = Cabal(jsHome, roomKey) + await ready(cabal) + if (process.env.CABLE_DEBUG) { + const key = await new Promise((resolve, reject) => cabal.getLocalKey((err, value) => err ? reject(err) : resolve(value))) + console.log('JS test storage: ' + jsHome) + console.log('JS Cabal user key: ' + key) + } + const jsPeers = new Set() + const jsMessages = [] + cabal.on('peer-added', key => { jsPeers.add(key); console.log('JS peer up: ' + key) }) + cabal.on('peer-dropped', key => jsPeers.delete(key)) + cabal.kcore.api.messages.events.on('message', message => { + const value = message && message.value + const content = value && value.content + if (value && value.type === 'chat/text' && content && typeof content.text === 'string') { + jsMessages.push(content.text) + console.log('JS chat: ' + content.text) + } + }) + cClient = spawn(binary, [address], { + cwd: process.cwd(), + env: { + ...process.env, + HOME: cHome, + SDL_VIDEODRIVER: 'dummy', + SDL_AUDIODRIVER: 'dummy', + CABLE_BOOTSTRAP: endpoint, + CABLE_DHT_HOST: '127.0.0.1', + CABLE_DHT_PORT: String(cPort) + }, + stdio: ['pipe', 'pipe', 'inherit'] + }) + cClient.stdin.on('error', () => {}) + cClient.once('error', err => { + for (const waiter of cWaiters) { clearTimeout(waiter.timer); waiter.reject(err) } + cWaiters.length = 0 + }) + readline.createInterface({ input: cClient.stdout }).on('line', cLine) + const cReady = await waitC(line => line.startsWith('C_READY '), 'S7-backed Cabal room startup') + const cFeedKey = cReady.slice('C_READY '.length) + await startHyperswarm(cabal, { host: '127.0.0.1', port: jsPort, + advertiseHost: '127.0.0.1', waitForBootstrap: true }) + await Promise.all([ + waitFor(() => jsPeers.size > 0, 'C client discovered by JS Cabal Core'), + waitC(line => line === 'PEER_UP', 'JS client discovered by C Cable') + ]) + const jsKey = await new Promise((resolve, reject) => + cabal.getLocalKey((err, key) => err ? reject(err) : resolve(key))) + assert.notStrictEqual(jsKey, cFeedKey, 'independent Cabal users must have distinct feed keys') + assert(jsPeers.has(cFeedKey), 'JS Cabal Core must identify the C client feed') + + await new Promise((resolve, reject) => cabal.publishNick('js-peer', err => err ? reject(err) : resolve())) + await new Promise((resolve, reject) => cabal.publish({ + type: 'chat/text', content: { channel: 'default', text: 'message from cabal js' } + }, err => err ? reject(err) : resolve())) + await waitC(line => line.includes('message from cabal js'), 'JS-to-C message delivery') + + cClient.stdin.write('SEND message from sdl c\n') + await waitFor(() => jsMessages.includes('message from sdl c'), 'C-to-JS Hypercore feed replication') + console.log('C SDL / Cabal JS Hyperswarm discovery and bidirectional chat: PASS') +} + +main().catch(err => { console.error(err.stack || err); process.exitCode = 1 }).finally(async () => { + if (cClient && cClient.exitCode === null) { + try { cClient.stdin.write('QUIT\n') } catch (_) {} + await Promise.race([new Promise(resolve => cClient.once('exit', resolve)), new Promise(resolve => setTimeout(resolve, 2000))]) + if (cClient.exitCode === null) cClient.kill('SIGTERM') + } + if (cabal) { + if (cabal._swarm && cabal._swarm.__shutdown) { try { await cabal._swarm.__shutdown() } catch (_) {} } + try { await cabal.close() } catch (_) {} + } + if (relay) { try { await relay.destroy() } catch (_) {} } + if (bootstrapper) { try { await bootstrapper.destroy() } catch (_) {} } + const resolved = path.resolve(root) + if (resolved.startsWith(path.resolve(os.tmpdir()) + path.sep) && path.basename(resolved).startsWith('cable-js-c-live-')) + fs.rmSync(resolved, { recursive: true, force: true }) +}) diff --git a/tests/interop/cabal/cable_sexpr_boundary.js b/tests/interop/cabal/cable_sexpr_boundary.js new file mode 100644 index 0000000..546f536 --- /dev/null +++ b/tests/interop/cabal/cable_sexpr_boundary.js @@ -0,0 +1,15 @@ +'use strict' + +const assert = require('assert') +const { quote, parse } = require('./cable_hyperswarm') + +for (const value of [ + '', 'plain text', 'quote " slash \\', 'line one\nline two', + 'tabs\tand\bbackspace\f formfeed\r', 'Unicode: 媒腻�┖ꈳ埳 �' +]) { + assert.deepStrictEqual(parse('(send "general" ' + quote(value) + ')'), ['send', 'general', value]) +} + +assert.throws(() => parse('(send "unterminated)'), /unterminated/) +assert.throws(() => parse('(send "bad\\q")'), /unsupported S-expression escape/) +console.log('Cable S-expression IPC: PASS') diff --git a/tests/interop/cabal/hyperswarm_loopback.js b/tests/interop/cabal/hyperswarm_loopback.js new file mode 100644 index 0000000..a1e21f5 --- /dev/null +++ b/tests/interop/cabal/hyperswarm_loopback.js @@ -0,0 +1,99 @@ +'use strict' + +// Narrow diagnostic: validates the installed Hyperswarm/HyperDHT package and +// the local bootstrap node independently of Cabal's Hypercore replication. +const assert = require('assert') +const crypto = require('crypto') +const dgram = require('dgram') +const { loadCabalCore } = require('./cable_hyperswarm') + +const { requireCabal } = loadCabalCore() +const DHT = requireCabal('hyperdht') +const Swarm = requireCabal('hyperswarm') + +function freePort () { + return new Promise((resolve, reject) => { + const socket = dgram.createSocket('udp4') + socket.once('error', reject) + socket.bind(0, '127.0.0.1', () => { + const port = socket.address().port + socket.close(err => err ? reject(err) : resolve(port)) + }) + }) +} + +async function main () { + const portA = await freePort() + const portB = await freePort() + const relayPort = await freePort() + const useBuiltin = process.env.HYPERSWARM_TEST_BUILTIN === '1' + const bootstrapPort = useBuiltin ? 0 : await freePort() + const bootstrap = useBuiltin ? null : DHT.bootstrapper(bootstrapPort, '127.0.0.1', { host: '127.0.0.1' }) + const relay = useBuiltin ? null : new DHT({ + bootstrap: ['127.0.0.1:' + bootstrapPort], host: '127.0.0.1', port: relayPort, + ephemeral: false, firewalled: false + }) + const swarms = [] + try { + if (bootstrap) await bootstrap.ready() + if (relay) await relay.ready() + if (bootstrap) console.log('bootstrap state', { id: Boolean(bootstrap.id), ephemeral: bootstrap.ephemeral, storage: Boolean(bootstrap._persistent) }) + if (relay) console.log('relay state', { id: Boolean(relay.id), ephemeral: relay.ephemeral, storage: Boolean(relay._persistent) }) + const endpoint = useBuiltin ? undefined : '127.0.0.1:' + relayPort + for (const port of [portA, portB]) { + const dht = new DHT({ ...(useBuiltin ? {} : { bootstrap: [endpoint] }), host: '127.0.0.1', port, + ephemeral: false, firewalled: false }) + dht._nat.add('127.0.0.1', port) + const swarm = new Swarm({ dht }) + dht.on('error', err => console.error('DHT error:', err.message)) + swarm.on('error', err => console.error('swarm error:', err.message)) + swarms.push(swarm) + } + const [a, b] = swarms + const topic = crypto.randomBytes(32) + let connected = 0 + const onConnection = socket => { connected++; socket.on('error', () => {}) } + a.on('connection', onConnection) + b.on('connection', onConnection) + const onDiscoveryError = (label) => err => console.error(label + ' discovery error:', err && (err.stack || err)) + const discoveryA = a.join(topic, { server: true, client: false, onerror: onDiscoveryError('A') }) + const aFlushed = await discoveryA.flushed() + const discoveryB = b.join(topic, { server: false, client: true, onerror: onDiscoveryError('B') }) + const bFlushed = await discoveryB.flushed() + console.log('Hyperswarm discovery flush:', aFlushed + '/' + bFlushed) + console.log('DHT node tables:', { + a: a.dht.toArray(), b: b.dht.toArray(), + relay: relay && relay.table.toArray().map(node => ({ host: node.host, port: node.port })) + }) + if (relay) console.log('relay stored topic peers:', relay._persistent.records.get(topic.toString('hex'), 20).length) + const lookup = b.dht.lookup(topic) + const lookupResults = [] + lookup.on('data', result => lookupResults.push(result.peers.length)) + await lookup.finished() + console.log('explicit topic lookup peer counts:', lookupResults) + const deadline = Date.now() + 20000 + while (connected < 2 && Date.now() < deadline) await new Promise(resolve => setTimeout(resolve, 100)) + const discovered = connected >= 2 + console.log('Hyperswarm state:', { connections: [a.connections.size, b.connections.size], + queued: [a.connecting, b.connecting], peerEntries: [a.peers.size, b.peers.size] }) + if (!discovered) { + console.log('DHT discovery did not connect both local nodes; trying direct Hyperswarm peers to isolate transport') + a.joinPeer(b.keyPair.publicKey) + b.joinPeer(a.keyPair.publicKey) + const directDeadline = Date.now() + 10000 + while (connected < 2 && Date.now() < directDeadline) await new Promise(resolve => setTimeout(resolve, 100)) + } + assert(connected >= 2, 'local Hyperswarm peers failed direct transport too') + console.log('local Hyperswarm transport: PASS') + if (!discovered) { + console.error('local DHT discovery: FAIL (the bootstrap node did not yield peer addresses)') + process.exitCode = 1 + } else console.log('local Hyperswarm DHT discovery: PASS') + } finally { + for (const swarm of swarms) { try { await swarm.destroy() } catch (_) {} } + if (relay) { try { await relay.destroy() } catch (_) {} } + if (bootstrap) { try { await bootstrap.destroy() } catch (_) {} } + } +} + +main().catch(err => { console.error(err.stack || err); process.exitCode = 1 }) diff --git a/tests/interop/dat/cable_hypercore_compat.js b/tests/interop/dat/cable_hypercore_compat.js new file mode 100644 index 0000000..e33fb0d --- /dev/null +++ b/tests/interop/dat/cable_hypercore_compat.js @@ -0,0 +1,87 @@ +'use strict' + +// Opens the native Cable user log with the bundled legacy Hypercore 7.7.1, +// appends a JS record, and leaves the C side to read it back in the runner. +const assert = require('assert') +const fs = require('fs') +const path = require('path') + +const root = path.resolve(__dirname, '../../..') +const home = path.resolve(process.argv[2]) +const roomKey = process.argv[3] +assert.match(roomKey, /^[0-9a-f]{64}$/) +const roomDir = path.join(home, '.cable', 'rooms', roomKey) +const feedDir = path.join(roomDir, '0') +const feedKey = fs.readFileSync(path.join(feedDir, 'key')) +const secretKey = fs.readFileSync(path.join(roomDir, 'user-feed.key')) +assert.strictEqual(feedKey.length, 32) +assert.strictEqual(secretKey.length, 64) + +const Hypercore = require(path.join(root, 'extra/dillo-dat/node_modules/hypercore')) +const randomAccessFile = require(path.join(root, 'extra/dillo-dat/node_modules/random-access-file')) +function memoryStorage () { + let bytes = Buffer.alloc(0) + return { + read (offset, length, callback) { + const value = Buffer.alloc(length) + bytes.copy(value, 0, offset, offset + length) + callback(null, value) + }, + write (offset, value, callback) { + if (bytes.length < offset + value.length) { + const grown = Buffer.alloc(offset + value.length) + bytes.copy(grown) + bytes = grown + } + value.copy(bytes, offset) + callback(null) + }, + stat (callback) { callback(null, { size: bytes.length }) }, + close (callback) { callback(null) } + } +} +const storage = name => name === 'secret_key' + ? memoryStorage() + : randomAccessFile(name, { directory: feedDir }) + +function ready (feed) { + return new Promise((resolve, reject) => feed.ready(err => err ? reject(err) : resolve())) +} + +function get (feed, index) { + return new Promise((resolve, reject) => feed.get(index, (err, value) => err ? reject(err) : resolve(value))) +} + +function append (feed, value) { + return new Promise((resolve, reject) => feed.append(Buffer.from(JSON.stringify(value)), err => err ? reject(err) : resolve())) +} + +function close (feed) { + return new Promise((resolve, reject) => feed.close(err => err ? reject(err) : resolve())) +} + +async function main () { + const feed = Hypercore(storage, feedKey, { secretKey }) + feed.on('error', err => { throw err }) + try { + await ready(feed) + assert.strictEqual(feed.length, 2, 'C should have written two Cabal records') + const nick = JSON.parse((await get(feed, 0)).toString('utf8')) + const message = JSON.parse((await get(feed, 1)).toString('utf8')) + assert.strictEqual(nick.type, 'about') + assert.strictEqual(nick.content.name, 'cable-c-user') + assert.strictEqual(message.type, 'chat/text') + assert.strictEqual(message.content.channel, 'interop') + assert.strictEqual(message.content.text, 'hello from C Hypercore') + await append(feed, { + type: 'chat/text', + content: { channel: 'interop', text: 'hello from Cabal JavaScript Hypercore' }, + timestamp: Date.now() + }) + process.stdout.write(`Cabal Hypercore v7.7.1 read C user feed and appended reply: PASS key=${feedKey.toString('hex')}\n`) + } finally { + await close(feed) + } +} + +main().catch(err => { console.error(err); process.exitCode = 1 }) diff --git a/tests/interop/dat/cable_hypercore_compat.ps1 b/tests/interop/dat/cable_hypercore_compat.ps1 new file mode 100644 index 0000000..81a7e26 --- /dev/null +++ b/tests/interop/dat/cable_hypercore_compat.ps1 @@ -0,0 +1,44 @@ +param([switch]$KeepFixture) + +$ErrorActionPreference = 'Stop' +$root = (Resolve-Path (Join-Path $PSScriptRoot '..\..\..')).Path +$buildDir = [System.IO.Path]::GetFullPath((Join-Path $root 'build\linux')) +$binary = Join-Path $buildDir 'cable' +$nodeScript = Join-Path $PSScriptRoot 'cable_hypercore_compat.js' +$runRoot = [System.IO.Path]::GetFullPath((Join-Path $root 'local-data\test-runs')) +$fixtureHome = Join-Path $runRoot ('cable-hypercore-interop-' + [guid]::NewGuid().ToString('N')) +$linuxRoot = '/mnt/' + $root.Substring(0, 1).ToLower() + '/' + $root.Substring(3).Replace('\', '/') +$linuxHome = $linuxRoot + '/local-data/test-runs/' + (Split-Path $fixtureHome -Leaf) +$room = '42' * 32 + +function Invoke-WslCable([string[]]$Arguments) { + & wsl.exe --exec env "HOME=$linuxHome" "$linuxRoot/build/linux/cable" @Arguments + if ($LASTEXITCODE -ne 0) { throw "C cable command failed with exit code $LASTEXITCODE" } +} + +New-Item -ItemType Directory -Path $fixtureHome -Force | Out-Null +try { + & wsl.exe --exec make -C $linuxRoot build/linux/cable + if ($LASTEXITCODE -ne 0) { throw "WSL Cable build failed with exit code $LASTEXITCODE" } + Invoke-WslCable @('nick', "cabal://$room", 'cable-c-user') | Out-Null + Invoke-WslCable @('publish', "cabal://$room", 'interop', 'hello from C Hypercore') | Out-Null + + & node $nodeScript $fixtureHome $room + if ($LASTEXITCODE -ne 0) { throw "JavaScript Hypercore check failed with exit code $LASTEXITCODE" } + + $records = Invoke-WslCable @('feed', "cabal://$room") + if (($records -join "`n") -notmatch 'hello from Cabal JavaScript Hypercore') { + throw 'C did not read the JavaScript-appended block' + } + Write-Output 'Cabal Core room/0 storage + Hypercore v7.7.1 read/write: PASS' + Write-Output 'C read JavaScript-appended Hypercore block: PASS' +} finally { + if (-not $KeepFixture -and (Test-Path -LiteralPath $fixtureHome)) { + $resolvedFixture = [System.IO.Path]::GetFullPath((Resolve-Path -LiteralPath $fixtureHome).Path) + $runPrefix = $runRoot.TrimEnd('\') + '\' + if (-not $resolvedFixture.StartsWith($runPrefix, [System.StringComparison]::OrdinalIgnoreCase)) { + throw "Refusing to remove fixture outside local-data/test-runs/: $resolvedFixture" + } + Remove-Item -LiteralPath $resolvedFixture -Recurse -Force + } +} diff --git a/tests/interop/dat/legacy_js_p2p.js b/tests/interop/dat/legacy_js_p2p.js new file mode 100644 index 0000000..591155e --- /dev/null +++ b/tests/interop/dat/legacy_js_p2p.js @@ -0,0 +1,69 @@ +const fs = require('fs') +const os = require('os') +const path = require('path') +const hyperdrive = require('../../extra/dillo-dat/node_modules/hyperdrive') +const hyperdiscovery = require('../../extra/dillo-dat/node_modules/hyperdiscovery') + +function ready (archive) { + return new Promise((resolve, reject) => archive.ready(err => err ? reject(err) : resolve())) +} + +function writeFile (archive, name, data) { + return new Promise((resolve, reject) => archive.writeFile(name, data, err => err ? reject(err) : resolve())) +} + +function readFile (archive, name) { + return new Promise((resolve, reject) => archive.readFile(name, (err, data) => err ? reject(err) : resolve(data))) +} + +function listening (swarm) { + if (swarm.address()) return Promise.resolve(swarm.address()) + return new Promise(resolve => swarm.once('listening', () => resolve(swarm.address()))) +} + +function sleep (ms) { return new Promise(resolve => setTimeout(resolve, ms)) } + +async function main () { + const root = fs.mkdtempSync(path.join(process.env.DDAT_TEST_DIR || os.tmpdir(), 'ddat-js-p2p-')) + let source, replica, sourceSwarm, replicaSwarm + try { + source = hyperdrive(path.join(root, 'source')) + await ready(source) + const key = source.key.toString('hex') + const address = 'dat://' + key + await writeFile(source, 'hello.txt', 'hello from old dillo-dat v1') + await writeFile(source, 'hello.txt', 'hello from old dillo-dat v2') + if (source.key.toString('hex') !== key) throw new Error('JS archive address changed after update') + replica = hyperdrive(path.join(root, 'replica'), source.key, {sparse: true}) + await ready(replica) + + sourceSwarm = hyperdiscovery(source, {port: 0, discovery: false, dht: false, dns: false, utp: false}) + replicaSwarm = hyperdiscovery(replica, {port: 0, discovery: false, dht: false, dns: false, utp: false}) + const sourceAddress = await listening(sourceSwarm) + const replicaAddress = await listening(replicaSwarm) + const topic = source.discoveryKey + sourceSwarm.addPeer(topic, {host: '127.0.0.1', port: replicaAddress.port}) + replicaSwarm.addPeer(topic, {host: '127.0.0.1', port: sourceAddress.port}) + + const deadline = Date.now() + 15000 + let result + while (Date.now() < deadline) { + try { result = await readFile(replica, 'hello.txt'); break } catch (_) { await sleep(100) } + } + if (!result || result.toString() !== 'hello from old dillo-dat v2') throw new Error('old JS replication did not deliver the latest file') + process.stdout.write('old Dillo Dat JS create/update/address/xfer: PASS ' + address + + ' metadata=' + source.metadata.length + ' content=' + source.content.length + '\n') + } finally { + if (sourceSwarm) sourceSwarm.destroy() + if (replicaSwarm) replicaSwarm.destroy() + if (source) source.close() + if (replica) replica.close() + if (process.env.DDAT_TEST_KEEP === '1') { + process.stdout.write('kept old JS source and replica at ' + root + '\n') + } else { + fs.rmSync(root, {recursive: true, force: true}) + } + } +} + +main().catch(err => { console.error(err); process.exitCode = 1 }) diff --git a/tests/test-json.scm b/tests/test-json.scm new file mode 100644 index 0000000..c8b4347 --- /dev/null +++ b/tests/test-json.scm @@ -0,0 +1,34 @@ +(define sample + (dat:json-parse + "{\"title\":\"dat \\\"thang\\\"\",\"items\":[true,false,null,-2,1.25e2],\"line\":\"a\\nb\"}")) +(unless (equal? (dat:json-object-ref sample "title") "dat \"thang\"") + (error 'test-failed "JSON strings and escaped quotes")) +(unless (equal? (dat:json-object-ref sample "items") + '(json:array #t #f json:null -2 125.0)) + (error 'test-failed "JSON arrays, literals, and numbers")) +(unless (equal? (dat:json-object-ref sample "line") "a\nb") + (error 'test-failed "JSON escaped newline")) +(unless (equal? (dat:json-parse "\"\\uD83D\\uDE00\"") + (list->string (map integer->char '(#xf0 #x9f #x98 #x80)))) + (error 'test-failed "JSON UTF-16 surrogate pair")) +(unless (json:number-token? "-0.4E+3") + (error 'test-failed "valid JSON number")) +(when (json:number-token? "+1") + (error 'test-failed "reject Scheme-only number spelling")) +(let* ((wire '(json:object ("type" . "chat/text") + ("content" . (json:object ("channel" . "general") + ("text" . "hello\n�"))) + ("timestamp" . 42))) + (encoded (dat:json-stringify wire)) + (decoded (dat:json-parse encoded))) + (unless (equal? (dat:json-object-ref decoded "type") "chat/text") + (error 'test-failed "S-expression JSON adapter object roundtrip")) + (unless (equal? (dat:json-object-ref + (dat:json-object-ref decoded "content") "text") "hello\n�") + (error 'test-failed "S-expression JSON adapter escaped Unicode string"))) +(let ((caught #f)) + (catch #t + (lambda () (dat:json-parse "[1,]")) + (lambda args (set! caught #t))) + (unless caught (error 'test-failed "reject trailing array comma"))) +(display "S7 JSON/S-expression adapter: PASS\n") diff --git a/tests/test_archive.c b/tests/test_archive.c new file mode 100644 index 0000000..149ed07 --- /dev/null +++ b/tests/test_archive.c @@ -0,0 +1,225 @@ +#define _POSIX_C_SOURCE 200809L +#include "dat.h" +#include "sleep_verify.h" +#include "sleep_feed.h" +#include "sleep.h" +#include +#include +#include +#include +#include +#include + +static int changes_seen; +static uint64_t last_change_version; +static int directory_entries_seen; + +static int count_change(const sleep_metadata_entry_t *entry, uint64_t version, + void *context) +{ + (void)entry; + (void)context; + changes_seen++; + last_change_version = version; + return 0; +} + +static int count_directory_entry(const sleep_metadata_entry_t *entry, + const char *name, void *context) +{ + (void)entry; + (void)context; + assert(!strcmp(name, "sample.bin")); + directory_entries_seen++; + return 0; +} + +int main(void) +{ + char directory[] = "/tmp/datgpt-archive-XXXXXX"; + char sync_directory[] = "/tmp/datgpt-sync-XXXXXX"; + char secrets_directory[] = "/tmp/datgpt-secrets-XXXXXX"; + dat_archive_t archive, reopened; + dat_archive_t sync_archive; + sleep_metadata_entry_t entry; + uint8_t *input, *output; + uint8_t parsed_key[DAT_KEY_BYTES]; + char url[DAT_URL_BYTES]; + char hex[65]; + char legacy_url[DAT_URL_BYTES]; + char link_text[DAT_LINK_BYTES]; + char probe_path[256]; + char secret_path[1024], discovery_hex[65]; + uint8_t discovery_key[32]; + struct stat secret_stat; + FILE *probe; + dat_link_t link, parsed_link; + dat_checkout_t checkout; + dat_url_t parsed_url; + dat_path_info_t path_info; + int serve_listing; + uint64_t old_version, new_version, sync_version; + sleep_feed_t remote_feed; + uint8_t remote_key[32] = {0x42}, remote_proof[] = {0x01, 0x02}; + uint8_t remote_value[] = {0x61, 0x00, 0x62}, remote_value3[] = {0x63}; + uint8_t *remote_read; + static const uint8_t old_text[] = "old archive value\n"; + static const uint8_t new_text[] = "new archive value\n"; + size_t input_len = DAT_BLOCK_BYTES * 2 + 123; + size_t output_len, i; + + assert(mkdtemp(directory) != NULL); + assert(mkdtemp(secrets_directory) != NULL); + memset(&remote_feed, 0, sizeof(remote_feed)); + remote_feed.remote_only = 1; + memcpy(remote_feed.keypair.pubkey, remote_key, sizeof(remote_key)); + assert(sleep_feed_append_remote(&remote_feed, 0, remote_value, sizeof(remote_value), + remote_proof, sizeof(remote_proof)) == 0); + assert(sleep_feed_read(&remote_feed, 0, &remote_read, &output_len) == 0); + assert(output_len == sizeof(remote_value) && !memcmp(remote_read, remote_value, output_len)); + free(remote_read); + assert(sleep_feed_append_remote(&remote_feed, 3, remote_value3, sizeof(remote_value3), + remote_proof, sizeof(remote_proof)) == 0); + assert(sleep_feed_length(&remote_feed) == 4); + assert(sleep_feed_has(&remote_feed, 2) == 0); + assert(sleep_feed_read(&remote_feed, 2, &remote_read, &output_len) != 0); + assert(sleep_feed_read(&remote_feed, 3, &remote_read, &output_len) == 0); + assert(output_len == sizeof(remote_value3) && remote_read[0] == remote_value3[0]); + free(remote_read); + assert(sleep_feed_append(&remote_feed, remote_value, sizeof(remote_value)) != 0); + assert(sleep_feed_flush(&remote_feed) != 0); + sleep_feed_close(&remote_feed); + input = malloc(input_len); + assert(input != NULL); + for (i = 0; i < input_len; i++) + input[i] = (uint8_t)((i * 31u + 7u) & 0xffu); + + if (dat_archive_create(&archive, directory) != 0) { + perror("dat_archive_create"); + return 1; + } + assert(dat_archive_save_secrets(&archive, secrets_directory) == 0); + assert(sleep_compute_discovery_key(archive.metadata_key, discovery_key) == 0); + assert(dat_key_to_hex(discovery_key, discovery_hex) == 0); + snprintf(secret_path, sizeof(secret_path), "%s/%.2s/%s", secrets_directory, + discovery_hex, discovery_hex + 2); + assert(stat(secret_path, &secret_stat) == 0 && secret_stat.st_size == 64); + assert(sleep_compute_discovery_key(archive.content_key, discovery_key) == 0); + assert(dat_key_to_hex(discovery_key, discovery_hex) == 0); + snprintf(secret_path, sizeof(secret_path), "%s/%.2s/%s", secrets_directory, + discovery_hex, discovery_hex + 2); + assert(stat(secret_path, &secret_stat) == 0 && secret_stat.st_size == 64); + assert(dat_archive_open_writable(&reopened, directory, secrets_directory) == 0); + dat_archive_close(&reopened); + assert(dat_key_to_url(archive.metadata_key, url) == 0); + assert(strlen(url) == 70); + assert(strncmp(url, "dat://", 6) == 0); + assert(dat_url_to_key(url, parsed_key) == 0); + assert(memcmp(parsed_key, archive.metadata_key, DAT_KEY_BYTES) == 0); + assert(dat_key_to_compact_url(archive.metadata_key, url) == 0); + assert(strlen(url) < 70); + assert(dat_url_to_key(url, parsed_key) == 0); + assert(memcmp(parsed_key, archive.metadata_key, DAT_KEY_BYTES) == 0); + assert(dat_key_to_hex(archive.metadata_key, hex) == 0); + snprintf(legacy_url, sizeof(legacy_url), "dat://%s", hex); + assert(dat_url_to_key(legacy_url, parsed_key) == 0); + assert(memcmp(parsed_key, archive.metadata_key, DAT_KEY_BYTES) == 0); + assert(dat_archive_append_directory(&archive, "/docs", 0755, 1) == 0); + assert(dat_archive_append_file(&archive, "/docs/sample.bin", input, + input_len, 0644, 2) == 0); + assert(dat_archive_append_symlink(&archive, "/latest", + "/docs/sample.bin", 3) == 0); + assert(dat_archive_append_file(&archive, "/versioned.txt", old_text, + sizeof(old_text) - 1, 0644, 4) == 0); + old_version = dat_archive_version(&archive); + assert(dat_archive_append_file(&archive, "/versioned.txt", new_text, + sizeof(new_text) - 1, 0644, 5) == 0); + new_version = dat_archive_version(&archive); + assert(new_version == old_version + 1); + assert(dat_checkout_init(&checkout, &archive, old_version) == 0); + assert(dat_checkout_find(&checkout, "/versioned.txt", &entry) == 0); + assert(dat_checkout_read(&checkout, &entry, &output, &output_len) == 0); + assert(output_len == sizeof(old_text) - 1); + assert(memcmp(output, old_text, output_len) == 0); + free(output); + assert(dat_archive_find(&archive, "/versioned.txt", &entry) == 0); + assert(dat_archive_read_file(&archive, &entry, &output, &output_len) == 0); + assert(output_len == sizeof(new_text) - 1); + assert(memcmp(output, new_text, output_len) == 0); + free(output); + changes_seen = 0; + last_change_version = 0; + assert(dat_archive_changes(&archive, old_version, new_version, + count_change, NULL) == 0); + assert(changes_seen == 1 && last_change_version == new_version); + memset(&link, 0, sizeof(link)); + memcpy(link.key, archive.metadata_key, DAT_KEY_BYTES); + link.has_version = 1; + link.version = old_version; + strcpy(link.path, "versioned.txt"); + assert(dat_link_format(&link, link_text, sizeof(link_text)) == 0); + assert(strstr(link_text, "+") != NULL); + assert(strlen(link_text) >= 70); + assert(dat_link_parse(link_text, &parsed_link) == 0); + assert(parsed_link.has_version && parsed_link.version == old_version); + assert(!strcmp(parsed_link.path, "versioned.txt")); + assert(!memcmp(parsed_link.key, archive.metadata_key, DAT_KEY_BYTES)); + assert(dat_link_format_compact(&link, link_text, sizeof(link_text)) == 0); + assert(dat_link_parse(link_text, &parsed_link) == 0); + assert(!memcmp(parsed_link.key, archive.metadata_key, DAT_KEY_BYTES)); + assert(dat_url_parse("dat://protozoa.nz/path%20name?version=7&stat=1", &parsed_url) == 0); + assert(!strcmp(parsed_url.host, "protozoa.nz")); + assert(!strcmp(parsed_url.path, "/path name")); + assert(parsed_url.has_version && parsed_url.version == 7 && parsed_url.wants_stat); + assert(dat_url_parse("dat://protozoa.nz+8/dir//", &parsed_url) == 0); + assert(parsed_url.has_version && parsed_url.version == 8 && parsed_url.force_directory_listing); + assert(dat_url_parse("dat://protozoa.nz/dir/../page", &parsed_url) == 0); + assert(!strcmp(parsed_url.path, "/page")); + assert(dat_archive_stat_at(&archive, old_version, "/docs", &path_info) == 0); + assert(path_info.exists && path_info.is_directory); + directory_entries_seen = 0; + assert(dat_archive_readdir_at(&archive, old_version, "/docs", count_directory_entry, NULL) == 0); + assert(directory_entries_seen == 1); + assert(dat_archive_resolve_at(&archive, old_version, "/docs/", + 0, &path_info, &serve_listing) == 0); + assert(serve_listing); + assert(dat_archive_find(&archive, "/docs/sample.bin", &entry) == 0); + assert(entry.block_count == 3); + assert(entry.size == input_len); + assert(dat_archive_read_file(&archive, &entry, &output, &output_len) == 0); + assert(output_len == input_len); + assert(memcmp(input, output, input_len) == 0); + free(output); + dat_archive_close(&archive); + + assert(dat_archive_open(&reopened, directory) == 0); + assert(dat_archive_find(&reopened, "/docs/sample.bin", &entry) == 0); + assert(dat_archive_read_file(&reopened, &entry, &output, &output_len) == 0); + assert(output_len == input_len); + assert(memcmp(input, output, input_len) == 0); + free(output); + free(input); + assert(sleep_verify_dat(reopened.dat_path) == 0); + assert(dat_archive_append_file(&reopened, "/page.html", + (const uint8_t *)"

    page

    ", 13, 0644, 6) == 0); + assert(dat_archive_resolve_at(&reopened, dat_archive_version(&reopened), "/page", + 0, &path_info, &serve_listing) == 0); + assert(!serve_listing && !strcmp(path_info.entry.path, "/page.html")); + assert(!strcmp(dat_content_type(path_info.entry.path), "text/html")); + dat_archive_close(&reopened); + assert(mkdtemp(sync_directory) != NULL); + assert(dat_archive_create(&sync_archive, sync_directory) == 0); + assert(snprintf(probe_path, sizeof(probe_path), "%s/mtime-probe", sync_directory) > 0); + probe = fopen(probe_path, "wb"); + assert(probe != NULL); + assert(fwrite("mtime", 1, 5, probe) == 5); + assert(fclose(probe) == 0); + assert(dat_archive_sync_path(&sync_archive, sync_directory, "") == 0); + assert(dat_archive_find(&sync_archive, "mtime-probe", &entry) == 0); + assert(entry.mtime > UINT64_C(1000000000000)); /* Hyperdrive stores epoch milliseconds. */ + sync_version = dat_archive_version(&sync_archive); + assert(dat_archive_sync_path(&sync_archive, sync_directory, "") == 0); + assert(dat_archive_version(&sync_archive) == sync_version); /* unchanged paths do not append */ + dat_archive_close(&sync_archive); + return 0; +} diff --git a/tests/test_base32768.c b/tests/test_base32768.c new file mode 100644 index 0000000..0b56e35 --- /dev/null +++ b/tests/test_base32768.c @@ -0,0 +1,46 @@ +#include "base32768.h" +#include "dat.h" +#include +#include +#include +#include +#include + +int main(void) +{ + static const uint8_t hello[] = {104,101,108,108,111,32,119,111,114,108,100}; + static const char encoded_hello[] = "媒腻�┖ꈳ埳"; + uint8_t input[257], decoded[257]; + char encoded[1200]; + char archive_url[DAT_URL_BYTES]; + char compact_link[DAT_LINK_BYTES]; + dat_url_t parsed_url; + static const char historical_address[] = + "dat://59d8099399510e23a3c693fb5887b7bba1586f4117f7ef1c5872622ada0c1181"; + static const char compact_historical_address[] = + "dat://�Ꭴ馊㜢��鴑╗�类躂ꜞ齂蠩檵�♠ʟ"; + uint8_t archive_key[DAT_KEY_BYTES]; + size_t length, i, decoded_length; + assert(base32768_encode(hello, sizeof(hello), encoded, sizeof(encoded)) == 0); + assert(!strcmp(encoded, encoded_hello)); + assert(base32768_decode(encoded_hello, decoded, sizeof(decoded), &decoded_length) == 0); + assert(decoded_length == sizeof(hello) && !memcmp(decoded, hello, sizeof(hello))); + for (length = 0; length <= sizeof(input); length++) { + for (i = 0; i < length; i++) input[i] = (uint8_t)(i * 37u + length); + assert(base32768_encode(input, length, encoded, sizeof(encoded)) == 0); + assert(base32768_decode(encoded, decoded, sizeof(decoded), &decoded_length) == 0); + assert(decoded_length == length && !memcmp(decoded, input, length)); + } + assert(base32768_decode("A", decoded, sizeof(decoded), &decoded_length) == -1); + assert(errno == EILSEQ); + assert(dat_url_to_key(historical_address, archive_key) == 0); + assert(dat_key_to_compact_url(archive_key, archive_url) == 0); + assert(!strcmp(archive_url, compact_historical_address)); + snprintf(compact_link, sizeof(compact_link), "%s+7/readme.txt", archive_url); + assert(dat_url_parse(compact_link, &parsed_url) == 0); + assert(parsed_url.has_version && parsed_url.version == 7); + assert(!strcmp(parsed_url.host, compact_historical_address + 6)); + assert(dat_url_to_key(parsed_url.host, decoded) == 0); + assert(!memcmp(decoded, archive_key, sizeof(archive_key))); + return 0; +} diff --git a/tests/test_bitfield.c b/tests/test_bitfield.c new file mode 100644 index 0000000..c05f053 --- /dev/null +++ b/tests/test_bitfield.c @@ -0,0 +1,4 @@ +#include "sleep_bitfield.h" +#include +#include +int main(void){sleep_bitfield_t a,b;unsigned char encoded[sizeof(a)];sleep_bitfield_clear(&a);assert(sizeof(a)==3584);assert(sleep_bitfield_set_data(&a,0,1)==0);assert(sleep_bitfield_set_data(&a,9,1)==0);assert(a.data[0]==0x80&&a.data[1]==0x40);assert(sleep_bitfield_get_data(&a,9)==1);assert(sleep_bitfield_set_tree(&a,3,1)==0);assert(sleep_bitfield_encode(&a,encoded,sizeof(encoded))==0);memset(&b,0,sizeof(b));assert(sleep_bitfield_decode(&b,encoded,sizeof(encoded))==0);assert(sleep_bitfield_get_tree(&b,3)==1);return 0;} diff --git a/tests/test_cable.c b/tests/test_cable.c new file mode 100644 index 0000000..77ee913 --- /dev/null +++ b/tests/test_cable.c @@ -0,0 +1,100 @@ +#define _POSIX_C_SOURCE 200809L +#include "cable.h" +#include "sleep_crypto.h" +#include +#include +#include +#include +#include +#include +#include +#include + +int main(void) +{ + uint8_t key[32], parsed[32], plain[900], sealed[1000], opened[900]; + char address[160],identity_dir[]="/tmp/cable-test-XXXXXX",identity_path[256];size_t n,sealed_n,opened_n;enum cable_address_scheme scheme; + sleep_keypair_t alice,bob;cable_chat_message_t message,decoded; + assert(sleep_crypto_init()==0); + memset(key,0xa5,sizeof(key)); + assert(cable_address_format(key,CABLE_ADDRESS_CABLE,address,sizeof(address))==0); + assert(!strncmp(address,"cable://",8)); + assert(cable_address_parse(address,parsed,&scheme)==0&&scheme==CABLE_ADDRESS_CABLE); + assert(!memcmp(key,parsed,32)); + assert(cable_address_parse("hyper://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",parsed,&scheme)==0); + assert(scheme==CABLE_ADDRESS_HYPER); + assert(cable_address_parse("dat://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",parsed,&scheme)<0); + { + cable_room_address_t room,parsed_room; + char room_text[160]; + memset(&room,0,sizeof(room));memset(room.key,0x11,32);memset(room.admin_key,0x22,32);room.has_admin=1; + assert(cable_room_address_format(&room,CABLE_ADDRESS_CABAL,room_text,sizeof(room_text))==0); + assert(strstr(room_text,"?admin=")!=NULL); + assert(cable_room_address_parse(room_text,&parsed_room)==0); + assert(parsed_room.has_admin&&memcmp(room.key,parsed_room.key,32)==0&&memcmp(room.admin_key,parsed_room.admin_key,32)==0); + } + assert(mkdtemp(identity_dir)!=NULL); + assert(snprintf(identity_path,sizeof(identity_path),"%s/user-feed.key",identity_dir)>0); + { + sleep_keypair_t first,second;struct stat info; + assert(cable_user_identity_load_or_create(identity_path,&first)==0); + assert(stat(identity_path,&info)==0&&(info.st_mode&077)==0); + assert(cable_user_address_format(first.pubkey,CABLE_ADDRESS_CABLE,address,sizeof(address))==0); + assert(cable_user_identity_load_or_create(identity_path,&second)==0); + assert(memcmp(first.pubkey,second.pubkey,32)==0&&memcmp(first.seckey,second.seckey,64)==0); + sodium_memzero(&first,sizeof(first));sodium_memzero(&second,sizeof(second)); + } + assert(unlink(identity_path)==0);assert(rmdir(identity_dir)==0); + { + uint8_t room_a[32]={1},room_b[32]={2};char home[]="/tmp/cable-home-XXXXXX"; + char path_a[512],path_b[512],dir_a[512],dir_b[512],feed_dir[520],rooms[512],base[512]; + sleep_keypair_t a1,a2,b; + sleep_feed_t feed; + uint8_t user_key[32],*block=NULL; + size_t block_length=0; + assert(mkdtemp(home)!=NULL); + assert(cable_user_identity_path(home,room_a,path_a,sizeof(path_a))==0); + assert(cable_user_identity_path(home,room_b,path_b,sizeof(path_b))==0); + assert(strcmp(path_a,path_b)!=0); + assert(cable_user_identity_load_or_create(path_a,&a1)==0); + assert(cable_user_identity_load_or_create(path_a,&a2)==0); + assert(cable_user_identity_load_or_create(path_b,&b)==0); + assert(memcmp(a1.pubkey,a2.pubkey,32)==0&&memcmp(a1.pubkey,b.pubkey,32)!=0); + assert(cable_user_feed_publish(home,room_a,"about","cable-test-user",NULL,NULL,user_key)==0); + assert(!memcmp(user_key,a1.pubkey,sizeof(user_key))); + assert(cable_user_feed_publish(home,room_a,"chat/text",NULL,"test-channel", + "Cabal-shaped message",user_key)==0); + assert(cable_user_feed_open(home,room_a,&a2,&feed)==0); + assert(sleep_feed_length(&feed)==2); + assert(sleep_feed_read(&feed,1,&block,&block_length)==0); + assert(strstr((const char *)block,"\"type\":\"chat/text\"")!=NULL); + assert(strstr((const char *)block,"Cabal-shaped message")!=NULL); + free(block);block=NULL;sleep_feed_close(&feed); + strcpy(dir_a,path_a);*strrchr(dir_a,'/')='\0';strcpy(dir_b,path_b);*strrchr(dir_b,'/')='\0'; + snprintf(feed_dir,sizeof(feed_dir),"%s/0",dir_a); + { + static const char *aliases[]={"feed.tree","feed.signatures","feed.bitfield","feed.data","feed.key","secret_key","localname"}; + static const char *storage[]={"tree","signatures","bitfield","data","key"}; + char file[600];size_t i; + for(i=0;i +#include +#include + +static void announce_new_messages(gui_state_t *state, size_t *seen) +{ + uint8_t self[32]; + size_t i; + if (cable_hex_key(state->cable_feed_key, self)) return; + for (i = *seen; i < state->cable_line_count; i++) { + const cable_room_event_t *event = &state->cable_lines[i].event; + if (event->type == CABLE_ROOM_TEXT && memcmp(event->sender, self, sizeof(self)) != 0) { + printf("RX\t%s\t%s\n", event->nick, event->text); + fflush(stdout); + } + } + *seen = state->cable_line_count; +} + +static void read_command(gui_state_t *state, char *line, int *running) +{ + size_t length = strlen(line); + while (length && (line[length - 1] == '\n' || line[length - 1] == '\r')) line[--length] = '\0'; + if (!strcmp(line, "QUIT")) { *running = 0; return; } + if (!strncmp(line, "SEND ", 5)) { + if (strlen(line + 5) > CABLE_CHAT_TEXT_BYTES) { puts("ERR message-too-long"); fflush(stdout); return; } + strcpy(state->cable_input, line + 5); + if (cable_room_submit(state)) { puts("ERR send-failed"); fflush(stdout); } + else { puts("SENT"); fflush(stdout); } + return; + } + if (!strncmp(line, "NICK ", 5)) { + if (strlen(line + 5) > CABLE_ROOM_NICK_BYTES) { puts("ERR nick-too-long"); fflush(stdout); return; } + snprintf(state->cable_input, sizeof(state->cable_input), "/nick %s", line + 5); + if (cable_room_submit(state)) { puts("ERR nick-failed"); fflush(stdout); } + else { puts("NICKED"); fflush(stdout); } + return; + } + if (!strncmp(line, "JOIN ", 5)) { + if (strlen(line + 5) > CABLE_ROOM_CHANNEL_BYTES) { puts("ERR channel-too-long"); fflush(stdout); return; } + snprintf(state->cable_input, sizeof(state->cable_input), "/join %s", line + 5); + if (cable_room_submit(state)) { puts("ERR join-failed"); fflush(stdout); } + else { puts("JOINED"); fflush(stdout); } + return; + } + puts("ERR unknown-command"); + fflush(stdout); +} + +int main(int argc, char **argv) +{ + static gui_state_t state; + SDL_Window *window; + SDL_Renderer *renderer; + char input[2048]; + size_t seen_lines = 0; + int running = 1, ready_reported = 0, peer_reported = 0; + if (argc != 2) { fputs("usage: test_cable_thang \n", stderr); return 2; } + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) != 0) { fprintf(stderr, "SDL init: %s\n", SDL_GetError()); return 1; } + window = SDL_CreateWindow("cable-thang live test", SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, 1000, 640, + SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE); + if (!window) { fprintf(stderr, "SDL window: %s\n", SDL_GetError()); SDL_Quit(); return 1; } + renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE); + if (!renderer) { + fprintf(stderr, "SDL renderer: %s\n", SDL_GetError()); + SDL_DestroyWindow(window); SDL_Quit(); return 1; + } + state.cable_thang_mode = 1; + if (cable_thang_room_init(&state, argv[1]) || cable_thang_network_start(&state)) { + perror("Cable SDL room startup"); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); return 1; + } + while (running) { + fd_set reads; + struct timeval tick = {0, 10000}; + int result; + SDL_Event event; + while (SDL_PollEvent(&event)) if (event.type == SDL_QUIT) running = 0; + network_pump(&state, SDL_GetTicks()); + if (!ready_reported && state.cable_feed_key[0]) { + printf("C_READY %s\n", state.cable_feed_key); fflush(stdout); ready_reported = 1; + } + if (!peer_reported && state.connected_peers) { puts("PEER_UP"); fflush(stdout); peer_reported = 1; } + announce_new_messages(&state, &seen_lines); + cable_thang_draw(renderer, 1000, 640, &state); + SDL_RenderPresent(renderer); + FD_ZERO(&reads); FD_SET(STDIN_FILENO, &reads); + result = select(STDIN_FILENO + 1, &reads, NULL, NULL, &tick); + if (result > 0 && FD_ISSET(STDIN_FILENO, &reads)) { + if (!fgets(input, sizeof(input), stdin)) running = 0; + else read_command(&state, input, &running); + } else if (result < 0 && errno != EINTR) running = 0; + } + network_stop(&state); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + return 0; +} diff --git a/tests/test_cli_parity.sh b/tests/test_cli_parity.sh new file mode 100644 index 0000000..a4f777b --- /dev/null +++ b/tests/test_cli_parity.sh @@ -0,0 +1,44 @@ +#!/bin/sh +set -eu + +dat=${1:-build/linux/dat} +ddat=${2:-build/linux/ddat} +root=$(mktemp -d /tmp/datgpt-cli-parity.XXXXXX) +trap 'rm -rf "$root"' EXIT HUP INT TERM +mkdir -p "$root/home" "$root/from-dat" "$root/from-ddat" +export HOME="$root/home" + +test "$("$dat" --version)" = "$("$ddat" --version)" +"$dat" --help | grep -q '^dat ' +"$ddat" --help | grep -q '^ddat ' +"$dat" --help | sed 's/ddat/CLI/g; s/dat/CLI/g' > "$root/dat-help" +"$ddat" --help | sed 's/ddat/CLI/g; s/dat/CLI/g' > "$root/ddat-help" +cmp "$root/dat-help" "$root/ddat-help" + +printf 'dat wrote this first\n' > "$root/from-dat/shared.txt" +"$dat" create "$root/from-dat" --title 'created by dat' > "$root/dat-url" +"$dat" sync "$root/from-dat" >/dev/null +"$ddat" status "$root/from-dat" | grep -q "$(cat "$root/dat-url")" +"$ddat" files "$root/from-dat" | grep -q shared.txt +old_dat_version=$("$ddat" log "$root/from-dat" | awk '/shared.txt/{print $1; exit}') +printf 'ddat updated this second\n' > "$root/from-dat/shared.txt" +"$ddat" sync "$root/from-dat" >/dev/null +test "$("$dat" cat "$old_dat_version" /shared.txt "$root/from-dat")" = 'dat wrote this first' +"$dat" verify "$root/from-dat" >/dev/null +"$dat" url "$old_dat_version" /shared.txt "$root/from-dat" | \ + grep -Eq '^dat://[0-9a-f]{64}\+[0-9]+/shared.txt$' +"$ddat" status "$root/from-dat" | grep -q "$(cat "$root/dat-url")" + +printf 'ddat created this first\n' > "$root/from-ddat/shared.txt" +"$ddat" create "$root/from-ddat" --title 'created by ddat' > "$root/ddat-url" +"$ddat" sync "$root/from-ddat" >/dev/null +"$dat" status "$root/from-ddat" | grep -q "$(cat "$root/ddat-url")" +"$dat" files "$root/from-ddat" | grep -q shared.txt +old_ddat_version=$("$dat" log "$root/from-ddat" | awk '/shared.txt/{print $1; exit}') +printf 'dat updated this second\n' > "$root/from-ddat/shared.txt" +"$dat" sync "$root/from-ddat" >/dev/null +test "$("$ddat" cat "$old_ddat_version" /shared.txt "$root/from-ddat")" = 'ddat created this first' +"$ddat" verify "$root/from-ddat" >/dev/null +"$dat" status "$root/from-ddat" | grep -q "$(cat "$root/ddat-url")" + +echo 'dat/ddat shared archive, address, read, update, and history: PASS' diff --git a/tests/test_discovery.c b/tests/test_discovery.c new file mode 100644 index 0000000..f8cc03b --- /dev/null +++ b/tests/test_discovery.c @@ -0,0 +1,234 @@ +#include "cesspool.h" +#include "dat_wire.h" +#include "sleep_crypto.h" +#include "sleep.h" + +#include +#include +#ifdef _WIN32 +#include +#include +#else +#include +#include +#endif +#include +#include +#ifndef _WIN32 +#include +#endif +#ifdef _WIN32 +#include +typedef SOCKET test_socket_t; +#else +#include +typedef int test_socket_t; +#endif + +static uint16_t free_udp_port(void) +{ + test_socket_t fd=socket(AF_INET,SOCK_DGRAM,0);struct sockaddr_in address; + uint16_t port; +#ifdef _WIN32 + int length=sizeof(address); + assert(fd!=INVALID_SOCKET); +#else + socklen_t length=sizeof(address); + assert(fd>=0); +#endif + memset(&address,0,sizeof(address));address.sin_family=AF_INET;address.sin_addr.s_addr=htonl(INADDR_LOOPBACK); + assert(bind(fd,(struct sockaddr*)&address,sizeof(address))==0);assert(getsockname(fd,(struct sockaddr*)&address,&length)==0); + port=ntohs(address.sin_port); +#ifdef _WIN32 + closesocket(fd); +#else + close(fd); +#endif + return port; +} + +int main(void) +{ + static const uint8_t hypercore[] = "hypercore"; + static const uint8_t namespace[] = DAT_NETWORK_PROTOCOL; + uint8_t key[32], expected[32], actual[32], nonce[24], input[513]; + uint8_t whole[513], chunked[513]; + memset(key, 0x42, sizeof(key)); +#ifdef _WIN32 + { + WSADATA winsock; + assert(WSAStartup(MAKEWORD(2, 2), &winsock) == 0); + } +#endif + assert(crypto_generichash(expected, sizeof(expected), hypercore, + sizeof(hypercore) - 1, key, sizeof(key)) == 0); + assert(sleep_compute_discovery_key(key, actual) == 0); + assert(!memcmp(expected, actual, sizeof(actual))); + /* Generated by extra/dillo-dat/node_modules/hypercore-crypto for a + * 32-byte feed key filled with 0x42 (historical Dat v6/v7 stack). */ + { + static const uint8_t js_hypercore_discovery_key[32] = { + 0xb0,0xfa,0xa6,0x16,0x21,0x2d,0x99,0x23, + 0x9f,0x50,0x7a,0x68,0x8f,0x50,0xcd,0x60, + 0xe6,0x51,0x4d,0xfb,0x62,0xae,0xf4,0xfc, + 0xfc,0x82,0x59,0xd6,0x46,0x96,0x35,0x91 + }; + assert(!memcmp(actual, js_hypercore_discovery_key, sizeof(actual))); + } + memset(nonce, 0x19, sizeof(nonce)); + for (size_t i = 0; i < sizeof(input); i++) input[i] = (uint8_t)(i * 37u + 11u); + assert(sleep_stream_xsalsa20_xor(whole, input, sizeof(input), nonce, key, 0) == 0); + { + size_t offset = 0; + static const size_t pieces[] = {1, 63, 64, 65, 127, 193}; + for (size_t i = 0; i < sizeof(pieces) / sizeof(pieces[0]); i++) { + assert(sleep_stream_xsalsa20_xor(chunked + offset, input + offset, + pieces[i], nonce, key, offset) == 0); + offset += pieces[i]; + } + assert(offset == sizeof(input)); + } + assert(!memcmp(whole, chunked, sizeof(whole))); + { + cesspool_t transactions; + uint16_t id; + memset(&transactions, 0, sizeof(transactions)); + assert(cesspool_transaction_begin(&transactions, CESSPOOL_LOOKUP, key, + 100, &id) == 0); + assert(cesspool_transaction_finish(&transactions, id) == 0); + assert(cesspool_transaction_begin(&transactions, CESSPOOL_PING, key, + 10, &id) == 0); + assert(cesspool_tick(&transactions, 10) == 0); + errno = 0; + assert(cesspool_transaction_finish(&transactions, id) != 0 && errno == ENOENT); + } + assert(crypto_generichash(expected, sizeof(expected), namespace, + sizeof(namespace) - 1, key, sizeof(key)) == 0); + assert(cesspool_discovery_key(key, actual) == 0); + assert(!memcmp(expected, actual, sizeof(actual))); + { + cesspool_t relay, publisher, lookup; + cesspool_peer_t *peers[4]; + uint8_t relay_id[32] = {0x10}, publisher_id[32] = {0x11}, lookup_id[32] = {0x22}; + uint8_t topic[32] = {0x33}, archive_topic_a[32] = {0x34}; + uint8_t archive_topic_b[32] = {0x35}; + uint8_t packet[80]; + cesspool_message_t message, decoded; + size_t packet_len; + uint16_t relay_port = free_udp_port(), publisher_port = free_udp_port(), lookup_port = free_udp_port(); + assert(cesspool_init(&relay, relay_id) == 0); + assert(cesspool_init(&publisher, publisher_id) == 0); + assert(cesspool_init(&lookup, lookup_id) == 0); + assert(cesspool_listen(&relay, "localhost", relay_port) == 0); + assert(cesspool_listen(&publisher, "localhost", publisher_port) == 0); + assert(cesspool_listen(&lookup, "localhost", lookup_port) == 0); + assert(relay_port && publisher_port && lookup_port && relay_port != publisher_port && relay_port != lookup_port && publisher_port != lookup_port); + assert(cesspool_set_bootstrap_server(&relay, 1) == 0); + assert(cesspool_add_bootstrap(&publisher, "localhost", relay_port) == 0); + assert(cesspool_add_bootstrap(&lookup, "127.0.0.1", relay_port) == 0); + /* A third party's REPLY packet must not mark the configured node + * online or inject forged rendezvous entries. */ + { + cesspool_message_t forged; + uint8_t forged_packet[80]; + size_t forged_length; + struct sockaddr_in *target; + dat_peer_t forged_target; + memset(&forged, 0, sizeof(forged)); + forged.type = CESSPOOL_REPLY; + memcpy(forged.peer_id, publisher_id, sizeof(publisher_id)); + memcpy(forged.topic, topic, sizeof(topic)); + forged.payload[0] = 0x1a; /* malformed one-byte peer record */ + forged.payload[1] = 0x01; + forged.payload[2] = 0xff; + forged.payload_len = 3; + assert(cesspool_encode(&forged, forged_packet, sizeof(forged_packet), &forged_length) == 0); + memset(&forged_target, 0, sizeof(forged_target)); + target = (struct sockaddr_in *)&forged_target.addr; + target->sin_family = AF_INET; + target->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + target->sin_port = htons(lookup_port); + forged_target.addrlen = sizeof(*target); + assert(datnet_send(&publisher.net, &forged_target, forged_packet, forged_length) == 0); + assert(cesspool_poll(&lookup, 100) >= 0); + assert(!lookup.bootstrap_reachable && lookup.announcement_count == 0); + } + assert(cesspool_join(&publisher, topic, publisher_port, 1, 0) == 0); + assert(cesspool_poll(&relay, 100) >= 0); + /* One client identity may announce a room and multiple archives. The + * rendezvous counters must report one peer across three distinct topics. */ + assert(cesspool_join(&publisher, archive_topic_a, publisher_port, 1, 0) == 0); + assert(cesspool_poll(&relay, 100) >= 0); + assert(cesspool_join(&publisher, archive_topic_b, publisher_port, 1, 0) == 0); + assert(cesspool_poll(&relay, 100) >= 0); + assert(cesspool_announced_peer_count(&relay) == 1); + assert(cesspool_announced_topic_count(&relay) == 3); + assert(cesspool_join(&lookup, topic, lookup_port, 0, 1) == 0); + assert(cesspool_poll(&relay, 100) >= 0); + assert(cesspool_poll(&lookup, 100) >= 0); + assert(lookup.bootstrap_reachable); + assert(cesspool_topic_peers(&lookup, topic, peers, 4) >= 1); + assert(!memcmp(peers[0]->public_key, publisher_id, 32)); + /* Expiring the lookup record must clear the client's Discovered row. */ + for (size_t a = 0; a < lookup.announcement_count; a++) + lookup.announcements[a].last_seen = 0; + lookup.bootstrap_count = 0; + for (size_t t = 0; t < lookup.topic_count; t++) + lookup.topics[t].refresh_at = UINT64_MAX; + assert(cesspool_tick(&lookup, 0) == 0); + assert(cesspool_topic_peers(&lookup, topic, peers, 4) == 0); + assert(!cesspool_find_peer(&lookup, publisher_id)->discovered); + assert(cesspool_leave(&publisher, archive_topic_a) == 0); + assert(cesspool_poll(&relay, 100) >= 0); + assert(cesspool_announced_peer_count(&relay) == 1); + assert(cesspool_announced_topic_count(&relay) == 2); + assert(cesspool_tick(&relay, UINT64_MAX) == 0); + assert(cesspool_announced_peer_count(&relay) == 1); /* Fresh announcements keep it live. */ + memset(&message, 0, sizeof(message)); + message.type = CESSPOOL_PING; + assert(cesspool_encode(&message, packet, sizeof(packet), &packet_len) == 0); + assert(packet_len == 72); + assert(cesspool_decode(&decoded, packet, packet_len) == 0); + assert(decoded.type == CESSPOOL_PING); + assert(cesspool_decode(&decoded, packet, packet_len + 1) != 0); + memset(&message, 0, sizeof(message)); + message.type = CESSPOOL_REPLY; + message.payload_len = sizeof(message.payload); + assert(cesspool_encode(&message, packet, sizeof(packet), &packet_len) != 0); + assert(cesspool_leave(&publisher, topic) == 0); + assert(cesspool_leave(&publisher, archive_topic_b) == 0); + assert(cesspool_leave(&lookup, topic) == 0); + assert(cesspool_destroy(&relay) == 0); + assert(cesspool_destroy(&publisher) == 0); + assert(cesspool_destroy(&lookup) == 0); + } + { + cesspool_t routing; + cesspool_response_t response; + cesspool_peer_t *closest[4]; + uint8_t local_prefix[2] = {192, 168}, local_id[32] = {0}; + struct sockaddr_in *address; + memset(&response, 0, sizeof(response)); + response.peers_len = 6; + response.peers[0] = 127; response.peers[3] = 1; + response.peers[4] = 0x0c; response.peers[5] = 0xd2; + response.local_peers_len = 4; + response.local_peers[0] = 10; response.local_peers[1] = 1; + response.local_peers[2] = 0x0c; response.local_peers[3] = 0xd2; + assert(cesspool_init(&routing, local_id) == 0); + assert(cesspool_process_response(&routing, &response, local_prefix) == 0); + assert(routing.peer_count == 2 && routing.peers[1].local); + address = (struct sockaddr_in *)&routing.peers[1].address.addr; + assert(ntohs(address->sin_port) == 3282); + assert(((uint8_t *)&address->sin_addr)[0] == 192 && + ((uint8_t *)&address->sin_addr)[1] == 168 && + ((uint8_t *)&address->sin_addr)[2] == 10 && + ((uint8_t *)&address->sin_addr)[3] == 1); + assert(cesspool_closest(&routing, local_id, closest, 4) == 2); + assert(cesspool_destroy(&routing) == 0); + } +#ifdef _WIN32 + WSACleanup(); +#endif + return 0; +} diff --git a/tests/test_dns.c b/tests/test_dns.c new file mode 100644 index 0000000..7f66d8f --- /dev/null +++ b/tests/test_dns.c @@ -0,0 +1,113 @@ +#define _DEFAULT_SOURCE +#include "dat_dns.h" + +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#include +#include +#define test_unlink _unlink +#define test_rmdir _rmdir +static int test_setenv(const char *name, const char *value, int overwrite) +{ + if (!overwrite && getenv(name)) return 0; + return _putenv_s(name, value); +} +static int test_unsetenv(const char *name) { return _putenv_s(name, ""); } +static char *test_mkdtemp(char *path) +{ + int i; + for (i = 0; i < 1000; i++) { + char candidate[512]; + int n = snprintf(candidate, sizeof(candidate), "%s-%ld-%d", path, + (long)_getpid(), i); + if (n < 0 || (size_t)n >= sizeof(candidate)) return NULL; + if (_mkdir(candidate) == 0) { strcpy(path, candidate); return path; } + } + return NULL; +} +#else +#include +#define test_unlink unlink +#define test_rmdir rmdir +#define test_setenv setenv +#define test_unsetenv unsetenv +#define test_mkdtemp mkdtemp +#endif + +static size_t add_txt(uint8_t *packet, size_t off, const char *text, size_t len) +{ + packet[off++] = (uint8_t)len; + memcpy(packet + off, text, len); + return off + len; +} + +int main(void) +{ + uint8_t packet[160] = {0}; + uint8_t key[32]; + uint32_t ttl = 0; + char value[] = "datkey=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; + size_t off = 12; +#ifdef _WIN32 + char temporary[512] = "build/windows/dat-dns-test"; +#else + char temporary[] = "/tmp/dat-dns-test-XXXXXX"; +#endif + char old_home[1024], resolved_name[256]; + uint8_t local_key[DAT_KEY_BYTES], resolved_key[DAT_KEY_BYTES]; + int had_home = getenv("HOME") != NULL; + size_t i; + if (had_home) snprintf(old_home, sizeof(old_home), "%s", getenv("HOME")); + assert(test_mkdtemp(temporary) != NULL); + assert(test_setenv("HOME", temporary, 1) == 0); + packet[2] = 0x81; packet[3] = 0x80; + packet[5] = 1; /* one question */ + packet[7] = 1; /* one answer */ + packet[off++] = 7; memcpy(packet + off, "example", 7); off += 7; + packet[off++] = 3; memcpy(packet + off, "com", 3); off += 3; + packet[off++] = 0; + packet[off++] = 0; packet[off++] = 16; /* TXT question */ + packet[off++] = 0; packet[off++] = 1; /* IN */ + packet[off++] = 0xc0; packet[off++] = 0x0c; + packet[off++] = 0; packet[off++] = 16; /* TXT */ + packet[off++] = 0; packet[off++] = 1; /* IN */ + packet[off++] = 0; packet[off++] = 0; packet[off++] = 0; packet[off++] = 60; + packet[off++] = 0; packet[off++] = 73; + off = add_txt(packet, off, value, 20); + off = add_txt(packet, off, value + 20, strlen(value) - 20); + assert(off == 12 + 13 + 4 + 2 + 2 + 2 + 4 + 2 + 73); + assert(dat_dns_parse_answer(packet, (unsigned)off, key, &ttl) == 0); + assert(ttl == 60); + assert(key[0] == 0x01 && key[1] == 0x23 && key[31] == 0xef); + packet[off - 73] = 0xff; /* TXT string length exceeds the RDATA boundary */ + assert(dat_dns_parse_answer(packet, (unsigned)off, key, &ttl) == -1); + assert(errno == EPROTO); + for (i = 0; i < sizeof(local_key); i++) local_key[i] = (uint8_t)(i * 9u + 3u); + assert(dat_dns_name_set("Archive.Example.test", local_key) == 0); + assert(dat_dns_name_resolve("archive.example.test", resolved_key) == 0); + assert(!memcmp(local_key, resolved_key, sizeof(local_key))); + assert(dat_dns_resolve("ARCHIVE.EXAMPLE.TEST", resolved_key, &ttl) == 0 && ttl == 0); + assert(!memcmp(local_key, resolved_key, sizeof(local_key))); + assert(dat_dns_name_for_key(local_key, resolved_name, sizeof(resolved_name)) == 0); + assert(!strcmp(resolved_name, "archive.example.test")); + assert(dat_dns_name_remove("archive.example.test") == 0); + assert(dat_dns_name_resolve("archive.example.test", resolved_key) == -1); + if (had_home) assert(test_setenv("HOME", old_home, 1) == 0); + else assert(test_unsetenv("HOME") == 0); + { + char names[1200]; + snprintf(names, sizeof(names), "%s/.dat/dat-dns", temporary); + test_unlink(names); + snprintf(names, sizeof(names), "%s/.dat", temporary); + test_rmdir(names); + } + test_rmdir(temporary); + return 0; +} diff --git a/tests/test_dpi.sh b/tests/test_dpi.sh new file mode 100644 index 0000000..7ed7bd0 --- /dev/null +++ b/tests/test_dpi.sh @@ -0,0 +1,71 @@ +#!/bin/sh +set -eu +dat_dpi=${1:-build/linux/dat.dpi} +dat=${2:-build/linux/dat} +socket_probe=${3:-build/linux/test_dpi_socket} +tmp=$(mktemp -d /tmp/datgpt-dpi.XXXXXX) +trap 'rm -rf "$tmp"' EXIT HUP INT TERM +export HOME="$tmp/home" +mkdir -p "$HOME/.dillo" "$tmp/archive/docs/adir" "$tmp/archive/docs/zdir" +printf '12345 abcdef1234567890\n' > "$HOME/.dillo/dpid_comm_keys" +"$dat" create "$tmp/archive" --yes >/dev/null +archive_address=$("$dat" url / "$tmp/archive") +printf '((title "Dillo parity") (description "Scheme metadata") (url "%s") (unsafe (begin (display "EXECUTED"))))\n' \ + "$archive_address" > "$tmp/archive/dat.scm" +printf 'nested entry\n' > "$tmp/archive/docs/nested.txt" +printf 'a entry\n' > "$tmp/archive/docs/a.txt" +printf 'z entry\n' > "$tmp/archive/docs/z.txt" +printf '

    historic setup page

    \n' > "$tmp/archive/index.html" +"$dat" sync "$tmp/archive" >/dev/null +old_version=$("$dat" log "$tmp/archive" | awk '$NF == "index.html" { version = $1 } END { print version }') +old_address=$("$dat" url "$old_version" / "$tmp/archive") +printf '

    current live page

    \n' > "$tmp/archive/index.html" +"$dat" sync "$tmp/archive" >/dev/null +new_version=$("$dat" log "$tmp/archive" | awk '$NF == "index.html" { version = $1 } END { print version }') +new_address=$("$dat" url "$new_version" / "$tmp/archive") +latest_address=$("$dat" url / "$tmp/archive") +archive_root=${latest_address%/} +listing_address="${archive_root}/docs/" +parent_address="${archive_root}/docs/../" +stat_address="${archive_root}/index.html?stat=1" +key=$(od -An -vtx1 "$tmp/archive/.dat/metadata.key" | tr -d ' \n') +unswarm_address="dpi:/dat/?key=${key}&reload=dpi:/dat/&unswarm=unswarm" +cache="$HOME/.dillo/dat/archives/${key%${key#??}}/${key#??}" +mkdir -p "$cache" +cp -R "$tmp/archive/." "$cache/" + printf "\n\n\n\n\n\n\n\n\n\n\n" \ + "$old_address" "$new_address" "$latest_address" "$listing_address" "$parent_address" "$stat_address" "$unswarm_address" | + "$dat_dpi" --stdio-test > "$tmp/response" +grep -q 'start_send_page' "$tmp/response" || { cat "$tmp/response"; exit 1; } +if grep -q '^HTTP/1\.[01] ' "$tmp/response"; then + cat "$tmp/response" + echo 'Dillo DPIP response must start with MIME headers, not an HTTP status line' >&2 + exit 1 +fi +grep -q 'historic setup page' "$tmp/response" || { cat "$tmp/response"; exit 1; } +grep -q 'current live page' "$tmp/response" || { cat "$tmp/response"; exit 1; } +test "$(grep -o 'current live page' "$tmp/response" | wc -l)" -eq 3 +grep -q 'nested.txt' "$tmp/response" || { cat "$tmp/response"; exit 1; } +grep -q 'data:image/png;base64,' "$tmp/response" || { cat "$tmp/response"; exit 1; } +listing_order=$(grep -oE '(zdir|adir|z.txt|nested.txt|a.txt)' "$tmp/response" | sed -E 's/.*>(.*)$/\1/' | tr '\n' ' ') +test "$listing_order" = 'zdir adir z.txt nested.txt a.txt ' || { cat "$tmp/response"; exit 1; } +grep -q 'Dillo parity' "$tmp/response" || { cat "$tmp/response"; exit 1; } +if grep -q 'EXECUTED' "$tmp/response"; then cat "$tmp/response"; echo 'archive dat.scm was evaluated' >&2; exit 1; fi +grep -q '"directory": false' "$tmp/response" || { cat "$tmp/response"; exit 1; } +grep -q 'dpi:/dat/' "$tmp/response" || { cat "$tmp/response"; exit 1; } +grep -q "None

    ' "$tmp/response" || { cat "$tmp/response"; exit 1; } +printf "\n\n" | + "$dat_dpi" --stdio-test > "$tmp/quit-response" +grep -q 'Quitting' "$tmp/quit-response" || { cat "$tmp/quit-response"; exit 1; } + "$socket_probe" "$dat_dpi" "$latest_address" > "$tmp/socket-response" +grep -q "&2 + exit 1 +fi +echo 'Dillo DPIP auth, socket lifecycle/EOF framing, versions, Scheme metadata, listing, stat, dashboard, unswarm/reload, quit: PASS' diff --git a/tests/test_dpi_socket.c b/tests/test_dpi_socket.c new file mode 100644 index 0000000..7eb51de --- /dev/null +++ b/tests/test_dpi_socket.c @@ -0,0 +1,118 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int connect_local(unsigned short port) +{ + struct sockaddr_in address; + int fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) return -1; + memset(&address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_port = htons(port); + address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + if (connect(fd, (struct sockaddr *)&address, sizeof(address))) { + close(fd); + return -1; + } + return fd; +} + +static int send_all(int fd, const char *data, size_t length) +{ + while (length) { + ssize_t sent = send(fd, data, length, 0); + if (sent < 0 && errno == EINTR) continue; + if (sent <= 0) return -1; + data += sent; + length -= (size_t)sent; + } + return 0; +} + +static int request_page(unsigned short port, const char *url) +{ + const char *key = "abcdef1234567890"; + char request[4096], buffer[4096]; + int fd, n; + int length = snprintf(request, sizeof(request), + "\n", key, url); + if (length < 0 || (size_t)length >= sizeof(request)) return -1; + fd = connect_local(port); + if (fd < 0) return -1; + if (send_all(fd, request, (size_t)length) || shutdown(fd, SHUT_WR)) { + close(fd); + return -1; + } + while ((n = (int)recv(fd, buffer, sizeof(buffer), 0)) > 0) { + if (fwrite(buffer, 1, (size_t)n, stdout) != (size_t)n) { + close(fd); + return -1; + } + } + close(fd); + if (n < 0) return -1; + return fflush(stdout) ? -1 : 0; +} + +int main(int argc, char **argv) +{ + struct sockaddr_in address; + socklen_t address_length = sizeof(address); + unsigned short port; + int listener; + pid_t child; + int status, fd; + if (argc != 3) { + fprintf(stderr, "usage: %s DPI-HELPER URL\n", argv[0]); + return 2; + } + listener = socket(AF_INET, SOCK_STREAM, 0); + if (listener < 0) { perror("socket"); return 1; } + memset(&address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + address.sin_port = 0; + if (bind(listener, (struct sockaddr *)&address, sizeof(address)) || + listen(listener, 8) || getsockname(listener, (struct sockaddr *)&address, &address_length)) { + perror("bind/listen"); close(listener); return 1; + } + port = ntohs(address.sin_port); + child = fork(); + if (child < 0) { perror("fork"); close(listener); return 1; } + if (!child) { + if (dup2(listener, STDIN_FILENO) < 0) _exit(126); + close(listener); + execl(argv[1], argv[1], (char *)NULL); + perror("exec"); _exit(127); + } + close(listener); + if (request_page(port, argv[2])) { + perror("request page"); + kill(child, SIGTERM); + waitpid(child, NULL, 0); + return 1; + } + fd = connect_local(port); + { + const char *bye = "\n"; + if (fd < 0 || send_all(fd, bye, strlen(bye))) { + perror("send DpiBye"); + kill(child, SIGTERM); + waitpid(child, NULL, 0); + return 1; + } + } + shutdown(fd, SHUT_WR); + close(fd); + if (waitpid(child, &status, 0) < 0) { perror("waitpid"); return 1; } + return WIFEXITED(status) ? WEXITSTATUS(status) : 1; +} diff --git a/tests/test_gui_p2p.c b/tests/test_gui_p2p.c new file mode 100644 index 0000000..dd4ae00 --- /dev/null +++ b/tests/test_gui_p2p.c @@ -0,0 +1,391 @@ +#define _POSIX_C_SOURCE 200809L +#define main dat_thang_program_main +#include "../src/ddat_gui.c" +#undef main + +#include +#include +#include +#ifdef _WIN32 +#include +#include +#define test_mkdir(path) _mkdir(path) +#define test_setenv(name, value) _putenv_s((name), (value)) +#define test_pid _getpid +#define test_close_socket(fd) closesocket(fd) +typedef int test_socklen_t; +typedef SOCKET test_socket_t; +#define test_socket_valid(fd) ((fd) != INVALID_SOCKET) +#else +#include +#include +#include +#define test_mkdir(path) mkdir((path), 0700) +#define test_setenv(name, value) setenv((name), (value), 1) +#define test_pid getpid +#define test_close_socket(fd) close(fd) +typedef socklen_t test_socklen_t; +typedef int test_socket_t; +#define test_socket_valid(fd) ((fd) >= 0) +#endif + +static uint16_t unused_udp_port(void) +{ + test_socket_t fd = socket(AF_INET, SOCK_DGRAM, 0); + struct sockaddr_in address; + test_socklen_t length = sizeof(address); + uint16_t port; + assert(test_socket_valid(fd)); + memset(&address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + assert(bind(fd, (struct sockaddr *)&address, sizeof(address)) == 0); + assert(getsockname(fd, (struct sockaddr *)&address, &length) == 0); + port = ntohs(address.sin_port); + test_close_socket(fd); + return port; +} + +static void pause_one_millisecond(void) +{ + SDL_Delay(1); +} + +static void set_network_environment(uint16_t local_port, uint16_t bootstrap_port) +{ + char value[32]; + snprintf(value, sizeof(value), "%u", (unsigned)local_port); + assert(test_setenv("DDAT_PORT", value) == 0); + snprintf(value, sizeof(value), "127.0.0.1:%u", (unsigned)bootstrap_port); + assert(test_setenv("DDAT_BOOTSTRAP", value) == 0); +} + +static int peer_ready(const gui_state_t *state) +{ + size_t i; + if (!state->session_initialized) return 0; + for (i = 0; i < state->session.peer_count; i++) + if (state->session.peers[i].state == DAT_SESSION_PEER_READY) return 1; + return 0; +} + +#ifdef DAT_GUI_TEST_MEDIA +static int test_fm_audio_is_playing(gui_state_t *state) +{ + gui_fm_voice_t *voice = (gui_fm_voice_t *)state->fm_voice_ptr; + int active; + if (!voice || !state->audio_device) return 0; + SDL_LockAudioDevice(state->audio_device); + active = voice->active; + SDL_UnlockAudioDevice(state->audio_device); + return active; +} +#endif + +int main(void) +{ + static gui_state_t left, right; + char root[1024]; + char left_home[1024], right_home[1024], file_path[1200], destination[1200]; + char feed_directory[1024]; + uint8_t content_key[32]; + uint16_t left_port, right_port; + Uint32 now; + int elapsed; + #ifdef _WIN32 + WSADATA winsock; + int fixture_attempt; + assert(WSAStartup(MAKEWORD(2, 2), &winsock) == 0); + for (fixture_attempt = 0; fixture_attempt < 1000; fixture_attempt++) { + assert(snprintf(root, sizeof(root), "build/windows/test-gui-p2p-%ld-%d", + (long)test_pid(), fixture_attempt) < (int)sizeof(root)); + if (test_mkdir(root) == 0) break; + } + assert(fixture_attempt < 1000); + #else + strcpy(root, "/tmp/ddat-gui-p2p-XXXXXX"); + #endif + memset(&left, 0, sizeof(left)); + memset(&right, 0, sizeof(right)); + left_port = unused_udp_port(); + right_port = unused_udp_port(); + if (right_port == left_port) right_port = (uint16_t)(left_port + 1); + #ifndef _WIN32 + assert(mkdtemp(root) != NULL); + #endif + assert(snprintf(file_path, sizeof(file_path), "%s/payload.txt", root) < + (int)sizeof(file_path)); + { + FILE *file = fopen(file_path, "wb"); + assert(file != NULL); + assert(fputs("a real GUI Cesspool to uTP to SLEEP block", file) >= 0); + assert(fclose(file) == 0); + } + assert(snprintf(left_home, sizeof(left_home), "%s/left-home", root) < (int)sizeof(left_home)); + assert(snprintf(right_home, sizeof(right_home), "%s/right-home", root) < (int)sizeof(right_home)); + assert(test_mkdir(left_home) == 0); + assert(test_mkdir(right_home) == 0); + assert(snprintf(destination, sizeof(destination), "%s/download", root) < (int)sizeof(destination)); + assert(test_mkdir(destination) == 0); + assert(test_setenv("HOME", left_home) == 0); + assert(test_setenv("USERPROFILE", left_home) == 0); +#ifdef DAT_GUI_TEST_MEDIA + assert(SDL_Init(SDL_INIT_TIMER | SDL_INIT_AUDIO) == 0); +#else + assert(SDL_Init(SDL_INIT_TIMER) == 0); +#endif + assert(dat_scheme_runtime_init(&left.metadata_scheme) == 0); + strcpy(left.archive_path, root); + strcpy(right.archive_path, destination); + assert(prepare_archive(&left) == 0); + assert(left.archive_open && left.version >= 1); + left.selected_version = 0; + assert(click_archive_version_bar(&left, 210, 80, 84)); + assert(left.selected_version == 0); + assert(!click_archive_version_bar(&left, 209, 80, 84)); + assert(!click_archive_version_bar(&left, 210, 73, 84)); + assert(!click_archive_version_bar(&left, 242, 80, 84)); + assert(click_archive_version_bar(&left, 226, 80, 84)); + assert(left.selected_version == 1); + left.selected_version = left.version; + assert(click_archive_version_bar(&left, 226, 80, 84)); + assert(left.selected_version == left.version); + assert(click_archive_version_bar(&left, 210, 80, 84)); + assert(left.selected_version == left.version - 1); + { + dat_link_t link, parsed; + char address[DAT_LINK_BYTES]; + memset(&link, 0, sizeof(link)); + memcpy(link.key, left.archive_key_bytes, sizeof(link.key)); + link.has_version = 1; + link.version = left.selected_version; + assert(dat_link_format(&link, address, sizeof(address)) == 0); + assert(dat_link_parse(address, &parsed) == 0); + assert(parsed.has_version && parsed.version == left.selected_version); + } + { + dat_archive_t source; + char uri[DAT_URL_BYTES]; + assert(dat_archive_open(&source, root) == 0); + memcpy(right.remote_metadata_key, source.metadata_key, sizeof(right.remote_metadata_key)); + right.remote_key_valid = 1; + assert(dat_key_to_url(source.metadata_key, uri) == 0); + strcpy(right.remote_address, uri); + strcpy(right.remote_archive, uri + 6); + strcpy(right.remote_site, "P2P TEST SOURCE"); + strcpy(right.remote_version, "LATEST"); + assert(strlen(destination) + sizeof("/.dat") <= sizeof(feed_directory)); + strcpy(feed_directory, destination); + strcat(feed_directory, "/.dat"); + memcpy(content_key, source.content_key, sizeof(content_key)); + dat_archive_close(&source); + } + assert(sleep_crypto_init() == 0); + set_network_environment(left_port, right_port); + network_start(&left); + assert(left.network_requested && left.session_initialized); + assert(!left.online); /* Reachability starts false until a response arrives. */ + assert(test_setenv("HOME", right_home) == 0); + assert(test_setenv("USERPROFILE", right_home) == 0); + set_network_environment(right_port, left_port); + network_start_remote_import(&right); + assert(right.network_requested && right.session_initialized); + assert(!right.online); + assert(!memcmp(right.identity.pubkey, right.pool.net.peerid, 32)); + for (elapsed = 0; elapsed < 12000; elapsed++) { + now = SDL_GetTicks(); + network_pump(&left, now); + if (memcmp(right.identity.pubkey, right.pool.net.peerid, 32)) { + fprintf(stderr, "left client pump overwrote right Cesspool identity at %d ms\\n", elapsed); + abort(); + } + network_pump(&right, now); + if (memcmp(right.identity.pubkey, right.pool.net.peerid, 32)) { + size_t changed; + for (changed = 0; changed < 32 && + right.identity.pubkey[changed] == right.pool.net.peerid[changed]; changed++) {} + fprintf(stderr, "right identity changed during P2P pump at %d ms: byte %lu %02x -> %02x pool=%d online=%d status=%s\n", + elapsed, (unsigned long)changed, right.identity.pubkey[changed], + right.pool.net.peerid[changed], right.pool_initialized, right.online, + right.network_status); + abort(); + } + if (peer_ready(&left) && peer_ready(&right) && right.session.content_key_known && + sleep_feed_has(&right.content_feed, 1)) break; + pause_one_millisecond(); + } + if (!peer_ready(&left) || !peer_ready(&right)) { + fprintf(stderr, "peer setup timeout: L=%02x%02x R=%02x%02x left discovered=%lu peers=%lu ps=%d mask=%u rx=%lu tx=%llu/%llu status=%s; right discovered=%lu peers=%lu ps=%d mask=%u rx=%lu tx=%llu/%llu status=%s\n", + left.identity.pubkey[0],left.identity.pubkey[1],right.identity.pubkey[0],right.identity.pubkey[1], + (unsigned long)discovered_peer_count(&left), + (unsigned long)left.session.peer_count, + left.session.peers[0].state, left.session.peers[0].remote_feed_mask, + (unsigned long)left.session.peers[0].rx_buffer_len, + (unsigned long long)left.bytes_received, + (unsigned long long)left.bytes_sent, left.network_status, + (unsigned long)discovered_peer_count(&right), + (unsigned long)right.session.peer_count, + right.session.peers[0].state, right.session.peers[0].remote_feed_mask, + (unsigned long)right.session.peers[0].rx_buffer_len, + (unsigned long long)right.bytes_received, + (unsigned long long)right.bytes_sent, right.network_status); + for (size_t i = 0; i < GUI_TRANSPORT_MAX; i++) { + if (left.transports[i].used) + fprintf(stderr, "left transport %lu state=%d wait=%d auth=%d hello=%d/%d ack=%d/%d seq=%u recv=%u inflight=%lu\n", + (unsigned long)i, left.transports[i].utp.state, + left.transports[i].waiting_incoming, left.transports[i].authenticated, + left.transports[i].hello_sent, left.transports[i].hello_received, + left.transports[i].ack_sent, left.transports[i].ack_received, + left.transports[i].utp.sequence, left.transports[i].utp.acknowledgement, + (unsigned long)left.transports[i].utp.bytes_in_flight); + if (right.transports[i].used) + fprintf(stderr, "right transport %lu state=%d wait=%d auth=%d hello=%d/%d ack=%d/%d seq=%u recv=%u inflight=%lu\n", + (unsigned long)i, right.transports[i].utp.state, + right.transports[i].waiting_incoming, right.transports[i].authenticated, + right.transports[i].hello_sent, right.transports[i].hello_received, + right.transports[i].ack_sent, right.transports[i].ack_received, + right.transports[i].utp.sequence, right.transports[i].utp.acknowledgement, + (unsigned long)right.transports[i].utp.bytes_in_flight); + } + for (size_t i = 0; i < left.pool.peer_count; i++) + fprintf(stderr, "left known peer %lu port=%u topic=%d\n", (unsigned long)i, + ntohs(((struct sockaddr_in *)&left.pool.peers[i].address.addr)->sin_port), + !memcmp(left.pool.peers[i].discovery_topic, left.discovery_topic, 32)); + for (size_t i = 0; i < right.pool.peer_count; i++) + fprintf(stderr, "right known peer %lu port=%u topic=%d\n", (unsigned long)i, + ntohs(((struct sockaddr_in *)&right.pool.peers[i].address.addr)->sin_port), + !memcmp(right.pool.peers[i].discovery_topic, right.discovery_topic, 32)); + abort(); + } + assert(right.content_feed.length > 1); + assert(left.session.peers[0].payload_bytes_sent > 0); + assert(right.session.peers[0].payload_bytes_received > 0); + assert(sleep_feed_has(&right.content_feed, 1)); + if (!transport_by_id(&left, right.identity.pubkey)) { + fprintf(stderr, "identity mismatch: L identity=%02x pool=%02x session=%02x; R identity=%02x pool=%02x session=%02x\n", + left.identity.pubkey[0], left.pool.net.peerid[0], left.session.peer_id[0], + right.identity.pubkey[0], right.pool.net.peerid[0], right.session.peer_id[0]); + for (size_t i = 0; i < GUI_TRANSPORT_MAX; i++) + fprintf(stderr, "left transport slot %lu used=%d state=%d auth=%d peer=%02x expected=%02x sessionpeer=%02x session=%d\n", + (unsigned long)i, left.transports[i].used, left.transports[i].utp.state, + left.transports[i].authenticated, left.transports[i].peer_id[0], + right.identity.pubkey[0], left.session.peers[0].peer_id[0], + left.session.peers[0].state); + abort(); + } + assert(transport_by_id(&left, right.identity.pubkey)->authenticated); + assert(transport_by_id(&right, left.identity.pubkey) != NULL); + assert(transport_by_id(&right, left.identity.pubkey)->authenticated); + transfer_disconnect(&left, 0, 0); + assert(left.transfer_paused[0] && left.session.peers[0].paused); + assert(peer_ready(&left) && peer_ready(&right)); + transfer_resume(&left, 0); + assert(!left.transfer_paused[0] && !left.session.peers[0].paused); + for (elapsed = 0; elapsed < 5000; elapsed++) { + now = SDL_GetTicks(); + network_pump(&left, now); + network_pump(&right, now); + if (peer_ready(&left) && peer_ready(&right)) break; + pause_one_millisecond(); + } + if (!peer_ready(&left) || !peer_ready(&right)) + fprintf(stderr, "resume timeout IDs=%02x/%02x: left session=%d transport=%d/%d wait=%d auth=%d hello=%d/%d ack=%d/%d right session=%d transport=%d/%d wait=%d auth=%d hello=%d/%d ack=%d/%d statuses=%s | %s\n", + left.identity.pubkey[0], right.identity.pubkey[0], + left.session.peers[0].state, left.transports[0].used, + left.transports[0].utp.state, left.transports[0].waiting_incoming, + left.transports[0].authenticated, left.transports[0].hello_sent, + left.transports[0].hello_received, left.transports[0].ack_sent, + left.transports[0].ack_received, right.session.peers[0].state, + right.transports[0].used, right.transports[0].utp.state, + right.transports[0].waiting_incoming, right.transports[0].authenticated, + right.transports[0].hello_sent, right.transports[0].hello_received, + right.transports[0].ack_sent, right.transports[0].ack_received, + left.network_status, right.network_status); + assert(peer_ready(&left) && peer_ready(&right)); + assert(left.online && right.online); + memcpy(left.chat_peer_id,right.identity.pubkey,32); + left.chat_open=1;strcpy(left.chat_input,"hello from cable over the authenticated peer link"); + if (cable_chat_send(&left) != 0) { + gui_transport_peer_t *chat_transport = transport_by_id(&left, left.chat_peer_id); + fprintf(stderr, "chat after resume: transport=%p used=%d auth=%d utp=%d session=%d errno=%d\n", + (void *)chat_transport, chat_transport ? chat_transport->used : 0, + chat_transport ? chat_transport->authenticated : 0, + chat_transport ? chat_transport->utp.state : 0, + left.session.peers[0].state, errno); + abort(); + } + for(elapsed=0;elapsed<3000;elapsed++){ + now=SDL_GetTicks();network_pump(&left,now);network_pump(&right,now); + if(right.chat_line_count)break; + pause_one_millisecond(); + } + assert(right.chat_line_count==1); + assert(!strcmp(right.chat_lines[0].text,"hello from cable over the authenticated peer link")); + assert(!right.chat_lines[0].outgoing); + { + uint8_t *received = NULL; + size_t received_length = 0; + dat_archive_t downloaded; + sleep_metadata_entry_t entry; + const char expected[] = "a real GUI Cesspool to uTP to SLEEP block"; + assert(dat_archive_open(&downloaded, destination) == 0); + assert(dat_archive_find(&downloaded, "payload.txt", &entry) == 0); + assert(dat_archive_read_file(&downloaded, &entry, &received, &received_length) == 0); + assert(received_length == sizeof(expected) - 1); + assert(!memcmp(received, expected, received_length)); + free(received); + received = NULL; + dat_archive_close(&downloaded); + assert(sleep_feed_read(&right.content_feed, 1, &received, &received_length) == 0); + /* A content feed block is not the same thing as one source file: the + * archive reader reconstructs files from metadata offsets and blocks. */ + assert(received_length > 0); + free(received); + } + sleep_feed_close(&right.content_feed); + assert(sleep_feed_open_remote(&right.content_feed, feed_directory, + "content", content_key) == 0); + assert(sleep_feed_has(&right.content_feed, 1)); + { + sleep_feed_t restored; + memset(&restored, 0, sizeof(restored)); + strcpy(restored.directory, feed_directory); + strcpy(restored.name, "content"); + assert(sleep_feed_load_remote(&restored, content_key) == 0); + assert(sleep_feed_has(&restored, 1)); + sleep_feed_close(&restored); + } +#ifdef DAT_GUI_TEST_MEDIA + left.audio_enabled = 1; + fm_audio_start(&left); + assert(left.audio_device != 0); + audio_cue_broadcast(&right, 1); + for (elapsed = 0; elapsed < 2000; elapsed++) { + now = SDL_GetTicks(); + network_pump(&left, now); + network_pump(&right, now); + if (((gui_fm_voice_t *)left.fm_voice_ptr)->dolphin && test_fm_audio_is_playing(&left)) break; + pause_one_millisecond(); + } + assert(((gui_fm_voice_t *)left.fm_voice_ptr)->dolphin); + assert(test_fm_audio_is_playing(&left)); + fm_audio_play(&left); + assert(test_fm_audio_is_playing(&left)); + SDL_Delay(2100); + assert(!test_fm_audio_is_playing(&left)); + fm_audio_stop(&left); +#endif + network_stop(&right); + network_stop(&left); + dat_scheme_runtime_close(&left.metadata_scheme); + SDL_Quit(); +#ifdef _WIN32 + WSACleanup(); +#endif +#ifdef DAT_GUI_TEST_MEDIA + puts("dat-thang authenticated Cesspool transfer, chat, and dolphin cue: PASS"); +#else + puts("dat-thang authenticated Cesspool archive transfer, pause/resume, and chat: PASS"); +#endif + return 0; +} diff --git a/tests/test_paths.c b/tests/test_paths.c new file mode 100644 index 0000000..ca16108 --- /dev/null +++ b/tests/test_paths.c @@ -0,0 +1,42 @@ +#include "dat_paths.h" + +#include +#include +#include +#ifdef _WIN32 +#include +#define test_pid _getpid +#else +#include +#define test_pid getpid +#endif + +static void write_text(const char *path, const char *text) +{ + FILE *file = fopen(path, "wb"); + assert(file); + assert(fwrite(text, 1, strlen(text), file) == strlen(text)); + assert(fclose(file) == 0); +} + +int main(void) +{ + char destination[256], temporary[256], contents[32]; + FILE *file; + int n; + n = snprintf(destination, sizeof(destination), "build/test-paths-%ld", (long)test_pid()); + assert(n > 0 && (size_t)n < sizeof(destination)); + n = snprintf(temporary, sizeof(temporary), "%s.tmp", destination); + assert(n > 0 && (size_t)n < sizeof(temporary)); + write_text(destination, "before"); + write_text(temporary, "after"); + assert(dat_replace_file(temporary, destination) == 0); + file = fopen(destination, "rb"); + assert(file); + assert(fgets(contents, sizeof(contents), file)); + assert(fclose(file) == 0); + assert(strcmp(contents, "after") == 0); + assert(remove(destination) == 0); + puts("user-state atomic replacement: PASS"); + return 0; +} diff --git a/tests/test_proto.c b/tests/test_proto.c new file mode 100644 index 0000000..30e0d79 --- /dev/null +++ b/tests/test_proto.c @@ -0,0 +1,4 @@ +#include "sleep_proto.h" +#include +#include +int main(void){sleep_metadata_header_t a,b;unsigned char buf[128];size_t n;memset(&a,0,sizeof(a));strcpy(a.type,"hyperdrive");memset(a.content,0x5a,32);a.content_len=32;assert(sleep_metadata_header_encode(&a,buf,sizeof(buf),&n)==0);assert(n==46);assert(sleep_metadata_header_decode(&b,buf,n)==0);assert(!strcmp(b.type,"hyperdrive"));assert(b.content_len==32&&!memcmp(a.content,b.content,32));return 0;} diff --git a/tests/test_replication.c b/tests/test_replication.c new file mode 100644 index 0000000..9fa7127 --- /dev/null +++ b/tests/test_replication.c @@ -0,0 +1,29 @@ +#include "sleep_replication.h" + +#include +#include +#include + +int main(void) +{ + sleep_feed_t feed; + sleep_replication_t replica; + sleep_replication_request_t request; + memset(&feed, 0, sizeof(feed)); + assert(sleep_replication_init(&replica, &feed, 1) == 0); + for (uint64_t i = 0; i < SLEEP_REPLICATION_QUEUE; i++) { + request.index = i; request.bytes = 0; + request.hash = request.nodes = 1; + assert(sleep_replication_request(&replica, &request) == 0); + } + assert(sleep_replication_want(&replica, 1000, 1) == -1); + assert(errno == ENOSPC && replica.request_count == SLEEP_REPLICATION_QUEUE); + assert(sleep_replication_unwant(&replica, 0, 64) == 0); + assert(replica.request_count == 64); + assert(sleep_replication_want(&replica, 1000, 2) == 0); + assert(replica.request_count == 66); + assert(sleep_replication_want(&replica, 1000, 2) == 0); + assert(replica.request_count == 66); + assert(sleep_replication_next(&replica, &request) == 1 && request.index == 64); + return 0; +} diff --git a/tests/test_session.c b/tests/test_session.c new file mode 100644 index 0000000..0815ef1 --- /dev/null +++ b/tests/test_session.c @@ -0,0 +1,476 @@ +#define _POSIX_C_SOURCE 200809L +#include "dat_session.h" +#include "sleep.h" +#include "sleep_crypto.h" +#include +#include +#include +#include +#include +#include + +typedef struct send_capture { + uint8_t peer[DAT_PEER_ID_BYTES]; + uint8_t frame[4096]; + size_t length; + uint8_t history[32][4096]; + size_t history_length[32]; + unsigned calls; +} send_capture_t; + +static int capture_send(void *context, const uint8_t peer_id[DAT_PEER_ID_BYTES], + const uint8_t *frame, size_t length) +{ + send_capture_t *capture = context; + assert(length <= sizeof(capture->frame)); + memcpy(capture->peer, peer_id, sizeof(capture->peer)); + memcpy(capture->frame, frame, length); + capture->length = length; + if (capture->calls < 32) { + memcpy(capture->history[capture->calls], frame, length); + capture->history_length[capture->calls] = length; + } + capture->calls++; + return 0; +} + +static void count_peer(void *context, const dat_session_peer_t *peer) +{ + unsigned *count = context; + assert(peer->state == DAT_SESSION_PEER_DISCOVERED); + (*count)++; +} + +static void stream_transfer(dat_session_t *receiver, + const uint8_t peer_id[DAT_PEER_ID_BYTES], + int source_fd, int receiver_fd, + const uint8_t *frame, size_t frame_len) +{ + size_t sent = 0, received = 0; + uint8_t incoming[8192]; + while (sent < frame_len) { + ssize_t n = write(source_fd, frame + sent, frame_len - sent); + assert(n > 0); + sent += (size_t)n; + } + while (received < frame_len) { + ssize_t n = read(receiver_fd, incoming + received, frame_len - received); + assert(n > 0); + received += (size_t)n; + } + assert(dat_session_receive(receiver, peer_id, incoming, received) == 0); +} + +#ifndef DAT_LEGACY_HYPERCORE +static int count_poll(void *context, int timeout_ms) +{ + unsigned *count = context; + assert(timeout_ms == 0); + (*count)++; + return 0; +} +#endif + +#ifdef DAT_LEGACY_HYPERCORE +static void legacy_session_handshake(void) +{ + cesspool_t pool; + dat_session_t session; + sleep_feed_t metadata, content; + sleep_keypair_t metadata_keys, content_keys; + send_capture_t capture; + cesspool_peer_t discovered; + dat_session_peer_t *peer; + dat_feed_open_t feed; + dat_handshake_t handshake, decoded; + dat_wire_message_t wire; + uint8_t archive_key[32] = {1}, local_id[32] = {2}, remote_id[32] = {3}; + uint8_t remote_nonce[24] = {4}, payload[256], encoded[512], plain[512]; + uint8_t frame[512]; + size_t payload_len, encoded_len, content_frame_len, frame_len, consumed; + unsigned discovered_count = 0; + char feed_dir[] = "/tmp/datgpt-v6-XXXXXX"; + memset(&capture, 0, sizeof(capture)); + assert(mkdtemp(feed_dir) != NULL); + assert(sleep_gen_keypair(&metadata_keys) == 0); + assert(sleep_gen_keypair(&content_keys) == 0); + assert(sleep_feed_open(&metadata, feed_dir, "metadata", &metadata_keys) == 0); + assert(sleep_feed_open(&content, feed_dir, "content", &content_keys) == 0); + assert(cesspool_init(&pool, local_id) == 0); + assert(cesspool_listen(&pool, "127.0.0.1", 0) == 0); + assert(cesspool_add_bootstrap(&pool, "127.0.0.1", 9) == 0); + assert(dat_session_init(&session, &pool, archive_key, local_id, pool.port, + &metadata, metadata_keys.pubkey, &content, + content_keys.pubkey, capture_send, &capture) == 0); + assert(dat_session_set_peer_callback(&session, count_peer, &discovered_count) == 0); + assert(dat_session_start(&session, 1, 1) == 0); + memset(&discovered, 0, sizeof(discovered)); + memcpy(discovered.public_key, remote_id, 32); + memcpy(discovered.discovery_topic, session.discovery_topic, 32); + discovered.discovered = 1; + assert(dat_session_peer_discovered(&session, &discovered) == 0); + assert(discovered_count == 1); + assert(dat_session_peer_secure(&session, remote_id, 1) == 0); + assert(capture.calls == 3); + peer = dat_session_find_peer(&session, remote_id); + assert(peer && peer->tx_stream_encrypted && peer->tx_offset > 0); + assert(dat_wire_decode(&wire, capture.history[0], capture.history_length[0], &consumed) == 0); + assert(wire.channel == 0 && wire.type == DAT_MSG_FEED); + assert(dat_feed_open_decode(&feed, wire.payload, wire.payload_len) == 0 && feed.has_nonce); + assert(sleep_stream_xsalsa20_xor(plain, capture.history[1], capture.history_length[1], + feed.nonce, metadata_keys.pubkey, 0) == 0); + assert(dat_wire_decode(&wire, plain, capture.history_length[1], &consumed) == 0); + assert(wire.channel == 0 && wire.type == DAT_MSG_HANDSHAKE); + assert(dat_handshake_decode(&decoded, wire.payload, wire.payload_len) == 0); + assert(decoded.has_peer_id); + assert(sleep_stream_xsalsa20_xor(plain, capture.history[2], capture.history_length[2], + feed.nonce, metadata_keys.pubkey, + capture.history_length[1]) == 0); + assert(dat_wire_decode(&wire, plain, capture.history_length[2], &consumed) == 0); + assert(wire.channel == 1 && wire.type == DAT_MSG_FEED); + /* The first Feed carries the responder nonce in clear; all following bytes stream-cipher. */ + memset(&feed, 0, sizeof(feed)); + assert(sleep_compute_discovery_key(metadata_keys.pubkey, feed.discovery_key) == 0); + memcpy(feed.nonce, remote_nonce, sizeof(remote_nonce)); + feed.has_nonce = 1; + assert(dat_feed_open_encode(&feed, payload, sizeof(payload), &payload_len) == 0); + wire.channel = 0; wire.type = DAT_MSG_FEED; + wire.payload = payload; wire.payload_len = payload_len; + assert(dat_wire_encode(&wire, frame, sizeof(frame), &frame_len) == 0); + assert(dat_session_receive(&session, remote_id, frame, frame_len / 2) == 0); + assert(peer->state == DAT_SESSION_PEER_SECURE); + assert(dat_session_receive(&session, remote_id, frame + frame_len / 2, + frame_len - frame_len / 2) == 0); + assert(dat_handshake_init(&handshake, remote_id, 0) == 0); + assert(dat_handshake_encode(&handshake, payload, sizeof(payload), &payload_len) == 0); + wire.channel = 0; wire.type = DAT_MSG_HANDSHAKE; + wire.payload = payload; wire.payload_len = payload_len; + assert(dat_wire_encode(&wire, encoded, sizeof(encoded), &encoded_len) == 0); + assert(sleep_compute_discovery_key(content_keys.pubkey, feed.discovery_key) == 0); + feed.has_nonce = 0; + assert(dat_feed_open_encode(&feed, payload, sizeof(payload), &payload_len) == 0); + wire.channel = 1; wire.type = DAT_MSG_FEED; + wire.payload = payload; wire.payload_len = payload_len; + assert(dat_wire_encode(&wire, encoded + encoded_len, + sizeof(encoded) - encoded_len, &content_frame_len) == 0); + encoded_len += content_frame_len; + assert(sleep_stream_xsalsa20_xor(frame, encoded, encoded_len, remote_nonce, + metadata_keys.pubkey, 0) == 0); + assert(dat_session_receive(&session, remote_id, frame, encoded_len) == 0); + assert(peer->state == DAT_SESSION_PEER_READY && peer->rx_offset == encoded_len && + capture.calls == 3); + assert(dat_session_peer_down(&session, remote_id) == 0); + assert(dat_session_peer_discovered(&session, &discovered) == 0); + assert(discovered_count == 2); + assert(dat_session_close(&session) == 0); + assert(cesspool_destroy(&pool) == 0); + sleep_feed_close(&metadata); + sleep_feed_close(&content); +} +#endif + +#ifndef DAT_LEGACY_HYPERCORE +static void receive_standard_data(dat_session_t *source, cesspool_t *pool, + const send_capture_t *source_capture, + const uint8_t archive_key[32], + const uint8_t source_id[32], + const uint8_t remote_id[32], + uint8_t feed_keys[2][32]) +{ + dat_session_t receiver; + sleep_feed_t metadata_replica, content_replica, reopened_replica; + send_capture_t receiver_capture; + int p2p[2]; + cesspool_peer_t discovered; + uint8_t receiver_id[32] = {9}, frame[8192], payload[8192]; + uint8_t handshake_bytes[128], open_bytes[128], request_bytes[64]; + uint8_t *data_bytes; + dat_handshake_t handshake; + dat_feed_open_t feed_open; + dat_wire_message_t wire; + dat_range_t range = {0, 3}; + size_t frame_len, payload_len, consumed, handshake_len, open_len, data_length; + unsigned feed; + char replica_dir[] = "/tmp/datgpt-replica-XXXXXX"; + memset(&metadata_replica, 0, sizeof(metadata_replica)); + memset(&content_replica, 0, sizeof(content_replica)); + memset(&receiver_capture, 0, sizeof(receiver_capture)); + assert(mkdtemp(replica_dir) != NULL); + assert(socketpair(AF_UNIX, SOCK_STREAM, 0, p2p) == 0); + assert(sleep_feed_open_remote(&metadata_replica, replica_dir, "metadata-replica", + feed_keys[0]) == 0); + assert(sleep_feed_open_remote(&content_replica, replica_dir, "content-replica", + feed_keys[1]) == 0); + assert(dat_session_init(&receiver, pool, archive_key, receiver_id, pool->port, + &metadata_replica, feed_keys[0], &content_replica, + feed_keys[1], capture_send, &receiver_capture) == 0); + assert(dat_session_start(&receiver, 0, 1) == 0); + memset(&discovered, 0, sizeof(discovered)); + memcpy(discovered.public_key, source_id, 32); + memcpy(discovered.discovery_topic, receiver.discovery_topic, 32); + discovered.discovered = 1; + assert(dat_session_peer_discovered(&receiver, &discovered) == 0); + assert(dat_session_peer_secure(&receiver, source_id, 1) == 0); + for (feed = 0; feed < 2; feed++) { + assert(sleep_compute_discovery_key(feed_keys[feed], feed_open.discovery_key) == 0); + feed_open.has_nonce = 0; + assert(dat_feed_open_encode(&feed_open, open_bytes, sizeof(open_bytes), &open_len) == 0); + wire.channel = feed + 1; wire.type = DAT_MSG_FEED; + wire.payload = open_bytes; wire.payload_len = open_len; + assert(dat_wire_encode(&wire, frame, sizeof(frame), &frame_len) == 0); + stream_transfer(&receiver, source_id, p2p[0], p2p[1], frame, frame_len); + } + assert(dat_handshake_init(&handshake, source_id, 0) == 0); + assert(dat_handshake_encode(&handshake, handshake_bytes, sizeof(handshake_bytes), + &handshake_len) == 0); + wire.channel = 0; wire.type = DAT_MSG_HANDSHAKE; + wire.payload = handshake_bytes; wire.payload_len = handshake_len; + assert(dat_wire_encode(&wire, frame, sizeof(frame), &frame_len) == 0); + assert(dat_session_receive(&receiver, source_id, frame, frame_len) == 0); + assert(dat_range_encode(&range, request_bytes, sizeof(request_bytes), &payload_len) == 0); + wire.channel = 2; wire.type = DAT_MSG_HAVE; + wire.payload = request_bytes; wire.payload_len = payload_len; + assert(dat_wire_encode(&wire, frame, sizeof(frame), &frame_len) == 0); + assert(dat_session_receive(&receiver, source_id, frame, frame_len) == 0); + assert(dat_session_find_peer(&receiver, source_id)->replication[DAT_SESSION_CONTENT].remote_length == 3); + for (feed = 0; feed < 2; feed++) { + dat_data_t sent, legacy; + size_t block_index = feed == 0 ? 1u : 0u; + assert(dat_session_send_block(source, remote_id, DAT_SESSION_CONTENT, block_index) == 0); + assert(dat_wire_decode(&wire, source_capture->frame, source_capture->length, + &consumed) == 0); + assert(wire.type == DAT_MSG_DATA && dat_data_decode(&sent, wire.payload, + wire.payload_len) == 0); + assert(sent.index == block_index); + assert(sent.proof_len && sent.has_signature && sent.node_count); + legacy = sent; + legacy.proof = NULL; + legacy.proof_len = 0; + assert(dat_data_encode(&legacy, payload, sizeof(payload), &payload_len) == 0); + wire.channel = 2; wire.type = DAT_MSG_DATA; + wire.payload = payload; wire.payload_len = payload_len; + assert(dat_wire_encode(&wire, frame, sizeof(frame), &frame_len) == 0); + stream_transfer(&receiver, source_id, p2p[0], p2p[1], frame, frame_len); + } + for (feed = 0; feed < 2; feed++) { + uint8_t *block; + size_t block_len; + assert(sleep_feed_read(&content_replica, feed, &block, &block_len) == 0); + assert(block_len == (feed == 0 ? 4u : 3u)); + assert(!memcmp(block, feed == 0 ? "zero" : "one", block_len)); + free(block); + } + assert(content_replica.remote_only); + puts("live peer byte-stream block transfer: PASS"); + assert(dat_session_send_block(&receiver, source_id, DAT_SESSION_CONTENT, 0) == 0); + assert(dat_wire_decode(&wire, receiver_capture.frame, receiver_capture.length, + &consumed) == 0 && wire.type == DAT_MSG_DATA); + { + dat_data_t forwarded; + sleep_proof_t proof; + assert(dat_data_decode(&forwarded, wire.payload, wire.payload_len) == 0); + assert(forwarded.proof_len); + assert(sleep_proof_decode(&proof, forwarded.proof, forwarded.proof_len) == 0); + assert(sleep_proof_verify(&proof, forwarded.value, forwarded.value_len, + feed_keys[1]) == 0); + } + assert(dat_session_close(&receiver) == 0); + close(p2p[0]); close(p2p[1]); + sleep_feed_close(&metadata_replica); + sleep_feed_close(&content_replica); + assert(sleep_feed_open_remote(&reopened_replica, replica_dir, "content-replica", + feed_keys[1]) == 0); + assert(sleep_feed_has(&reopened_replica, 0) && sleep_feed_has(&reopened_replica, 1)); + assert(sleep_feed_read(&reopened_replica, 1, &data_bytes, &data_length) == 0); + assert(data_length == 3 && !memcmp(data_bytes, "one", 3)); + free(data_bytes); + sleep_feed_close(&reopened_replica); + { + char cache_path[1200]; + FILE *cache; + int byte; + snprintf(cache_path, sizeof(cache_path), "%s/content-replica.remote", replica_dir); + cache = fopen(cache_path, "r+b"); + assert(cache && fseek(cache, -1, SEEK_END) == 0); + byte = fgetc(cache); + assert(byte != EOF && fseek(cache, -1, SEEK_CUR) == 0); + assert(fputc(byte ^ 1, cache) != EOF && fclose(cache) == 0); + assert(sleep_feed_open_remote(&reopened_replica, replica_dir, + "content-replica", feed_keys[1]) != 0); + } +} +#endif + +int main(void) +{ +#ifdef DAT_LEGACY_HYPERCORE + legacy_session_handshake(); + return 0; +#else + cesspool_t pool; + dat_session_t session; + sleep_feed_t metadata, content; + sleep_keypair_t metadata_keys, content_keys; + send_capture_t capture; + uint8_t archive_key[32] = {1}, local_id[32] = {2}, remote_id[32] = {3}; + uint8_t feed_key[2][32], handshake_bytes[72], frame[256], request_bytes[64]; + dat_handshake_t handshake; + dat_feed_open_t remote_feed; + dat_wire_message_t wire; + dat_request_t request; + cesspool_peer_t discovered; + size_t handshake_len, frame_len, consumed, request_len; + char feed_dir[] = "/tmp/datgpt-session-XXXXXX"; + unsigned discovered_count = 0; + unsigned poll_count = 0; + static const uint8_t mblock[] = "metadata"; + static const uint8_t cblocks[][8] = {"zero", "one", "two"}; + + memset(&capture, 0, sizeof(capture)); + assert(mkdtemp(feed_dir) != NULL); + assert(sleep_gen_keypair(&metadata_keys) == 0); + assert(sleep_gen_keypair(&content_keys) == 0); + memcpy(feed_key[0], metadata_keys.pubkey, 32); + memcpy(feed_key[1], content_keys.pubkey, 32); + assert(sleep_feed_open(&metadata, feed_dir, "metadata", &metadata_keys) == 0); + assert(sleep_feed_open(&content, feed_dir, "content", &content_keys) == 0); + assert(sleep_feed_append(&metadata, mblock, sizeof(mblock) - 1) == 0); + assert(sleep_feed_append(&content, cblocks[0], 4) == 0); + assert(sleep_feed_append(&content, cblocks[1], 3) == 0); + assert(sleep_feed_append(&content, cblocks[2], 3) == 0); + assert(sleep_feed_flush(&metadata) == 0); + assert(sleep_feed_flush(&content) == 0); + assert(cesspool_init(&pool, local_id) == 0); + assert(cesspool_listen(&pool, "127.0.0.1", 0) == 0); + assert(cesspool_add_bootstrap(&pool, "127.0.0.1", 9) == 0); + assert(dat_session_init(&session, &pool, archive_key, local_id, 3282, + &metadata, feed_key[0], &content, feed_key[1], + capture_send, &capture) == 0); + assert(dat_session_set_peer_callback(&session, count_peer, &discovered_count) == 0); + assert(dat_session_set_transport_poll(&session, count_poll, &poll_count) == 0); + assert(dat_session_start(&session, 1, 1) == 0); + assert(session.state == DAT_SESSION_DISCOVERING); + memset(&discovered, 0, sizeof(discovered)); + memcpy(discovered.public_key, remote_id, sizeof(remote_id)); + memcpy(discovered.discovery_topic, session.discovery_topic, + sizeof(discovered.discovery_topic)); + discovered.discovered = 1; + assert(cesspool_add_peer(&pool, &discovered) == 0); + assert(dat_session_peer_discovered(&session, &discovered) == 0); + assert(dat_session_tick(&session, 1) == 0); + assert(poll_count == 1); + assert(dat_session_find_peer(&session, remote_id) != NULL); + assert(discovered_count == 1); + assert(dat_session_peer_secure(&session, remote_id, 1) == 0); + assert(capture.calls == 3); + for (unsigned feed_index = 0; feed_index < 2; feed_index++) { + assert(sleep_compute_discovery_key(feed_key[feed_index], + remote_feed.discovery_key) == 0); + remote_feed.has_nonce = 0; + assert(dat_feed_open_encode(&remote_feed, request_bytes, + sizeof(request_bytes), &request_len) == 0); + wire.channel = feed_index + 1; + wire.type = DAT_MSG_FEED; + wire.payload = request_bytes; + wire.payload_len = request_len; + assert(dat_wire_encode(&wire, frame, sizeof(frame), &frame_len) == 0); + assert(dat_session_receive(&session, remote_id, frame, frame_len) == 0); + } + + assert(dat_handshake_init(&handshake, remote_id, 7) == 0); + assert(dat_handshake_encode(&handshake, handshake_bytes, sizeof(handshake_bytes), + &handshake_len) == 0); + wire.channel = 0; + wire.type = DAT_MSG_HANDSHAKE; + wire.payload = handshake_bytes; + wire.payload_len = handshake_len; + assert(dat_wire_encode(&wire, frame, sizeof(frame), &frame_len) == 0); + assert(dat_session_receive(&session, remote_id, frame, frame_len / 2) == 0); + assert(dat_session_receive(&session, remote_id, frame + frame_len / 2, + frame_len - frame_len / 2) == 0); + assert(session.state == DAT_SESSION_ACTIVE); + assert(dat_session_find_peer(&session, remote_id)->state == DAT_SESSION_PEER_READY); + assert(capture.calls == 5); + assert(dat_wire_decode(&wire, capture.frame, capture.length, &consumed) == 0); + assert(wire.type == DAT_MSG_HAVE && wire.channel == 2); + { + dat_range_t have; + assert(dat_range_decode(&have, wire.payload, wire.payload_len) == 0); + assert(have.start == 0 && have.length == 3); + } + { + dat_range_t want = {0, 3}; + assert(dat_range_encode(&want, request_bytes, sizeof(request_bytes), &request_len) == 0); + wire.channel = 2; wire.type = DAT_MSG_WANT; + wire.payload = request_bytes; wire.payload_len = request_len; + assert(dat_wire_encode(&wire, frame, sizeof(frame), &frame_len) == 0); + assert(dat_session_receive(&session, remote_id, frame, frame_len) == 0); + assert(capture.calls == 6); + assert(dat_wire_decode(&wire, capture.frame, capture.length, &consumed) == 0); + assert(wire.channel == 2 && wire.type == DAT_MSG_HAVE); + } + + assert(dat_session_request_block(&session, remote_id, DAT_SESSION_CONTENT, + 12, 4096) == 0); + assert(capture.calls == 7); + assert(dat_wire_decode(&wire, capture.frame, capture.length, &consumed) == 0); + assert(consumed == capture.length && wire.channel == 2 && wire.type == DAT_MSG_REQUEST); + assert(dat_request_decode(&request, wire.payload, wire.payload_len) == 0); + assert(request.index == 12 && request.bytes == 4096 && request.want_hash && request.want_nodes); + request.index = 1; request.bytes = 0; request.want_hash = request.want_nodes = 1; + assert(dat_request_encode(&request, request_bytes, sizeof(request_bytes), &request_len) == 0); + wire.channel = 2; wire.type = DAT_MSG_REQUEST; + wire.payload = request_bytes; wire.payload_len = request_len; + assert(dat_wire_encode(&wire, frame, sizeof(frame), &frame_len) == 0); + assert(dat_session_receive(&session, remote_id, frame, frame_len) == 0); + assert(capture.calls == 8); + assert(dat_wire_decode(&wire, capture.frame, capture.length, &consumed) == 0); + assert(wire.type == DAT_MSG_DATA && wire.channel == 2); + { + dat_data_t data; + sleep_proof_t proof; + assert(dat_data_decode(&data, wire.payload, wire.payload_len) == 0); + assert(data.index == 1 && data.value_len == 3 && !memcmp(data.value, "one", 3)); + assert(data.has_signature && data.node_count > 0); + assert(sleep_proof_decode(&proof, data.proof, data.proof_len) == 0); + assert(sleep_proof_verify(&proof, data.value, data.value_len, content_keys.pubkey) == 0); + { + sleep_feed_t replica; + sleep_proof_t first_proof, forwarded; + uint8_t proof_bytes[8192], forwarded_bytes[8192]; + size_t proof_len, forwarded_len; + memset(&replica, 0, sizeof(replica)); + strcpy(replica.directory, feed_dir); + strcpy(replica.name, "content-replica"); + assert(sleep_proof_create(&content, 0, &first_proof) == 0); + assert(sleep_proof_encode(&first_proof, proof_bytes, sizeof(proof_bytes), &proof_len) == 0); + assert(sleep_feed_append_remote(&replica, 0, cblocks[0], 4, proof_bytes, proof_len) == 0); + assert(sleep_feed_append_remote(&replica, 1, data.value, data.value_len, + data.proof, data.proof_len) == 0); + assert(sleep_proof_create(&replica, 1, &forwarded) == 0); + assert(sleep_proof_encode(&forwarded, forwarded_bytes, sizeof(forwarded_bytes), + &forwarded_len) == 0); + assert(forwarded_len == data.proof_len && + !memcmp(forwarded_bytes, data.proof, forwarded_len)); + assert(sleep_proof_verify(&forwarded, data.value, data.value_len, + content_keys.pubkey) == 0); + assert(sleep_feed_flush(&replica) != 0); + sleep_feed_close(&replica); + } + receive_standard_data(&session, &pool, &capture, archive_key, local_id, + remote_id, feed_key); + ((uint8_t *)data.value)[0] ^= 1; + assert(sleep_proof_verify(&proof, data.value, data.value_len, content_keys.pubkey) != 0); + } + assert(dat_session_peer_down(&session, remote_id) == 0); + assert(session.state == DAT_SESSION_DISCOVERING); + assert(dat_session_peer_discovered(&session, &discovered) == 0); + assert(discovered_count == 2); + assert(dat_session_close(&session) == 0); + assert(session.state == DAT_SESSION_CLOSED); + assert(cesspool_destroy(&pool) == 0); + sleep_feed_close(&metadata); + sleep_feed_close(&content); + return 0; +#endif +} diff --git a/tests/test_tree.c b/tests/test_tree.c new file mode 100644 index 0000000..6f57ec1 --- /dev/null +++ b/tests/test_tree.c @@ -0,0 +1,4 @@ +#include "sleep.h" +#include +#include +int main(void){uint64_t roots[8],v;assert(sleep_tree_index(0,0)==0);assert(sleep_tree_index(1,0)==1);assert(sleep_tree_index(0,1)==2);assert(sleep_tree_parent(0)==1);assert(sleep_tree_left_child(1,&v)==0&&v==0);assert(sleep_tree_right_child(1,&v)==0&&v==2);assert(sleep_tree_full_roots(6,roots,8)==2);assert(roots[0]==1&&roots[1]==4);return 0;} diff --git a/tests/test_utp.c b/tests/test_utp.c new file mode 100644 index 0000000..a517c02 --- /dev/null +++ b/tests/test_utp.c @@ -0,0 +1,154 @@ +#define _POSIX_C_SOURCE 200809L +#include "sleep_utp.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +static char received[4096]; +static size_t received_len; + +static void on_data(sleep_utp_t *utp, const uint8_t *data, size_t length) +{ + (void)utp; + assert(length <= sizeof(received) - received_len); + memcpy(received + received_len, data, length); + received_len += length; +} + +static ssize_t receive_packet(int fd, uint8_t *packet, size_t capacity, + struct sockaddr_storage *source, socklen_t *source_len) +{ + struct pollfd pfd = {fd, POLLIN, 0}; + assert(poll(&pfd, 1, 1000) == 1); + return recvfrom(fd, packet, capacity, 0, (struct sockaddr *)source, source_len); +} + +int main(void) +{ + int a_fd = socket(AF_INET, SOCK_DGRAM, 0), b_fd = socket(AF_INET, SOCK_DGRAM, 0); + struct sockaddr_in a_addr, b_addr; + socklen_t a_len = sizeof(a_addr), b_len = sizeof(b_addr); + struct sockaddr_storage source; + socklen_t source_len = sizeof(source); + sleep_utp_t a, b; + sleep_utp_packet_t packet; + uint8_t wire[1400]; + ssize_t n; + uint16_t sent_sequence; + assert(a_fd >= 0 && b_fd >= 0); + memset(&a_addr, 0, sizeof(a_addr)); memset(&b_addr, 0, sizeof(b_addr)); + a_addr.sin_family = AF_INET; a_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + b_addr.sin_family = AF_INET; b_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + assert(bind(a_fd, (struct sockaddr *)&a_addr, sizeof(a_addr)) == 0); + assert(bind(b_fd, (struct sockaddr *)&b_addr, sizeof(b_addr)) == 0); + assert(getsockname(a_fd, (struct sockaddr *)&a_addr, &a_len) == 0); + assert(getsockname(b_fd, (struct sockaddr *)&b_addr, &b_len) == 0); + assert(sleep_utp_init(&a, a_fd) == 0 && sleep_utp_init(&b, b_fd) == 0); + a.on_data = on_data; b.on_data = on_data; + assert(sleep_utp_connect(&a, (struct sockaddr *)&b_addr, b_len) == 0); + n = receive_packet(b_fd, wire, sizeof(wire), &source, &source_len); + assert(n > 0 && sleep_utp_packet_decode(&packet, wire, (size_t)n) == 0); + assert(packet.type == SLEEP_UTP_SYN); + assert(sleep_utp_accept(&b, &packet, (struct sockaddr *)&source, source_len) == 0); + n = receive_packet(a_fd, wire, sizeof(wire), &source, &source_len); + assert(n > 0 && sleep_utp_input(&a, wire, (size_t)n) == 0); + assert(a.state == SLEEP_UTP_CONNECTED); + { + uint8_t oversized[SLEEP_UTP_MTU + 1]; + size_t wire_len; + memset(&packet, 0, sizeof(packet)); + packet.type = SLEEP_UTP_DATA; packet.version = 1; + packet.payload = oversized; packet.payload_len = sizeof(oversized); + assert(sleep_utp_packet_encode(&packet, wire, sizeof(wire), &wire_len) == -1); + assert(errno == EMSGSIZE); + memset(wire, 0, sizeof(wire)); + wire[0] = (uint8_t)(SLEEP_UTP_DATA << 4) | 1; + assert(sleep_utp_packet_decode(&packet, wire, 20 + sizeof(oversized)) == -1); + assert(errno == EMSGSIZE); + wire[0] = (uint8_t)(SLEEP_UTP_STATE << 4) | 1; + assert(sleep_utp_packet_decode(&packet, wire, 21) == -1); + assert(errno == EPROTO); + } + { + sleep_utp_slot_t *slot = &a.unacked[a.sequence % SLEEP_UTP_WINDOW]; + struct pollfd pfd = {b_fd, POLLIN, 0}; + slot->used = 1; + assert(sleep_utp_send(&a, (const uint8_t *)"blocked", 7) == -1); + assert(errno == EAGAIN); + assert(poll(&pfd, 1, 20) == 0); + slot->used = 0; + } + packet.type = SLEEP_UTP_STATE; packet.version = 1; + packet.connection_id = (uint16_t)(a.receive_id + 1); + { + size_t wire_len; + assert(sleep_utp_packet_encode(&packet, wire, sizeof(wire), &wire_len) == 0); + assert(sleep_utp_input(&a, wire, wire_len) == -1 && errno == EPROTO); + } + assert(sleep_utp_send(&a, (const uint8_t *)"first", 5) == 0); + n = receive_packet(b_fd, wire, sizeof(wire), &source, &source_len); + assert(n > 0 && sleep_utp_input(&b, wire, (size_t)n) == 0); + assert(received_len == 5 && !memcmp(received, "first", 5)); + n = receive_packet(a_fd, wire, sizeof(wire), &source, &source_len); + assert(n > 0 && sleep_utp_input(&a, wire, (size_t)n) == 0); + assert(a.bytes_in_flight == 0); + memset(&packet, 0, sizeof(packet)); + packet.type = SLEEP_UTP_DATA; packet.version = 1; packet.connection_id = a.send_id; + packet.sequence = 3; packet.window_size = 1024 * 1024; + packet.payload = (const uint8_t *)"later"; packet.payload_len = 5; + { + size_t wire_len; + assert(sleep_utp_packet_encode(&packet, wire, sizeof(wire), &wire_len) == 0); + assert(sendto(a_fd, wire, wire_len, 0, (struct sockaddr *)&b_addr, b_len) == (ssize_t)wire_len); + } + n = receive_packet(b_fd, wire, sizeof(wire), &source, &source_len); + assert(n > 0 && sleep_utp_input(&b, wire, (size_t)n) == 0); + assert(received_len == 5); /* sequence 3 waits for sequence 2 */ + packet.sequence = 2; packet.payload = (const uint8_t *)"next"; packet.payload_len = 4; + { + size_t wire_len; + assert(sleep_utp_packet_encode(&packet, wire, sizeof(wire), &wire_len) == 0); + assert(sendto(a_fd, wire, wire_len, 0, (struct sockaddr *)&b_addr, b_len) == (ssize_t)wire_len); + } + n = receive_packet(b_fd, wire, sizeof(wire), &source, &source_len); + assert(n > 0 && sleep_utp_input(&b, wire, (size_t)n) == 0); + assert(received_len == 14 && !memcmp(received, "firstnextlater", 14)); + for (int ack = 0; ack < 2; ack++) { + n = receive_packet(a_fd, wire, sizeof(wire), &source, &source_len); + assert(n > 0 && sleep_utp_input(&a, wire, (size_t)n) == 0); + } + { + uint8_t large[2401]; + size_t before = received_len; + /* The preceding out-of-order packets advanced the receiver through seq 3. */ + a.sequence = 4; + memset(large, 0x5a, sizeof(large)); + assert(sleep_utp_send(&a, large, sizeof(large)) == 0); + for (int part = 0; part < 3; part++) { + n = receive_packet(b_fd, wire, sizeof(wire), &source, &source_len); + assert(n > 20 && sleep_utp_input(&b, wire, (size_t)n) == 0); + n = receive_packet(a_fd, wire, sizeof(wire), &source, &source_len); + assert(n >= 20 && sleep_utp_input(&a, wire, (size_t)n) == 0); + } + assert(received_len == before + sizeof(large)); + for (size_t i = before; i < received_len; i++) assert(received[i] == 0x5a); + assert(a.bytes_in_flight == 0); + } + assert(sleep_utp_send(&a, (const uint8_t *)"retry", 5) == 0); + n = receive_packet(b_fd, wire, sizeof(wire), &source, &source_len); + assert(n > 0 && sleep_utp_packet_decode(&packet, wire, (size_t)n) == 0); + sent_sequence = packet.sequence; + assert(sleep_utp_tick(&a, 1) == 0); + assert(sleep_utp_tick(&a, 2000) == 0); + n = receive_packet(b_fd, wire, sizeof(wire), &source, &source_len); + assert(n > 0 && sleep_utp_packet_decode(&packet, wire, (size_t)n) == 0); + assert(packet.type == SLEEP_UTP_DATA && packet.sequence == sent_sequence); + close(a_fd); close(b_fd); + return 0; +} diff --git a/tests/test_version_bar.c b/tests/test_version_bar.c new file mode 100644 index 0000000..c924398 --- /dev/null +++ b/tests/test_version_bar.c @@ -0,0 +1,160 @@ +#include +#ifdef main +#undef main +#endif +#define main dat_thang_program_main +#include "../src/ddat_gui.c" +#undef main + +#include +#include +#include +#include +#ifdef _WIN32 +#include +#include +#define test_getpid _getpid +#define test_mkdir(path) _mkdir(path) +#define test_setenv(name, value) _putenv_s((name), (value)) +#else +#include +#include +#define test_getpid getpid +#define test_mkdir(path) mkdir(path, 0700) +#define test_setenv(name, value) setenv((name), (value), 1) +#endif + +int main(void) +{ + static gui_state_t state; + dat_archive_t first, second; + char test_root[256], first_path[300], second_path[300]; + char first_key[65], second_key[65]; + int n, fixture_attempt; + setvbuf(stdout, NULL, _IONBF, 0); + memset(&state, 0, sizeof(state)); + state.archive_open = 1; + state.version = 12; + state.selected_version = 12; + + /* The displayed left/right halves of the version field are click targets. */ + assert(click_archive_version_bar(&state, 210, 74, 84)); + assert(state.selected_version == 11); + assert(click_archive_version_bar(&state, 226, 93, 84)); + assert(state.selected_version == 12); + assert(click_archive_version_bar(&state, 226, 80, 84)); + assert(state.selected_version == 12); /* clamp at latest */ + assert(!click_archive_version_bar(&state, 209, 80, 84)); + assert(!click_archive_version_bar(&state, 242, 80, 84)); + assert(!click_archive_version_bar(&state, 210, 73, 84)); + assert(!click_archive_version_bar(&state, 210, 94, 84)); + state.archive_open = 0; + assert(!click_archive_version_bar(&state, 210, 80, 84)); + puts("dat-thang version bar hitbox and bounds: PASS"); + + /* The visible version is the number of metadata entries after the feed + * header, while the Merkle tree counts every block including that header. */ + assert(merkle_version_block_count(0, 0) == 1); + assert(merkle_version_block_count(3, 0) == 1); + assert(merkle_version_block_count(3, 2) == 3); + assert(merkle_version_block_count(3, 99) == 4); + { + uint64_t roots[4]; + assert(sleep_tree_full_roots(3 * 2, roots, 4) == 2); + assert(roots[0] == 1 && roots[1] == 4); + } + puts("dat-thang Merkle view uses the selected SLEEP feed blocks: PASS"); + + /* Regression: browsing to another directory must not erase the loaded + * archive's address during the one-second archive refresh. */ + for (fixture_attempt = 0; fixture_attempt < 1000; fixture_attempt++) { + n = snprintf(test_root, sizeof(test_root), "build/test-version-bar-%ld-%d", + (long)test_getpid(), fixture_attempt); + assert(n > 0 && (size_t)n < sizeof(test_root)); + if (test_mkdir(test_root) == 0) break; + } + assert(fixture_attempt < 1000); + assert(test_setenv("USERPROFILE", test_root) == 0); +#ifdef _WIN32 + assert(test_setenv("HOME", "") == 0); +#else + assert(test_setenv("HOME", test_root) == 0); +#endif + { + char path[1024]; + strcpy(path, "."); + assert(parent_directory(path, sizeof(path)) == 1); + assert(strcmp(path, ".") != 0); +#ifdef _WIN32 + char cwd[1024]; + assert(_getcwd(cwd, sizeof(cwd)) != NULL); + snprintf(path, sizeof(path), "%c:\\", cwd[0]); +#else + strcpy(path, "/"); +#endif + assert(parent_directory(path, sizeof(path)) == 0); + } + puts("dat-thang directory browser resolves relative parents and stops at root: PASS"); + n = snprintf(first_path, sizeof(first_path), "%s/first", test_root); + assert(n > 0 && (size_t)n < sizeof(first_path)); + n = snprintf(second_path, sizeof(second_path), "%s/second", test_root); + assert(n > 0 && (size_t)n < sizeof(second_path)); + assert(test_mkdir(first_path) == 0 && test_mkdir(second_path) == 0); + assert(dat_archive_create(&first, first_path) == 0); + assert(dat_archive_create(&second, second_path) == 0); + memset(&state, 0, sizeof(state)); + snprintf(state.shared_paths[0], sizeof(state.shared_paths[0]), "%s", first_path); + snprintf(state.shared_paths[1], sizeof(state.shared_paths[1]), "%s", second_path); + state.shared_path_count = 2; + assert(select_shared_archive(&state, 0) == 0); + assert(state.archive_open && !strcmp(state.archive_path, first_path)); + assert(state.shared_path_count == 2); + assert(!strcmp(state.shared_paths[0], first_path)); + assert(!strcmp(state.shared_paths[1], second_path)); + puts("dat-thang right-click archive selection preserves the shared list: PASS"); + assert(dat_key_to_hex(first.metadata_key, first_key) == 0); + assert(dat_key_to_hex(second.metadata_key, second_key) == 0); + assert(strcmp(first_key, second_key) != 0); + dat_archive_close(&first); + dat_archive_close(&second); + memset(&state, 0, sizeof(state)); + state.identity_valid = 1; + memset(&state.identity, 0x5a, sizeof(state.identity)); + snprintf(state.archive_path, sizeof(state.archive_path), "%s", first_path); + refresh_archive(&state); + assert(state.archive_open && !strcmp(state.archive_key, first_key)); + assert(state.identity_valid); + for (size_t i = 0; i < sizeof(state.identity); i++) + assert(((const unsigned char *)&state.identity)[i] == 0x5a); + strcpy(state.site_name, "first.example.test"); + state.site_name_editing = 1; + assert(save_site_name(&state) == 0); + state.site_name_editing = 0; + { + uint8_t resolved[DAT_KEY_BYTES]; + assert(dat_dns_resolve("first.example.test", resolved, NULL) == 0); + assert(!memcmp(resolved, state.archive_key_bytes, sizeof(resolved))); + } + snprintf(state.archive_path, sizeof(state.archive_path), "%s", second_path); + refresh_archive_at(&state, state.archive_loaded_path); + assert(state.archive_open && !strcmp(state.archive_key, first_key)); + assert(!strcmp(state.archive_path, second_path)); + assert(!strcmp(state.site_name, "first.example.test")); + refresh_archive(&state); + assert(state.archive_open && !strcmp(state.archive_key, second_key)); + assert(!state.site_name[0]); + assert(dat_dns_name_remove("first.example.test") == 0); + puts("dat-thang selected archive address survives browsing: PASS"); + puts("dat-thang SITE alias persists to its archive key: PASS"); + assert(test_setenv("CABLE_THANG_MANPAGE", "") == 0); + state.cable_thang_mode = 1; + load_manpage(&state); + { + int found = 0; + for (int line = 0; line < state.help_count; line++) + if (strstr(state.help_lines[line], "SDL2 Cable room chat client")) found = 1; + assert(found); + } + puts("cable-thang F1 loads the Cable manual: PASS"); + return 0; +} diff --git a/tests/test_wire.c b/tests/test_wire.c new file mode 100644 index 0000000..f1431a6 --- /dev/null +++ b/tests/test_wire.c @@ -0,0 +1,11 @@ +#include "dat_wire.h" +#include +#include +int main(void){unsigned char buf[256],payload[3]={1,2,3},peer[32]={0},golden[]={5,0x13,0x08,0,0x10,1},want_without_length[]={0x08,0x05};size_t n,used,i;dat_wire_message_t a={7,DAT_MSG_DATA,payload,3},b;dat_handshake_t h,decoded;dat_request_t q={12,4096,1,1},r;dat_range_t range={0,1};dat_info_t info={0,1},decoded_info;dat_extension_t ext={3,payload,3},decoded_ext;assert(dat_range_encode(&range,buf,sizeof(buf),&n)==0);a.channel=1;a.type=DAT_MSG_HAVE;a.payload=buf;a.payload_len=n;assert(dat_wire_encode(&a,buf+32,sizeof(buf)-32,&used)==0);assert(used==sizeof(golden)&&!memcmp(buf+32,golden,sizeof(golden)));assert(dat_wire_decode(&b,buf+32,used,&n)==0&&n==used);assert(b.channel==1&&b.type==DAT_MSG_HAVE);assert(dat_range_decode(&range,b.payload,b.payload_len)==0&&range.start==0&&range.length==1);assert(dat_want_decode(&range,want_without_length,sizeof(want_without_length),12)==0&&range.start==5&&range.length==7);a.channel=7;a.type=DAT_MSG_DATA;a.payload=payload;a.payload_len=3;assert(dat_wire_encode(&a,buf,sizeof(buf),&n)==0);assert(dat_wire_decode(&b,buf,n,&used)==0&&used==n);assert(b.channel==7&&b.type==DAT_MSG_DATA&&b.payload_len==3&&!memcmp(b.payload,payload,3));assert(dat_request_encode(&q,buf,sizeof(buf),&n)==0);assert(dat_request_decode(&r,buf,n)==0);assert(r.index==12&&r.bytes==4096&&r.want_hash&&r.want_nodes);assert(dat_info_encode(&info,buf,sizeof(buf),&n)==0);assert(dat_info_decode(&decoded_info,buf,n)==0&&!decoded_info.uploading&&decoded_info.downloading);assert(dat_extension_encode(&ext,buf,sizeof(buf),&n)==0);assert(dat_extension_decode(&decoded_ext,buf,n)==0&&decoded_ext.id==3&&decoded_ext.message_len==3&&!memcmp(decoded_ext.message,payload,3));assert(dat_handshake_init(&h,peer,7)==0);assert(!memcmp(h.protocol,DAT_NETWORK_PROTOCOL,strlen(DAT_NETWORK_PROTOCOL)));for(i=strlen(DAT_NETWORK_PROTOCOL);i +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#include +static char *test_mkdtemp(char *path) +{ + int i; + for (i = 0; i < 1000; i++) { + char candidate[512]; + int n = snprintf(candidate, sizeof(candidate), "%s-%ld-%d", path, + (long)_getpid(), i); + if (n < 0 || (size_t)n >= sizeof(candidate)) return NULL; + if (_mkdir(candidate) == 0) { strcpy(path, candidate); return path; } + } + return NULL; +} +#else +#include +#define test_mkdtemp mkdtemp +#endif + +int main(void) +{ + sleep_keypair_t keypair; + const uint8_t content[] = "block"; + const uint8_t *blocks[] = {content}; + const size_t lengths[] = {sizeof(content) - 1}; + #ifdef _WIN32 + char directory[] = "build/windows/datgpt-writer"; + #else + char directory[] = "/tmp/datgpt-writer-XXXXXX"; + #endif + assert(sleep_crypto_init() == 0); + assert(sleep_gen_keypair(&keypair) == 0); + assert(test_mkdtemp(directory) != NULL); + errno = 0; + assert(sleep_write_feed(directory, "../escape", blocks, lengths, 1, &keypair) == -1); + assert(errno == EINVAL); + errno = 0; + assert(sleep_write_feed(directory, "unsafe\\name", blocks, lengths, 1, &keypair) == -1); + assert(errno == EINVAL); + assert(sleep_write_feed(directory, "null-block", (const uint8_t *const[]){NULL}, + (const size_t[]){1}, 1, &keypair) == -1); + assert(errno == EINVAL); + assert(sleep_write_feed(directory, "content", blocks, lengths, 1, &keypair) == 0); + { + char path[512]; + struct stat st; + assert(snprintf(path, sizeof(path), "%s/content.data", directory) > 0); + assert(stat(path, &st) == 0 && st.st_size == (off_t)(sizeof(content) - 1)); + } + return 0; +} diff --git a/third_party/base32768.LICENSE b/third_party/base32768.LICENSE new file mode 100644 index 0000000..541cfcc --- /dev/null +++ b/third_party/base32768.LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 qntm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/tools/make_windows_icon.ps1 b/tools/make_windows_icon.ps1 new file mode 100644 index 0000000..80a4dbd --- /dev/null +++ b/tools/make_windows_icon.ps1 @@ -0,0 +1,47 @@ +$ErrorActionPreference = 'Stop' +Add-Type -AssemblyName System.Drawing + +$root = Split-Path -Parent $PSScriptRoot +$assetDir = Join-Path $root 'assets' +$iconPath = Join-Path $assetDir 'dat-thang.ico' +$bitmap = [System.Drawing.Bitmap]::new(64, 64, [System.Drawing.Imaging.PixelFormat]::Format32bppArgb) +$graphics = [System.Drawing.Graphics]::FromImage($bitmap) +$graphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias +$graphics.Clear([System.Drawing.Color]::Transparent) +$points = [System.Drawing.PointF[]]@( + [System.Drawing.PointF]::new(32, 3), [System.Drawing.PointF]::new(58, 18), + [System.Drawing.PointF]::new(58, 46), [System.Drawing.PointF]::new(32, 61), + [System.Drawing.PointF]::new(6, 46), [System.Drawing.PointF]::new(6, 18) +) +$fill = [System.Drawing.SolidBrush]::new([System.Drawing.Color]::FromArgb(255, 39, 54, 71)) +$edge = [System.Drawing.Pen]::new([System.Drawing.Color]::FromArgb(255, 55, 205, 83), 4) +$graphics.FillPolygon($fill, $points) +$graphics.DrawPolygon($edge, $points) + +$png = [System.IO.MemoryStream]::new() +$bitmap.Save($png, [System.Drawing.Imaging.ImageFormat]::Png) +$image = $png.ToArray() +$ico = [System.IO.MemoryStream]::new() +$writer = [System.IO.BinaryWriter]::new($ico) +$writer.Write([uint16]0) +$writer.Write([uint16]1) +$writer.Write([uint16]1) +$writer.Write([byte]64) +$writer.Write([byte]64) +$writer.Write([byte]0) +$writer.Write([byte]0) +$writer.Write([uint16]1) +$writer.Write([uint16]32) +$writer.Write([uint32]$image.Length) +$writer.Write([uint32]22) +$writer.Write($image) +[System.IO.Directory]::CreateDirectory($assetDir) | Out-Null +[System.IO.File]::WriteAllBytes($iconPath, $ico.ToArray()) +$writer.Dispose() +$ico.Dispose() +$png.Dispose() +$edge.Dispose() +$fill.Dispose() +$graphics.Dispose() +$bitmap.Dispose() +Write-Host "Wrote $iconPath"

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μ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 | Select-String 'DLL Name:' + foreach ($depLine in $deps) { + $dll = ($depLine.Line -replace '^.*DLL Name:\s*', '').Trim() + if (!$dll -or $dll -match '^(api-ms-win-|ext-ms-win-)' -or !$seen.Add($dll)) { continue } + $sourceDll = Join-Path $ucrtBin $dll + if (Test-Path -LiteralPath $sourceDll) { + $targetDll = Join-Path (Split-Path -Parent $binary) $dll + Copy-Item -LiteralPath $sourceDll -Destination $targetDll -Force + $queue.Enqueue($targetDll) + } + } +} + +Write-Host "Windows development build: $repo\build\windows" +Write-Host "Windows portable package: $repo\dist\windows-x64" +Write-Host "Windows distributions: $repo\dist\installers" diff --git a/configure b/configure new file mode 100644 index 0000000..4e5001a --- /dev/null +++ b/configure @@ -0,0 +1,97 @@ +#!/bin/sh +set -eu + +prefix=/usr/local +enable_s7=1 +cc=${CC:-cc} + +usage() { + echo "usage: ./configure [--prefix=PATH] [--enable-s7] [CC=compiler]" + echo "requires a C99 compiler, make, libsodium, libcurl; S7 is bundled and required" +} + +for arg in "$@"; do + case "$arg" in + --prefix=*) prefix=${arg#*=} ;; + --enable-s7) enable_s7=1 ;; + --disable-s7) echo "configure: error: S7 is required for JSON and Scheme data handling" >&2; exit 1 ;; + --help|-h) usage; exit 0 ;; + CC=*) cc=${arg#CC=} ;; + *) echo "configure: unknown option: $arg" >&2; usage >&2; exit 1 ;; + esac +done + +log=config.log +: > "$log" +echo "configure: checking build tools" +for tool in "$cc" ar ranlib install make; do + if ! command -v "$tool" >/dev/null 2>&1; then + echo "configure: error: required tool '$tool' was not found" | tee -a "$log" >&2 + exit 1 + fi +done + +tmp_c=conftest-$$.c +tmp_exe=conftest-$$ +trap 'rm -f "$tmp_c" "$tmp_exe"' EXIT HUP INT TERM +cat > "$tmp_c" <<'EOF' +#include +int main(void) { return sodium_init() < 0; } +EOF + +sodium_cflags= +sodium_libs=-lsodium +curl_cflags= +curl_libs=-lcurl +if command -v pkg-config >/dev/null 2>&1 && pkg-config --exists libsodium; then + sodium_cflags=$(pkg-config --cflags libsodium) + sodium_libs=$(pkg-config --libs libsodium) +fi +if ! "$cc" $sodium_cflags "$tmp_c" $sodium_libs -o "$tmp_exe" >>"$log" 2>&1; then + echo "configure: error: libsodium headers and library are required" | tee -a "$log" >&2 + exit 1 +fi + +if command -v pkg-config >/dev/null 2>&1 && pkg-config --exists libcurl; then + curl_cflags=$(pkg-config --cflags libcurl) + curl_libs=$(pkg-config --libs libcurl) +fi +cat > "$tmp_c" <<'EOF' +#include +int main(void) { CURL *c = curl_easy_init(); if (c) curl_easy_cleanup(c); return 0; } +EOF +if ! "$cc" $curl_cflags "$tmp_c" $curl_libs -o "$tmp_exe" >>"$log" 2>&1; then + echo "configure: error: libcurl headers and library are required for Dat name resolution" | tee -a "$log" >&2 + exit 1 +fi + +cat > config.mk < config.h < '' then + PathValue := PathValue + ';'; + PathValue := PathValue + Entry; + RegWriteExpandStringValue(Root, EnvironmentKey, PathValueName, PathValue); + RegWriteStringValue(Root, 'Software\dat-gpt\Installer', 'AddedDatPath', Entry); + end; +end; + +procedure RemoveInstallPath; +var + Root: Integer; + PathValue, Entry, NewPath, AddedEntry: String; + Parts: TArrayOfString; + I: Integer; +begin + Root := PathRegistryRoot(); + Entry := ExpandConstant('{app}'); + if not RegQueryStringValue(Root, 'Software\dat-gpt\Installer', 'AddedDatPath', AddedEntry) then + Exit; + if CompareText(AddedEntry, Entry) <> 0 then + Exit; + if not RegQueryStringValue(Root, EnvironmentKey, PathValueName, PathValue) then + Exit; + Parts := SplitString(PathValue, ';'); + NewPath := ''; + for I := 0 to GetArrayLength(Parts) - 1 do + if (Trim(Parts[I]) <> '') and (CompareText(Trim(Parts[I]), Entry) <> 0) then + begin + if NewPath <> '' then + NewPath := NewPath + ';'; + NewPath := NewPath + Trim(Parts[I]); + end; + if NewPath <> PathValue then + begin + if NewPath = '' then + RegDeleteValue(Root, EnvironmentKey, PathValueName) + else + RegWriteExpandStringValue(Root, EnvironmentKey, PathValueName, NewPath); + end; + RegDeleteValue(Root, 'Software\dat-gpt\Installer', 'AddedDatPath'); +end; + +procedure CurStepChanged(CurStep: TSetupStep); +begin + if (CurStep = ssPostInstall) and WizardIsTaskSelected('addtopath') then + AddInstallPath(); +end; + +procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); +begin + if CurUninstallStep = usUninstall then + RemoveInstallPath(); +end; diff --git a/docs/beaker-networking.md b/docs/beaker-networking.md new file mode 100644 index 0000000..9d7bd4e --- /dev/null +++ b/docs/beaker-networking.md @@ -0,0 +1,109 @@ +# Beaker networking notes for `dat-gpt` + +These notes use the archived `beakerbrowser/beaker` 1.1.0 source and the +included old `dillo-dat` plugin as references. Beaker was archived in 2022. +Its later Hyperdrive network stack is useful for subsystem boundaries, but it +is not proof of compatibility with the old Dat protocol or a replacement for +the Dillo plugin's `hyperdrive` + `hyperdiscovery` path. + +## What Beaker separates + +1. **Name resolution.** URLs are parsed first. A 64-character key is used + directly; a domain requires a resolver. Beaker has a `dat-dns` based + resolver and local name/key maps. Its v1.1 HyperDNS implementation leaves + the remote DNS logic disabled, and its `dat://` handler calls a separate + `dat-dns` resolver. Name resolution therefore belongs above transport and + should return the archive key before opening feeds. +2. **Drive sessions and persistence.** Beaker's later stack talks to a + long-lived Hyperspace process through `hyperdrive-daemon-client`. Sessions + are keyed by archive key plus version and read/write mode. The daemon owns + the storage and network handles; Beaker expires idle sessions and reconnects + them if the daemon restarts. +3. **Announce versus lookup.** Beaker explicitly configures a drive with + `announce: true, lookup: true` when it should be hosted and discover peers. + Merely opening a drive and resolving its key are separate operations. +4. **Archive reads.** The newer `hyper://` handler resolves the host to a key, + opens/loads the drive (which starts network lookup), checks out an optional + version, reads its manifest, resolves the path, and streams the selected + file from the drive. It supports `GET`/`HEAD`, byte ranges, redirects for + directory URLs without a trailing slash, manifest MIME overrides, and + content sniffing. A drive with version zero becomes a clear “no peers found + yetâ€� response, so lookup-in-progress is distinct from a missing path. + The separate legacy `dat://` handler resolves through `dat-dns`, runs the + legacy conversion/download tool into temporary storage, and then serves + that filesystem through the same path resolver. That conversion route is + materially different from the native Hyperdrive read path. +5. **Operational visibility.** Beaker has separate debug categories for the + application, Dat, DHT discovery, DNS discovery, and the Hypercore protocol. + That separation makes it possible to tell whether a failure is lookup, + connection, replication, or file resolution. + +## What applies to this C implementation + +- Preserve the existing `NETWORK_PROTOCOL` switch and `cesspool` spelling. + The default Cesspool build uses the V7 message format. `hypercore` selects + the old Dillo dependency's V6 channels, nonce-bearing feed handshake, and + XSalsa20 stream encryption. The `CSP0` UDP discovery packet format is still + separate from the old JS `hyperdiscovery` format. +- Keep hostname resolution, discovery, authenticated peer sessions, feed + replication, and browser path handling as separate layers. The Dillo DPI + helper already consumes the URL/path and local archive layers; its cache + lookup is not yet a remote archive loader. +- The archive session joins a key's topic, consumes topic-tagged Cesspool + announcements, carries peer feed channels, exchanges HAVE/WANT state, + requests blocks, and verifies/forwards signed proofs. The UDP discovery + packet format still needs old JS `hyperdiscovery` interoperability work; + Noise authentication and socket-to-session transport integration are + separate networking layers. +- Beaker's daemon restart/session handling suggests a lifecycle boundary for + the C version: one archive session owns the discovery membership, peer set, + replication state, pending block requests, and close/retry behavior. Keep + this below both the CLI and Dillo DPI callers so they can share it. +- `dat-dns` names should be resolved as a mapping to a key, not treated as a + second archive identity. Cache the mapping separately from archive content; + keep the resolved key visible for debugging and canonical `dat://` + links. +- `dat_dns_resolve` follows the old `dat-dns` URL-to-key contract. The bundled + Dillo plugin disables DNS-over-HTTPS and resolves through HTTPS + `/.well-known/dat`; the C resolver follows that same path after checking its + local alias map. It does not call the operating system's DNS TXT resolver. + The record contains a `dat://<64-hex-key>` line and optional `TTL=`. + This lookup returns a key only; archive discovery and transfer remain + separate Cesspool/session operations. + +## Suggested implementation order + +1. Define an archive network-session API with explicit `open`, `announce`, + `lookup`, `peer-up`, `peer-down`, `request-block`, `block-arrived`, and + `close` transitions. Keep transport details behind the existing `cesspool` + boundary. +2. Make one peer connection carry a real authenticated protocol handshake and + identify the metadata and content feeds. Do not start with browser DNS or + directory listing behavior. +3. Connect remote metadata block requests to feed verification and archive + reads. A cold `dat:///index.html` should be able to wait for and serve + the requested bytes, while earlier verified blocks remain cached. +4. Add retry, peer removal, and session cleanup. Expose peer and missing-block + state through a diagnostic command before adding a dashboard. +5. Add name resolution and any Beaker-style convenience behaviors only after + key-addressed remote reads work. + +## Source references + +- Beaker 1.1.0 `app/package.json`: `dat-dns`, `discovery-swarm`, `hyperspace`, + `hyperdrive-daemon-client`, and `pauls-dat-api2` dependencies. +- `app/bg/hyper/daemon.js`: daemon setup/reconnect, per-drive sessions, peer + addresses, and session cleanup. +- `app/bg/hyper/drives.js`: key/domain resolution, network configuration, + drive loading, manifest reads, and version checkouts. +- `app/bg/hyper/dns.js`: local name/key map; remote DNS implementation is + commented out in this archived revision. +- `app/bg/protocols/dat.js`: legacy `dat://` URL host resolution, local + legacy download/conversion to temporary storage, path resolution, MIME + selection, and response handling. +- `app/bg/protocols/hyper.js`: native Hyperdrive network open, historic + checkout, path resolution, no-peer state, range reads, and browser response + behavior. +- Beaker's repository README identifies version 1.1.0 as the last release and + the repository as archived; its debug namespaces include `dat`, + `bittorrent-dht`, `dns-discovery`, and `hypercore-protocol`. diff --git a/docs/cable-interop.md b/docs/cable-interop.md new file mode 100644 index 0000000..c76d725 --- /dev/null +++ b/docs/cable-interop.md @@ -0,0 +1,154 @@ +# Cable and Cabal interoperability notes + +This document describes the **test-only Cabal/Hypercore interoperability +harness** under `tests/interop/cabal/`. It is kept to compare historical +behavior and is excluded from production builds and runtime paths. + +The production `cable` and `cable-thang` clients are C/S7 applications: +Cesspool handles discovery and transport, Sleep handles persistent local +feeds, and S7 represents room events as S-expressions. Cabal CLI and +Hypercore are test targets, not production dependencies. + +## Which client to run + +The working historical Cabal terminal client is `cabal-cli`. To join a room, +run: + +```sh +cabal-cli cabal:// +``` + +The `cable` executable also supports local Sleep feed and Cesspool bootstrap +commands. The native Cable chat client is `cable-thang`, using a terminal-style +SDL2 layout inspired by Cabal's TUI. Build with `make ENABLE_SDL2=1`; create an +address in the terminal with `build/linux/cable --new`, then launch the GUI with +`build/linux/cable-thang `. Use `/join NAME`, `/nick NAME`, +`Ctrl+N`, and `Ctrl+P` in the client. + +## Cabal behaviors being ported + +The Cabal CLI's `/whoami` (also `/key`) displays the local user key, while +`/nick` changes that user's display name. Those are different things: the +user's public key is the stable identity within that room; the nickname is a +readable label published as an `about` record in that user's room feed. +The CLI's usage and commands are listed in the +[cabal-cli README](https://github.com/cabal-club/cabal-cli#commands). The +[cabal-core API](https://github.com/cabal-club/cabal-core#cabalgetlocalkeycb) +documents `getLocalKey()` as the local user's key and documents messages as +records published to user feeds. Cabal creates its local writer feed inside +each room's storage, so a user's key is room-scoped, stable for that room, and +can differ in another room. A room key and its optional `?admin=` +moderation key are separate from each user's feed key. Cabal text records use +`chat/text` with channel and text fields. + +## Dat-era Cabal reference + +Cabal Core 5.0.1 is a concrete older reference candidate: it was published about +seven years ago, and a 2019 Cabal walkthrough describes the chat as running +over Dat (the walkthrough was later updated for Cabal Core 5.0). The bundled +Dillo Dat tree supplies a nearby protocol reference with Hypercore 7.7.1, +hypercore-protocol 6.12.0, Hyperdrive 9.16.0, and hyperdiscovery 7.1.1. Use +those versions to guide backwards-adaptation; do not treat current Cabal Core +16 or its Hyper:// networking as the target. + +The repeatable JS behavior tests remain useful for room identity, nicknames, +channels, and record shapes. To run them against the historical package, point +CABAL_JS_MODULE and CABAL_CLIENT_MODULE at a Cabal Core 5.0.x install, then run +make test-cabal-interop. + +## Current C groundwork + +- `cable address` accepts room keys with `cabal://`, `cable://`, or + `hyper://`, including a `?admin=<64 hex>` parameter, and prints canonical + `cable://` room addresses while retaining the admin key separately. +- `cable whoami ` and dat-thang expose a persistent local user + key as a `cable://<64 hex>` user address. Each room gets its own 64-byte + Ed25519 secret at `~/.cable/rooms//user-feed.key`, with owner-only + permissions. The key is stable in that room across restarts and distinct + from the room and admin keys. +- `hyper://` parsing only aliases the key representation; production Cable + does not implement the newer Hypercore stack. +- The library defines a bounded Cable text-message envelope and libsodium + sealed-box encrypt/decrypt helpers. +- dat-thang's left panel is a peer list again. Double-clicking a discovered + peer opens a direct-message pane; messages use the C-only sealed Cesspool + record and can be exchanged between Cable-enabled dat-thang clients. +- `cable-thang` is a native C/S7 room client. Cesspool handles discovery and + authenticated transport, Sleep persists local feeds, and S7 builds signed + room-event S-expressions. The room UI supports room creation, named + channels, nickname changes, and channel cycling. This path is Cable's own + protocol and does not claim Cabal wire interoperability. + +### Hypercore feed storage check + +The test-only Cable compatibility writer occupies the `room/0` slot used by Cabal Core 5's +`multifeed`, exposes the expected `localname`, and shares the Hypercore tree, +signature, bitfield, key, and data files. Run +`tests/interop/dat/cable_hypercore_compat.ps1` on Windows with WSL and Node installed. It +writes Cabal-shaped nick and chat JSON blocks from C, opens and verifies them +with the bundled Hypercore 7.7.1, appends a JavaScript block, then confirms C +reads that appended block. This verifies the standalone C feed format against +the legacy Hypercore fixture. It is separate from the native Sleep feeds used +by cable-thang. + +## Side-by-side regression harness + +`tests/interop/cabal/cabal_cable_side_by_side.js` creates a Cabal JS room, publishes a nick +and `chat/text` entry, and verifies that a second Cabal JS instance receives +both through Cabal's live replication stream. It then gives Cable that exact +room address, including `?admin=`, checks address normalization, and checks +that Cable's local user address persists in the room and changes for a second +room. This covers address normalization and the CLI's local identity utility; +it does not exercise cable-thang's network session. + +`tests/interop/cabal/cabal_cli_features.js` uses cabal-cli's own `cabal-client` API to create +a room, execute `/nick` and `/join`, traverse joined channels using the same +channel ordering as the CLI's `Ctrl+N` handler, and exchange chat/text entries +between two independently opened Cabal clients over live Hypercore replication. +The C SDL live harness is `tests/interop/cabal/cable_js_c_live.js`; it launches one Cabal +Core JavaScript peer and one headless SDL/C client, then checks peer discovery +and chat in both directions through the Cabal Hypercore feeds. Run it with +`make test-cable-live ENABLE_SDL2=1` after building SDL2 support and installing +Node.js and Cabal Core. The test uses a local Hyperswarm DHT bootstrap node and +separate client UDP ports. + +Run it once the historical JS `cabal-core` package and Node are available. The +test invokes Cabal JS directly; the UI command for people is `cabal-cli`: + +```sh +make test-cabal-interop CABAL_JS_MODULE=/path/to/cabal-cli/node_modules/cabal-core +make test-cabal-interop CABAL_CLIENT_MODULE=/path/to/cabal-cli/node_modules/cabal-client +``` + +On Windows with Cable built in WSL, run the same harness with Windows Node and +the installed Cabal package, routing each Cable CLI call through WSL: + +```powershell +$env:CABAL_JS_MODULE = "$env:APPDATA\npm\node_modules\cabal-cli\node_modules\cabal-core" +$env:CABLE_BIN = "$PWD\build\cable" +$env:CABLE_RUNNER = 'wsl' +node tests/interop/cabal/cabal_cable_side_by_side.js +``` + +The harness maps its temporary Cable home directory into WSL for this mode. +This is useful when the WSL distribution has no Node installation; no second +Node runtime is needed. + +The normal `make test` runs this harness when it can resolve `cabal-core`; it +prints `SKIP` when the configured Node runtime cannot resolve the optional JS +package. A standalone `make test-cabal-interop` reports the resolution error +directly. + +## Compatibility boundary + +The upstream Cabal components documented here use Hypercore feed replication +and their own Cabal database/index conventions. Cable's current GUI messages +use a Cesspool transport record and do not enter Cabal feeds, so they are not +yet readable by Cabal JS clients. `/nick` in Cabal appends a `{ type: "about", +content: { name: nick } }` record to the user's room feed; the public user-feed +key remains its unique identity. Cable has not yet implemented that nickname +record in the Cabal message schema. +The next compatibility layer needs the Hypercore transport/session selected +for the deployed Cabal version, feed discovery, Cabal's multiwriter user-feed +model, and its JSON message/index schema. Dat archive wire identifiers and +`dat://` handling remain independent. diff --git a/docs/cable-live-streaming.md b/docs/cable-live-streaming.md new file mode 100644 index 0000000..d353d3c --- /dev/null +++ b/docs/cable-live-streaming.md @@ -0,0 +1,60 @@ +# Cable live media + +Cable live media travels beside Cable room chat and Dat archives. A live +session is not an archive, does not append to a user's permanent chat feed, and does not +advance a Dat version. It is sent as volatile Cable records over authenticated +Cesspool/uTP peer sessions. The current audio path fans packets out from the +broadcaster to authenticated room peers; multi-hop relay is not yet wired. + +## Audio, first slice + +`cable-thang`'s `BROADCAST` button opens the default SDL microphone as 48 kHz, +mono, signed 16-bit PCM. FFmpeg's AAC-LC encoder converts 1,024-sample frames +to 96 kbps AAC and wraps each access unit in an ADTS header. The `cable-live` +packet header carries a random session id, sequence number, GUI timestamp, +media kind, and bounded payload. The C viewer validates packets, decodes AAC, +places PCM in a half-second maximum SDL playback queue, and plots those decoded +samples in the waveform panel. `Ctrl+Shift+A` toggles the local broadcast. +Received room audio begins playing as soon as its start packet is seen. + +Cable audio and video are experimental and disabled by default. A normal SDL +build has no FFmpeg dependency. Enable both together with +`make ENABLE_SDL2=1 ENABLE_CABLE_MEDIA=1`; the makefile requires compatible +FFmpeg 5.0+ development files (`libavcodec`, `libavformat`, `libavutil`, +`libswresample`, and `libswscale`). To build the SDL clients without compiling +either media feature, use `make ENABLE_SDL2=1 ENABLE_CABLE_MEDIA=0`. + +On Debian or Ubuntu, install the development headers and enable the feature: + +```sh +sudo apt install libavcodec-dev libavformat-dev libavutil-dev \ + libswresample-dev libswscale-dev +make ENABLE_SDL2=1 ENABLE_CABLE_MEDIA=1 +``` + +With media enabled, the Cable video pane can listen to a local OBS MPEG-TS +output. Click `OBS INPUT` or press `Ctrl+Shift+V`; Cable listens on +`udp://127.0.0.1:5000`, decodes the video on a +worker thread, scales the local preview to at most 640x360, and displays the +latest frame. New frames are reduced to 160x90 JPEG and sent in 4,000-byte +fragments over authenticated Cesspool/uTP sessions; receivers reassemble and +decode them in the video pane. Transmission is capped at five frames per +second. In OBS, add a Custom FFmpeg Output with container `mpegts`, video +encoder `libx264`, and output URL `udp://127.0.0.1:5000?pkt_size=1316`. OBS +input provides local preview and direct broadcast to authenticated peers. +Camera capture without OBS and multi-hop forwarding are not implemented. + +## Storage boundary + +No media packets are written to `.dat`, test-only Cabal JSON chat feeds, or the +versioned archive metadata/content feeds. Closing the room discards the +decoder and playback queue. This avoids turning a live broadcast into +permanent archive history. Hypervision's +historic approach chunked WebM media and appended the chunks to a dedicated +Hypercore feed. That is a useful precedent for chunking and peer redistribution, +but an append-only archive is not the right retention policy for this live +session path. + +The packet kinds cover audio and video start/frame/stop. Direct camera capture, +multi-hop relay, remote stream selection, and adaptive bitrate are follow-on +work. diff --git a/docs/cable-thang.1 b/docs/cable-thang.1 new file mode 100644 index 0000000..66f12bb --- /dev/null +++ b/docs/cable-thang.1 @@ -0,0 +1,124 @@ +.TH CABLE-THANG 1 "October 2026" "cable-thang" "User Commands" +.SH NAME +cable-thang \- SDL2 Cable room chat client +.SH SYNOPSIS +.B cable-thang +.RB [ cable-address ] +.RB [ --offline ] +.br +.B cable-thang --help +.SH DESCRIPTION +.B cable-thang +is the C and SDL2 room chat client. Create a new room address in the terminal +with +.BR cable --new , +then launch the client with the printed address as its positional argument: +.B cable-thang cable://address . +Launching +.B cable-thang +without an address opens its SDL room window; choose CREATE ROOM to create and +enter a new room, or paste a +.B cable:// +or +.B cabal:// +room address there and press Enter to join it. +Cesspool handles discovery and authenticated peer transport, Sleep stores +persistent local feeds, and S7 constructs room records as S-expressions. Its +fixed-cell, terminal-style layout +shows joined channels, room messages, local nickname, room address, and +authenticated peers. It joins a room by address. +.PP +Click PLAY AUDIO to start sending live room audio; the button changes to STOP +AUDIO while active. Ctrl+Shift+A toggles the same control. The first media +path captures the default microphone and sends 96 kbps AAC-LC. Incoming audio +is decoded and played in the live waveform panel. Media packets are relayed +through Cable peers without being appended to chat history or Dat archives. +See the cable-live-streaming document for the packet and storage boundary. +.PP +Click PLAY VIDEO to start sending the local OBS MPEG-TS input at +udp://127.0.0.1:5000; the button changes to STOP VIDEO while active. Ctrl+Shift+V +toggles the same control. Configure OBS Custom +FFmpeg Output with MPEG-TS, libx264, and that UDP URL. This previews the local +source and sends 160x90 JPEG frames to authenticated room peers over Cesspool. +.PP +Room creation stores the room signing key and local user's persistent key in +.IR ~/.cable/rooms// . +The room address is the same 32-byte key rendered as +.BR cable:// . +Joined channels and the local nickname are stored in the room's profile. +.PP +Without a configured Cesspool rendezvous, the room broadcasts discovery on +the local network automatically and shows LAN DISCOVERY. Messages are kept +in the local chat until a peer connects. Local network broadcasts do not cross +routers. For discovery over the Internet, a reachable Cesspool rendezvous node +is required; configure one with +.B cable bootstrap add host:port +or set +.BR CABLE_BOOTSTRAP . +If the local UDP network setup itself fails, the SDL2 client still opens and +reports the error in its status line. Use +.B --offline +to deliberately skip network startup. +.PP +The production client is C and S7. Dat archives and Cable rooms both use +Cesspool, with separate topics and record formats. Cabal CLI and Hypercore +interoperability helpers remain under the test tree and are not launched or +packaged by the production client. +.SH COMMANDS +.TP +.B /join \fIname\fR +Join or create a channel and announce the channel to connected Cable peers. +.TP +.B /nick \fIname\fR +Change the local nickname and announce it to connected Cable peers. +.TP +.B /channels +Show the number of joined channels in the status line. +.TP +.B /help +Show the available commands and key bindings in the status line. +.SH KEY BINDINGS +.TP +.B Ctrl+N +Select the next joined channel. +.TP +.B Ctrl+P +Select the previous joined channel. +.TP +.B Ctrl+C +Copy the room address to the clipboard. +.TP +.B Enter +Send a message to connected room peers, or execute a slash command. +.TP +.B Escape +Close the client. +.SH ENVIRONMENT +.TP +.B DDAT_BOOTSTRAP +Comma- or semicolon-separated Cesspool rendezvous endpoints shared by Dat and +Cable clients. +.TP +.B CABLE_BOOTSTRAP +Comma- or semicolon-separated Cesspool rendezvous endpoints in host:port form. +.TP +.B DDAT_PORT +Local UDP listening port for Dat archive Cesspool discovery. +.TP +.B cable bootstrap add host:port +Persist a Cesspool rendezvous endpoint in +.IR ~/.config/dat-gpt/bootstrap . +Use cable bootstrap list to inspect the shared Dat/Cable list. +.TP +.B cable bootstrap serve host port +Run a Cesspool rendezvous node in the foreground. It must be reachable over +UDP at the advertised IPv4 address and port. +.SH EXAMPLES +.nf +cable --new +cable-thang cabal://0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef +cable-thang cable://0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef --offline +.fi +.SH SEE ALSO +.BR cabal-cli (1), +.BR cable (1) diff --git a/docs/cable.1 b/docs/cable.1 new file mode 100644 index 0000000..c49e063 --- /dev/null +++ b/docs/cable.1 @@ -0,0 +1,114 @@ +.TH CABLE 1 "October 2026" "cable" "User Commands" +.SH NAME +cable \- Cabal/Cable address, feed, and bootstrap utility +.SH SYNOPSIS +.B cable --new +.br +.B cable address +.I address +.br +.B cable whoami +.I room-address +.br +.B cable bootstrap add +.I host:port +.br +.B cable bootstrap list +.br +.B cable bootstrap serve +.I public-ipv4-host udp-port +.br +.B cable nick +.I room-address name +.br +.B cable publish +.I room-address channel text +.br +.B cable feed +.I room-address +.br +.B cable --version +.SH DESCRIPTION +.B cable +provides address, local feed, and bootstrap utilities. Its SDL2 chat client is +.BR cable-thang (1). +The CLI normalizes legacy +.B cabal:// +and +.B cable:// +room-address forms to canonical +.B cable:// +addresses, preserving an optional +.B ?admin=\fI64-hex-key\fR +room moderation key. Raw 64-digit hexadecimal keys are accepted. +.PP +The room UI uses embedded S7 and S-expressions for its C-facing data model. +Cesspool handles peer discovery and transport, Sleep stores persistent local +feeds, and S7 represents room records as S-expressions. Hypercore and Cabal +CLI interoperability tools are test-only and are not production dependencies. +.PP +The historical JavaScript terminal client remains available as +.BR cabal-cli . +.SH COMMANDS +.TP +.B --new +Create a new Cabal room, save its Ed25519 room signing key with mode 0600 +under +.IR ~/.cable/rooms//room-secret.key , +and print its reusable +.B cable:// +address. Pass that address to +.BR cable-thang (1) +on this or another client. +.TP +.BI address " address" +Validate a Cabal/Cable/Hyper room address and print the key as +.B cable:// +plus 64 lowercase hexadecimal digits, preserving any admin key. +.TP +.BI whoami " room-address" +Print the persistent local C-side user's +.B cable:// +address in the selected room, creating that room's local keypair on first use. +.TP +.BI bootstrap " add host:port" +Persist a Cesspool rendezvous endpoint in +.IR ~/.config/dat-gpt/bootstrap . +.TP +.B bootstrap list +List configured Cesspool rendezvous endpoints. +.TP +.BI bootstrap " serve public-ipv4-host udp-port" +Run the legacy Cesspool rendezvous server in the foreground. For a persistent +node with a stable identity, service unit, and operator guidance, use +.BR cesspool-bootstrap (1). +The UDP port still must be reachable through the host firewall and router. +Clients can configure its endpoint with +.B cable bootstrap add host:port +or CABLE_BOOTSTRAP. +.TP +.BI nick " room-address name" +Append a local S-expression `about` record to the room's Sleep feed. This feed +is experimental and is not a Cabal Hypercore feed used by +.BR cable-thang . +.TP +.BI publish " room-address channel text" +Append a local C-side feed chat record. +.TP +.BI feed " room-address" +Print local C-side feed records. +.TP +.B --version +Print the Cable version. +.SH EXAMPLES +.nf +cable --new +cable address cabal://0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef +cable-thang cable://0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef +cable bootstrap add bootstrap.example.net:49737 +cable bootstrap list +cable-thang cable://0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef +.fi +.SH SEE ALSO +.BR cabal-cli (1), +.BR cable-thang (1) diff --git a/docs/cesspool-bootstrap.1 b/docs/cesspool-bootstrap.1 new file mode 100644 index 0000000..ca9add1 --- /dev/null +++ b/docs/cesspool-bootstrap.1 @@ -0,0 +1,82 @@ +.TH CESSPOOL-BOOTSTRAP 1 "October 2026" "dat-gpt" "User Commands" +.SH NAME +cesspool-bootstrap \- dedicated Cesspool rendezvous node +.SH SYNOPSIS +.B cesspool-bootstrap +.RI [ options ] +.br +.B cesspool-bootstrap --help +.SH DESCRIPTION +.B cesspool-bootstrap +runs a Cesspool discovery node in the foreground. It answers discovery +lookups and tracks peer announcements; it does not relay chat, archive blocks, +or other peer payloads. Leave it running on a machine that should provide +discovery to clients on other networks. +.PP +At startup it reports its bound UDP port and prints the Windows Firewall rule +for that port. Use +.B --advertise HOST:PORT +to print the externally reachable endpoint for client setup. It does not +query a third-party service to discover the host's public address. +.PP +The node creates and reuses a persistent Cesspool identity at +.IR state-dir/identity.key . +Keep the state directory across restarts and protect it from other users. +Losing it creates a new node identity, but does not change archive or room +addresses. +.SH OPTIONS +.TP +.BI --bind " IPv4" +Local IPv4 interface to bind. Default: 0.0.0.0. +.TP +.BI --port " UDP-PORT" +UDP listen port. Default: 3282. +.TP +.BI --advertise " HOST:PORT" +Print the externally reachable endpoint in client setup commands. The +endpoint must resolve to this node and accept inbound UDP on the selected port. +.TP +.BI --state-dir " DIR" +Directory for the persistent node identity. Default on Windows: +.IR %LOCALAPPDATA%/dat-gpt/cesspool-bootstrap-state ; +on GNU/Linux: +.IR $XDG_STATE_HOME/dat-gpt/cesspool-bootstrap , +or +.IR $HOME/.local/state/dat-gpt/cesspool-bootstrap +when XDG_STATE_HOME is unset. +The directory is created if needed. +.TP +.B --help +Show usage. +.SH RUNNING A NODE +Start the executable directly: +.PP +.nf +cesspool-bootstrap --bind 0.0.0.0 --port 3282 +.fi +.PP +Keep that process running. Allow inbound UDP on port 3282 in the host +firewall. If the machine is behind a router or cloud firewall, forward the +same UDP port to it there. The program prints a Windows Firewall command; +other firewalls need an equivalent UDP allow rule. A public hostname can be +used with +.B --advertise +when one is available. Clients set the printed endpoint in Settings, or run +.B dat bootstrap set HOST:PORT +or +.BR "cable bootstrap set HOST:PORT" . +.PP +The periodic status line's peer count is the number of distinct Cesspool +identities with at least one current announcement. The topic count is the +number of distinct announced discovery keys. One identity can announce many +topics; a Cesspool identity is a network key, not necessarily a human user. +.SH EXAMPLE +.nf +cesspool-bootstrap --bind 0.0.0.0 --port 3282 \\ + --state-dir cesspool-bootstrap-state \\ + --advertise bootstrap.example.net:3282 +.fi +.SH SEE ALSO +.BR dat (1), +.BR cable (1), +.BR cable-thang (1) diff --git a/docs/dat-paper.pdf b/docs/dat-paper.pdf deleted file mode 100644 index 500427ebdaad558f509a97ea22abee2b4eb6b0b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 248958 zcma%?)2=9hf@HUC+qP}nwr$(CZQHhO+qQK!COzp)I_Y`1^%Iq~QbnpDB1X$d#|lNd zurj&<#mLS;z(8PcWCg{;14S=mYG>|ZLBPzy!c6dg14S=pY2#w*L_jZQW9VWkVrpz} zVhY8_2j%SIWNK&&<*}8jCFy)ThT8W}{Q<2)Dr6D5ZRZA49*11nag}0{a1M|}n?#CW zzy)}1p`Xv*8lgy{T#2i|z0dFZUGMr|xqaVH$yF+qa;;OhmzQ3pOnGOWdgz>T@_BXn z?v?)JxS5YewhX!TuaC{}#r7;qS1t^TF>(&gm(yK4e3uNI-^KQRRwJ%g-bY>f>7NHh z^~fgxvu(fs$NRGUzTeWORAtMYbF2x9rS0{qmb#^?HQAB#^DAn+t1fu?1<~iW-}#rg z-CyaWEd;FF)XDQ3eciv*wLE;P+=;CDSr3cs0qef}GK6q>!FEqd2+s9z_Fi4LtELr@0FM5 zJSa8lv#?fbQ)Of5qcJVbWVOtoe40Q)(7zLR>;$1^T+eVSHCHY4t^rt0E%j=c-<7JB zY8}){x`2ga8tLjJwg>b~Ggl`sg-Cp{9{*gM@HldJx|V-=BQU&X;4DB97^LLIyKo_s zxrdr;L1H{e-E|g|2U=4Mv~VFHQ47TCPbI!*_Z|R^iq(&C*t*B>OCm@Q&{S&V;br^C zxGYRBh9GW3iN@eT^T{ZlHJ?)*Mo@G+1(R!7F1yO#7x0VRX=ivW(OedPQHDEnV%gsN#9ptCUN{VcI@Km?-cG4vv$fN{N_l5`D*!uy-8s%&4up35qI}twZqf?*Y7HO*Vq& znd9bo-=xfK&i)WP-qH{-Hj@2@%V3%b6OwRls1{0!8Z1yHU1t#O#MIKvy#$o>O4`uo zc|Q&z(*Wbx-xz2Oq=^lou!Xqb{%4zjw8{|2iDByR2aN_%JyQqz9tbTJ?%FO|mLR09fp}n(v1&ebj6cfMA59j|5bJe);4}WWxMY`^v}TZJFVRXu92?vsQFZ zl`BDDz==9gX%?q)zbP(Q(}a?4)h@gQ)l8S05b!#~h}$d3FVgyz_6G4gNgLhF z)|7H1m(y0#!QAYkLtZ@CbegRFyDd-3RuE3paEOj;MmU^>O%B$5dn;h$8|Yg)Xa7lXqq zu}hcdQdCBRQhGpyQThOq^-azZ{NsP@QVj??L>K1T=N)YiN-?4hUC&GCiD;9JM{r$_ z+~Rp84O)yCyoPlxv&%KAvhR%MQKSCS`iz53bDz)BY|q5j;w?wpt$G$wwP`g zuD8&awC;QvKU7h^5R$p?#L5BfT5q#tp;bm=iVGSLT73(RtHd(5pzrZEkpb~c6vh;= z!{KIuGM6NVrQ3>J7HyxcaI)B}6_#8L9%1RDdk=cn?_H!yLsb*3*9Pbh%jbN^9Y?bX|B^3I}lVmg&?Usofb(oM5tKqJuSkQ!ya>;%&7AP@IuS{MJD$e?lTS*$?UbeFC)(g zsTvaH809bBnJ`~WdB6Ovjc#46I!zHwo_agkr*i?$jNWtPoQ)j!7E%@~rkwFdxBlxPD^cPBdN?^A4(D522XrZ9h6hV^_(!((P<`EdKFL75YQYEW+wI zBrQd_hdeT;j$}bJARZ)PW78vRqfN<)Y>Z?K) zv|)7(_&$+!SP658_dw3^0Mk>(hfb1-Ff~~tZdA!tYw7%rDhA^0f01KwBw-m?UmoFk zLLiHUHJyMf!oxLT?NV{hcv~4vKOsSVnuj~y;GC=n-6IG!MAt6>k-`aZGfAm~t}`qk z0~1e*9}uMb>kuQorEsGG3@%{jnzx;-lc0W*5-AZuFR4;wf$0act5 zY2`JiI2b(K_T6U3g#(`^`CZ>BnX-lm8!qIvCQWMqFbPnRmIALF-ymFTVN2Esl*Ldn ze-rk{Vt=|VbgTduEXy1_fM}mTZSi`^xIsn~KyHI3y(+C#CE-){A!a!^myTNm+Z=pN zkG?hVZsXNzZsaY#`Q`yzBlzt!dQ?|QtK2~L9si(HLKTu3CF4Nc`Wt;6v-NlkUmj6^ zS+sOxJ#7ZA&w}G+MGrl$=t0ToDEp~TWM584N$2QvF-A51t9(>qc8%*bNkn+qbj*eb z43&Xkp$pW1hy@rej^^~lj(Z=mwDN|`n$J%p99x zH851&`p%3Tg!?qG1EU$R>6C>6)&KRio1{eFH0;lb?>1$t!08fcR%E0v5HG)GdW)k8 z6s7O02tgpR_}FLx3f@8X(yoJ+ahn5!o&)2qly)@DVQN@bY>+Vs zotQ2avaqnGiAi`{IMM+&{Rj$DADx5qjEH^x4U-Ebq}CooHseV_UH57E1Wkanr{hu< z&DFfv>jSS5XB8#Zgttt&$@$8C-ql=U5)1~?`SzHXG=?RNzXzw=Ev}J@9n&myR1%lL z%cdX%K@zWodZ6HE7BKF>C{OQ=1xJy`?a=9VfBXbs{l#U(Qj%AmYg>1C&A>nDP(sk( zkq9=BNi+ClRl8-hd1|8hd|Jo%`^e1xIFg_*Zfe}$J(W}|7xfW4cvrBt_Uai4m#CRd z&4>W05twcL#ueA08XdfHgW)02OAwcb1?R7;12Q8YJcbF*c;?Mg(JeFe5gFK#h{M!| z0a0o89jiAD9RN&&ih0l`g;(kX04LnN%W6y4f{$xyG8wVjrCnB5%}aY+C7{_0>ibbK zfrh3~Rz0s~4f!idQ$NsenfeX?1LaB1u5JpJJKbzonAUZSt|gGkY}e)s5Ep26_y!c7 zb$v3&-2Ue_viA9IDOjVv^?f^Njp9va6-isSS)^r5G@Z))SC_@KZ>>3C0jhz(+h{=( zpKK;Jui|t(g=ALbm+=NpsTiiAU2*1h{f!4}JAn;GtvranIS>Pqz zWeqwL7`*na5Bb;blfDWWfZ0#2Z@_;-V&CTDqK$Z)6+-)M)&ybZhukJ?%f@1W1_#x ztqY#5^xHXOc`V!M#*@abQ+-byJ@ze8Q~wC|Kfmwm?fWK^#>w8LM&<|LY|YIR^;)F7 zt|IZ#$Uf-CUrCtOcKQc;_zS8V<-M`M?lsK2gXQ)hcsyq}eI ze0EK&)YZ>vl8T}TT_ZK>J^Q<5718}PVm23-8*ZMjaj$O0MEs8CrymWVXaiTB`(|cL zWbBVp(0Ga1*$AwI(pb0hCVK6Y?+Zu4o9gW<&OC&?zg`bMI`jkQLuqel}&<@muMj;N2}Mb)OUey{tvWLmNZzLiK`3zY@%o% zetWmy)toz+~g3$_Lt;7%MhN@uZ%yZkf?Gggsuy`;pvPry0$-4emG6O?o$= z_}&k`g>kzV216&?)~PVf4chd z8@|6x)j565kznJ*Wzp72$^rR7Yb+%eE8^Bd@0s23eKz2BQ1By;0r{Rrb7m#sAO8^b zUwnUP=!3PKf_HvG135sSl-(^8N5(A5Zenn5JX7=uz0V8pK95=>b6As=(u2LBe{J>SXa<>?z#@+n+)TXd*+}3VRu?wL8W=jV z#(jAuG@$#z^Y#m`Vvl0e6X{_S?X}^J_4#wfIBrR+1KMb!m1{HF4tZkmZb6Ob2l|Qa zVQYaGz;FUAwBea9TkWvyR8J?a;^Q&k0&1$R1J{Y=HKL$+#$^Iz2NNU7vA=x~UIG^Q zq`P;Vfa7mRwgo7d7rire*?=pffuO+efXG-`6~K^^^UM#>XD@k2M*m4_mF{iXcY!(-GCzFmet^BJFJ9vn2hQoN3|tHh>q5il zZ4YnYdJ4hI$%^VeBi5wsf%z3jwDah=+B^yRsK3xi9<4;IZwFBlBT`yGAtwg)o7{xe z7MODfQ7-^)O~bOW4x<=TpBuE|bX}pm#LvC^M-~nvCGUP1CnHFkwrK1Eq=SVte+O!%XO|{v~3Cmi+sQ9-}A_4}@DE9KF z7IaBK^SM&M7ccG-;+o&lews&#U=hnLEBGwjU>)yY9ixr{ILFwM<#4QY_ip6OL7Dgt z){rxn8-!2^-C2u&MLTa`#2IM9y8HZnd?2@DDKf8l`h;gh;|40&*mJi99Adeujb|c} zwc=uBLcxI&l%1RN@OtPlyz;sybXKSs0lVm2kUTOO!VR+sjdB(l&Et~xzee?%Hn?2` zID<&i^`K!`_z4xH{_Kb44B7}N*%$<6TpM^t0EVD58xu9N^GEfP}UMc zQC>Y@TCLDt>Ck|GQ&pqTfrR(AzGfZVv)diFl+!5?k%%nO}frSg#`) zO;C7X?%L5C_$d7(x4eNG1a^^{W&_EbZ6R^t1x0QKG}l01fzc{4V^B#xsp;SYMkT-kzf%^UHoJI4qf6G)zE?L0U)E;nNFFr3s2wW_e5!@h= z&N4-e8Dvnff78}L-%XoAI>&6REr#Ah;)X!vL?h_`EOl{)4zktMYU zHoEr?BIKTnf*R}tZDx=6d-#zib{^mJz7uWxb_ff6TnaIXfE3Ja`c`HK%?~lAWy6_~e)?-Z=__VF_YqO=)zZ00qPioYTW_t0MW_=A~8lQjm*Y~ur(=&((=n_w)OC^95(d8OC?1fFCP~hzkge& zV5C};#48{OrsfK5wz;AM;InBc9b7OJwo|OaT1NE1Dz$%*UKnSG&9csm)Es_^^vXf8 z;<&!3P(ie&#sNBp=tz-5#?gp`E~qWf%h$N&Y#%?@io(|@C9Zta)&4sx55W)iP0$d7 zqpxr;7_JDp;!?KEi70I4nkmL4fJL{s+E@>7;)i}f8U+@{`=`vB2Q*f7>Key*?K+np zlm(%QQb>)idI*|H@DJMT1LO^yN`kKM}CG{pttpaZr#z)%mEFuAtny#7M zO>-d9?Ek4G*pR&(xAh|&@i;!(GNVee1!^_GV z)TYg4p~ksvJsmAynkt7U=#(wq#N55DSVNIDa#*oI~H*B0JBNuV+K+I z9u70`BXn8di$yIJaF8(Plq5n5pG}+*d(=3Y+hLJ^5jR0Th!ZD4sm}1z zu&%9)N~OTd&&X0BSZPojZUm@Gy2bz!Z3O6^&wcO+W7tUpQu3I9YCeMk@(Fxm;XpTm zM3hOoDzr-@t@qHBh7lwNZ|PPV3-FgT^x<~JE^zeN1s0+OudmPP^}tAVquV=m+~7^W zZ*`a(YA2bD7mP)~I|X)K{~&Wk{venl{l)(q>^@=(d zb3a^?jEGmjEaM3Cg}fK6t)lhs242atE0jIq)}*?pQs57=>l3@9sBU5*8$@pEnFMP5 zT+GOGF5sxGFe8RHD5IlrO!gy|N}@Tkh+O&*gL$5SzK!v2k5(H8&%30$6+xm}^~>bE z#Wy3oLA!u~R(S=u~IXvwzj9()n5 zJfT)vmCLqQ1yBwJ1{{M&2iZDq1|+*jBoRH-A;(U)s(*%sNo7-hKeDq+U`r0I6Sqp# zFd<_viJ<&U9L1105j0I0!di(U@U-Y#WnXMDP`1;gqI>Ngq3^k5*+ab5!Hb7jy4kL$ z(%F?uRqAY$U(7jVIN-;nF%aKZq@b%teJ~g;ossu0fN@0VdOx^L$xjF=BMnB9yZ}ou z7&yH9icrDXaBx;=loHG%Oysn9A4 zx^Z4sZ*4zUeI9OR8+RrSq~bRaaGz+B39hAuO53v;Xz9y_^t6+K*-5|+Lgj;aql5Hi zWz_XY8iWqt9pf-*W;&Eu@-G5mroz5yP)S)xy$gkyqsZ1?mMOrPQjOvARUxhW~`h!t4#ph{Nm+>+mczp;Kli9%W0SJC-o(&I`*G1=%PP>py^vKgX602@EfnHmfi^*0(#g3LtTKN3#Y(f1FJ4wdMEPd5A0oo z00uTGsFZr3I#~@Xd4rCBn4A$1wGKy}z&eKS_?2{y$29lkxwj1Q6)!?s&=!4(lj5^oI2w7EjURbTkB@m7XFn}ya`IfQ<`@m!WE3p?(@xUW13{P z!n)&4`0_3(&=L=24a2d(a^@!F%u`$@58KdOLJFRLVB2`vZMfBNtpryaoII1wx|_EV zRm%46N~B0hA!}O?B)|P}k#*|(b)-Nn9KA7rPA@#5x z*v-c~;74gdY5C>#Bxqj85Xl~dd|y8$OAfw+u;(5&nuJz^!%e9vvJs^E@c!?L&BH@} zS6iju_;b0LPAh$Qo;3-cDG3mXdlAsc>N%&f;55dD?f`!OFDGkTTG{Uk{Au1a31FxM z02_k6g2@)}Jc*tFFuqM7I5f{;^D3Vd%p!9!sdS)lmSv_{(w^ZZ5qhmu%n{{%F%A!C z60Z2a059B+i3<@$rOJCiIC`6~sEBYy-)Y=oc2a^~C<;E8o74|)jzAE8=qf}hUw%oVM*r^)p4 zro%VnSUxx6aWj%qx<<8#p;SXLC~mPZ79BqWGN)2%cwNXI_3KGKL+)I+tmqD@(p35o zQG7_otF9&iVhq$+a0jyRwd@fzy&I6*`G`SAw_bQuMFG?`j0>~TktJzX9!JO%MnnBZ z2mVVa5K2ah*7crw{^@Zj3edyu3o9jN;1Jppa1|)YCjw7f%8Ug~~b5C<6>hswHE=66(#p*4bHF%8cc_ z@SdJl=22|g450RDlW~%~P;WT|lO4jTlW?dNw(eXox5nsj4jTq8ay#%~N0Dr$IUPzC zH`U{1DNX{&eZ37Gj|Y<}H)tR0soafLi(&$RrEB%2sjFSp3P6t%4xqV%7#L1RP8J

R&99>UCxf&?f2IZbWxpgS7C(2uo@^VqWIm!=3`Tx)j z7jz>A6$n(Y6BSgVLWT;Lpu*><$PpD~qMHWj=2~>~HoAq;ttseM6uPaAZf``ltI(aE z=uQ;6tA*|!LU$jdd%e)TCFoujy59-i_eA#}p$8q%gDL32ZS)YMho0zR4k|W7#Q~`J z9V)RvB}Y+71A4RsJ-UNR`=ZiisPrm&+#Wr4Mvp_$lNRX7eDvfzdh!pIjX`C{QCTB; zx&}S1M&;vCc^<0hgDQeiMLl|EhMuiK&&ttrfAqW>z372ntVAzLP~|XGc?4B{LNBMF zmyzgIQ}ikry}F62bWqhwRCO6u_eIsGQ1x$AtF~zt31>HP)!YO{{Q}%eZMb+|&yA&6x88!=w8w4c;5PSgTQ}Uc0&5y$&BIvpHEy>Ox68*`L$OvI z)^3Ki{jhceZtsKJS7IG2tg{<;Xp1|{#T}mFj=gcmZMfqL+-Vu^REBjYW8FO5*%^0E z!Cka*7f;;fGwy1OyXNC=hPYcG?)C}m&BS`wv3?({AC2|@;Qy~h4#vYVj>DuaCR;Ii zjbR3ccuc!s>W67PX0tI%z8|}bGwb*z8HhzYCPr<#f;67%!PXsoxz$Q1b z>1b@4jQe)PeN(VmPi(dsoBhN6Jh6EjY`zkkSK|KOxc>`0z!wkrhzC00fq8h44jyz8 z4{m}7hu|Svc*sU<(HvWNVTKL}B*g68+C~R{I57og#k7L_z*ft9f z8-a)2$HPbA;aBm9-grb3wll+aJFwkHJaQr)nTJO?;!&@#eK5BFj7Lw$qpR?kC3wsa zJk}YHy@$uy;&GSp_=$M@TRdR|o^TFNY=`{f6ZpKSL;AL)jSr%SC z7%#tsSB$|c%CYA->{)?V4#z8>;8lb0s#xsR3VZouuMc?j8oc@kUb7IdX~1jO;kBRf zx-h)n0IyHR-jlF*Dc&#zZ^*_zhqs1d zKZO0x;cdov+coSz6K`*Uw@<^{lktw$c!w+A5r+d>;edrWpcwBo#XC>pz~(q`Iu5*z zgA8%dE*#tv2hYO61$dVY-gN};ZiaWS$Ggk%o-ue&9^N|=?@h(~?D4*8ynj00{{|oM z!3TcegKO}?KlsoFeCR7aybB+(z(?|M$V43S7$2R9kAA_&cHmG$96A+;uE(JfIJ6Rn z>ESRB92Skk-s5mP9KIKaKgP!eK0XZ}KZcJ##}OlNL@Ykh9-o+wPn^Ifd*YLm@yQMN zWE?(OiBB2gQ=9OqYdEq!jvRv{qi|#$j+%s{f^gJ59Nh;;@4(TSIQlO>JqDlNhELzZ zF%~#x7moRW&lun{EAW{VeC8uQI~JckgU`Of=lbAt3-P&UI9B1<034fx&v(P;_u%ti zaok)ScOA#~#_>To{vA%}jT5%ugbJKE0w*5AiO+D-7@U-blL04h!O7R~g?9MD3Vh)a zPO-r$8*$1#oZ1(suE(i&@Wr0^;yQfs4o=g9cWq8op$JFRj9tuHnnA z@MULwITmNM!WrvuMmfIH4_`TouYAE*UGdc`I8)%vz{D$0GxXa=l;fdt~f6b=M$Xoh4TyX4TNtj!#4_X zfx-pralsv2I06^?;0& zBu#pdCVNOz1JZOSX<9{^8Ifi&q4gLt0ECE%HgrL8N5}Y59e;T1Z-zlh$KN z>vYn_jI=pM+I%8yok`nFqS=jTZXlZfk#=K9yBkD{5Up6EZAi2a6YYPb{SKnjl<4@9 z4lPNCouorO={SmXyg@pdkxqw6r&^*rl;~b2o%KoQwWLdD(&aGe@|JY9CtWj1H(k=r zhje>R^sI>9aiZUr=uaa0IRqIIltQox!9E1vB4ijLF$9_um_^_Lp$iGUN!UQb(h2WP z_+G-l5wVGNDO}xBY$GlNQ_q!;|HYobkaME^syj)P7;$g#AFpQc~49y64P?hcPQx_PR#U) znGZ2*ApMq+e(#C-Xkwl~`fHK?GfDr8WPm9daGeY^A_M)&z?WpuU@|D03^pKxPm>{( z4B1VFd?XeQ#NslsB*gLnv3yCatcX=4vF<{wHxug$Vl$T5cmf$QfQ(2cc8J)mA$Bjw$Z=$3Dj8)#Mr9EDKEytVjMgBdeaPrKGG;Rw z^Nx%iM#iR*aYM-%duCs~jJ2Gt^nRbs%w3A@1#n`&Q!qiOlsRbIZuQnPlEA zGJgu0|C%fqO%~*mh5g9Fc(SM`S+t)l?nV~xAxoN&B}>SX7sO*N@%TuV?jlS7kY&@! zvM*%$GP1mite8Po+#;UiiRVqS(t@nKO;$}Mt1^j~1@SsTR(B$+cat^xWKA+zYfaW( zBkTH*b?3AzvT+gF_>63FC7bS%&30sS z1liJ&Y&k}@b|G6+h@TPh+fDqwk!=Tve-q*#Nc_K%?OtSiE!i=H?07-~97w<;veS+1 zEGL0(B=9>4T10|MN$>~~oJ4l@AiILeuFqumBC`7h*)xaic|`WwkiD15K6A3~GTCoI z_Ggm=i^+jMw&chLa^wRESwW6=BS#OBWBTM+7zs5Xp-Cjn znuOgT;dUfElN|3yj$b4Zff6Sm|;4mmlNoXjDo%*d%Q64{AF9wJfgNz`!?-HSw@ zAg6ni(_tjWki&DI}@O$VDA;F^F8OCuwU)+AESi zfuvs}mu8Slh2-)ma`^(uAS5G%T%qJj47oapTumUEgGgo^$r?toa>z9Ya_u?E_8{3c zBxfVJ-i2HbC%HpO?sbyqMDof={!Ehpo7~t>3J@tsBZZ?#;S*A{oD_W{HxH6qI^@_eWkB~PMAnKdaZB~Le#ax+qXhg7(eXRXMyNb-CTd7e*RxRAs`r!X&!onY)TENvTI6*QdHtKbaU^f9lDE3#?RN6E zp1fO0-aRI@_M|qCymun+?~xCd{`80-nDk1d)NPQ~# zY(PFokS`|W%VqM_l6<{O8s?FP3i53N`BqN8?;t<4$d66r$2anG4QcF38uyc5n&j68 z^6NYK9ZCLh@+Xx1H6(wJkbk=5UovP&&0t^vXcr5cGF|eghx}K#_o30S*S-0AxClGJt6S z`vDq&+5=4orVnfxusq=Xf$stS0>mf~5g`77bOKof$_i8js6Wu%5xSoT0~0Vf2nLPN zV-@sx2|cGm&ur*r4ZUuH;Xp8q2O~2ux&p={!1y-wwuat$&}TOEsREPEU@{s^V!*T+ zn9c;#bzpiBOjE$L8v3??zJ}0uG4wqReV>7u9+*u4vkhPt31)AhpDFa41N{P^-*xEs z70eyM+#k%7!TcZecZL3kq5nM?&=dwtg8@fiKrsw7fPwxn@GT57fI+iiP&f>#hQU2y z@M;)*3I;!fAzfjJD-4MTi`HPV04%b=;wxD80n2@0SqfGFRv3Rx8m#NV zrYG2J1Di@1+69Ilf}y{_b~MjJ~(!LSq<_78@ShT#Wb_-hzp03#;DhyyU< zFWAihyR%?d4OpAnR-(dQ5n0^XoG=UlOVa9!MW8k(6+!|q~Bg{;NnZIDx6quC? zv)jOIYnZ(cW>>+S$uK7!+%>^{A-G?LxqV>n1(??s=J~?B$1vX*=5K)c`LMte7956! z&0*nYSXc~;>|jw8EN%{qXTaiISfXG_5G*MN4+0)>u+$8ehQQLFuxu7AD}d!=V0j#@ z7yv7xz_S&2ZUE0RSUCVzM!~8Mu*w5g<$;$Gc*VkM0;{*f>aVb7Ev%`AwIg9|G_2Eu zb&;^XC#>HF>z{$Q6?mt?hM}+_34HXyXD9g7fbS&my#yO=U}Gd~Y7Ltf!lqlWc`9s9 zf-OB^OCoIT4O`>EPY?W}VVi(${;=&U_-}{pny`Hb?9hT8dtt{92-pGvFJR{)*!c+p z=R@FI2=auWXAnFcf^WgDd9b?;>^=^Adc&SH*gFLFI>6pFur~$vwS;|BU|$mKZwdQd zVE;)tpaBQ$;Xn)=XoQ0!;ou=S_!kZ>fkU_9urVAy07rD;h#!P>fRHE%xerIR;HU!} zJq5?wz%dUv_6kCKLg*F<&4#dE5EcgEO(A?Gguj5}R&cxsA}B<7LPQ~)=m{sb!HEhu zIT}vpz$s@ql>(7HATkf4Tp;Q*M6ZJAbcp@|r>)?$ADq4iG0h;x24Z$W%nLZv2hMDS zGlg)r1)Oz+v+;1Y5zdW)bNk@j9f;M0*eMVj3bBviynyq2;Cwa24TrdM5LXTH9O9=! z{BcOohJ=lfa1|1pK;lS9+zpA(AxS~fDo9F)cPZrFfjk9yYauTK^8P^nG{}#J8=7!q9^A-> z0t5w%p&%U!6%?+5!U8BVgrWc_Du&3dsvA^ofvPW1JsGMCpr#enjE90dj|Yy13%WmkI(RPDEv%-MhcC=(D)X9S-`LT@T&rT_k`bD;P+?vGX?(Sz~5f* z_W=C;3;!mdd4%IkW?#9gh89zt?dt?U+P6N!lrf z>b9o3i>Piq?W|2Z2hh%yw96#gC5v{orCnoaH$=M~qTL#)-gv5)PW5f6{%ML@Qsh8U z2K_%CO`)U{CCe#!LSYz%Pzv>wT2gwN(yx?xQkFw`XUgYMobtd zRSNB{NxQq#?)lVU5H*OQJp}EsmG-zpdur33i)qh%+N&?^6-o`eQNx4Ou!RP z=pQwnMUC@l@5!`x0_{U-pWU?2Yicrynq*Q_18RDRntq^tooL^5Y6jHIkDC3Y{T9=H zh1A@PnupTqjw0{L1;7$h=(Sd{Mz$7}zf(|-H2i4KRE_84<9Wsax38NP6sf7o% z$flMpspTqad5c<^QLAIr>NmAsM6FAx&1hFR4s_UJI;@Be zA4i8r(-EAG*h=lRsNG&__neMgNk_hZ9{s^7Wg-(d16B(U&giidAPMSj}Wm5-J>JUaJcc7D3(#a)s${;!= zhB|hkjy}|}nohN+Q^V-gpVY~gIvt@-KdJK~>in6yY@#kz)O9>{jiu8lowk@x%cj$f z>GUKzqc@##h`P0=ZX2ju1)bTC&P<@Q1f8{s&Td6#ucfog=^RHo=K^&{)O`hYe?;fb zrgKy1JWS^W(RuIad^bA3h%PXv3+~W`!|1|vx(L%nzI0I~UF=L3m(e9d>5>@g(S~|# zpdQt9=?JeYpM z?WbPf=;}#ybv|7)g|4|y*Y>4r1L@jYx^6CAS47tfx_%q=Zb!Y>Q11tH!%VuNn)+B% zpNrJD3-t}AzKwLF1KpTHHxasN8{PDRZnmeJuhT8V>6Uo9wIki?L%05*esihcQ@U+3 z-FBDy3+f+7x0}-KF?2^;x?>LAkwpUxX+Q|w*_7^draSM_KzkZ^h6XVjbeIOcp~16g za1q^QMRz68-QDT#2)d^;-Ls$WZBF+F(|x*h-)g$=4c$M3?te%ROrQs{=|LxYFqa-; z^iVK8+?*cXNe|c2BOdg~QySt#L(b8o&FRr?^ypW5%#9vA9OU)|kegrsvzz^XuvPN*ZTH<4(|c z0~)`dCbXgn%V|O#O`Jp%b7_(}O^T<<18MR}dZ7)y;7>2q)0EXT78}-P8Gd7h~5pQ_uA8Y+vvS_^u8y( z|ARhQLLc0t4;Rpf&uH;fT3kv?Y-mXiebkpex=2eAEnQDbztYF<^l<@wVo#qG(J~8K zcAGwRrcZNe`CwXpnpWu2io^6-7y8VfKKo6dd(jsf^u;+^sc7W|`f?CZ{@XDV$}wDBPQ)sp@?M1Oapzqiugjr7k{`X`tE zb)bI>>AxxT-xH=Wn`zu;O~$b%iL5DMO%qr%E!HfUHSfflhq4y!SqnebQirt+W-b4* zRw1mlF>8I2wQ0}VEM;wKSz9;O_94@p$~51wc5bZQC#E%%X}x9I4otg8@hBUs>l(tcy1563Du?V_griZcSOYQ%ui@=_N9K z#q^Icq|4A|hPyL-ijn?|WHK1cAeK>6Mq?T4&sY}YlNrCwge4PMOxiMenJGJ_9<%Q5 zta~jp*vfjeVm%JAp50l`RMu-W>-B^gj$nq#%*dD-B{Ab3%(#&Cp2m8AVSSb`6K!U4 zl$lay8pZk!XMGEp*<@x`#QKe5{a!J1FJ}Ic^>=3dU$Oz4*ubW2;5jzPfDO9B29IWg zOWBY~Y)CP)aAy{;nB^K~)r48?XVyl{`XaL#$!sdvP+w--lG*NO!}Qs(yKJ}%8~%ok z*v#x&GrNOqq%j+rz(%>SQT5C|h>h;fMn7d^R1_HdHe(K(QO?{ZFt^ui<}Nm?Gn=)C&DLPE{n_k) zY|cD3C!e_+GxsxWZWlIpH=C!+<|VQDjLkpH7PMpweAvPkY~fb6@D*D$mo2Jbi|4V$ z)oh6eTT;b5+}P44Y-uQ4rpuO{Vat24<)_#R6Sg9adCp;;-`L8wY^7i;r?HjC*~$-W z)hM=V6I*qYd9`C+mdxuA^Q!p2et7jFwz`z9>Ce{0v$c|~-O1Kgvvm$^T{c@!+4>c1 z{Z;0z!@OOYcO2UQY(pgT(Pln7na^Y9JCga9v5m%T<9@bDk8N7VHkGr@c5HJI+cKVQ zDPmiPv8{Q`&yx9_WZT-XZM&F1V*Wds|68_wA={zBcC2DM-m`!?EZ`a2IiKx($O8Mb zzzh~NfCZ(nU_%xh$94^5yW-ex3${Cz?Qv#%^4VTXw)ZmIXU+DNu>H<#e>pqg!49@y z2VK~~LUyPhJ9L5_?#d4DW=C4HBe5)`2MdW|N3Gb=40g9bSISfmDv?9U=SS>$sTWyYd5 zv#1&tJ%dI2v*<_cbbof*kDabyG5uJ~Y8G>soiSo(jr}EY6X|6|(q6EdCx#&}Ru}Sz-^CxPvABWJ&8;(s!2Z#*%Z{1$%ZO zkEP6HDeqb843=8MF3w^XU$Qh8mR7{lEm?XbyVR9kTE{MTVV6T$MlY7}lwHwhR~EA? zXW5kx?5ZWZx{O^-W|S)*B2D$DxGt~s-7=`6c1%MN1MUs=vbmXpM; zYqIN0*!6Oj+n42ru-tl~%GJGlIPdVsBfsw=>w=v+P|L z_RfvHD`2(2YW-MkCVStKy&ucoC$aZm*@tQD!+G{!SN5ML`!Ah+?7%**V;?J7-8fbk z%RY5spJuX8*I7Mh^?|JZF8kb%efDFYE7=!I_9cLQ)n#9|u&-sT!Id@KW8WD2wv>I# zXW#YN_u1@wB>Vn}{jg*|g4mBo?57F)>CJu?vc>_daX=i+U)mM_WK?C zW6b_|u|JjU??m?ZBKxPq{!M59(zr$^t}%;iWbh^}coR3?q=+{)<4ps2({kQSk2l-P zn^p1Vvv~6q-h%KJYk7;uyyYm~axZWBn76{b)jr;;fwvyVTc6~u|L`^*yiGB0YsTBg zaZOFG>BTj_@OCqKyED99E!UdFwa#;GBd&dfYk%SGop}39uG5<9cygUjyu%>gA&htI z#5*qK9gBG4dFMa8izDxn!Mk?kUA=kNXS|yQ?-s}P zbh(}%*ZaoxS91N291Z5^635**-pX+eCoY_1aM0s0m%}wqM{}CN8RpEBvnTxjIn0GF z7n8U+&c!D#-MGx+sw-DJxO&OE598f;@$Nsl!61g(xQQn>DdMJf-1IOv{loh% z=Y2nLvoYK(k@p+P`(5Vdikl~J^S`|RO5XoJK42Ul5WxpF;RCnxfyI2#P(CP-5BkXm zPvC;ND3mJeUThyTY%jNv1a_=q3eZZx+m;3EzA$lZKYTRzI4kNU*z7jXL$ zKH7$lF5qMO@i8HMOf4TfgO9z-$C>eQr}%iz#~l@!~F@ zxa(Z*dY4Zd&8MB@(*>WtiBEseXSnbg`P^*)ce~4H4&pOY`K-=-RtTTngU|Nov;Xip z9(+zNcQ@zmNBP{QeC`}RH=EBhhP5t z_{w^|Y7$?S%Drs3*A>3H4`02Huj$Fx`0zFLeC=euHkGe4;p?*a`r&+iDfjNlz2o_Y z)_lWu?jyL*Ztl~-eTQ-1D88`+-?)}w{9@V!U*-k*HmY`!m_@9)X?NAm++ z_<=xv;2l5c&JW(?hlcS(IsC8@Kb*ji*zhCCJfs5;*~CL$@uSZCXgWVe`LR9x*c%?Y zn1|ltVJY%D*| z`T3nZt}~DG;c+i{{9GPi#S?VLtx|s5p5M;nckKC{Tz=P*-#yRo zaegn7-|xWh@8S;>e-OeSn(>F{c(EBT4&fy&c*!DO@{~WC%paBT(nY-V9)Ik`AAjdh zeEE|KUS`J2j`F9i_|p(xuE)!F^78Mz!ks_U;Lld@XEpr!O#VEVzcA-7GI^y1uROtD z>hPDr{N*?P%9~f|@~U87-IiAe@|xznW)-jb#9s&U*Z=sNmHbU3e;dx{E!bx{~g4Czv6$q z`JX!ecRK%D#{XIHe;0*@xzI=zO-x0TJkfN3X!=+*8z-7Q6wQZ==66Ml5u!zcXlX85 z7Km0eM5`yF^+M6QT(q$iZ7z$pE~0Ij(3~MOOGUfUqTLsvwNGd@3T-c;{YSJ96*^i% zXOGbNDLQNr9h-`dzM@k%(J55u8VlVB(b-0HelEI95M4?{*J+~bE75JK&;y~DCGw$vRaTj0UiQA3c6X)e}b(Q?3dtMg=i_nRv|tKxlX8-LTwb?+llUb zME769;GpQyRP^u^J>H3)&Z1|5=+#^FIxh?jgkg#>vJgg@!g#1K&KJGMi{4qH&n(gB zi7*)>Osa&bqcD9W`t}ulZ($cSnq$B*qtu2}{I;=VGFVnD|ai+9n)23WpSDwpC0w7Sl__jKyL`jc{`pZuMejxR^CW z%qkPJmx(#8#T*|o=Z|pr74EfS?n*JYLCjky=GBS$E5-aqv0$%Qh{eKFVv)XBbV@Ad zV(~?>#6m1779P`tN4;3ONi6FvmYw_mLuh%1STRVf$QGW9gy#pbGC-^Xu_{S;*$A%! zv3izR{YI?WEY|!JYxjzEZN$2hVto&>{;cpGF1*XdhCt!dLHHyJ-?75COl({vHvSTu zwu#MpVsot6Vl1{?6HG+v0`4nefjS+h>X$ZN!d4A^?kkc(HSm*jXe3 z7l^<*5wuqXOA%Zsb}bgWn~L2DV$U$Kr$p>sD)x02`!d9SN3s8-IIvF~)DZ_yi$gZz z&}(sctvI5@kxUUXO@zD=N5jOiKH^x02z3-;O+?rM5pF2Lv&8Y4;&`oy*e6c(7AFeD zNk?(AQJhK=kv1Z-UPK)b(fvg9FL63X#Mp_LQgOyjoNX)4#)@+z#knUUcCk3$T%124 z;!H%`4G}*}B(xL>F(Pq}Nc=34B1N*LNPZ(O_=}X`BITG!DG;elq;3(Z*TqFCE_#cL zMIy~mq|Fy;7e(4vk?ttcZ;4CY#HBUj(rt0sNL;=kGCGS4N0Ct^t{8|b$Hi48uC5YS z3q|H2k(ng2dWkG=k@Zeo8!4_OiR|to`;5rxCUWM9oNRHux46DVT(1_n14M3s$bBpF z28g^=k*_K8H;ep7;>K`s1^@zhA7t;<({JawWwGiDjLMI+2YxA@!VcKKP#U97B9Am7cWKSFj09t6;$4NPohWMa z#Cs$0K1O_KDn6_hA8N&a>%@Pb#K#rl<1bOSUex^*p900F@1ova)L#{!yNS=6#pmzh zi-Y(QExxuBU(bpLE*b*Fx8~y89Puq*eAgA<_loZg;)j>`Q6+xbiJuvwQHjPl@vFV~ zwNd=46Th9r?-cQ;yZ93!{&o<5kBEPr#J>aL-!G}LUut}lO%}-}6|(7A+4P2NHcB>2 zlg-=9=IdmO&a%Z}*-}Hc^pGv@%2rEctH-jnt!y1H+jN#~mdZBIW!uTJ?MtaSS88U; zcD-f0!&0lY)Y>GqzDVuGQv0!NZzbDblR8}L_(`2A*}+wIxGOvMmmM=?CllFepX~He z>aLKw#j>-z>|7?hOp#qOWLGJ>9+TZPWj8O`?Yq>QFZIf#zMIr9lW4X?r4qYJoGVFB zNzO{>AYqH7dXk1qW+K^P$u%WkCixvHCP{HwN!ew=?B^o;-InH)rFn|%KScJwAqP0i0dM5MIdb3|IcT~Z^jZ$~ zl7nmI5KlRzURtb@mRiy>Qd)JCR_CR)k+i-pZ3al2hjOT+9GW6+hfCWVa+tjwmMVvv z%i)P~gpnL^M%p!%cB`b_PdU={dIj5VP z6EEFOrF(&#J3-E^kn@(vd2i(WC31m=T(DLysFe#7<)R*PQJP$ACl@EnB_Nk1NDpJ_ z5hs_L%B2tFvITP4e{y-CTwx$rT$G-Ua;1h`c~Gu0kgKjpFBj=mE>|y?Yc%DWT)B3H zT>C_>+a}jHm+Oy8?(w2f6*4+%Zt@I41+J4A>@jwv;=4JW?q`n#&M-8FEyH zl**$$GDJudBRJcxG7Ikd2*FJ`A(jiFHdF4NG%yTLq;acsP-~yn~bWI(e^Sr zQbsq()05=sJ2J*t#+;OAn#(gQ<(ZH2>^ONgU7lm|++KOER>p3Sv2W%1nezN|88=kM zCCT{VGX9cGu#yQmGI4-RJSUUd%cK)BxsOafEiYKe3xzUexlH*jQ-kEiuJYmunKo3W zRmpS{nZ8=4m&;3o<)u^dGM1NvWJVL2;UY60$}2|lN}#;*US6FguiloK<}x!}W_6NT z2V~YCdCfsydnmK}$n5N!~muZ)wO|^W?1}dD~dtPL+3*yt7B%)s}Y?fqNezI0W)^3#VHRbzY`9WKL2$uh~ zmH+LLAF2F!Mb?d$bw%>iR$1Ry)@RAjz2)a<`DK{=k|@9Sm0y!(gOO}_AioWj-(uu< zJNf;I{4qoRsFXh!%b&Hf(MvYIm%rx9UsdwAhy0@{|3t~ZhVt(z`LBokm#Q=@l}3?j zVy>FxtENL#(<0T(RW*B~n)|30ZB&cHs->}NnWkC|RjnSW)~>4cf2z%9)wZ*0dqQdU zRGN9J-DK77mD2K6+DvKZsP=1>4pBO(s>5j2;l1kUtva<-o%Sl-{z~_m>g=hyv{PM9 zs;*Y5Yq9FKN$FvwcTwrjQ>djvF$!BLT%pKf1z5ptMVBkqMX_Uw+bZ6mgs+mFmAt0Z z4As4v>K?BQoRq;Q)gwans8>CgsGi?cueqw%du6Dt4Erj>(aLbOG7MFQMarn7GICW$ zyOq%;W!zF34^_s?m2r{kt)Y7NRlQfK-W95kp6at%^|_=>w3W#+Ws;*zb(QHdWtyx^ zKd8Rus&BCBTcympC^Hvjc1iWqQ~f+tzl*A0qcR_%%nvB@TGii4^^aEltJHw*YJi6t z5TyouR|B2Yz%(_emm0KQ4JuHB+pEFOYVct-_^BF#)R5I`NQ$!1P!=uxV;ooEr8+ z4WFlm-%}%cs}cTcM1`{Juk5xfyFxXxof#`IHTcBrv!)L3sd_KF&()Hr`N?!Fptt;Qcz6ExI>DQdzcHL;DFI8{x|R+IG9 zqy=iyJ>{UU9DJ0+Ej8IzO+K%tbX8OQ)Rb?^ai(&-s-{vkb(5Ozq7bKWR-FXetu&DB(%NS zwZ=`YNmpxa)Y@pZu8msftkzvt>-(zp0c!nU7jg%C|^zGyHfd9 zsEt-?V~*Nnsx}=~n~B;Sptk6$Eo;=48nx9)ZM~`dOqE}h+SW{M+p4yGR{n0v|AyK= zRc(K*b~vgX_f>$M3P@8sb=1y4weyb(+@J#MRnS5eRIh@Us^D6+Yns}1P3^W&yRWM~ z`_O4 zsSpnp@>w1AP)GCCF-vtUQ-#{9&|50ZMulBd;U+3PO&xbs#|u=1p^DhAPH3qU8`a6K z>f~N^s+BsmRGoUKB4?<`H!8|WMZHwfQ&jXrb=pIn{;Xo=sF+vkjE_3=Tb&J5=US$#Z6Xm&s6+e75`EtEK&(SRpL&SSgew4R8p!+CMx-eO8%`b_^T9M zl@hN~omFapO1-WwHv1n%=i$!v*MMO}X^2qTsL$Ewd@E_8lxU)*&{QepS4x9QsYIbA z3PouuO(G&nBqJ+AR+CDikj%W-`)54&ecjh1#m$rAVx-3-rN_IZ$5~Rmp%i~oitm&X z%%y~2DdD5^#8!HeASG%`iRY!nFH+KUDe0W_R84xiMS7YgB`=baqooumr5uw|eoN0> zrDs*r^I6jKBq`NYN{x|T%#mJ{NNHwL+7&5%pp@<_y&NpPjFd8Tq>TI0tI^V{b5iD5 zDKkRK8ZBj+NLhhWR*{sgEoEm0s_XUbjiPTcz9*DQ}9D zw^PcClk(c7{8>`Iuay5?D%c__T$rq_~fm9kVm1#<4o29b1(mNo% z^OoLymC6mI@;y@dYpG(iRN*I8R7jOjs`Qd7Go>mlRryI(jZ*b~sk%gZZ!EpPCVgPi z2N&tXZ|S3j^zpv*NlE&&Px|yi`aD|t>?3{tEqz%feTkN8pj2~JsvRuV-jlxem%iFa zUkjygqor@BrElM)?`G2Xda2G@s(T~-VA78(QoXKJe@d$Fk$zs28uX-wT&Z!A)Obt! zHBK(v$xdzMrzTLT0Eqddg+gq^rt{-RgzkrrPfE%UnS}9 zBI)ln>F-ym%~EPhmD;tW_RUiJ9jQZE>adhL9!i~5>bxO!4UoFlN?n;!_e7~XRQjhV z{o5q{dnNVElzQ$1xiLU47|1IF`PD!^87RyG3L!wD3G`Y4dffwxqk-Z^p!f?Y*#V{3 zptm{beHrvo1AR_`K21Q`7${!?eMf-4&Y*7*=r3XFRP#@mDOf58L?FyRl- z+YR*cz{Cw;VmZ*?4D@TjBxf+G8cbdTCZ~WY0!)bkQ-_18!C=}zFzpmD0Kgyt7@7dX z3ShJj82tj%Ey48fV8(7>tOSe$z)T%5GXTsQ4rW~dCjEhlKbSoT%nkvjqk(BWFtY$= zAHkgUV9q}P=Jy5j{lNTYu)rNGs0RzTgN4n&`~m1bfYm%;RSA|mg5_;s#Svhw4y?}u8+Bmg18gC% zjRq@cgO#7aDp#;t9;^-lYi5Eqbztprux>h7_X^l~0J|En-UO^q2OH*r4QXJb0oYgs zHnstK9bj((?7e{fU0`1UHmQS6tH7pEu;~NXtO+*n0-Mu-!$jcV4ID~=<6z(z3>=$* zlNoS24xBo`7BjHr1laNxI8Ol1CxLS<_-{J+?>zXg25dD2TOWaK+F+X(*j5i*76X@5 zuzf1no(Oh~1v~BoS7YEB1a=MqJMVy97GPI4*u4(yehl{X1$$h>CI6C4>D2V1Fs_*aSQp!2vgLa0oa!1svQC4#t6lzk%mW;CTXg=72+!!J$BK zs0$ok3JyO2M}~qUhry9_;H3k+4g;?OaC9U%x(6IB2Hp#S_XFVl7x-)iJ_X>|cyR1G zI6fR4KL?I?1K(Z1HwXAl1%6k7{|Mmk2K;-#2`6yk3pi;BPUeDBbHS-RaC#v)9Rtpo zfiw2t%sFr-9h~U|0pmb`0|*EK0gd3S9yogjoGk%?LqVV|2#f!%Ld^x5Pk@Rr-MuC;F2A<6a_9dfy;*Aasase14NjEh+80{6PX^b2_*g4{o-BNE;A&7u*^I zZn=Y7ufXjw;I=oo{RKo>fT##?M;_eS1n#tgyNkiyKybGa+_MGuLczVC;JyL4e+N8J z0T0%J2gTqa0}oGwhut9hG>GX9V)lcWM(}7mc=Q3p&IYk@AWj*?Z2)lv;PG_u_y&mY z2jUNd_&Sg<2PDLSCo15HGkB5-5-CXZ1c~(^$p9o>15ZbQr|#hCFOa+*BtHi!2&A}! zlq&FSCU_PLo(~4k1HkhiAk`eCCV>~4;Kdg3q6MTa0BLa`orClX;H4IL=?-3&fQ$to zBL=+U;8iNfGy$2>AZs|t+6%JkLAE)_P6RocAm;$c`2$|B1h1cgTy2o+2XcRaycr2nKO7W{0R^W)p#muM1cjC0%@Xh?8Wago6bgz5fnrxs+yvh418+Zr5<5_m4N8}T z()Xau4wPksck{u!XP|r*D1QVhj6p>nsGI^SuY)RGP!$8J8K@2i?{&fZ6X1O__+SS< zyayj$z{g7P$sTQ;lg58#J8s8%EfZta@lN@MT1)8pd z=H8&$7Br`UmcF3H6STB~Keph{Q_yM(TGPN^Q}DM4v~2-x7eHG9Xzvf&=YsarpgkRQ zD1wf8pd$iw6o5`W(76Y6#)2*#(B%ZWazM8Px_vOqeT^c(~|kDy#% zDEA+f%YyO(%3px;4N$=tD%^$&zhST0u-8r4s{tzNLq!j$m=Bfop^_g|`T%?D!``m2 z_aoT57513{`-H+i?NHeoD&L06-(cVQuU@W~rcgH?j*;M) z066A79J?Hjy#plxr2|lEgkTv2@emG%@Gyjp5Y2=r9^#o0KY&C7l4FpxLTU|Z5@bUm z^MtGd@@bG?fMOUF9#DLQvYAkp2ge~e?mQgV1jk#$@iB11Bsd`i>h*zo&QLEKPSk@F zqoBSv)V~1rf5AzM;G{S>c@~^}4^HlcQ#QgWPvKO3IMp9cZGh7p;ItfQU;qt*pkW_q zxDgs=LL*~nbPG<`h12)K8AIWWJ#arrZDC*hnPICl@6TMXwdf%CH9{N-?dI$VI^g0pbJU%2ov zG}nOU$Dw&Ov@n4dkb-_K z1Rdr;hhpgH3?0j%lR0!sfm^1-EveAi7&BPaFCjgMKy8e8 z42JZBA?sjBF$^_^p;_?4YEOz0A`24?4K}aDa?5YUo-gn1bkf$b0@-FKbSWN=Iw%cpJ4uK zn4bj;7Q%u^Sf~XH1K=AD--N?AKVXpsEV>Gdhr!|?So{gTb%1YkVTlneIRi^oV5u7{ zEr4Zuuq*|>gYca{eAfoconiSaSixY$d043nE6>0xC0Ml>Rwct~2CHwv_haDuAoxKM ze)tc5Xn`Lc;m3FIlNJ1w0zXfNpYOmgI`B&xtkHutX|Q%Otc`(Rm%^`Q@Y_cCT^@cv z0>8Jyx=paI4E}J0KdNB;6j&b&e~yJePs0XH*l-;-8o|c5@YgE%s|Ehv1%J20CTG~J z0-FP1OE1`R2>wxofBfN}4%oU8w${PFJK^6>*cJlYA#9I;9phlfUD&A)J7Zv%40hds z-3G8b5B@WT|MFmuGwgYX`<@1s8Z5}C z+(*g_k#aavZb5x*P~R7*-xSpEIO9XHfrNXuxVT;0aP0h*bQLN);Mti3VOq z0~?X5Ia0le)Fhc^3KJ<^ztG|nT9A4u~*q}hrF??QvK(2yx; z$W^2@5NVx2LkFUv+tJWBXqW*S7KDZmLc^b<5i`(;`)K4qH1Z4@H583G}jMCB1ZjhH%O7Z7KNMNWB3{BXI zCZr=hJ){?a^g7T)Ycw$&P3%DWZb<(%ngr1#S2QUdO;$#eSE0$D(3Ascsyv!%gQmVh z(~QxyU^J~A8EirZMaa+?8Qw=m5;8i1j2hAO?Pz)fnz0AX=tjne(M(M=GZM`*MY9f~ zSMGNPng%{AmPGr6dnYSPdBV-YPEb5V^8L~W&EIZMn6=>0G zw0Htq?1dJ8MoVU)CAZPiK4|GywDb#FW`&leBP$JLbr@OIq2*i9@@Hs;1zM4ftQoQn zLe|~LW-+pPfo%1W?FD4}6|J0(R^CIa`k+<5XjK(jZG~2+{r|gl%@MSw9j#r1)>fi* zv(UO5$W8;Fk3pM;piPI-rWUk$GuoVk z9AwDh0&@6=9PN=~B61psoNk~ks%Xnu4XYbV;~inbLY zmubi)4Q(fAdpO#miFSA(S9#=Whg?h0&JAd1IodS=?Mgtq2cX@D(e84zXDQl~jNAxv zdw|^ak$VtwZ$Wzxp?&?)zTIeFCE9O=_TNJu6OqRgbYLMmkdF?|LH^_f3@_&R*P;}xVI;n|H9zdshp;M0NR0TS1gHGq8Ggj!#D-@uI z0us?#jLtqsfm2anCJLH>f`ZVw0qERTbglxOpN!65K*0(q_%I5tLLqBVNC66+i$b%| z1#5Jn6kS||E>@$k^(gE+3SWo9i_xWJ=+ZlMc|5utjw1S@2oDs|g09R*SMH*#0$nXY z*H)ryZ_ss9biEYaFhe&kp_{|e%`g->0!5xdw|b*n57BK+bo&g7QbbYvQB)JUvliWH zL3j6}yG`hxBf8gs?w>^ubkKuS=;3hm@DYj@DEbMCF+wpZ=+Q#-s2au2L9sVcoIr7p z(PLZm_$i9lL-ENd!3rh3Mo-MplMIwN3nhL)N%kn|3wpW@B`c$3Ka?VeQXY&`4C~p$V%R%`|P<}ot*o_L+P@yk+BSUYlqoP@;CU%exE>1`lu-jHCv+QI@EF#{gFd|_Mtzi zs8t=cI-=GT^mhXKdkM7-L2av0+auII1hsER?d7P0qK*rwqXTvBL7hKP*J{)ii@N)u zZV%L5i~h|)|1P0_t*B=u>dC-zI#_N8mMg*X6R`XtEMJTj46(vVtWbe_8Q@+~SWy!z zZpVtnSZNAYx`ume;NIJD?@ruj8}5^jmG!Z5BJMjJ_uY;Ae#8CN;(iIZKf(P&@c=bE zz#R{0!74ugkA??^V^u}0>V#FlVYO{otribjg9jC0bu+A9k2N-7jbg012y4E;gE!*A zZ}E_gct{o2a==1-c@MU;-9v)$ZN95v>bMeSrJZc#pRg6bF;?a#* z+ZAiKVjVZEtA=%bux=+F#xT8 z&+sIMCwb$^T6nTIo?L^cti)69;;94hR9`$z9#7ker~SkRuGpX!8!pF&#n@;%HoAy-GNQ3u-O7^ z_5jZrh39zUxr%u1K0LP@&)baWRpa>YtoU@Bg43oq2f3$I{vWo*75TMWP!?%3id zwmgh2Yw)5Kcu^8wJRL92#!Jlbk_NnV6JDBumzm*ZRoH41wt9h=bG$qhuTaG+cH$Kc z*m?uDuEI8(u}ulKU4m_^@k%qi@;P3G@T${zRU2OIfme6pHFkJS9bUT>ug%8mmf&?| z*v<*BSH|nT@%k>j;TYb~fj92P8-HN?ZP>m6Z(4;neaD+!@a8Yr!45m*U`H$L_y#*| z$4+1I7H90NfStXub2I+W2mjZBxBB93a(J67c2U4CK6v{?ygdZ(7=w4jV%MqIH3{#W zgm=c^T>|fVhK+-0-1xeApEq?!ZTUvDXOfbr~Pk#YdlD@7dTp5&O)TU|esvyaa-4Y&XBpzGT%2u>v%7K5ar~O&*N<`TTAbU4^UmUYjPs*$ zfeaVq;6iI$_yNB;fQts>q6l0(9v5few_EYs9$XTROZ9PS7A~{GWu5q)7cSSs|= zJ+A1)l_zo4Xk3+ttG)31G5GyM{9zXUP>etB#Gl6EPs#YR2mUe?e|d~+=HZ$WT)PQ> z?S;Rd!QT-6R)oLr#C1b(T?+m&9sj7r^&4<~BmU`*8;0YC>$p)5H)i5r*7#R3{%wnY zf5S~axLE@?-@`4|xaA}MvjPA4hg&b=zX1O&#cc<0yDn~jgga!oBMW!B;;zxS>n`rL z#NEaCpBwH`#yt^4P7t|bBEN?yXcC1xq}LMCtCJ`O5v3VKsfhGGO8QJ9eTs;(GwG{L z`raV@rjvekr2iQ*K#&0yL}fo2h{(V^qPmBuaiaE-3_3^DC8AzHG%gTLJED1!Xx5X# zCS>plGWZ)AVn~MECtAaZRs6B!>ygc|#6cns zkBH+8;uuIA9}>sU#7T!Zts_qHWQz*fVnepPB+eSdc@J@ZK%DEyfAh(Ihsl2hWUC_C zx|nP|L$&MeTy_zc$Hb+LY&R#{TgVOvvLl+fDiGK8#5I}h)F3-O$j(n> z*HW@8o$S^pyTizyzGRO-aZ?~}`-poV;=YBr=aIb&$lhmUp8?qyL-upB{{``|Bp&7D zfD1V|iX5Cz4tkJ-X~c6V@!U#0>&PKfa_BZWtV<5Nk;9qf$P98Mk$9;Pug%0OlN?<@ zjy@yaV~O`U;$264Y=}=SIkt`*`$&$@B**U%Uqj*>P5f36zY^kaPW)ey6Q<-u3OTue zoJ=66#*tIWO6bT_DA$JhDbBEkT!IB#)v=tT~CTCUINHV+Hcqnmj&39)BnCb4Yv$iEktc^GQN9N$4g|%*m4n zWF&DvNvtMGIwZ-OB!!WrkL2lO^7K4O?oE>INOB5EQ6(vXB&Cf!n@65qAkVtV^SR`C zJV`|)HIlp-M_vSzv|c35m8AD4>8Hp`KwjP^8M-7Rp1fL3UNw@;BP2_MWJQzgwIti0 zWWOXi@+4;&$+M{<)$o-WBdPV%}*{zj5tNDA~wK^iHPk;2=g z@H=_4jJ&x>-gJ;6D^hfi6w8z19i;dfd8z ztdYE1N#13Xa#Kb|5poV*`K-k&1xd&mcW@=+om zFOg5<$fx_{vjO=WL%!&cFJYucgVY=+HSMIh;E%NOm`A*39C!}r^sasC! zJV{*{`C&|c1dw`VQtv?Ov&qjHEw?-`E#ALDw9@M(po_N4kdq& zlE3drn>J~)Cv8tj+dtC2oV4F2?VY4!9O*biI)0JPailYpbheSMnWQU-bhVT2#iToy zbpIp&rjUO}$iHgRGlBH%Cp{mioIaI1N#zQu{4gr-N#!%C!VIbqLlwGcFKgN>l=iBj zic_fKS*qAXl?c{?sm5rkv6E_4P)$lT!>Q&UI(Q`=oJEH$r9&>zAw5)UJJrgjL&wmer|Hmc zI?Rp^tD?ha(BZe~2yHsTn~o@6kfm%tbn;nU38;$L3N=Q0Xd_nklfLAb~<%3LjH6h9W6At~)sjw)q*LqZw1srq zQ)xy| zA3Cdsn#`dl@pSfNIy;P-DpS*~)btZIv!!Nl=o~{jCz8%prE?F_xnJnK&2(NBoo`9! zKc)+`=mHnIpoT77Mi*vM^NrNJh+0gb7E#o4G_?$*maTM=HC>cT7tf-LpVB4M=#nJ5 z)Pyb#pvy+mWyk5Va%yEkt#asc6S_Q>t{6sF1k)8g)OsnkzE5p*s7(yD9ZzlV(3K{1 zWh`A~L|5IQtL5nGP`U=vHQ{vaFuL{xUHg}=J524AsGS?NtEKB#()HzZgEieyL^qn! zjb+q+0kw~#n+)luyL2<9n}g}*R_d^kI=rKfw$$+}b#kIx6zG;MbW0<3cA?I-^uN{g zzgKjtA>I0lZZn|UN~ntkb;+jN7t-y|=niwb;}Lb8NL^#;PCdHw4c#@H?n93Y{RrJVobD~7`~2vBiSD0E_lMK{AE*bS9+&9>1$w}p9=Jvil+c5a z9^6L{7E(_@Jr7aOR(i;d9(qU*b9(qTJu;XcIYp2Bp3J(He(OwTCMGdt*+S{mR)10K<{8uaXXdiE|297h9B(!d%T zw2THlq387Jxd-&TIz8_}&v(<{jWjr!h76@42WUtu4Lv|ZKhX4ivoQITFeLN9)z zVRLBMT^inphI`OUa`e&`dZ~e4cBPlAXoNkD$fH+g(JO`Y>LPkIgI=3YuRW&M$I$Dc z^oESy2&Ffb=*_(}atw{UL~p6lTj%L*L~ke1sEITxk=`+*cXH`n2YOGL-aA3>{iF9I z=mSU}#L$NZ^xhrr7@oLQ7`%^g2tND*h(6=i#}GNkE3Y(P8$D=CJdqp`{1}zk|Iq?qDjB$)4ufS9QyP$eVRz0cG6@Un*5L^|E4LFrmUhV7ir3O z`i#?O=jpR7`s@#Vu0x+&(C3Hf^Awueho&0QR0oL2=IJbkg7zR0I(h^8H- zX}L5V&~!VRewU{Ip)Y6Bmp=4mHO(-f8KE?zguasKD;N4Ihh{3#%mp;_In5eFv$oKz zD4MN7v**$5K$`u9=FFlwAvC9fzBZz-qiC)I&0R%veQ0hT%~POx%W2*#nlDH5Eor_l z&HqRX04+F73qI09Gg|0L3*XZ>#`MiW`sNKS>Q9R-Xi)$ys;0$8w0I9KE~IZEed|Zx zR?(9AwB#f$siLJiv~(RU4WXsqXqh1`bEjqZX;}+>XF%T_qwh*-xdAQTM$5x!c^$3L zqZN*{;ts87rj?9V?x&UaXk|04T2HGYXjK=jUQVkY(dv5o-ip5Wqwl}b53A^h^Yp_9 z`ca2|bf+IP=%-%v(|Y5=dKe=$~Qq&so|kM_Xso*2}cDjs9Im{|3^()wE5Awr!_vDYUJNw%gM77}_zC zcI==XX|z+Db{?ah&uEt>?OH~=9@Fm8w0j-xzDc|P(SIB1zZlx1NPCXbp07-99+Qh@ za$QV*1C!5S3Y;nIWeV?DFFn>Pf+;F7#gk02gemDWr6WwKne{egy`xy~pRA8H>yyNk zM=|9iOu3x(ozMCvuzvEa-xAg@jP-lZ`Y&SrQ`i6Mlv??8XMWc zMlEEcUa-*v+31yQ^i!s-%CxUY+@19H)i@N zY!YLWj>nmdCOc8f?WHw&Fgs?!~MRFzb3| zvw+!LXSThV?O|q{!&Z)ED^IbN4Q!PiTlIpi)?urC*y<*>#)GZNXKPK_+8DNO3|n`I zt*c>nw#@DsTR)ktPhcCg*am;Lp_XklVjHhB`;p8(nAtb8O@?e!Fx%XhZFXUsvzdb# zbGXGEx|pLQb1Y;|kU3ptPMvJaX11k}InQR!&)I+T*ngF5>k_v0E!$?owq-IGUFPD! zTt2YvmTY?{+up-=9AG=@nd=Vb`jYKbV>>OE~Jev7(=6jj>jbeUL%&(pKA7v+cu@i^biSO*BB|DkIPEBQ}jAI{EquwZKz9LGXN zvJf8@idg7rc0q$(2xb>r*hNcrF_MK1XJH3fSRD(uW#PH((o%LQgI#9qawLn;U=h*m z$~blwPsHrvt-1QgIID8OL1T+RqWYj_N39j^ers?AA9*9ds)LW7PE{p_R5~UDq)$nEHj#A&0$%|EPDdWj$}EI<#@52Pwcfd zdtJkFC$ikrEKi5!9cB6bS-wBZ|IG?4SwRvjoW=^P*&9pt<^?OFtmrl?`p1gBSaB_T zyMeuZ&q_R4sT?ca#!3rVnGGxZ#NKUW@9J6k7FOQKDz>wV4pzCIRW`9IM^;tJs#mh= z4EBBsdmqI<$gvOm*hdxi(T{ynV4pnM=YH(7FZ&|TzPPh59js<6t5ssP?yRp}%YZUu+n*Em8 zZ$I|?J8N3Onu=KSI@X-ZS}=K*Uato1PaJAnPYz}gg8+aA`gz}gS8_HNd3 zfOWL9&h4zTlXbbV?%}N4i*^5F|Mswd^{i(z>-o;*EVzjm9@C?QLfy{``YoooxGnL@7KipJM#YD_<&V>Ks8s{ z!c{u?KvzDnnyaqjs*PN2Cs%9WgFLyq0$0DoH8i<~AJ=4D^Bx~Oh7Z2Yhp6%)r@7W} zt`*LQ&gMh&`LN}DSREgJgpU};N2KzRv-!wmKFW-bD&wPFxwbslKFPJ)xsE;8`OJ0g zx$Y-EW-TA{laKw6kNvXfup4ywQ}slaW(%xgX`infU_~2g>gQH z^DA6T;NlgR&Ec{_K5ji9_nnVF$|nrv6RvPQQ?8fECobRk<5vB+)h=$8 z%a;%4%UAQ|S$xF^zQT^LNafZ>-1-c+>BVg>ahon~>&I6r^OeW>Dm}iclCQSms~h>6 zy?m|2*RJ4e&+@f3d>!ZOw()iM`MP#)XUy#`a=Y(*y(wRRlCRI@8+7=Fm3%`O-|(4l zoX0mF;~NXPJ>d5HxqUO=WX3l|@J&5@vlZWbnQ!jo4i?sSI;XC&89sjuNI_~<4@8o=^3*VW_cj@w72l%dceD`#|`#9hIf$w2_&q2PYhP&Bt zw_Dt;le^F2?w7cG58rFg_rB!&wE4aRd|wCO@67kda}N#f;m19`@&kJOz+QeJjUOcZ z;Cb$;$vthk=Y4)?5I^M34;AslCj9VSenf>I@#06?xz}9o70Hhxe$<&C&E(!%-1{W= zZsIRvpRnR5p7N7p`N<>v z0 zj`B-Q{IUhV9LFPcdBhbS(ZsJT;a4*G)p7jlHGWN%UpvUJedgB}@#|0djZyr@L4Ko$ z-#o-`w(!U`JTjf%n!s<}fhP^)Nqc$HSN?Pbe_GCyH}K?gp0bIjyynl0 z`Lo;nxh{Y1%Ttwj>Rz7O!(Z&-FFJUd15c~u=^J=@F@L$2zpUgLHaz1Cf3KB?i70st!W+uTVQC1`QO0yoLG*p&lSK6orPD(Ci~LPl>^XVsNY& z!o`qyp*2@%Rf(Y+#jsvtShyHITMW+?BTU7JA~ABK7}Zyd@)M)`h|$-C_I#mTBXq2V z&R?PHBy`)wnB8J*KQZ>CkOm6rh5%52R|2jTFjt`M0(A=EFVH_%EjuA6&h*?v`tO8*ogo&Lnxg$(|irG`d?6bmDRhT*o(=WnosxZ4O z=E%gHi(;;#nCm6xeh~Al#Js0ses3|~R?L4c7I3lPv{)!77TSx2CBhsC^V`BgTUeYG z7Qcn%T47lx7MY4g55?mCV)0I~_>)+&KrG1?OBaZxkHxa#Vwta4_DNXT2&>0pc^|Rd zRxE!YR*VuW_6cisVeKib3x&-xVUr?k#|qnH!nRGUbQCLpid8mZRjgR8ELQIqtBb{& zv0{ynSkorfnu@iNVjULijte_wVYge@b&2%{#QFxYVXfFuCpJ2YjW31$6k&f|Z0aR8 ztrVLch|PV)<|ATrqj0bh4)=wlns5vjPFljrML3m%v)1INJ;7H{w4i{__)C z6~tCYu{A|(qhg!CaFG`-n}y2&5WS{zs^4!jlz zr-_46!c#?fx(d%UacHhMlqwES5rH@Np78 z72=qQIF>AqPZr0Y3SXJGTm;05vtz|s9}(DJ1YQzBBSlcKI48up2jV;x=i^1Nr3ii@LTp4xsR&&y zLjQ;huHvGaxOhy2Nh0i;2!|s4p17ndE?pFt)x~8m5iwjuToG3$iz~0i)n(%92XSqi zxYjAIyNc^|;>K}tqetA_BO(<!aBCel^J0%_~iN`MDaifUe zE)sf+goEM<7f&9GL<5mnC6cy^r@h2ef04{Y@=KB8C{mimGgt9^fOvjcq)rm4wc>@F zNE<5Bo{IF9;-$QJc}--@6d8^pBV1%;iC0*>Iw)RciOd-yvr=R^imV2aeMIDFh@4oF z^Ig1NAYR`RuiHiLa* z!$pyoC~6SJi$rmac)MM^eJ@JvL`khET_{T9MHv%i55&6(;$4I&A0)~{M8$YfaamLj z5|w_UN<~x!i0a;=I#|3PAm00k531tBDe8Y4bE6QAdb&*kDvfAPghe0eNt zR7A~MQIjBQIz;UPQ5z*{yT#X4;%kBUW-h*Ei|;n#`%6)$FY5e7U6c5+N&F}g^;V+3 zNc>zTe!dqCD?~%8Xq1V@IPq(_`1MNso+W-4izW-vR3w_0h~^y8Vj^0S#UC^Ar$V&a zh}MtduZ{ToLA31>ZQY{XS9EBKjtiobi_T2ZmMTP-zpnmFB_03 zQ$aG7T{4w-vVrSl1CwQ{K&E4YiUD4U-N1CL1>Ie-xeP zTZn%H#Y1HlDKsgWrSBfM%qAg{RaS$HWMyS!BuPSPP-!TogviJ!l?X{j$S9JeBqKj7 z)N?)O_4y0V=bY;Ry{kaq0_dj!15IGC0~izoLq}kk0u0-LkrOaV14bV~--V#>3D8di z^jiV?l>uWjV7v(!w*Zrgz$6y*?+yBUf&S-#sRc0I2Tb3C0b{{{Yhd6gFfaiOYz2ds zfI;`bU;+lGf*}|Ti2_4AfT5mXXaO)Y2WBzA>>Dt50Oko`*kCX$2n?$M7UO_LDzNws zz%l^t184)_Wq>3=hXC#d@Irud02u&C9H9LH4F$9Xu!(?`0FD5U2mB8Z3xOyD(izBG zKoLO60mCi9@IqkO8(4+_%Qi4#0T}TJjC2Jf--1yS!KgeidNvq+4~!WG#w3HWhG1+Y z82b}gxdN+FU~LYpV}Ol1u-OP~9s^rzV0#wW^#^vL!0rdIp9<`+f^iUx%LL;!!1y5G zpaC4V1BXvw!Xhvs4@?wb;u$chH<+{qOezPC*1$0vOr8fO-vLuNn34#l`~^-vV5%mV z8VH=b1LrW{{2NSL2d346>Eprl>tKcnn2`r&VleX#m^B{EDgZ9_z@-AXP64hr!EAFd zI|a-!0(16&Ie)-h4>0#Om^TT`%LViMg82u4n<{YI2;9B_cX!}k20R$>$N`=MfaeLY zz#J^t2Nre(3)g{#HNeXec-;VtC|HyN7HfgU!C*;Gup}KU?FN<}1>T*3_Xgno5-f8D z%W{Da1U?a9c^9xe1*|XyD-yvK_2U<5Yg1HVzguMBJ)2R1$gn?`|6=fP$KHm8Fv9BerQ{H=k11=wm2 zwq6I@tiiSvuzeud9tU>VgB@icz!n5t1v^K9osU7FF9>`BcFhL6%0bXn5L5(qPXfEI zgJ1%J?}0s2!Jcy3c@`> zco{h84kFrthBf!Z*aLO5+dH_zZ23b8oRvI{C z3eKd1vk08M0kSuO>`HKMJvjFroIeOIbORUmf{UZU#d46d0bJ?{E@gnrHsJDeaAh61 z+67!a3v%ayJav$l2(DR!Yu`bBPmn(vR6^W`cqMP*4a8K7c}9Q0NT` z(?H=XaNQ1E4*=KCgQCu$$QcwJ21W0{4IOa93*5*8HyXfA32q(+Hyc6mR8V{j-0A{u z%>=hDfmd7K$!|CvjJrXK-oiZ-yYmQ4<2ZM2kziODtM>^9?k;~&w)ohz@xd~(Fstl z2Fim$`4jNi1U&Wyk1N3w8}Q^Jc-jR#oerL+g9=qp;RGt~fM*CiI|82l0ng`x=jGsq zIe4)Ty!Z@Wjt4I+1Et3TlNRPZ_*RCfT?UZDCTcry;X zi2-l^f||LYrU<+p0^UZ0x4%KHGpIcc-f4h$OToKWpl$)Edk)^)f%i$E-Wb#efrfUV z!3{Lzf)8fk!(q_a7c@qI#-HG$5BT^3G%W^AwcwKt_;eP0?g>5zgD-~Q%TDm+H~8uY zzJ3JFGeL7X_~r<{6@mXof&a?DcMiUv0zWLkkK^E{3HX@`ej)Jd0{FcIv}k~qSny{S z_|pvj27-S^;GZ-27Y6>_1+8O2YYJ%71#OW~MGdNKgYEQTyWOxoh3)^s4i}-SFI4>i z)x4ov6;z)H)o;R%Hn8Jk*l8T>lm$C;*f}3|A+XCi*wqepy$ZXHh21_wjpH z-TTAtdtna@dt8TFlb}{D?3oI+ZJ~A{)R_i#%3-hNu(uZMoeld~!air9?pUZ>4E2UX zy+Ww(0QH|kgN4wb5*n_AhQFcFI@nhY_B{am^@ROSLSrXr{2w&&gC@UW{}5=Z4^5Nc z00sxtz=2!gpiXd50vzlFhjf8M&cLDG(5x3Ui-YD0G;fB(&cI<`poKZK*aj^gLSPQT zatQ81Xa-?0gfAhof+!AREr?e`d^$}=e3T;B5&1Y!q4sENT-3(~=656{%`rC z$%Rg1q0?uEO91o`%!D-jvbXz$6C7dxI&bSF@E{8K~;4F9O(hj=pfi7*( zH4x5*aCR1)gW;S^I2XaWX>i_1I4=**w}$g`pxY$qRtDY2K=)GUF%o)|LeFW?vj8q| zfD4|&gIRqIg5DH* z$H8T)aG5(?mI0TwK_55h^8_v*2A6M!%X8t1PH=?_Tu}g54umTc;L0|*$`!7<16PlL ztHa^y2Iy-AedFMo&T!2vxTX}Yod(xlhU*|)cL1(ygX`DB^%Zc#Xt?1h^y>}%)@yhW0ON|_k^b<=5g6Ya#vgQTEe4`V6qxao(z-YVe(U$G7P5p!<3is7=ykHH7!L$N+ zTo)eS0FU2->3T5T6{f$084fTb4xZ=?Pb`KfQs9Z7Fw+5MM#9W@@T56BxeT7X3Qzuk zr$)e2`{1b;@U$*G?FUc4hgkw<`M|8Z|6fd=IRel8hG*Bpvv*;(Jnl8NN3-eWAz7NcQ01Fr_h=2v3VBri{SO~8h z!Rvcqkv1&ygGJxqjS28ZB)riAZ%&0b%V05s#gXuqI=tluZ&kzF_V9Kry!{j2@qu^V z!IGJ<C4-Zt$5eeD)ST zUk;x?hA$X=kqcj%!I!bHQWI9Lg_R#+l^3jPfUjKOt9$VEDEK-DR-3`jK~2fVF0@HUYjfg6{(0yWg;GHLR`0Dp@+f7QU>bKvh5*y0EO=)*rL@UMh_@56r{ z@ZVF|>Iz#cVcP=OR*zJcAe9Ex&L6e=huRbzO(L{y^QlQMXE@u@Y%CBTZ+dS&q7UqV6A2kKIVC zE7FQaJ$s;@2a&cm(msfE`XZg%sFx+`^#=70M13?+pLC=Pk?vijHv{Q+MEYq+zXlnY zA%oq>pd1oR3TfB9jng@)PxU zLH!>h(;>(-6%Eip1JuP!hH9XpL1^fEWHuR@ z6(aLK$b368uR{N)L`BGAGP1}IqWYrm2ZAMlt$a*8P{)B9nAe(o{))mtK)IcUZLG@}{KT!&_;&MH{)qBiA9wwFJ#xjAnmFa}J=n z63s0_^X$>QIy8S9avO-;a*_LVrtLLHBImp)-`R+l!f6y9xv?d*`)k14GqP6eQ zx)8M92(3SgHgrTA7NQOL$j=`6-9Q^hqmAik(@?Z22W_5!HaDOxhG>gB+ERr44UqpT z1o?FibgiMB_e9Xe>oeiYCR1*}2=Khe%06xa&|o<+NC(XRbyS0f7Y zL_x37ZV$BkBHH~D1$RWjvrzDH6x@jR^h106(Vkos(gTIqqY!@-autRAM0>5!-bl3f zISREvp=(fRJ_>C?`$nOCerR7I+TRiFcS8HK(18x1(e-}&bgy=Rp`7cI)4{k zFhLg%q6@9);v96b5aobnl&^;J7oz;@sGvV8*o_LBQQ<~Z_zGPgkFFP?qN%8;4BfCp zH;$v5z0u7GRIG}MJyCH9x`oiKG<3T?y1fhC{*3PUp^^@$WCtqwhwg4brR`Da22}b7 z-CK$7Jws()sH_UzcSQH^p$C)DgL~*9Ll5K7qe1A=8B~r@c_w-+(c`n|2}e)L&{GHW zG!0b@K^3LwnJ0Qyfu7Gp&zsPTc=XZ~y}XVpJyGRfRAq*$7NDvE^r}62wG6#VL$6+= z*T(3zJ9=G=sym?S#i%+Ry=jNuOha!jqZ& zdbbe0D@JvusBS%~yN=!)q4!?seJQFpNA){U{Y%s^3^i;<4OQp^Kpz6ohhM1C3N=Qc z#%AdH6zq~1byp? zzHLI^o}&K{`tLCMu8O{Upzp8Ik3s0i0raB={hW(_o<+Y5(XRmX>j(PniGCNN7H!nB z4z+wle;m-CBk0c`^w$;reS!YDqJM>`bpUD&My;Pv+b-1h9jmxtmD{-85Zo>lx9@=4 zFUIZbaEBSV!*#6cja93$+8C^sfz`WX^_5utDeh>8J7(Zc!*Hhv-03gw?1ei&#$B9o zm*=?a4BWL4cjLHQGS=vhHFjW)Hmtb-YyQIBSK{t3aF2z!$6c(2u~shbsfT+;VQmYn zeH!anVVyI$7r?!a;oe%fcL46)fcq@QeI8+5PptbE>#fFmud)6*tp6DsEXM|au;Es0 z_zN5D#eI9>zPoVW@3`Ma-0w3sUXG3bV3QrVzaj3QjZG(D(>Hj)Vm#nKJkSph>VyYH z;=vFP&cs6m9&!s0^}}Xb*z6QG`-sgsHs6WO8}YE=`2X1ZC$_N077^Iu8wT?+D8#To zhPyEQgwYa=iZLFJaTvybFqx0ZEldqC-H2%|W(sD}n0>OV|?ewwTWo$nT+uy_EhU0OWczjPhJ^+vZhaDDUhX;7V za6BOgPaK6OX5dL$c+wH<*b_TO;>m`1aw48$il-dGPMxvSAw0D|o|=fAdt&EEJWU%< zOUBb(@brs##&A5N9M9a0XQ|>@LDoWp{oSzteY|x7-g+KyGr-#p|q?Ig5$h# z+yi{X0Uyc5@fJ8f87EA|39oSCQk>X~lV;$gOZeyrd^8s)JK*GNI7PuJ7w|C!A4|rm z9dYVfocac*IpDOb`1lZf{2)$eI6V<(Fr0A?pD@EGVsNGo&fJGjYU7ik_*5@^DiEJm z!>706)8BE{2AuT-pYgzF8u8g(`0O{Fy%c9Z#OEyWxomu%;qyoFg#q|N7{1sZU-ZEj z>v7H!oYROe?ZcN5z8sIQ48vE>;;V!3)#EsK7|zYcc@XEN;A_M1wFI2s1LtqW1?_Ob za$L}a3)kSndVJjrU$4eR({a&Dd}B7gQGstx!Z+{Z;wiZJ6TY<^-+F^@d*j=`@SPBR zrv;bzv833T=@}K z&BRq7@hd<4+7!P|z}0zsD_}xTO*Q z*@XY9;lG>k-zNNTE&lftw?^VNQ`~lks4OS#)JeO8q`eMlA5J0Us3 zj3zxwiPm<~b2RB$Mzp6B?T19ik?1@my~dGV^`v(I>7z&b#1q|~L^qP?0iu^q^oJAu z0%9G-zs8kN{o|<2`45ONq&lZxM(uof{f254vaYDkqNWOgcdS!Gns_Qq>&#QS$LXwSrV`3WYI>l=qFix zjx4bwOYW1U4rFOP@!m<6jU>ymh>sodsUgcZk`-EHMLbza$;xuF%7?7_OIAk`UrBr) zk~QyYg+WCtNTZj%5f5>QEY`jJ2*5|~YP%_c!zNl+Qt?MZgOBf+c5o_1tU z1PQSqA$Q2$jU-f$geH=Go@Bok*?*cGkmSHa61I?p{UYHz$w5tWFrGx%kcd|#GLS@B zkf`e|Q%T!Y7L@FCe zRTz0Sk-SzXuM0@EKY3$J-aH^RYslNa1}NG5NTKe5@f&h%^P0 zrh4+pj(oaIJ`W|IcaqOdfpM3Kr-@cLm6!Kp% z`R@_=PRRFNm#-|KdrjK4}dmt&OB@0cm?kRR&WPf2#73wzHt^QfYfV+Wr9T zph`P<(GFLsss>e^Lsc`WT4$=}Le=u9dVi|EjjDg89p}-G*|d``?X-b*?m#;)q@62i z7bn`Kh;|)DyY8XgdeCk`v|AO`aHJYlRMUlOUZ>qL?Vd(^n9?2*v_})wnoqT!(4IE5 zXC~FwqS||?_6Mpnis~fMUaGX$Y})Gy?LCL~&ZT`g?UPA$J5k+jRJW1pO{99~slGYY zkD~@^)L;QMD4~Xc8XloWs?^An8r`9N9cbSxw4X8UcZeFRQsa5l_!%|9)Z{enZ%q3K z)Bg3;bT&0DrUM-5fGRrBmJWPQ2ieglpd#yQIF) zg^n$uR<6{loLbML)~~6}RBH2r+D@jnPpF+6wR=JBJ*oW*I&LQ&--(XTpboRB!#6r% zC!J_WCoZNF0?d#uYkqD4n^3&a9!cM$%aa z>8wWTGKRWbqptm^YZ!HXLub#RvkU1QLg&QOxjpFI9dzz%I!~eV_R)Et>HKMQ{#ELx zLESb{x6jnwlDel+kM`8Vn|j=!p61lkpDyS_7kJYJO?06nU3ijunNzR5)T@;)T1^+d zr;F|B;yk*3hCIo-OBZq26KdeLp0=(aa>yCvNoOSgZcJEqbdr)WS=8nBfH6w#f% z>CU}$=X)AxNdwd9u1<8<61uB|1|b@>m+sc2yO+`3#WWbu;0(IQi0%oYdvfTW*EEFD zki9hIDc#$P?ww8d=F(6t8oGgozM=ap=)SFV-(9+Y1l_-n?teiKjHUm;nu2 zLBnp+a5Wn4Lc>$&!H)ExJw14eMs%VP-ZUbMM*K%31&usSBU@;c3yr!=qX~@;rqR{( zkR?5|gC6=!56_~9FVGlCV?t=m6B=tpW0PrY3ypK7amVSA?)1n`8sC-1`_lw%ny`i@ ze5Q#rXkso+GNehN^k^4)bO}9LLX#;?4yVcAX^IO?c}kB7dMu3|`$|)tXzERxHkhXQ z(zF-!_&9nzgQgFn>BnhCUz)LpX8ffm=F=0kG}DG=9;GKc(35WTWCJ}lj-JY*rx86J zPqQ>=)>fKTOV7B_GZpkKrDr2(wi?Y2q}e~|IR|>Knx40#=QHVr_Vj`;z0gQ6&Zigi zXwD3pQ$#NfrI*6!rB-@*Bfb2AURg=6Jfc^Z(5v@pt|iUALi486yhrq!9ldsy=1-yd z*J;5hT5yUMj-Z7X>2(`=y_^;~)1vS6h6lY-MQ=`~H{a0Wd9?T|y%kJvccZsc>79=B zP8=;6NlW}`$$ffvIK8`@-fg0#vuNp6dT$WD7eMcQqh&6%>><5xL+_uX4>ak6h4jG# z`p}X-jHC~L(?{#+qbIa{1TBxFk4@?0H2P#HeR6_6?MI)6(~6F?B8oo4^jSQ8K9)Ye zPhTveFT2v0fwZy*t-M65ENRt!`pSjA`b}Ra)9O*QI*3-+(l?O4*-78j(;6RIbBn%J z=-W(M+mqI=qP4a3-5C1rG_5nGb=zrO6@70_-(RHlMznq#t*@gEb7(^){V<$IUBnR+AZ zxQKQ9k9AtZI(KEA<5-uztjjspbrkFRigjDTH2O1*5~gX(H1DzQyIA+%tj9s7)t70V zW<95{o*$U@PNrSJwEr@lc}%B(_3FiX`LkYMSZ^2B`vL1ShV?nebS;_g6{cIw^ae4# z6-@6n)7NGCwoLyxGw96>wlTx@%y1JkyvU5ynNa{Uy3hKKV11KV-_NX{1M7F28INJc zvCO0cGnvOsF0=l9SpP(3s>)1PGt)9Qz?=;@$Oe321AW=RXKauM8&u8)k70u|*$`7U zR0X5NdLA7#Vz*{}dM>^-yaU=}YJ7|B2`gH{aIFf@&!+YDod zvlto7NIIi^8I5I3i?KtD>oK0l1ZCnRlM0j9nPSb9TsGW_4KHPu_RR7n8?lUysAMDE z*~l_B%8`v~Vxu>+F&){M3^uku8yn2544Bn9W^Kl-OPI|RX7iNUdNMmLW|z(EM=|>n zHf|0Z_mho}VGef8;USx_>Hj^xi7(kCT{dYKb5voD-psLzO?G0F^Vk%`rbIKRZp>*1 zo2tvE#xQ4n<{ZMNsjz8#+4RnAdIp=J%Vr#5GXa}-&AbCt|BoXzgY zX3u4_FS9v)*qk6X=QNws$mT9&b6>D|%h7Duzi&1}hBw&V$0I*~0+ zW!{~c_j>02oGqKomYrrky_wH)=2Ojf6kB z81oHhzRhgSRJJCEt)0ZyX0dgUtvk-v_hsv2*#-l)A&B{PXMWLaqdwbsjBV=AHXUM{ zb=l^H0@MPFx!X0k(N?C?}} z_yddC%wn}!Y%+@*%i=z>Bhf4#v-q_v{w7N>WC<%-!cCT_&k|EvQU{jg#ggu^qmUil z!;b!8$!l10IZLr(DM#2bV|FZ#rFLVf%UEh7OLJpsW$d^kJARL)yR-Csmcd!ZS$2Z6 z6WJ^iv&_@%q&Yho!A^Bzr#7%tFWBj2?DS8TwU?dg%+73MXZ6_G5SDGuvXj_3J$5dg zogc%_?`Ib@*oEcn!b^5>3cHxaa{98IPZD_zS-e7Rc^HcE5%_Fk}z* zvj>&z;av8xfITu{j}EX$e^~ikR^GrKZ()yX*^?>k$piM(jy)}A6=PY&9rg^fXT|LK z1or#_dx6-CX!g>8y$oTMnyhjctNh2RjKA+M&R+jx)xNCy4STbVy=i4N zJK0+;_BN2!YO~s4R{N8^3u5p7vAQ7kUWdI;VfCEVUt$e&Si?v5VGV2S#~NeV$ARqQ zHP$qXH8rzO8`$Sg>~lE#V#U6+v9H_M*FUUzJNstBzCB_8*|Go1*!Pv}djtD%i2WSI zex6{z2>W%4{dQ%)+gM8!`!ksRImiA^Wq;qZf1B99zpQl+Ya7hku5p!FT&0G$^X2V3 z@%Bl)!*JfAn5#N+)ncyZ&efiCb${N`kayhAI}PBS&hpL^c;^?q%O>8nEAJY{yY=VY zj&hA@T%(3-uI1f3^6qiGhr)Z5b1fIH^@aD`&9z5!?OR-DE$^kvdzJFuA-s>m`~2X# z{kg6u*G=TQbzE;I*GuGjpSV8f`Y~L;jT?A#gK};-fg2v-hCjK{NN%)~8@=Xz&3WH= z-nWtWo67qYb7Na>e4d-=ag+7jhcxq{zIeSaaEp%IVn4U2Qtm0Ia(;b{?aaPFxuj^dQ;-Z1e?OeX%$~>+V^WnyPco?@-#vJsyg&8PI_Q?~Lc&D_a@JH6pkz4_EP+<6su{=%oZ^J&lc^l5y08K2?6 zXI$YkXYiR{`K**M$abH3pY_nX80YWc=(e3KsEbc%0w=bNALElat-3il7?TN&Sa zgKwM4w>{JlmFM=ks%R{M>DRekMPEonM&7FEsIsn|Y2F&vD^7 z=lLZ~e#wttD&?2^@XOozO| z174EC@0#(uH+boEUfRI#t>tCid07@OtLOIz@%vl({W|_&7JpFBA1>t&-||P(`J+3$ z+?to?^T*cwaUOpH_>)Zj)RaGs4`JZL{PbUA{p8ws<|9<8Fym@O+-kQYQOnFz?&Wg@lbj}l9JVe)CqHBuiHcE7R zCp2~lO-rF!BD#Bs?)9R_e$nHn&{`(6Dn(Bx(es|rwh-D!gibG^vsvi;7QHr#-s+>`|N#I$W< zdM7bGUCbCLW~7Um)?(%(G0RHKDi$to!lgmD`ij|_Vs@~Yqa)^Ii@9^f+y*f(NX*v| z^W%h@iEt|u?ry^UukhF>Jhg@AJ+Z(;EO;vxt`Q5r2(N8oQBSexp;$aqEN&G`!o<>1 zVrjYXULd?%#j+&f!-danv3#jmp(0l75G(tMm1o2%d$FogtllYnHHGgHv1Y7T^IWW5 zE!MV)b^FEoeq#M8u|bLr*M*;>@GBJ?gT$tuVpEdXY$Z0o7Fz;@zk%@25?hJbS}L}! z7TbG^?U`bSmDo`#0=9{r1I5ll5oj+0pNU;-L{JYAbW!Yf6}wwS@F}rpfe7g?LPEq| z6S22Rghq;eW@6tnvEN@D&=Lnuh%grs)+WMZ#K95bV55lGEFz(Z%n?xwMYN`fP7sHj z#bJGMI7h@x6ERI9Hd@4u5pm_>h_8q@7V)=4!gi6^OC(+pN%kVCMjYKEl81`qdm?4A zIHnKdyBILB740!*F~JWBF=k@3qo8}5f{gai|0g+mdM#Ha=wU5HsaDnaamtn4iZ-L6Q4M#Ai40xm0{H6kh_xmuKRu5MRT@*E-Q`Bbv{O zZ$rhmK=G|b{I@{-S1G^;`U2D1MiUmVu%r zTC{u;e`bq6SH)jr@i$of{U!c+ihmWN)lIZM7HyM7TcK33kSejVowjTjAlto@?cHVj zQrTg+?2sW#~cP>~dCi z?Jc`*lU-Y7x7AXkgVfk0HGWCW`BL+d>^@d@&y_uFWsh8`HCAe!kUa;;p0QH9z0{7B z+O1M&oz!_NdwI!TFJ*6g+53X*(_i*EDs{D_Zi3X)k$UH)K9l+>(m+ER9FT_U((t%6 zvX@3@WZ%)U?`7E!$bLD}SW4rI(qybODV6;lWd94&)IyqO$pM4pfDAd%Kn_flgAC=M zBXY2g9DH03(UC*;%b|vH=s{_wCC!4Rc^7HELz@4P!#2oaZPLP9S~N+pQi4wsZkF(u zL|Y_A5@$+cB1x>Ix{`)U`cE=H$-77%D20X;N2N5BGE*wXQrRnq_mIO6NJ~>`nI=b! zkt2%aNGmz=sT?&!j`}1=yUNjJa*UH4b6<{ilw+%;m5a1`EUkT{^?PZvSlX&d+nv%* zUD|DucJHLUj~v%oj!Tu}2g&j0rGu?>cr7Qm$_Y(!;!-)OgPfEl9ZjWUnw;z)Cs)WR zu5wDXbXp^)wwF`4O6Q@{IZIA6mD951bSpXiv7F&1XMB`1Bjqd;IqQgYv5+noq$`oG z8FKbyIs1{E<09wO%DKzs+z)cz7CB!-&fhKFOr=}0bVt%XUV7L_kLS`eR4y1I7u=Ey z-K1A9>6IlH4VQ~5QNxm-mqkC!V3%M~}| z%E@wNxm@KbSACGH*GpeD=^G~343=y1x1Nm-f}~N^cy1mlI2EM zx$&RebWm>Am79;rE&b${3)0^~`j^VBK5|=UxvfBMpCY%n${mMffUOL8Aa|~nfyOd0 zPwtv1ca_MXWpcNc+%8;LO?|ixUq6}4$q0?n(w%lhV_wAPZ z-pT#8a(|pW&{ZDrlm~9fu%R+6Oor>oa6cLTS{|Gr4_=TF{bj@f8QD%oy2{9lGD=TI z1%w{T(LaDFoMcA6JTXh2_#iWv%giQu(odfJEKhBcr<&zyA9?zp%u14Hs62B= zp2hO)C7JCgvzz6)KzUwUp1&_I_{ob}^5Qmm@tw?>E^{8qOLp?oBYAn5y!=XDv6WY@ z$*X4aYP!soGWWX7Gn08Itgex7JYn>|w%Xd!lU4^XkmUY$g{T%teMAkdW`drztNH)BXAG~Cvfo!}iKhBdMn`F}t z`AI{5ik6?T{QN5A_pHJkkAo;67 z{$4JB|CTMmvgNV-(@Xx@CjWerf2Yg8XXL+L@?WTI?IK&lWt*vND^yg*C@O_YyM9W$ zRZ6=`rTqY99-b(4wd=S5*HgYJrNnfuep$>1eEUj8QrbQ95NSou@0E ztCTK#lrGet)!!Tk9A6qCPizzqV-+r zxl-x*S<&`WbW{|b2&I>)(yLhM?W^=bN}n5w?p8(jo}vd7y=X=6hobMU=s#8rCMyQb zis25$sHru3bl^sQ3*g)7Exit$aw#8NTISNcPxf0ANqq?mduruUTrTp19m3}{gX z`YHoIDT5r9L50d-rVKu$4C$Z@S+5NFq6}T63@ug6tQ51Gin*O)o~sO-s0^!8Eaoc~ zH42!ZfCdFlQ{W>7jaSf31v@JEjza7dQlii~3T;qWn8N!i{DdOR6>&$ASdrC=;;Rf- zSB7UPmX3;LsABnE8R4jmh*L&*+(4w8{kM#z@EDXS=Dl}(W*l7>;rC`3XEkr^39Mpi<| z4t>{m|B#yoM9{#VG_a5cg^;Zk*~XAv4YC_UcBwSjmRG~R&5&!q8BX@VzBI7<_aY2sv>c!3;+95;~TUz#+F zCS}uP2bxlgri9bfel+zVO&dtlp3?MFG@~`m2%(v^Xyyr;Wka*R)9f&s<4AMM)7-T* z_Xf>lnzxJQ3(Y@4^FPr7S8{4VPKo49XlYei z+KHAr)6y_ndYfEIkxMsnaU_?`F!UB9Ce0v79`@$m1?~{2uj%9!L0);}U5mVJ$UBg{AJV%2Xx#`}w~y96qxJP@y*;hZpbeI^VJvM3 zqz#v7LlJGPLmQ{i#=W%h8EtAsn;dCVC~bN}o14*QTiU#mHlL==FKJ5++R~l2jG-+a zv?Z3d6w=n_wAG%rdehcJv^9mc7SlGSZF6YbUfOnteCm*oE%~^SPc-?wCf}OmJCJ;R z$oCR$H=ymkY5Nk|ev)<=(T-8HV;Sv8CqF~-vnD@p@;gm_*|f6;?Hoiqm(tD%+WC_F z>yUpx^7kPBI}}id0)|k44+UJKfbSI8fdXe!;7$t6pui#uYDYmH6m*V){!wsG3SLOT zdnu$Uh0LRnAPPyNkRsYulXh9tu64BQD((79ySvlwS+qNfcHgDlzi5vI?HNyd0%^|$ z+LKM8O(=8-g*sDcB!xbp&|=zKkM=sy-qo}>jP{>llFNc%?8zHPMc z1nqlC;bkejABB5ScszwarHJMf;Xo0aC?b&}3MtZrBCRQM21TByC_{=er>Joh6-rT= zw7)X#?@aq$Y5!r`pGOCp(Sf0KU@sjgqJu;SH_*Y;bTEewwWUMT>5wlSdQ8!#6g`2W z4^Z?Qim6L6!zm`1Vs6u6Lpt1n4sWBwxfI)tVrNtA7K%Mcu}>(@fZ|$F+#HGvrMN6Q zQjU%cpd+4iB$STaq$7oNv_2iRr=#w4G?0$oqhn?0SSLC*l#XqrWASwCAsuf)$L;C( zB03&J$8S-5Ly8|u@v#(NLJ8KC;7kd-DB&h0e4!I9=)?><5ke=@=ww+sX-Ow1)5)E5 zGLcTcrBl`D)Fe8!k4|M#Vl7JSLy6v$c#;x}>2xPL?L?;&>GT&mV@hWx)0sdzbB@lu zrljhW)Q*y-P?8TN#ZuBkI%`H}XVckTbT*fgYfy4ON?t_CXDGRd&b6m=i|O1UI+sJ| zE6{lhIzO1syU_W=bp9?~FrW)v=z;@X2&M}+DWw*r45E}ZlyaC-3g}`3y68X`gX!W8 zN^MN3j+DBcQu8Uzg3{Jg+8w%NOqXowk{ex$qf7a8xi($yMVI~P@_o8efv#B7l?8Og zhpr@3dJRe+P3eA=ewotW(bZ;j)tatur>l49T4}mwMc16^S}a{FqKr0_F`F_@Q^s$) zZcf*S(sf_Dov7K(*p_|p{rWM^>PB&BN=5M;yoNn3DtsuIUMYk)^?csFW zmu@H0?ay?l0o}2oJ6q{a8r}IsnI@FkkuoPy<~qteO_{lL*MRPt(cLk0*PZU3rMuth zULU%*mhRo8tg4jNpR$%vRvcx$ru)Wpzc1bQp!=uj{tJ3wM-NizVHme8}7^lUOc+e*)J>3Ji1Zb#3z z(ep%lQH@@Vp%?4uMH;>MN_p)l&yMnfDepez{h^oU^l~V@oK7!&>186l{7SEy(km-^ zKRO>gSaoALCz(`QTiynsFj z(dTpY`8R#(Nne)Imw5W}mWqt2s1+62Q_*rNI!Hz7^tC#D9YbGt(ANv}tpa`PN#C65 zTNr(NK;KH}dl&k?iN4>a9~J0F2l_FYet6N3^Yo*TepaKO1L&tK{X9-TbLm%k`Za-m zMbWQk^t%@Q9#6mh>31d-m#1P|Dqc&)Pw7u%`ZJjRY@|Qu=+Afh+l2nw(BJL!_d5ME zpnrqtpBMd0qLNBfVn-#*spJrqd_ySL(z{Xm2FjE|8B>%Q zi84D-CKm<`U|<6SZx|%Npb%wiqO2XtE<@Q9DAxq#+)(Z=%2!8uE0kY~@;6YS4k~m< zg&C-@3KdSHLLn;FN5!tFI35-KQ85RGHDEXlhFf5m2*W~DYJf^(QE3w@#i7zqRPKz* zPN;kkRg6((CaP>lm9wbw5LIiU>M&GYg{nJIH5*3NVPpxTbudbY(RUb+fpHLw(_#D* z)qrZ7Q0+RZS48zesJ;@_kDz)!YScoF?x^8`8gZzRi<-u$*$Fi_qUIgcs)Sk=s5Jq# zmZR2g)VhIMg{a*OwHKiFVbp$z+Qq12i8_-|=OF6*fk`izEQ3iHOdi0b1a-Tk?mW~@ zK;1mlGeA8n)N@6>y{PvA_03R!1nRFw{hMe|1r5y6zzq#fqrnF>Y>b8uXt)v$PoUuo zG%AHgghnoCbPtVNpz#DW4n*UNX#5^cnxn~3G;u{dWE~3R}m^Oqd!E`B1cf<54 z{;P@q#^Apo{FjTCwb61sT85zIRkW&vR-@4>5Up;)tQ^eR!E7+h*1_y7%<|B>8CpA_ z^)|FVh1T!UrYYLYK$|^ib02L@&~^aYx}xo6n3spS70g{=z6a(H(XKJtjYPX0XqSTa zMrdz~_WRNPB|5Z0hso%$5gneQV-IxPhK}#h$pW3`qSG#PdJGFASPX*223VYgMFBc@ zK<8=b9Er|Z=wgB{1JT6`U6Rlx50;H!*&mj3VYw2Pv9P?4uFcWa5nU6}^((qHL$|)@ zwgBBWqFWNEET{))2`Cx#1E>u+02_dlz*Df%|BEc10^SCG2&oCNhpdL|gJeSup{=3a zp(~-sp~dKKj_!leeLcG0MUQIeVS^r?=y3%-OQYu$^xTb}&tO#>R>NTx2CF-;DnhR& z=rtC-)}dE4dObp~U$FiU*3)4f0PA%0u7KX%(AyEcccFJWdjCP6Md%ZYK9A71CHlIc z?f4DXHMGckM*MwG?~2aNE;h;)qjh>?vkaxg}2#mHogd=G~aaEOINAx2qX z)IyBfhf%LFx(Y^*z-S+gPQjS67(*Dd5MvHw%tMT=gt2Wfb~46p#MomP`vT+s!?-aR z=Z|p@FuoGT&%*e7n9vCmrei`VCZuD+7fkGgiBm8!7!zN?u{j(E!Ep;5&%yB{Cbhz( zVVE=rlTKlBIZW<@$*VCr6H{to$|y|Pj45|8)d*89Fm(#1?!?qnn3|1g^)PKBrtQSE z3`{G*^d6YL0MipNqYP&B!i=q$k%gH?m^mCXS7K%yX5Pfi-jaeTsyBTIr z#Oz?qzK+?SFsCo(tizlunDYyB+hFcE%niWYisKIR?3ysMaB5%UT2-7!B3 z^HVUt2nz;b!A>m5hf@zYZHH4ZoMPaV45wRg%7fDnI9G;qYdG7$c`BSY!+95+Q{bGB zg@#zz3=2D9;Q%b0jD_x4=!b;|u425%u`&!RQ?T*|R_0?>Rjg`@Rd!gl4Xd7D)nBach}C1U+6AjOVRaN%U%~1R zSkn?~oUmpm)||kazgXJ|YX@NMQmj1>uiEeeyyn6y5MHV9t^)6I@OFjw1$ckLy7pMN z7VC~-T@Kb8V7(>QyJGzXtbd6Ob+Dm5HjKdrPi(k?4gavw8XLW_@fJ3I!=`%JG!UCs zU{fSEWnyzRY#xlwF4*jg%@?t`1X~7TiyO8iV9Q%8fL91KN1!bNy%D$zf#(qT5kZ8Yu?TWP&?W?hA?OK$8za~P!IKbt0Ks<sW!|oT@(;9n*W6x^riNc=y*z*pd)e+hm zp@R@Q51}ClJ&(}O*xLYmyJ7D*?DfXp3)uS!VKotEiLhY^b4A!ugcV?4OYEDEecsrY zf$;hWABFIP2!D=Ij>rLsT#CpzM4m(B7erM<)G$OXL{ucAo?w3q?6<-G zW!N8y{ZDY9A`Y~}0Y@CziUa95SP2KMac~I^9>bwpI5ZN6ym9CnqFW$(Bcd-O#t<<; zj0<9RBIW{O-r{g093GCtzBrtNSVP2iL+mod-bGwP#4SSHdc++;+#SSyz>(TGG7v|$ z;K(B!HN?@uIO>a|X*l``$6Djq92^V9u}mC$kK@&F+yTe^a6BBx&*1nw#J52FSj2Bb z{2L@xKtg9EEI~pL5-uX)6HZjYiHSJjj}w=0;uTKT#7TRc+=`R_IGKf$B{Ksn}MdDy2EBX?2lifwaL$n}f71NQ*;S4lY&1r4U>?fXfqc*#(zVaQQo~)Wj8YT(QQLskpKX zSGMCyB(5am%1vB(iYuRxUK;65k=`BYbCB+X^oO|G3|E1x!*F#KuEyf(4P5~2i$he4%EM$Dg^_sYDj_U(( zeFd&Z;CcqGzr+m#+%Uxr!Ht=?5r7-XxbXxxe&S{W-0X#$<8gB~Zu;P67;dKH=1bf% zz^(SUH4(Qwa4QzKQgQ1GZa2bh3)~)s+b+26huhJ(eFe8);!Y{tsf{~!xU&>@cHmAD z?qnjfG%`(**%z5hkhuk!N09jtcT3@}8SdKQ?j+pxz}*A5dlPq`;qC|AtB!lXz5cj2 z3-|nRFBSLR<6bec+9JyuS(B0Fi7X#vMI-A5vfko;Y1}u%{cgBF3-?#!{wCay!~L7M z{~HgC@t_+XIO0JP9vb7}L_9o>hyU*GIMmvU?+Y9J1#j+Y8x# z$c{jEJhD@feHYm;ko^TYWsp+?In9yN4mpe*8{~{Yjw5oMkh2LnA;^hB&Kcy~Le5j< z6e6btkB#uS5gzx#;qiMssfi~&@Wc^M*5FA5o;*OVA#&>?w>xqt zB6mA-k0bX9a)07!Gd#7%QztwP!qY2w`X0|3;F;jrI6U*kvlKl0j_0lLd^(=*#q&5k zPr>thcwT@PRq&!6UfAP>D_#WQ#c8~_hZnz)R~LD`kT(r^+mUw;d3ktQ1222x5Vt@ z@MbICoWz?fy!nl{4e(a+b|&6#!P^sfdjoI3Aipy5O_4tc`HPUh3;Fwze**b8k^cty zKk?2G@7mzqAiSG~cT4cj3-7k#T>#!i;awWuW#Qcy6jVlmISTAhFb4(eQE(6iS5T0P zf?~Wk!}}q4zXIy-{3<-e6YiZjrec|AKu_&C4B6LkE8L?8z1BF@e)41MqyEkV&?6g@}LZ+xwZuY#`=@YM}p_u^|hzJA6x zBYf+CZ}#}M6yE~y?IOOt!S}NGZj0|b@I4nlTH=S`$9nv@fuFVUb2@$=!LJJVH44AN z@#_nIGk&kZ?`tTohT^d(UX0=`DBgwQvnalb;`=CmgFof*r#1dC{tUpM@%Xb8f7ano z2LAlP-&XkB4}aI-Z#4cs#NW^O*AV|~@NW+O1>)af{L4Z~Rg`o^$sCmIN68hGeCJa2 zxKs}=HJ?lQa;ao4^_ol9a=mknC8 zK|eMa#|CTJ;4GJ|z-8-l*#%rSjmy>La!t8hdoE|qUQ^E?nU@S2W{_J-Fg1uIS7aS8+u@t{BY~&v3sWG~C0r+f>%3!=N^CNOP2AZeflWSe-9}t@2G@&@kQkz6m0>l<=?E3WU%^~1RSJ#J8)8(4FLIo#j?H+aSkt8&A3+;Ap0 z4C98+xlvtiG?W{y;6_`y(NS)6l^ea_#>U*(iW|@7#{0POHEvRdo3!92{ke$~H`&2W zPH>Z#+~gZKt;bDWx#=x#X35R`xY>JdK9HM-ar5uo{2#Zd!Yw*-i^1Gt3AfnEEz-Hg zCpN9arq*n_fK5Z#w21#3#{V7Q|Ne5z;oR~7w>--&pK!~6+^Pw;>cg!jaH|#EY7e(M z$*t~itM6=P!e+!~c5F6<&D_~6l+BXZ>=Cyv&#n7&YY%RHhFceL8*^?mg4@jFHjBAU z6t{WBZJTpj;kHA#trNEmoU%}=9Y<`^0ud(?bZr708 z4dixnxZQ4Uca+;@bNlk#-i+Ii=KmMW$GQD&ZvU1$ROAi~xr1_tY23k=J0x(2d)(m* zcWlZX`*X)(+;Iwbbmxv+xMKu&jNy(KxML1?EaHyE+^H3J8pNG;a;G%z^ph=Gvc*8Q zSi~0bZ1IOX_u|fO-1!7|{={ACbC==V#hbgtbC)-4*`6(Dvt=Ax=Cb7*w*17Fzqo4+ z?%JHYcHpi(xT_s^9m`!8a@S4VHH5n!;jYEpZ2))M%iRi@?3f~$zA^?ff*7xvyD+b2 z-o<>H`6-Jb%YQ5bSZ1^Mvz%ag#9EcLH>(rtUe@!hS*(TJ-GsXj;O;ZIdjNO8!QG3v zhbi}P<{mq^M>_W`$31&;Pap32l&#val|5TcW-AxA3TCT3?$v^O&E;P4-0KHhH)HE1 zY@Nl{f4R4E@6Fshjr&yKKGxi40rv^zKDW7VDek*~`ySxFAGx0c_uIz(Zn8}ow&}t) zQ`lw)+ni;a67Emje-8KG$Nf`zKouS^kO!>i0r5N_hX+>Xf&F>lG9DPo1M_%L3m!C< z2YKjV4|>bC<=D18+xBMLd2G9pZIjtHo9#-oU3<2h%641W?i&xDz=KnHh$#=* z$V2w@bfV4zt619@UXY zIr6AOJn9FJZqK9bd2|Gie#~Pk@|Z3>W5=g#9U z@%W}Zemal$=kf1&LJOWSizjU12{}B`m?sY8i5q$1Gj=p#$0h6-&yN3i(iom}g(vsq z$xb}^Bv1apQ%rfvY@TwFr<(9o8=mUPQ}6OL1D@86r+Mided6p;7+RC%yc-CK@J(g!5 z;n^iTX9UmL$8$dNT;aLvcmXYUc6`kFB-**=JKLw zUR217O?dHGUL44aGkHlJUNVuF9ONZGd1)_Rx{#L!^U@paQiWZJT}H9XPIkG$F2C4y zAiE~=vU{*RHd$8w3_T0vvM|ovAUfF?H4&jwc zdF38nnZzsq@hWp(HIr9`^Qr<~ZOW@B@apZn`Vy}x%WFFDn#sIoC$CB5H9vW6OI~Zw zYkhd_VP1QN*JkkAFYIN^UY6`-$6j;T%ZI%p*ei>@{<3#l_MXGu{_Gvj-q+Z>fY+7d zb@se2nAg4F^__XWC$E3N8_ama65eo$HyZKAQM@sXH@@LbU3ilRZ@SEzYx3r?yg7n5 zf8{Mbc}oy)$>*(>yfuioe&cO@cv~_1*s_lc`vkDhW%hZ;zN6XKi+wNg_8PpsGjHF+ z+Y@;E4c`8mcNp-F#=HZ(qZjXR<{gK4$7}Yh#C}cKuOs^{XTRILvkC8<%{vo$X9@fF zWB;w}pUDBOIlz_!=5xR%4oKjDEDrd>fi@f%&4GCw)R2P)anO1Wy1+r-IJh4N2Xk;T z2S4TDzZ_!5AwxK1K8Kv+U5$9x65e&0cQ@kQ^LTd>@2SLlM)95~-c!t>)*QN#L!aZzb3_nFq;SM*jx^y&YmS`Fk-Ita3P-->C=-q{=cpkZHHo7|B5cRqBG553^%3LI_5(Uu%-&C#aEx-yNRC;?G2R@L$T1H% z<_#aN#)muc;r@JhIv-xchyD0)Bp=S;!_PRjG{+ioYy*z9=Gbu@>&CHu9Gl3ocR2PV z$2H+N^$42onFFv-LkDcISxB1v7K3z{vC!FJiJU&sEPmJLcEBQn; zpUCEuHTh&uKIy_IBl+ZYK2?fOb>vga`P5-PRm6!+II$-u&fvr~oS49gS$w(rsz3#Xss^g_PclCMtZ zs|WdNAz$mt*F5;zRnDlx88)1;f-_S2dU?J+jIVFt>$mtub-rQEH(dF~F}_jAH|z4v zzI@Y-Z=U9xZ~0bRzO|TdMe?m2zFnVhPvF~;e7k_}SnwTBzLUXs{%~d|&RoWsNqo0F z-|fzKz4-2BzWamkne)9Fd@qvk-Q%nZoMp{f>pANz-!I4ahx7eSeE$kRsKO6M@`G@G zP{!$pA7{6Y}uW#_1YW!v#zuCcW9`V}-{MMG=uI9H%{Pq{;H|P8zobSc?sht0X-&yiI z4}KTN?_P0%DHqJ-f;cYt#_yHiZ{YV?{GmL55dN@+KV0IE)%c?gf853&Z*yU3F6_#M z&RiJBh1vYckUv@Rr`7!FIDh)VpL_A=CHy&-KY!#eo%zcO{&I$wszqRIXi}>3~{+7?*oAUSZ{CzinKgQp0@%L~1<3Ik0JDY|1{-4WBHF4|GCJ2%kke%{C6t<-NS!#_+KOb=g9x|@xQlR(veHXbIBGixhbWp zNGW?MRP z+DxjBk*ceuYOGYv6{8wr)K-k_#K={QV#Mf{7*`PEK4Lsaj042@np7(z)p|%ZH>q|~ zs{N4akm`<7eWO&rB-Q^)jqXxow$zA{8jq!B8L4S5HOEQKZBjErY9>j|M^ei`YPFDB z!=%=FsTCo$E=jEqQoEki9w4>drFM+eekOJ5NgYe6Gg|6wk~#@e=YyCSi%BmrSs*5x z#3WiwZi&fHscRy2A$6xo-5{xZUh4jqdMx!ONxdynFGcG8mipbK{$iwUb8UrIC*`N{~j`(%4WM_maj7q;ZTi{vb_E zq=}6*StU)9rO7{O+EbdYlBSoXnUORjX*NTe9gt?PrFmOvK1P~{Nb`r%qKvfYC@rQ+ zi#^gJQCi%W7JtRGotO?4)AeGSDyFaGzpCZY-^BrM0uP4wu#srFF5iF_kt$ zrOhg7lOSyhq-_&vJ6hUqm$nJgHb=~Bh`EiJuMzVYF@GuTYDv3p(r%)(+a&EyO1mr4 zE?3(9mG&*Ay|uJ=lJ@(g{aI=ML^_n04xOdLH0j_k9WG0UKhm+Y{QnuBFC9arV}^7p zC7qf{CwuASDV>6)Q=)WwEEZ+OqM=woEGCM@R zkS?>OOO$kZA(jSWX(pDAV!1;sZ;0hr>DpGhPLQsi(ltW5-jJ@v(ygI%>m}V5NVh2I zc3r4~kd2VD&|abYg6e{y0w2Lm;TpoDgjWg23cnX=B{D}OSmdE-4N(!DB^n}nTe_E) z?kwHirTb;+QCfNol^&7O2pW=mY2T0rSAsm`#}2Dk$$tKUy9h25t{*G6C^eT(%)M8 z2TT7?GQd^_9FhU=WMBswxK0K>l0jW$kh=`Z6kAiVoi4V~V*6F>ti{e%>=MN8mkjPN zgV)R8Y#Cx9Ll(=BGcwdrhK`q^@iMHE44W*&lEuEh*pCzYFtPt3!w1UnC>j1n!6uWn7Mo?;_*U5bJE>rVlT34CoEz=&$^d>TWkxaiJ)Bnnh z9x}sAX55sSWn^Ygndv1n@5-ziGHbfbN|sswWOiSfy;EktkvTnNj=#)#FLV3L+$fp* zTIRKtd23`|mdx)c^Ic{BO?JWJ5XG z&`UO~mkr6Xv4U)Lkd519<8|3oLpF_wi@vUQMb z-6&fh$hIc3ZIo>DlWq6Jr>^*n5T7mLb6tGvi0@GG4He(dvb~*bcarU=Wk&_sF-CU8 z$d13_H$wali{D$>X(l_}WM{hgHxhqG@joX1|0IAVz)b?qOJGF_R0*6XfsqpUT7v3I z&_oG3BtcIk*hGTINbp_>&X#lZIE4=vb(PAo*}!#WcNGS(_Z#W zkv$=@=YfQpNN8UPT_&ON68cm2wv)XsvNuik7E2gOn1_U2mVGs4-z3==EBi_$++M;r zN_d(?RF{ac5)mU2Zza-FB0VHBK_Uw!s)Ix=mZ;qll_C2}$^Krlf2r(0BL^zT0hR-c z<-l<{@J$Y?9Q2lh*X2-MIW$WS?UF-T65Uv$9VPmRM8B1oP7*Vk$Is92-yGZODiA|6=1BoMvTP$&>B(6k`43Z-Oa^!^^?JP&V<>(nX`dyCokz?EB z*dsY!OO89r@hCZ-E%7xaewf7jNqm~b|BwU=NpO{fcuDvpCkDxhHFDy-oGdFR?c`*L zoO~mvy2z<@aw=OAt4pG-BnC_3D>>a&PA`xRZX(`N!Aw0x*=JA<$fo*KS%B#ko))KfuTI;Cl9vDgEV<)EDs&!VURq0 zAdec$quKH(QL-yYwu59xOLmFmkmM|toHTiCERUzi<9+h@lRRO0vRIxRl_y^$mn3(H zi1*z2upjJd2lSpXGT+c|Kg8yUFu-dHz;j z{3kC4%Zv5$B1vB4N?tk1>m+%TC2xh~?UlT1l2;-xP37eTdAUnoUX+)G^2$_RO^{b1 z^6G)St|hM>I6m*sXXDNu4fvZ|LOTK2w*Z1T`Q4PZSv=a{H-p39pvvy z`FmLY7RbMr@^7~M3zB~iq@=o(u$0V{l3*#hB_;2)R8=iST57(Q+M%T~w6vj??x>|F zXz3kVIz!7;)G~dw%o;6|rUn(%puHN5QG)b^V@ow2r^Y+fI8BXTX|+mPt&3KhqSaPwwS!vi zfmW}k)uGj0w0fde&(j*Uw1%D5*rYWwv}P5pX{R;)v}U%}DyOx&XsxAM>$ukXp|#Dl z_H3;krL}Ks?GmliS?kQzI@`3)X|3~KOutXa**G6{QXpJ^H zppBksbdDrUTS;t(vB&X^H;VUH@C9 z|E1}F@3m!JZRwybeYIt#wkoBqdT1+GZM9chWoWBEYSu~3`m5P=HQS(Or_}6`wyvVB zZM5|)ZSAA2&uZ%r+NQa-8KZ5sX`6IyTVC6?(zZji?K*9HLfaOpd3`k>rsiwaJW|cC zs(F#NYo_h2wcTuO7p(0vwcS5$-$UE4)%J1P{;77TryWLVhhXiHtsNU`$En&eMmyfu zP8GG2t#;a?oo;BSe`?WFE!@>&zglEzXG87WOFPff&VJhYvUV}}f6mm!MY|+xm-lMf zOf4s>yr6NUh#Yn|g#dSr#ausD# zp0Dh$d`G2*3RGsO>{hw2T1$19s*h@>cCVw|yKDEk+C4_QztJA1+GCjZ*rq)$YmX1w zv#$2+uRT|2&p_>&q&;7#Rc*BrwOXWBv1*m2y$rNh7wt7$dxdMSCu&_stw*T!cC|jO z)~~d8Q|&!gdk1UpE83@=_F?TaMf+^iKIgU1C+%yXeVb_C-r9Ge_Vv-eDcbj^_N%M? zdT76S+Amc5-BX)NYGb7~?rL*NZ62sivG(tz{ikaGJ=#B2`~T7bZFImu9k4(L1nPh^ z9gwR7OXgXo3!X zrNip$upv6kONX7)VZYSAuG)**JFERZwa-@j5*^+}hfmYtYjya39ez`X|JD&Tbwn2( zF+oT8>WFw9@lHp!)RBX9q>GL`q$8iHLj!eiP>1d6a7Rbg&{5rV)Fd6XOGjnu=;}IJ zb+oIFj@8kbI=WcLwAL}>bj&gxvq#5V)3K#>teK9Ts$(N`>~$Smtm7hzU5{j5&U*BNbehO^Gtt26HD%<4MRUS|gC%*Q&bna-N4 zvrg))S30|{&YqyNLv{8Wozqa~Ox8L3bk1#^YoK#Q=ep`#U!8kQ=Vt4?syeTu&YP+8 zB6ME5&MVUSEp`3`ogbw0@9F|0T`*V|_~?QJUGPMm>Z;Qab=sy*r`74JI=4~hA?mzL zo%gHrLv{Y43mfY~J6*U=7aq}t?{!goT{K1)ZPG=ly10}s?xl;@=;BmeVyH`o>JoQd z60J)Lbg7vxov%wnb?H-eX`n9s)Mcr<98#C(>he!rTd3<$bzQHnaq60{%UbI)dtJ6# zmnG`5B3*8w%NOeMqq_X7x>n=NDaRsVkT1$|PN7psQ?k)pA`Gt*buhYI9w^ zSXb}Y)%m)nm980~YrJ($lCJrtYb)#8HoA70u3e*RBXsRU^{Sy>mg+T8y?oRwO}+lA zcUSdxQtw#xey{6{bzN6o=cMZ*b=`AaUrE>Z*7dHsK1A22==u-3p^=ChOLZy3JI#P1J2`b=y(h_DX$B)n~Z+1gKB0`WmZmKlSxi-*nw>tlJ%Q`ySn% zuRFTxj+we+x9-SOzn1E^Q2h?7U!m^oq&uhR&OqIHNBxb}-(3BNslTWCpHTnD8epOU zqcmWZ1{~3V*BaPN11D+VUJcCEpynDhQ-gdpC|QI4XmB?TcGKWP8vIs6nrp})4Oylk z(HinhL;mWn7P@Pg?%Jrkj_Ixsy1TvZ9<94K=eJPa0lR!&SrQ zYIvxIXK93yM)cK))f$na5id2;NF!}Da+yXZYUFc`s;^OlHEN+o1!+{8Mt#-&Cc58B z_b<}@vARD;4>ZyP)AYc0J&>jcOX)!?J?Nzeujrx5dT4|m3e`gydZKKRV>~q`Mq?i9;Tn3_Ru3=L!{K`PfyUO+*nt|mRb%gKTm_Bmp>ZA>cR`O-&?AHO zh`%0ru17oQ(MfvLUynZ4W6ky0d_5Mf$MW@fdp+)`$9L-Sn;LJd@$EH!xW=#6_(Y9= zp$SbjVX7u<(S#&TDAE%h^n{b1h|?1v^<-x~xm-_%>&Z+#RZCCV>#6N}>Vlps(L`wC z6ixKe#1u{Zp{HxmP)zM!Xn>6xy2W|E%q)ibGj=C>wUYSJQ2iq)hKde&Ue zI_Oz1J)5Fu|7vm%O%&#%(+r}cb} zUNF!Lo%O;@y|7m=JkS(lO|jLKO`398Q!@2pCB4`~FS_Z)aJ`tVsZ}+#gQkwu)K!{# zL{l>~wNTR<{Eve3U<lCrmi zjHqZKiZT+FR6^1?*ZCdyP#$_H4;Lv9FDeg9l}CokqyEaHWy+&#%A*S9v7_=hKzV#w zdHhFtVyZkDq&(TAJV{Uz8!Cz2mBe{U;#nmzT}iA}o;oN`eUzt*m8W}@r?-`-h03$W z$}>fIwm^BdTX}Xzc~+z(nJ7u4m8307Qi76Hrz9&%@&qM$jgov*NiJ4Wtd*3ZO3FGV z<+_rRrKHp-&#jf`qm}1Nl;@|F=Q+v?W95ar@?xv<;-!+>NJ#}Hb(xYHucS3n(uOE$ zyOgv%CEZ?0pQxmVE9q~QjP^>#OeN#6lJP~!v{5p>l+0~PW}@=4k@C_(c{y2mxlehS zqr7USyz*3D9aCPtR$l8XuSYAd4=S$q)mizvMfv(h z`PN1G=C6FarxZ3-3f+{#^-AG0rAS{X>ZKID{PI+Ool$=MQ+_)tzx|Zo zx0FAc%AZlnpB>7d_e!OeQaM4Xj8H1Gl)vqizx|ZILCW9r%HMLO%2KJCqg35cs=h1# zY?Ob#%D+p>zZ#`_fKnZf`&h6Tm#Mepg9~g4})e2G*F?zUTBaC4RxX6 zcxZSU8dgE0KG0|*G)f1pCZII{v_e5E6|~AhyFF+R2kot(eG9bzL1Tf&0nqp)H2w%W z=AbhZbYeiK9GWQ5WCJw022H*}(=O1|ADZ5Prr$x=5p)AV_bBLQf?iY58vuF>LGLW+ z6+p8t&}XypN|!lBh0XsrXSU7+<^X#E1(=s+7UXcGf%3c=7G41>Y&3A8nWwtmp| z9vB&d(L6AU1*0Eetb*|lF#ZbdIzzh^&@LI;H-`4^&^`>>zXcObFmVDCKQOrfCZE8x z6PN~pX*8IAfeyCNApknu2D3(B<^g6$!K@rQT0_Ul&@mc1mV>!~`7$tn0v5Vpp@PLy zu($~p|G=_6SPlTorC@mjEc3z23asXU)d8?d1#4Zf9tzgsV4Vdv?ZIXS*jxge4`AC8 zY`wsCAJ~3`PHmvmc<6KxIwe7;TIk#pItN1Mz0mmqbp8o;UBPZL*c}DCLa=WO_Cvvb z6WAw0mnP6j@_{~v$ff*v~1!wY&uL62JKISP7ig`O$U%M^O~L9aW| z>mT%Xhu*uOcM9~;fj)zw&ray`1p3y4z5;!>K;L9=HU{SaaE<}zQc#>h2?gaHKv#ee zfNa1XfPsLQff@mg1v(E@3hV;B3pfo#A7m`ZNsv@f>x1eH>IzV=fLaSKL%?MXxWs{L zeQ+HJuDiiC5Bk|bzva;H1@vzK{TcdigZ^3IW)5x>!7U2he!u_=7%%|_9Do7uU|=H{ z*c%4=!N6D;_z~Q#!94)nPl5YK7-R;6d|*&C461;^&M0 zL&w9=GcdFWhMB>zaWHH@40{8^s$jSU4EKcLJ7M@;7+wn=F5oc_JWhhg2N+=nBc{QK zC>Ze_M)rh}t6*d-jLd^k7BI>OMxBFEc`(`nMo)#&J79DIjIILD9^g3vJhy`9BN(Fv zWBS0Dl`!TRjH!jOyd-6C+__5=^XxNqt~aFig4)lWJkIz~oIZ zIUOd~!GB%hzxnXrCHU_zOtFJ0{xBs5ro4rzO<=0P)TJ%8_Y<8nayFQz|1u;^DfN%2D1!d z)?k>m7G|A>S?MslAxyN8`0r*>kzbE)_0{{EqUjqRhAYc##tbl;q5bzb|*}^=3m=_82G9a)y z1bRT=9tg~a`5j>XY?yx)=6{9-ZD7H8Sa1jye1V0=uy8UgJOT?}K#&Fm^@X4y2s#2m zX|PBK77c+#t6S-|9hSa_ zWo==Z4=g(i%W`44AuRWV=BkzwN|*mxHl5ZV?(6$qUHq5C29HEeDGoBP4$&9M0vZ2kdT>|l!@Y`Fwm{=ilRwyuY* zX|SyxZ0io&f??ZD*scNFd%^Z)u>B5fFM===2=j!noe*{n!al(cL)b9}cC3dTPhn>Z z*r~$K#jx`R?EDAeDui!>@Ko5<4tC9gUHf6z3)tNnc6-6@6R@9!@J%|_x5t|_55=4A}eTJ}aB<$M>`_f>4bJ#x$_V0uJ4`BZf zIM4+S%z^{w;6OGUG=hVp;NWsNcoq)kz#)A&)CUgDghPknP##3KfJje>JOYs!aJVTP z9s-AV!r^QjxKu{w8t`2~!yWr|WxLOI<5U#C)YtP_%J-FT< zu5W&U4mO>aJviK z4uIR&;C3$DX$E(O!ku++CkgIUL0lh*n-6gZA?_)}{e!#R;I22^-2`{D3GuY67Ir7EqFKx9LKNFEKz2O;@BBo{+U2S}L!DO({W4W1jp^MUYu z1w6k4&uig@E4%A^)iQW>A6}KgYfE@N6JDQ% z*JY4p4OxDWbr!PT!W%t!;|6b5!J7l{CLZ1t!CNzU>jrOE!`plC_A6w!hU~$Ry&ke3 zLXHOHFyw?l&J)P_0q>0Aofo_dhj%aGy(YXDc)tYRAA|SL;e8$CIzg@{-Gk5C@L7S+tKf4y zeEtskrjYLm`8y#09OS=%{3`h30AEJIm#y$63BHs=fhiP>f`YA3@C?3c!BO6HTRMEJgF*!g7eL_wD0~cszoDoT6uCf=4-{>MqI*#E35t!N*b|DE zLh&gmegof|!}me(eLZ}?4<+@XM1_(WP!a|uN$^7hehB=S13yl~k9Sb20i~Ux)C)>C zL+KePO@Y!XDC-7gW1%b@$}*s=8p=CE`4lKW1m!vK(-?kw!_P?g`3WkTLPZ~_2!@Kw zQ1KgnS;8+L_!SAip1`jP_-zHh$HVW%@H-NIC&3>L_+txyeBjRs_>%&Es-dzwRL+IU z7^p0Uzn$T)Km5G{f8Rls4pa?h`@hA(QIMvZ%@@c}h;P!mvdGHUKc%?#A6#0KWrz!Mv6#RiGkP!k&>He7)X zVear!DG?MV-B<^9q|7VUtPNWFt1YicKo8sRK3*#HL5FX&&ktp{@(+2B7XS z)XhTOU#Mqu_xS2HRL*n*eNc1lweyp%EI6L&KA3n2l}CuMB`W3t_ikt#dgcF-8F1if$h6vdrxe?9NS;R_C;u7gC-tm z5{f2w(c}Y~YNM$Wn);yWIy8+%(_HLegdO@}hqc%t5j*@sGaEFUie|^qEFU}CU`Jo< zco{oZqq!}bd!zXwG|xc`bF}b8i+yPE8Z8^3We>FUMaz?D`3x=ppp_k3`J&Z+w0eiu zmT2vX)?3j!8Eu-OjVIb1LYvQM+ZAn>qirnOe!@LkiCs=(moMmGi4L>SAr>88W7h`Q z)djn*z^?bPYbkbfz;3g!+fM8jkKM}9(Hb3H(Q!69MxbLVI#yzLC+xluyC25xFVIN~ zom|i<1f3GFhbHzIfIYThj~m$I3-+|ao>Q>rDeU^mC!Zo$5Z=&X&-ozZzTI&VYgcyz8q z#RmTm8+M~|1C?BaCJ4?56A?Bb+(f8A?1<=t7>$^P)C9>5X#-LcvL5mXjZS&fv%a@uRZn~iv8AMzlYdg z3;Pen{=2b%4!YT(TM)XPMzyC=FYMECvZ zo`~*sILHrtaZm;h*1*Aiac~e0zJ`N;;gB9UWC;$rheN*M(6%^q91h)wL$h$0 z77pu=!&c$2C>-_*hil_-#Np#`_-Y(}7Ki7ehZcG;dMrne)9CRUN9f=P!4b=F#7!Jg zfg_!8g9h@{6CoREAH*iuJPHu~n`{CpuoE(jl@8RTk_@4&;*A4$0j{k+=f4A|! z9Gs$wQyg*1G@Nn}r)1z%9h}-9r-tCv6FBuL`qV?8?&vcYeRiWyGWyiwG*g`Biqq!e zw1YV9CQf^g)0^OQ6{pX_=_hddXPlvnGZdULA7{kkjPE$p4rlt}%=0+20%x_yS?)M% z6V7^wv$b$`Pn^9FXP?K}S?JpkeS4v=ANp=X-|Oi673Y}aoS`^pE6z#7Iko854gKbz zUo`ra;M{IFcLC15gZ>TC-x2+%qW>xM&&L2e448xg$1ord=jq}+#CZ#FUM$Xgf%7Uc z&=v!IFmO8tKF9g`IG=ESAkL4$`Ng=v0T%?~f~&Zo1{e0nh4XRY30zo!K~@;#he1a$ z=o2n7!$m`J(Mnu&6&L-$;5HcCAA^@;@Ldf4fs37R@d{jg5f>NYlFqm!0GHgvr5d<& z5H8(^OTXeWV_Y@@mmR=mdAQsjmruv#=W+Q@Tw#SPCg6&FxZ(}2_>C)#apeG9xe`~N z!j&Izl^L#b$5o4Q)dgHtgsVH@>RGt@46ZK5HQjKHH?G-$YaU}reGGBMkog#L2}6o; zZ7W=>;@Snc_AIV_hHHP~ItyGk3D=miWd#LZ1`vlDKfhMSMz<|N#rgIk8; zmZi8Q7PoxEtv0yT7q`aZ)-v4I2e+-kZRc@Y9&R_s?Ne}j6mHMKFjEYhfMKB+b{oUW za7Sm{F#>n2!5z17M>X#3f;$)B&UoDU6T|H>+#kcQVt5_y8i>2Xao0!O?TEXlT zxZf7{Psjboaep2j=!gfV;ejo9;5r_tz=QU9a4sI)j|X4lp=NlfCm#9_5ADE1kMK|- zMz+Do!5Fz2BX4134Ib`?hlB8N3?44VBMx{Z2#>_!(FS;QI37KON1x*{O+4m~$F}0J zEIi%}k9*?r2t582kJsXf-gqJiPaMV*sd%z6o*axPSK`UXcuF5nx#6ibc-MZ5Z_q&l=*{sdzRD&%Vd#HW)n`qxWL;4UGPP=k)R1 z5Ih%*=dR4G+Ar0B;<^8|iqX3U7AD zn+x#f0lb-rH~--+N4({Sx6b0Ne7tRmw`bw)FuZ*iZ~wzPUGdHWymJ)qyvMlK7zY?P z6XPNE zEI$5@Pn__{N__GR6I)^8G)%mXiN*NT5}yX((_8qgF+NlA*;ah^3X_^(k}D>y!K5dc zREWt2nA{JOS7CB2CYNGLFHG^ll)adefzJ)_`51h@AD_R+7uNV<9=?dg7wMR4h^gL~ zdI(cfG0gzeJTPqqrk%&Md`vgRbjI}An7$X&Q!%|7Gx}i063jS*89A6~fSE%vb1P;( z$CtYJ(gR=a!j}*5Wd*+Kfv*ghV(fDpHzPpa^UgNtueD8qoXW{#Be4mE7EisodHxP5rVQwMj*ui+md?S_BUqY)WgW3>ES4R^vN9~U#qt?g9)sn7@Mk~# z8IC_cV1*M_Y{ZItSW%6?-0{~g{FRTt&GGj<{Cx|5m*bx<_{SgrT){tiSlJRQhhya) ztW3tgt?}4#Gx)C>tDUeq0ITm}jW*VJV@(9se8Ji- zSUVkSk74a6th2^CZ>-yeb+4#iN2)iG>cvuhO{yPduYEVH92T{ZA)bJBEa->E9)aVj5`b%1_q&0`M4w6Mj5$R4O-JPWSjP$ff z&yDl~N$(WteWYeK)NCR(J44O>P;&=r?nlipQ}Y^XF^F31qZS3E-(~1 zWU!wMim0V6wOm9k?@`N2YUM<&7E-HQ)anbhHlo&^)cPQ`PNz2QsEseRIZkapkfAjh z&LhKkYO70a$5Go@GSVQUzGM_cMi0qYkBldiaU>ZRQ#&VWH;3Atp?2S>y*;&`MD6!e z`*&p0iA-jY$qh2qBhw*dx|2+EsY6%lu#P&sB{K&y+eBucsiQe{oK78YlX+7z_aXBr zGOs0zp=7a@EKRL?QT&deC>UNJD^~rHEIbI{jO6u-T-S<%UbaK)s zr!nMon4B`GM>Fa%l6oAa9=X)B8}-~sJrk*y9`zbey<(|%1M2Nfy)RIoCe&vJ^*K#_ zim0zp-*D<%OwNwvypf#WkkXcv38b7NON9mlZz#}_>s#=a``~69m#b%xrUKz0=d>wKNsp3O#SXt z|7O&`KlKlx{)yz)g4`yOTQs><(g1fFaEJzE(!gdka6An>NdteAyCb=;B=;xeUQ2`e z(4fUMD2@gd(%|+q*pmhypurh5q#X_Mqai0~$Ojr~K|_6M=rtOqNyCQHut*wKPQz_! z_zW6;g@*qnk0Im{N*=Figei^iq!Bx4L>i53MI+s4v}l@EPSb&= zZ=vaLX@)J$SWYw2X{HX%97r?w)64>z)q`fur&;%Dwhql!Y4!@59Z$Xu$hQai`jhWD z^8H0~y3w2!H0K5R>5`uZ`9+Z5Cz|U>b3z?}m2Q@{_J*PZ5V zpn2&O*qj1KP~bia%%k~@X}&YfUr6(B(){nVpa(5jLkn)uf(lyLn-;F3g~=3TNI^an zbd?rq(ITKli)c{-1vjDKaTL6lfngAJOW+w5AuWnMP|))0z?rF`kS_ibw zkJcTfbp^EEme$Xw^-;9`2W@bm4U=d?IBiI#joP%aJ8hg!8;{b)FSN;+Hu=z|OSI`5 zg<4UlFNI#A&Gl*XP}+QeHh-rrOj{PwmP@pyjD);=_nTt6Q_L!gNu-!MitS6WODOg(#r~l4HgtX#oxeio zf6xV_3p?q;3%aOJ7bnogb9AweE{&i|C+SiRUG}8Q;dD8Zu2|ERAi9!5SDVw-sdV)s zT`i$&4s>lHU5lq{C3M}AuKUpS7`pz2Zgi&`>*&Tkx>=uYy3@_Qbn`3S>O;3e=~f=y z?nSo)>Gnmsqd|9u(VY`?C!gYaQrsGfi=((|y32HTBi)UoyOk90Oz|5j{t?}4P50)| zy=b~uO80^8Z=?I4=s|CKu$3MZP=ZPct100jJ#0%4XVb$3dZbN{CeWi8dQ?o09q92Q zdYnLyf76pr^kh0cIY&=EQlcFtE~UiFlvqYjd(qR?^z;QiYfsO7>DgUM(x)VMN?K1z znUrio$pMrcOUc!g(vMP>Q_4+BsiNmZ&ll43%k=y&y%=w%eWETUK4=+!cM^^9IOpx6EB zbr`+=Oj$ORwTiOd(HnDm<4g?N z{a8ysQs_q|mD*D2WGX#QrEjRL1C{wx*$FDkrSc9`K8?y_sr)bfROx3J{rpH3ZKz@- zRYX$72l~~We$Ay{H|bX${dS_?e)Kzm`g4^2lu%_Cs$4^r+4R?l{!XI5 zG4!{Nszy`QS^8Ix{*9x5*XiGHs#d6a7gc|y8lajrRP&l@t*Lf8)kaZm8Px&R1ykL1 zs;lLC!?@mVu2;zQ`*Hn^TtAI9ImOzP5;g!f2xWsDZfVagXK~9YZdu8#`g5y&+$x(}+jHw6ZheVc zmvft5+-4cKiRL!hY-qrSquFo+8>VnuLvA~m+pg!fiEO0DMq}CN7#kI{u`?TQX5)A^ z{=@Br+il=>FS&g)ZtunIBe{J#o9M8K8=GuolWS~J%%+{#G>}ctvgvQ`;Km(RbBFtE z)|kykv)M^D`^6pmamW4Kv6Rg_vH46kzrhv_*kUYOM6ktIw(QQ9ery@dmL+V}gRRE1 z)n>MOz}A{RTQ{~1W83%K$(}n+=1zOLQyzD=Ae;)?@6&7{nOIq{lRj=>$_PvoMD;XR~x=*~IdZRcBWBv0BM4&g`;=U9#D=E4yxG z*H_%nf%}DUzsKBPm-~Bh|0wQXz;1TzwwT>gc|aQ;5Xb}4d7v>5oXrF8vwJ&sU&`)p zc~Bo7bchFQ@Zdo_ID!ZN;UOb<$WaH{5f2;5!%p(Be>}Vk51+-u&++iT z?9r1wHn7J#9?_0R{Kq3=c|;wL9K|D}cw{Y)8q1?D^Qdwj-GfJm^5|^#bYjoN?D?F> znDCehJmv_G`O0IRc&s0fJ;!5zvX?7+tz)mZ?Cr$fi`e@fk88o>rt`RX9&gCw=kfR# zJfRCun8y=t@Wd88aXL>-;7Lt+(lnlQlP6X4WEY;iohRq=f35kyk^El-|5w0Mta!>| zo|48>^?9lnPd&p^|FO>q_KD(Y^?2HNo_3R`Rq=G<>3ewkH=g0jGeUXB2cFrTXD;BG zH+hyO&+_0|n|RhUo^8Ohr}OM6_N~XhTnj(Bgm*91z3vGhUX*~Z5FVN-%{dmDf zUXZ~HJMzMXyfBf2j5%mN2PN~O#=OX#7aih7#T@Ly!RtBr4KKFh#Z!6l30_>tOS1$rrjh8LpWpTV*kC#v7<#%{RD_${|S0wYw&b)Fiue{2u^mx@& zUUiRGH|Et-c=a`2{g>A;ui3(DayX<7hj??yNe-#vwZv;9cw&-WSIE3V45C-oKmo=kS5ethx)pVHw|)A`gBKCQ>6$MNZle5MJXnZ;)wbCd-~ZQ!UP zKI_P5SMk|Aj#fB&A4h-Tb1r=D0H6EFG4>p@oMX~Bwk5}o;n-sw`-ji>e8Y=xT;!X2eA9z(?&q7ud`sn92l>`7zCDa@$MPLLzT?4n4)C2n z95;;P&Tw2A-*xA^k$m?9$J=oH0*-&k@&EXqGv8am_fq(NGrm8C@89J6HT=MhAMD}> z`JB*~6LxUI7k$B!)e(K3FN&X4W+@gjbl#!ou(lj-~L6hPPXFY<(&MIQ(AC}2d5n8lrnzq#?LqL z^K5=$!!LaJ#R-1#jZ+;tbtb2t;?!bJQ#frIr=@bbEvNf(`en}0&1g_c*&gXS;LuUe5l?Iej=Mlymalz*VXIuND8B%>OQPwGLNLmExzGpV;o>fMq04W<4_see-HXNpD>(Qp@yaM4H> z&9ox5oDrSB(qx!4*(Oagq-h6fI!~HD7F}b}4G`UDqGu_3 zKB9M8^s1%V5NUQwniWZNmgZs7{Drj8mlodA;*_*_Bl;~xe~jp#5d9Bg;3x(g#o&pw zY$`3iq~%#@StqSVNvkMnttqW1O6xMMvKvYG5RLPHe$R~j5DNNOKCSw+MShlHPU{Rw2u}O4KbN0Cbz|;T1-VuBgC{w zI`o$gTcyKiG3zO2i^S}XbZjIYM@h$3(($BpOc(RUV%}BEeZ@Rl%rnJ8Q!M(5#X_;T zA{IZz(ormf#PYUSeio}1Vl`Z>HjCABv8oYkMXZCw`mk6hi*>EoIEjs~*z6aZG_lne zTNkn2D7NX+siAa2>9kThJ(SM6(%DlwAC}JF#ZD1Bf3Z6yc0a`4N$gjP{VnODDP7#8 z%M$5wQM!B=2YYdtDGmq3;gxhXmafC4>ptmPAl*!)+c@cVM7n(w$DZOCB95ujy`ywr zDBaIV_X2Tp5~tQG!K@7Uh$G zslW(<6#|z8N(D^>Ckvhyd@ZCaG)QQxP=;_@;Yq@0g@21!iFk_~5&0;pov2epJtOKj zacL_qL&RmhxFm?nM{(5>S4VN3C9dJ(`ds?8kbZro-z@1DDg9nc|CZ8!r1TGy{^{ao zC~hKd^Th3fxE06%dl@iZ1{{z9FJ(Zj4D2ieC&<8X8F)tq7KyvLxci8Egt+I)AbS}! zSqANsLD@3cOa}YP;43n?R)!3bA%|p0stnbUp`J1{Qic}Dunsb8mJExLVR`CUneXdGW)ss))QYvd}oU9 z8S(utbBtxqFqyMi<|K(TQ1LvOQPAtR-x|gvCf$iR|bl zJ3?hgitMzIovUQ$BMEOV;Zr62mh5UJy8>ialI-p*ySK>hG}&V$dlt%`WZBzU_6Et` zG>Nd5h&d8*RrYDhzR|L8t?auk`>JHWi|k(}`=81IV>#d{2lmQ=JUM7D2Up0!7jmeB z914;{DH5qIk!}(hA(8oVxUU>uDuqhWINr5v-6V^ihW zH94*)$A`)BJ#su>PV|-&+vUWU|HnEf_sPlMa;le{S|+D5<8jhtmUyH(CUlW1d!o+r^4B>I<}Q{`N^ock&T zK9%z(a^7FgC&~p~xiCd8T#^fAa?wdHE|ZIia;d&tV!5q^Xgvgyo64y-PCP>_Qxm#cEj+DD+C0;|~QQ}ufe5%|t zk$Vf|-ZiAfV| zN%C4rPL>oKN!cVR8S>m*o-dW>ujNHAd9hbs{E^i0lKMc>no63tq}`JA){?$h(w|C( ztz@i~jOUWsPBQ%^^RZ-B%gesK>m5;oNlUH@}+D%^XmDfd*Tjgq}yvfoQiSIG&KoE!2^Q{H*VyDjoAQQkL|_ule8Lf#ij zu1M}`$$cVutt4-T`Nq^68X(DwfZxd=8h-8Is>d z@+V7vjO6FYmlpD6uzU%ZFDX)BC@l~?7jj{Kb}fA2|Ed#PF?RXOsH<=+PRmoC)~QoUKKi>0QQ z)a;U)A5z;>YFA5bn$+1y-2|zNkh&bTo~>GMmRc`Pt>0R$KU%H7U#AyigmOs||zIhH+{meYMdPwb4dR1yO7q!_IwOPE{?5En?L2bTJZ62*Q|E0EYRa-1mTijCh>#6$g zs(ys3|3x)$QVlk!2H9#$1GVKawdEnTWr5nNkJ@UH+Uk+oT2F1=Uv0fcZJnUD(N^2^ zRog63+gwu(HB>`X4Y#R=pVYQyYTIdQ+l#7Eebs2RY80g!)u_gvs__NYxI}H&Rc*Ic zZI`LGw^Q2(sO=xBCI+gBhiVe8n!Hs_EmhOus%eO7dROhBsdnhCc37=;c%hm#Qq5dc zvyG}*n%dDs?dYp^yrG)wsOA$?^IfX>3)P~XYB5co0n zR68%#E>^W~qS}vA?PJs~_0=w}YM0Gwm)EL;wdycMbvUUyl&W3(sa-?WuAkIyqITP; zc6+Hhc2XTfRL4BEJE-0FtKF+rr%9^QDb=Y=?J-pCaa!%Et@a$P_PnI_(ouVPsJ#xV zy^7V|toB~2_P(q3X{`2{sP;Lo_Ni9;daHeps(njTXBXA^pz8crRXkPYvI?zK2v#9p zMI9AqsCZ8$GnMA5l%%q`%Cl9zs!Bsuyi_@$%4b#Wp{il3nytF@Rb3)gm!kiB$FBQS z*GjeDIJMthwZEC#f0Nq3{Qo51ElhR$sSX&S4!EffY^o0QR|lr3?nbJ+uj+nZ9b~Ky z8leu_p$^JY2ivNHXRCwb)gf)wA>-8{C)FXp|3|@jxP`#>VbloOBYTsTz4v^6E7?*= ziT3D*|2Pl0TrJtr2)m4j7EjpkUy;cS~%D`0_ zyikVym7$F?yr&lTR*Rk0;v%(Vf?DFQmi$#qNi997mR2ewRz?xZ=$l%$KrM?>%RVdP zNy^wu89!IcJFDf!YI(R?UaMA2RV$9F6=9y}RNLCCZN_R_xZ3tz+36}fH)VH6*|%2qYn6Sp+ODCtuTtBi z)b?t%W0u+xsvPPnhZV}9Cs?mbmi1WIc-)>ca^h_a^9+(bJWg3YUdWUGfBBL zS1$i4mk{Mru3X0|*8|EmPq~dzZpW0{OSNm1+O<#ZdaK;^lzX)DXr?^2D35&Q*++R= zE6*ot_XxH7sM=kw_UNiTp=!@3wRfu8>!bF*Q(nE4*GlCTp}cC;z6omIezmVcc@I+F zHp=_9+TU62w^sY_s{?)20XKEvr8?MG9o(c2-d2Y?t3%t=p=9OLN%@#7p9{+8hw=sG z>#Ka9sKcGrVKa3&Ssl?-M@-d`+v;dbb<{*1jaSFC)G=#yEJqz5rH&t0$N#Dmy6VJH zb)s7NEl_@!m45@}zef2dtAHjdV2KKdR)O_Y;1m_;qylfNppGhNg$lZ?g4?R#r7Ad7 z1%Fo|dMf0!3i+!}E>mJL6``dfOjN{871>8cZdQ@WDyp-JvQtq7>dYi{CQwCdsOU{9I$oV^tj?ON zvkz3vP!;2>VqU0olhrv-buL$(AE?f|s`K~Mg;wf^C zRB>HZoTG{>QJ1EvOJVAAb9H%%x*VadXsRn_>dFHZ-&Msss`w&xwZFQ$Rb9UyP0(o;#1>PAy_W3#$(Mcvd;H%-;e zWR=`aC2vs4>FQPwb!)AOrb{*hoEGpdOx94}Yph`sz`P%4(#t)~c*KD!YlwUZ}Fqs>k)z ziHY>Vw8HZOTEZfr9)Mzn<_0s@T2*;gRsK*PS$*88KBlNo z&D5ur>Qkut^jm#isy+v+&+k>$G*xv>Ree=o=BY2y>T5Ig)k1x}q`v-D-;C6^i|Tu8 z^?kYeo}j8*scJ)29ipl~sUJ%Ha8N&Ps2WXGW2|aIR85WgX`p`ktDohnc9N>~Rkfeg zuUYC>g!-+ies55}FR0(O>W`uNb58wjq5c}FzgN`1=IWom`WLAFeSkXCpw3RHa}Vlv zhPt*;HyP^nfO>9FFAM5xLwzTxUkn=4LE|(uXb24~pusa}*asTgLc=_0G!7aCL8B_r zTm+i2p!p9P&xgk0(4-DD84FEpph+S$Z30d8plJ{^{Q=D;Krn)JPS7tE z`nQ1o|3Uw&FhC0iSipdLprZpidqL+B4D1L4tzlpW4AOx?HZbTB4DJhqH^AUH7*ZF8 z=);gbFys~ttqVhE!BBS?nghf7!LU^@EEI-)gyEB6_ra7{HWZnDP;(PKT-P zFf|jVb%JSDFf9S5w}a_hVR|ymXazIOU`86KKA?7idJQlW;5@)jKmm*Zst>dl=n8Oq zU@PEjAdNwcLE=C@gYHn!wE^8Un5h9Xm%z-6F!L+uO#(eH(0dNE#=)!uFsmG9Bg{Sp zvtPrUfiTAv=H$ZMkuY~V%zX^=`oO%kFz+nP`vLO_<~zguYcT&GESLieykJ2wEF29B zZD3&{=xc($5$IolMGaw*F)WG)11&Jn2ZLi^Pzi?9!0-eZK8M9)V6g`*z5`2|z!H5} z5&}ye!BP!aIt`Y3z|v$eY6?axz~~ql<-)Qqu*?*eT?FGgU_1wmgTeSaEFTKXEns;P ztk8fJMzG=ptf&B!kzleBOd`SL9jqJ-2tWrux28xafCJLuvQz^?tryVU|ly@w-wf9fLU`eTLfkyVD=f-4}1}@egb=giUc^-U`gk!Tb?ei~@`8U~wBZ_lC_*u=zPy z4g^b2uzU$tI$&i3R%x)M3v8JWTfAV)Be3oY*2Z9c0c;wBjV{=DgH0jW4gp&?u+4+5 zLt*P4*jfhL`oOjguq_4b+JoIju)6^E4Zz+2><@$eGuYk(wr_;(L9o35c65Rrrm*7_ z?5F|<1czPVkO7Wb;J6YTFM^W>I2nLbG&t7*=jq_Q3!I<9P94~}6L#JKm#*Nl9$ccp zr4n4nf~zyQUIf=NaO(wb>%c7#+{$6sSlDF&yUxO{FW^2I-1mZe9(eQv4}0)<0Gg@fziU=kc^28Z$q`(=`Cv@P%IyeypCq9GUB=9o>KVR^>1OD~Fe>nJCfqy*szkz@*5U>OSPC!5j z1ZqQ|83e{c;2#JY3_&Ik6beCQ5IhuuS3__l1Xn;v2ME!F5LXDf0U@8@WOq1e04I;a z$#gi?3{DxssXcHi1y0w8)4Fi_5S%W6P;Cgcg3x3L{R&|NAL2E9uZ#I}Uk6%ZQ>ag88uI>b3b+Y zWjb6jhbzb7N+!f>Li`wr-wN^9A^tsF?Fm~6*Q+3@CnPO~q~nlu4{o%88%yAZ7u-mLn+@Qm z9^CYWn?;b^7m}?ZIR%ox!>z$^%NA}WLP{e@nGPv?Amst1)`8S;^Ul;Drf%~p-KM5Yxfd>K)eBePbJRATI z?cm`(c=#6{je|$7@aP_7b%ZQ^$O?k2*O1)_vKK=3LCAg#+3(?Tb9g)s9&d!l2jFo! zOs+LC~}3OyHKnN#p9uP6BLI*aVeBEhLQKD8o2Cvt_>ri-I24xMP zYzUMshq7Z(b_>e>!5al{EZ|KDyeWY4j!0K3sqg6;Rm+Dovp> z6e?fCM;-XM89ttXkLmEK5qtvp=d}$0{bl}T8__71OM8lWo@U=aB)q}5Y@bwmaEroBI@NF7=Gly?y;9C}aZv@}R z!}lfd-2uLb!S~1T{X0~5foeUdc7^H~sQv&yhQkjd_^}s$q(V&{sF?vZu27Q-KO4i( z1@LnR{5%UkKSAwKsND>;!BG1GeszIg1i!4|S2+AChTje0_i*^V8h(es?`-(f2L3F8 zKOXQW5&ryuzvJPrBm7N)e~sYZ0{9mQ|1#m@W%$Xu)#-c zI0PG-VZ$(Nn2n8EU?ajt8?n(5Y?OwLexPPs)EtMJ%Teg6(!=yHspfi|r?1dsA#5jP0Ld`&#UvjU5(X2RH0+7CYo%$9mXt z5_UAjjwi9>E9}$)J59q*cG&4McB;nCSNc# z*fk5g{=#kpvD*slb`-nCVYfW&-T=E##_p@Jdk}Un!5)pU$5`xPjy)o=M>+QFhdtL| z&nWEq5qovOUWmQiu-6UjT^D-~$KIQ~Yw69QGE6xBkC$IeZ%qzly`(;)tO*!VX77 z=3#Ycnsf1Iv1965o&PYMk2$i7PiYf|KDMB}dsR-*3jw9qC z{6(CC=z@3-u@tE*(lVqVq*CNQ$eWNaBfmt^LNP=+f>MOKEm3zN>K;VhB-H(gGyCAo z6*$u!XU5{pa@6aGdIqR>9Q9t{tkyV77iW3mtPGsp8fP!T*}*vbJW% za|hsDYn*!q=T_moQ8>>O=bgZLIXJ&H&L4#H=i+>OoPQ4I=iq{7xIhmVSmA;oT#$nc zo8iKlxX=R^rlNiW)YnD*&8Y8(`pz+Qe3Qqi;ZycAzYk=OSEu_5iU82ODb^bXk6-oOJi|qIT{T`BP%qzjz(W` z*+g9CfXiZV*-JF;ipEB0?1sjd(6|zp569)JarqfsUX3dT;fl4mA_`Z0Mw3Zs;(;dF zxUv_n+=443aODg9ZxH^s1^>H+|NX{Qf~&l7RSK^9g{vpv>K(W`09W6})zxS^0!@w4 z)E`Zs!gbSeofWRTgzLVd*+eumN3#oP_7T^2 z!S%Coy(_N2j_ZqYLnGWU0XNv28fE^gX~o3hZn6Pm9^ z^FwH!iWZH~LeSy>TBPFUM!1=Avkh(z$IZoP*%2+rpyd*@bV182Xjy_*9nfkCS~;WD zS+shNTiWB61-NBDZn=lnjnR4zTHB!YDYSl$Hf_;n3EH@$O$yr7qHQmi92WFPJi5)g)WWJWf8grp-UyY_Cr@IbWKFpPv|xP-PWR8G`c-Rw;#Am2X~p` zE+5?W1l_x!yDqx#K=*ib|A8Kp(8CEmQqZ$0dK#doD|%i*&)>LvD(-f{-Is9p58N{b z_pHJ_zPRTe?yZk|`{3TWxOWHcy@Y#TpjS8aT7h2s&?_0ee&D{5xX%jr#p1py^d5uW zcIX|B-g&sc6YgJ%`-5=*V?59v4=lg~&UoM=9;m>BJ@DXoJZOXmcj3XSc(4c$wZ%gV z@X$6qbQ%vmL!XxDGYfq@(I*vsHPDyQcN6*^Mc>DGxCtKC!^2zga4a5vi$_}GkqLOj z43GHZk!yJ56&}^XqjT`+HavP6kJjL^K6uO!j~&Eg5Ak>_JiZW*AI9T_ctRUb%)=AT zc;XhG_=kRke%sJ50sX4czc>0XM1N28Pe%WI^smJLZ45BP0DBC$fB_#cur&rw#=y-O z7>VdF($Rdq*<7>4U^(A={MdOh&NW? z4L`h5fH%A2%~g0a9B+QYt>bvB2vfRX%1lhzfhn<=Qi-YU zFcmR%2c}-f)JjYniD?d)b{^9z@U{-#w!qu*c&85DnTdCf;hi^_-U-v^WBLh9e~x#D z;N5k2_X6Jij`s%OJrlg=kN1i&qaJ1q$Bea@5sn$TnAsUKS7YW8%zS|No8$cjc>e_6 zFU1Es_+TqONW%x!_;4^jw84j0@sS2TnuL$s@X;N7RD)SVF>5_$MPk+y%x-|$Q!(2G zvoB%xFMKTc*a08M5Wge;FDB*QiD%N;8O>Dnu@t?Fn12-9>?4g ze5QrZX5+KH`0PGDdyjcqm31!-8=6btpS z&>0KQV__8*4Z|W^EQ-LQw^-a4ix*(A3l?9&;*VI;5li&2WEYm)#ggCn9P#-fe4dUk z>f;N+7YFb~A(r;XQfn+t!S|H2RB@xylfa0@?_V`V$6oQ0KMSa}UA|KUen{J0%I#^T2>_-QPD za>7q{@N*0NybM1_;palEYK>J(u__R&-r|>@_{9vrMB|rY{Hldt=i*my{2GT}U*fk; z_{|8vdEvJ#{N5SA&%y5=_&pt~n_;y9RtI5qIsO=fKX&1dc>M7ZYsO)X1J>NbpY`zP zEc|%@f2QEipIAEvYi+Ui7}nmz+DiP@AAc>vUq1LN4}WXm@3HvX0)L;u-|z6xVEnTQ z|6IgBwfMIm{#}ZH{qgTp{MQ}-&BuQ}`0o+^`$csIQJs}k=LFTcO?7@y-H}vxE7gsm zx;0c!m+CoDy+o?_kLv4E{UcPrlr)Bt#!k}6p$3hq0aJq`)Zhg*983+(sNp4Q_=_5i zphg?1(HUw~Nt!)K(||O6Nwbg|ccjLq)c6WD{!C4VQxiLCl15G2Qd3iEdWo7fre-Ut znIARFqvivs`8H~vNi7;vi>1^ef?AYP%O2En6}3D|EuT=UdemwHwX&yHXQ|a!YCVlw zTT$yMYW!tnYWITLx2N_C zsQp1|e}&qYQHKuH!GJn=P=~wJu?2OUK^?bK$1BvanmSFSPCKbn8g*_?oeik-Zt8rK zx-_6Jder4Gb$LKt+f!FV>bi%zCR5kn)NMRDZG_9O+ciz?L*{3=LdO1N~`WDh>QcgQnA<4K&D)20f<1Eoksu z8oZwdKcpdAG-N3aaibv@X-E|f9YRB`XlMuxeL=&z&@iH5)-)`fh85HBCNz994c|(` z6KVKI8ZnkexY39N8u6V*PN0!4H1YwB>O`Z~(5NdkT9ZakrO|dYI*rCOr7_EB%rP4C zipCD4u`6k;AC1kYab0QL92)0F<8IOT#x#BgjrXSUB{ZQUO;|z`LTJJVnmCyz?xTrC zG-(7)@}x<(XwpBLJdY;()8tPyWf)CyrYRXTRfDD~n(9vf|H}D8(?-!WE1H%-)2e9t z6q>%Drr)I*nlxiR%?O|wA4p9mwU1OO!8igdg7XCb2=xeq2up~D64?{Q5mgb7Cf-3D zOZ<^!B*{*a8>HKcbk~t?1nE}N%t zEotF0T6mTg{w4j29LN&L9i&p28X>T$$BGaQ}`i$0er8RSCjVrCWNoyO@+9|YlH?7U1 zb**XLd|G#m*1aUN5oETL%pTEtZCYo3y!kF;S5ZP-m43Tb0!+PI!J#?i(Pv`L3H zZKO>XX;UGYHzRW*^W9{AjV$Vs#ayy*Ba0ifxh`$irOm#yxr8ir$kLuHGsyBES&b(v zH?qp0EuCn~BH9v6Tgu3~Gg<4C^&zs(AsbDy0kZKRn;f!jMYjKuZ9Lgl)7D9}bvJFz zr)@)Nn;UJ*BfF7g=S_C^$-X7oo09!y+TMb;>(ll~+FnCD#?g-Lv?GNaT9AW~g9kZ0 zAjj_HXhM#c$f*T6Eg&a9a(YkB)5-Y+Ie(#@vuI}&xoDEhEOPN7m-pm4om>OS^$odA zBR5}i%cWi2XxDn$l|Z}Rk$W$4UrFv!>Qo6*4)bnr4AYEFk{(xE+csE~Yg$j6y{ZjrAR`K~120P-!O z!@cP620DC%4&R}}f9S|`I__ion|_{pH3H0Xgdm> zPocgP`hvpxQP?&Ldr0BUDSRP?2UGZGiXe*cqlixwIg=v8DDpK$^`|HYipruhI&@|e zow-HPZ7EuxqK{H^37s86XIIeKBXssD#dN0_BZ@ggG5_e?L^`*F&fTW-T6BI5oxegC zH0go~U5KX(U+Cg!y11Jz=25H;#crV3YZTXr;uOWXP~2_0)SNC&rb{+-={#Mkrpv?W z@(#M3L01~m6U$UYp{#Q2b50+M2GeqN_o4wS*FeP{IyMcu0w@C~*}fMpI%r zUF%QRHqo_=?C4ILN`3=MmF8-LN_{VC0k(qbv? z3*DYbw>{`~F5T%zcec`B}hn1f{>FyJP6?9=dyv?scMjrgSfe?iEo+56W0e z83!mMi88)Y<|xWsO_>pt`I+wbru#;8-;eH>(1U*Tz=9rJp@;S9;R1T-Mh_F{QC)h3 z^ynZxx=UHDC`+HR+$rlCWmQx57|M2_?A!FX9X&Ro$MKZYh;kNE&S}bdPfteE6GwWI zMo*s8lRxydBRw5QPZ!ctb9(AYPs8cyEqeNdo|aQ?9m>_B+%c4^Pq`Z?*OPKjQtnmC zeMGq>lv_p5n$WX_^emE|Ra4$H%3Dr(UX&L`d5NGi&pq8ch5K*cMlcsCWN zQ}GWf(V>!6R1!=jFX?$JdOnAq@1W<2^t^;#XweIz7moBIkzRbJ(hgL*j7o#4^cB7A zOD{Lj%X{>y6TPyaSB3Oin_e5z>&H|!m&z{Co7VKkk>0$bato^HKot@6mgwy(dS_1W zo6-AV`k+Z49O*+DRsKhnZ|S2YeJr6*qv%rxeO^bOOQ_0`s-Dr8#q{MaeO*XjFVeU9 z^z9FQH>dCARK1+4tLTRz{kTCjqo^jCelDh;PpNhj)t1n&G4v~ee)p%}{`5zS{_LT@ z&FHT?{cA}7Lby&#uH(;j+i+bUuKSDY0oS|1_4{#sE3RL{8mn02E;rES1~<6jG;Wy2 zjgT89vgS(GOyS0Tx$#bJT*XbCxM>S+8pF+cbF(OJ-kX~TaEoEwB8XeO;Fb%yWgfTE z%{FGxm_u@pT+HqxWjnvaDqG5=Z*`w z<1OwqfjfC}ry}k=n>!bAmu1`~n!9%3uIsq#Ywp&KyRGDIW!&9>yT9cg7Tn_o_uR}q zzi_Vu-0Lp)p2EG)v$i>F$8evK+^2~9Zs&e=xt}}t`_29Jx&J2~u#*S;W}Qu}Q@{ha z@W4AfXe1Bv;6Yz_@LC?6#Y2pFNEQz@;-MdTm^%+2!^4AkcqNZ8;Su>fate>Uz@vun zsAwMDiARU>m}Wd?H;=8)V@-JMM;_Q|mNi>D>?bmHmpJfkDeaOW8xSUIsuV=!lU&Nz=ThN(Z( zW#+ETM_KB!Sh0L#T|L$fLWsA{lagsN8;mx~w z^DnkEVoN8s%wsEEwmQ#S=JJ+www}P&d)fLk+ZeOWEw-J;w&861jJK}it%bZzo40M` zZK-V6gYB%?E}QKquzd_~@6X#$@s4J^Ba$74v%?d19LA1r?D&EmYuKq3JFQ`-*X*pz z&bN5ySl)S|l>7 z_FTlCd)c#)ckA%(Fy8%v_iW)krM!1C?>)?3P1q}(_x0j^2ibcdd;78XXWl=D_uu6M zBly6n|A&SLpYWkMeCQ7QjA5S$_W8xWOWF4#A8y8njrs6jK4QT~iutGsAAQcpw(zk$ zKE8yH7xIY-eBv7W^g^2KzH zHRRYNj%&bi3pwr>#})CVUVO=vFU9bsFMN3%U*5@=Q~8P}Uoqe-0eq#3Pu9$=x`4BPS>DtpYWcPX z-)_UVd+_aXe0v_>-pIE-`Sw-5{f+Movy3@w zFJ~olRuN~_a`qt3Ud7q&oPCC~i}-N|emtEYZ{)`Z_;DORe#Vb~aZW$ZS-?3~oO6hC zQaI-aKk3R(h@WiYCr9|nRen;;Puug;1^m>5pC)mx2ItP<+=HC^l%KWbX9oQ2AU}J+ z&)#ufQ_dT~dCNG@hVu?_UOeZ$VV*=T~!qHW$q1 z0s}5s&IRUN;K&8OToAzGmQT%=RZFDCzk&d z^WS#-cQOCn#eWm{?=Sv0mj7+we_{OZKL7hCb*4!jQ>o)Abh_bm3#INB zsk>L|rbxZIQg4XV+a&cuq+Y4iZ!YzxNqrNke^lxxN&PC(&=w72(eMzBRMDuC27{%+ z8fg$J4W3HFj?!?BG&~>;Q>BrHG*Z&YTN+)LMpdFYOf)x(=5^7`7R_2|JX{*jl*Xpg z_@FdSmB#O-NpESQCrzBCNw74@mZnXl>2PUkE=~QV=~HReOqxxWW?Q7$acTBYn*EaI zy`=eEX}&?4AC>0!r1^JgF+f`ANec&Q5hgA2rNuvKIZ|5Ala_m=WsJ0ZEv?#0t69>@ zOj^ZD>*msWhO`cn*59Pf0%_wVZLUh2x1!bM{}5ZtQM6)2>y@E*(xvhfL{M zUpkU>+$J5*NXK01)L1$Vl1|H{(*fy}DV@GZ=ibtJzI67G&ex=KjdU3xUH+3UVbbM` zbR8&NH%Qk*(ltT4mP@y`(oIRXP15a*bjz1+f2I3y>24t1J*9iBbblv3I!lkm(!*bR ze2|{qq~~Jk=_NhyORqZ8Ylif)lwN10*GuW$S9)7Z??lno6m1Y~2hlz$+8Lr1U7co`Zi!v@Q+qcVK349}Jkb~3WJjLel$ZZf)_jNUI}n#!2lGFDH< z^^tLBW&CUzA1mYE%LJ4Ot}@}WO!y=db!4KkOgt(R-^nCxnPerCVr9~MncPPvZ;;7x zGPzWyw3jKmGG&WQ$&{&@GF4=%k4$|c(3uQ*2%y=X- z{)n0>%1cy=sCNQ`1l9}O5Ns$oS8%^zh0tK36+)3hWy0NsZG_JX7l`zb|Ib|{UUVCX zE{Lv!=-v|DTAAr1dZR_pUi5B>-d~wDL}ppZtcx1>}B3VncrFFuafzZGXJ+MFqQ>zvQR@7>dC^Bvhc6yFA#ly(XW<8 zB8xm^(OFqkD+cq$V7C|)i(wNnoGFH`Vwf+BN6KO^S^QjtJllwbF%uAm<|_HTQR*Vraxtkk~QwKCRf(>khP|=Hb&M~%ery0 z&Q8|F%ev2EHbBhG#O%D7eUbGd>yOC#EZNXcHf)xS(_~|WY%G>d+r_-Om~RsE?P9)H z%!9-{M$A*hJX_2^i$yE37%mnp7G7fUR5o{z%}Zsozicj)%^zg*AF*sBmi@(YxL8gQ zOC^^2VreRtmSX83mhNJ?Uo4M_%9jRBVdHrc!MFh;3`J?JKq;#CD31t$MO`g=}?_ts%1YifpSR+w^4HVcAwB+p5H_x!Cm+yK!PSTkH;s zU7*-q6uUcO_fqUWihW(NZ!h*E#a_gIwb*YLdmphsC-xa)UoP9b$@ayv-CDN$%Jy{G z{#AB#kR8)xhpy}}kR7(Nq(Jjz!|sK%Cl&Q(tkKBTkOuNO9H|=WXH~EY3H? zxlo)x%gzR}bDZp4FFOy(&IH++Cp*83OAB%7FD`S$#Y$WbiOWTC$q<)U;__Qu+llKK zaWxQETX8)iu8HFMOk6*R>pyWDByRJ>ZKJrki(9z3rHfm+>}o2zrpc~tvMW$_rOU4O z;@(o+M~eGmakminJ>q^;+#|$2QQRMi`v>u8DjvhcW3hNxh{qoB2oR6U;*l>N)#BMn zJg1B2f8x1aJR`;Pfq1@`-OXk96xqF5b_dJuY}wO5_DqsJ#`|HMmEyylD7 zI`Ogv(Ba-diawwHr5=!^J_5FZd92k|*0J|*JwSA3_4ua)?o6WUyhc_(Z6zRxEz}*$2Q2ZeRAxA z9LtmAb>w(=InHu?g&f~0$0Ox6QkvXk(}5rCtT&kDLIiSewyNED1IBoZ>#vZ zh~HuH3lzWW;#Vks-^9PQ`1cln691Lrzghfuihr2+$BKWJ_*Y6m6A9=d0aGPljRfqH zfQu6FSOT6)poRnvmB6JE=pcci5|}SRbtFhff=GglBxsuic}h^C1eHs$h6MMJ;PDc? zSc2^&I7ou8N$^JrX)Pg2LY7I0lZ1FnNRWgiOGuG~{Faj)Wy*873zq zl~Y-A>Z6=)A*aX7X+Nq-{i~)IpZN`?#h{LIrC1U>q~T3i5?@-Qzd$}M4y!C zS8}$OoHdcNC*^Fh#B`CE|0L$5#C(!-6>`2*EiR~`2<`R2IVxuMYkHqzs zxXBXdFL6)gQX9FnKrWq=OEq$Nqg-hsSDfU^XNg}T@i*iu%hl6zwLlU&Ny0)&xFLys zC6Of2QW8TY@v~fGxt1W;d&~91lGIv~c1lvI+*lzuLgnUYxfvrjv*hLcP7Aw!5 zNM0|=GnBjl$@?eyOC]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